英文:
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("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
}
// Use the access token to get the user's GitHub data
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
}
// Set the content type header to JSON
w.Header().Set("Content-Type", "text/plain")
// 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(() => {
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>
</>
}
答案1
得分: 1
我理解了,网页是从http://localhost:5173
加载的,并且它向http://127.0.0.1:3080/user
发出了GET请求。所以这是一个跨域请求。
默认情况下,在跨域的XMLHttpRequest或Fetch调用中,浏览器不会发送凭据(例如Cookies和HTTP身份验证)。必须在XMLHttpRequest对象或Request构造函数被调用时设置一个特定的标志。
为了告诉浏览器将cookies发送到跨域URL,fetch调用应该像这样进行更改:
fetch('http://127.0.0.1:3080/user', {
method: 'GET',
+ mode: 'cors',
+ credentials: 'include',
})
更多信息,请参阅带凭据的请求。
看起来后端代码已经正确配置了CORS头,所以上述更改应该可以使其正常工作。如果不行,请检查浏览器的开发工具控制台。它应该包含一些错误/警告消息,告诉你出了什么问题。
这是一个帮助调试问题的最小演示。
-
启动服务器:
go run main.go
-
在浏览器中导航到
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
。 -
在浏览器中导航到
http://127.0.0.1:5173/
以打开一个页面。 -
在该页面上点击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存在。
英文:
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('http://127.0.0.1:3080/user', {
method: 'GET',
+ mode: 'cors',
+ credentials: 'include',
})
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.
-
start the server:
go run main.go
-
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
. -
navigate to
http://127.0.0.1:5173/
to open a page. -
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 (
"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>`
Incognito window can not see cookies from other window. Please check in the browser to make sure the cookie is there.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论