无法使用React从Go服务器获取cookie数据。

huangapple go评论93阅读模式
英文:

Can't get the cookie data from Go server with React

问题

我开始在使用React之后进入后端开发,我在后端服务器上添加了GitHub OAuth和会话以持久化数据。它们在后端方面都正常工作,我可以使用会话等从其他处理程序访问数据。但是,一旦我尝试从React中获取后端的会话,我就无法成功。

以下是要翻译的代码:

func (h Handler) HandleAuth(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Access-Control-Allow-Origin", "http://127.0.0.1:5173")
	w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
	w.Header().Set("Access-Control-Allow-Methods", "GET")
	url := Oauth2Config.AuthCodeURL("state", oauth2.AccessTypeOffline)
	http.Redirect(w, r, url, http.StatusFound)
}

func (h Handler) HandleAuthCallback(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Access-Control-Allow-Origin", "http://127.0.0.1:5173")
	w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
	w.Header().Set("Access-Control-Allow-Methods", "GET")
	code := r.URL.Query().Get("code")
	token, err := Oauth2Config.Exchange(r.Context(), code)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	// 使用访问令牌获取用户的GitHub数据
	client := github2.NewTokenClient(r.Context(), token.AccessToken)
	user, _, err := client.Users.Get(r.Context(), "")
	if err != nil {
		fmt.Printf("Error: %v\n", err.Error())
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	session, err := store.Get(r, "session")
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	session.Values["user"] = user.GetLogin()
	session.Values["access_token"] = token.AccessToken
	err = session.Save(r, w)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	fmt.Fprintf(w, "this is authcallback: %s", user.GetLogin())

}

func (h Handler) HandleCurrentUser(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Access-Control-Allow-Origin", "http://localhost:5173")
	w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
	w.Header().Set("Access-Control-Allow-Methods", "GET")
	session, err := store.Get(r, "session")
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	user, ok := session.Values["user"].(string)
	if !ok {
		http.Error(w, "Invalid user in session", http.StatusInternalServerError)
		return
	}
	// 将内容类型标头设置为JSON
	w.Header().Set("Content-Type", "text/plain")

	// 将JSON数据写入响应
	w.Write([]byte(user))
}

我尝试了很多方法,首先我使用的是一个名为scs的不同会话库,我以为可能是我的库出了问题,但事实并非如此。在更改代码时,我得到的错误不同,但每次后端都正常工作。在从后端发出的API请求中,有时会得到空字符串的数据,或网络错误,或找不到用户等等,但每次我检查后端的代码迭代时,后端都正常工作。以下是fetch请求的代码:

function App() {
    const [user, setUser] = useState(null);

    useEffect(() => {
        fetch('http://127.0.0.1:3080/user', {
            method: 'GET',
        })
            .then(response => response.text())
            .then(data => {
                setUser(data);
                console.log(data);
            })
            .catch(error => console.error(error));
    }, []);

    return <>
        <p>Logged in as: {user}</p>
        <button onClick={() => window.location.href = 'http://127.0.0.1:3080/oauth'}>Login</button>
    </>;
}
英文:

I started to get into backend after React and on my backend server I added github OAUTH and sessions to persist the data. They are all working fine in the backend side, I can accesses the data from other handlers with sessions etc. But as soon as I try to get the session from backend with react I am never able to.

func (h Handler) HandleAuth(w http.ResponseWriter, r *http.Request) {
w.Header().Set(&quot;Access-Control-Allow-Origin&quot;, &quot;http://127.0.0.1:5173&quot;)
w.Header().Set(&quot;Access-Control-Allow-Headers&quot;, &quot;Content-Type&quot;)
w.Header().Set(&quot;Access-Control-Allow-Methods&quot;, &quot;GET&quot;)
url := Oauth2Config.AuthCodeURL(&quot;state&quot;, oauth2.AccessTypeOffline)
http.Redirect(w, r, url, http.StatusFound)
}
func (h Handler) HandleAuthCallback(w http.ResponseWriter, r *http.Request) {
w.Header().Set(&quot;Access-Control-Allow-Origin&quot;, &quot;http://127.0.0.1:5173&quot;)
w.Header().Set(&quot;Access-Control-Allow-Headers&quot;, &quot;Content-Type&quot;)
w.Header().Set(&quot;Access-Control-Allow-Methods&quot;, &quot;GET&quot;)
code := r.URL.Query().Get(&quot;code&quot;)
token, err := Oauth2Config.Exchange(r.Context(), code)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Use the access token to get the user&#39;s GitHub data
client := github2.NewTokenClient(r.Context(), token.AccessToken)
user, _, err := client.Users.Get(r.Context(), &quot;&quot;)
if err != nil {
fmt.Printf(&quot;Error: %v\n&quot;, err.Error())
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
session, err := store.Get(r, &quot;session&quot;)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
session.Values[&quot;user&quot;] = user.GetLogin()
session.Values[&quot;access_token&quot;] = token.AccessToken
err = session.Save(r, w)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
fmt.Fprintf(w, &quot;this is authcallback: %s&quot;, user.GetLogin())
}
func (h Handler) HandleCurrentUser(w http.ResponseWriter, r *http.Request) {
w.Header().Set(&quot;Access-Control-Allow-Origin&quot;, &quot;http://localhost:5173&quot;)
w.Header().Set(&quot;Access-Control-Allow-Headers&quot;, &quot;Content-Type&quot;)
w.Header().Set(&quot;Access-Control-Allow-Methods&quot;, &quot;GET&quot;)
session, err := store.Get(r, &quot;session&quot;)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
user, ok := session.Values[&quot;user&quot;].(string)
if !ok {
http.Error(w, &quot;Invalid user in session&quot;, http.StatusInternalServerError)
return
}
// Set the content type header to JSON
w.Header().Set(&quot;Content-Type&quot;, &quot;text/plain&quot;)
// Write the JSON data to the response
w.Write([]byte(user))
}

I tried many things, first I was using a different sessions library other than gorilla which was called scs and I thought maybe it was my library but it was not. And while changing the code the error I got differed but every time the backend was working just fine. On the API request from the backend sometimes I would get an empty string for data, or network error or user not found etc., but every time I checked the backend in each iteration of code backend worked perfectly fine. Here is the fetch request:

function App() {
const [user, setUser] = useState(null);
useEffect(() =&gt; {
fetch(&#39;http://127.0.0.1:3080/user&#39;, {
method: &#39;GET&#39;,
})
.then(response =&gt; response.text())
.then(data =&gt; {
setUser(data);
console.log(data);
})
.catch(error =&gt; console.error(error));
}, []);
[]);
return &lt;&gt;
&lt;p&gt;Logged in as: {user}&lt;/p&gt;
&lt;button onClick={() =&gt; window.location.href = &#39;http://127.0.0.1:3080/oauth&#39;}&gt;Login&lt;/button&gt;
&lt;/&gt;
}

答案1

得分: 1

我理解了,网页是从http://localhost:5173加载的,并且它向http://127.0.0.1:3080/user发出了GET请求。所以这是一个跨域请求。

默认情况下,在跨域的XMLHttpRequest或Fetch调用中,浏览器不会发送凭据(例如CookiesHTTP身份验证)。必须在XMLHttpRequest对象或Request构造函数被调用时设置一个特定的标志。

为了告诉浏览器将cookies发送到跨域URL,fetch调用应该像这样进行更改:

  fetch('http://127.0.0.1:3080/user', {
    method: 'GET',
+   mode: 'cors',
+   credentials: 'include',
  })

更多信息,请参阅带凭据的请求

看起来后端代码已经正确配置了CORS头,所以上述更改应该可以使其正常工作。如果不行,请检查浏览器的开发工具控制台。它应该包含一些错误/警告消息,告诉你出了什么问题。


这是一个帮助调试问题的最小演示。

  1. 启动服务器:go run main.go

  2. 在浏览器中导航到http://127.0.0.1:3080/callback以设置cookie。

    Set-Cookie: session=abc; Path=/; Expires=Tue, 18 Apr 2023 18:34:49 GMT; Max-Age=86372; HttpOnly; SameSite=Lax

  3. 在浏览器中导航到http://127.0.0.1:5173/以打开一个页面。

  4. 在该页面上点击fetch按钮。它应该将会话cookie“abc”输出到开发工具控制台。

注意

我刚意识到cookie保存在域名127.0.0.1(不带端口)。所以页面http://127.0.0.1:5173/也可以读取该cookie。

package main

import (
	"fmt"
	"net/http"
	"time"
)

func main() {
	go func() {
		_ = http.ListenAndServe(":5173", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			w.Write([]byte(page))
		}))
	}()

	http.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) {
		http.SetCookie(w, &http.Cookie{
			Name:     "session",
			Value:    "abc",
			Path:     "/",
			Expires:  time.Now().Add(24 * time.Hour),
			MaxAge:   86372,
			HttpOnly: true,
			SameSite: http.SameSiteLaxMode,
		})
		w.Write([]byte("done"))
	})

	http.HandleFunc("/user", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Access-Control-Allow-Origin", "http://127.0.0.1:5173")
		w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
		w.Header().Set("Access-Control-Allow-Methods", "GET")
		w.Header().Set("Access-Control-Allow-Credentials", "true")

		cookie, err := r.Cookie("session")
		if err != nil {
			fmt.Fprintln(w, err.Error())
			return
		}
		w.Write([]byte(cookie.Value))
	})

	http.ListenAndServe(":3080", nil)
}

const page = `<html>
  <body>
    <button>fetch</button>
    <script>
      document.querySelector('button').addEventListener('click', () => {
        fetch('http://127.0.0.1:3080/user', {
          method: 'GET',
          credentials: 'include',
        })
          .then((response) => response.text())
          .then((data) => {
            console.log(data);
          })
          .catch((error) => console.error(error));
      });
    </script>
  </body>
</html>`

隐身窗口无法查看其他窗口的cookie。请在浏览器中检查以确保cookie存在。

无法使用React从Go服务器获取cookie数据。

英文:

IIUC, the web page is loaded from http://localhost:5173 and it makes a GET request to http://127.0.0.1:3080/user. So this is a cross-origin request.

By default, in cross-origin XMLHttpRequest or Fetch invocations, browsers will not send credentials (such as Cookies and HTTP authentication). A specific flag has to be set on the XMLHttpRequest object or the Request constructor when it is invoked.

In order to tell the browser to send cookies to a cross-origin URL, the fetch invocation should be changed like this:

  fetch(&#39;http://127.0.0.1:3080/user&#39;, {
    method: &#39;GET&#39;,
+   mode: &#39;cors&#39;,
+   credentials: &#39;include&#39;,
  })

For more information, see Requests with credentials.

It looks like that the backend code has configured the CORS headers correctly, so the above changes should make it work. If it doesn't, please check the DevTools console of the browser. It should contain some error/warning messages that will tell you what's wrong.


Here is the minimal demo helps to debug the issue.

  1. start the server: go run main.go

  2. navigate to http://127.0.0.1:3080/callback in a browser to set the cookie.

    Set-Cookie: session=abc; Path=/; Expires=Tue, 18 Apr 2023 18:34:49 GMT; Max-Age=86372; HttpOnly; SameSite=Lax.

  3. navigate to http://127.0.0.1:5173/ to open a page.

  4. click the fetch button on this page. It should output the session cookie "abc" to the DevTools console.

Notes:

I just realized that the cookie is saved to the domain 127.0.0.1 (without the port). So the page http://127.0.0.1:5173/ can read the cookie too.

package main

import (
	&quot;fmt&quot;
	&quot;net/http&quot;
	&quot;time&quot;
)

func main() {
	go func() {
		_ = http.ListenAndServe(&quot;:5173&quot;, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			w.Write([]byte(page))
		}))
	}()

	http.HandleFunc(&quot;/callback&quot;, func(w http.ResponseWriter, r *http.Request) {
		http.SetCookie(w, &amp;http.Cookie{
			Name:     &quot;session&quot;,
			Value:    &quot;abc&quot;,
			Path:     &quot;/&quot;,
			Expires:  time.Now().Add(24 * time.Hour),
			MaxAge:   86372,
			HttpOnly: true,
			SameSite: http.SameSiteLaxMode,
		})
		w.Write([]byte(&quot;done&quot;))
	})

	http.HandleFunc(&quot;/user&quot;, func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set(&quot;Access-Control-Allow-Origin&quot;, &quot;http://127.0.0.1:5173&quot;)
		w.Header().Set(&quot;Access-Control-Allow-Headers&quot;, &quot;Content-Type&quot;)
		w.Header().Set(&quot;Access-Control-Allow-Methods&quot;, &quot;GET&quot;)
		w.Header().Set(&quot;Access-Control-Allow-Credentials&quot;, &quot;true&quot;)

		cookie, err := r.Cookie(&quot;session&quot;)
		if err != nil {
			fmt.Fprintln(w, err.Error())
			return
		}
		w.Write([]byte(cookie.Value))
	})

	http.ListenAndServe(&quot;:3080&quot;, nil)
}

const page = `&lt;html&gt;
  &lt;body&gt;
    &lt;button&gt;fetch&lt;/button&gt;
    &lt;script&gt;
      document.querySelector(&#39;button&#39;).addEventListener(&#39;click&#39;, () =&gt; {
        fetch(&#39;http://127.0.0.1:3080/user&#39;, {
          method: &#39;GET&#39;,
          credentials: &#39;include&#39;,
        })
          .then((response) =&gt; response.text())
          .then((data) =&gt; {
            console.log(data);
          })
          .catch((error) =&gt; console.error(error));
      });
    &lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;`

Incognito window can not see cookies from other window. Please check in the browser to make sure the cookie is there.

无法使用React从Go服务器获取cookie数据。

huangapple
  • 本文由 发表于 2023年4月17日 22:31:23
  • 转载请务必保留本文链接:https://go.coder-hub.com/76036268.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定