fetch 和 postman 结果的区别

Difference between fetch and postman's results

我有一个简单的登录后端,它有两个路由 login 和 get_user。用户登录后,将设置一个 cookie,以便启用其他路由,如 get_user。我用 Postman 测试了这个后端,在正确登录后,cookie 被设置并且 get_user 响应用户数据。

但是,当我尝试在 React 和 JS 中使用 fetch 或 axios 时,我遇到了问题。在获取登录后,我可以看到发送了 cookie,但是,获取 get_user 就像根本没有设置 cookie 一样。

我提供了一个最小的例子来说明服务器端会话在某种程度上不适用于获取:

前端:

<!DOCTYPE html>
<html>
<body>

<h1> Set value: </h1> <h2 id="set_value"></h2>
<h1> Get value: </h1> <h2 id="get_value"></h2>

</body>
<script>
    // ..\..\Flask_general_framework\backend\venv\Scripts\Activate.ps1
    async function Set_user_fetch()
    {
        // Get user        
        let user = await fetch('http://127.0.0.1:5000/set/gggg', {
            'method': 'GET',
            //credentials: 'include',
            mode: 'cors',
            credentials: "same-origin",
            headers: {'Content-type': 'application/json', 'Accept': 'application/json',
            'Access-Control-Allow-Origin': '*', // Required for CORS support to work
            'Access-Control-Allow-Credentials': true, // Required for cookies, authorization headers with HTTPS},
            'Access-Control-Allow-Headers': 'Content-Type, X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Date, X-Api-Version, X-File-Name',
            }
        })
        user = await user.json();
        console.log("await user:", user);
        document.getElementById("set_value").innerHTML = user.value;
    }

    async function Get_user_fetch()
    {                
        let user = await fetch('http://127.0.0.1:5000/get', {
            'method': 'GET',
            //credentials: 'include',
            credentials: "same-origin",
            mode: 'cors',            
            headers: {'Content-type': 'application/json', 'Accept': 'application/json',
            'Access-Control-Allow-Origin': '*', // Required for CORS support to work
            'Access-Control-Allow-Credentials': true, // Required for cookies, authorization headers with HTTPS},
            'Access-Control-Allow-Headers': 'Content-Type, X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Date, X-Api-Version, X-File-Name',
         }
        })
        user = await user.json();        
        console.log("await user:", user);
        document.getElementById("get_value").innerHTML = user.value;
    }    

    Set_user_fetch().then( () => {
        Get_user_fetch();
    });
    
</script>
</html>

后端:

from re import I
from flask import Flask, session
from flask_session import Session
from flask_cors import CORS
import redis

import datetime as dt
app = Flask(__name__)
CORS(app, supports_credentials=True)
app.config['SECRET_KEY'] = 'super secret key'
#app.config['SESSION_TYPE'] = 'redis'
app.config['SESSION_TYPE'] = 'filesystem'
app.config['SESSION_PERMANENT'] = True
#app.config['SESSION_REDIS'] =  redis.from_url('redis://localhost:9876')
app.config['PERMANENT_SESSION_LIFETIME'] = dt.timedelta(days=7).total_seconds()
server_session = Session()     
server_session.init_app(app)

@app.route('/set/<value>', methods=['GET', 'POST'])
def set_value(value):
    session['value'] = value
    return {"value": value}

@app.route('/get', methods=['GET', 'POST'])
def get_value():
    return {"value": session.get('value', 'None')}

app.run(host='127.0.0.1', port=5000, debug=True)

服务器端

为了在现代浏览器中支持 cross-site cookie,您需要将服务器配置为使用 Set-Cookie 属性 SameSite=None(参见 Flask-specific example here)。不幸的是,这还需要 Secure 属性和启用 HTTPS 的服务器。

对于本地开发,您可以通过在同一主机名上为您的客户端和服务器提供服务来解决这个问题,例如 localhostSameSite=Lax(或省略 SameSite,默认为“Lax ").

“相同的主机名” 我的意思是如果您的前端代码向 localhost:5000 发出请求,您应该在浏览器中打开它 http://localhost:<frontend-port>。同样,如果您向 127.0.0.1:5000 发出请求,则应在 http://127.0.0.1:<frontend-port>.

打开它

宽松的 same-site 限制不会发挥作用,如果只是端口不同的话。

客户端

你这里有一些问题...

  1. 您在请求中发送 headers 不属于那里。 Access-Control-Allow-* 是必须来自服务器的 响应 headers。
  2. 您将 credentials 设置为 same-origin 但正在向不同的主机发送请求。要使用 cookie,您需要将 credentials 设置为 "include"。参见 Request.credentials
  3. 您没有对 non-successful 请求进行错误处理。

您还设置了很多冗余属性和 headers 并且可以 trim 显着减少您的代码。

async function Set_user_fetch() {
  const res = await fetch("http://127.0.0.1:5000/set/gggg", {
    credentials: "include",
  });

  if (!res.ok) {
    throw new Error(`${res.status}: ${await res.text()}`);
  }

  const user = await res.json();
  console.log("await user:", user);
  document.getElementById("set_value").innerHTML = user.value;
}

async function Get_user_fetch() {                
  const res = await fetch("http://127.0.0.1:5000/get", {
    credentials: "include",
  });

  if (!res.ok) {
    throw new Error(`${res.status}: ${await res.text()}`);
  }

  const user = await res.json();        
  console.log("await user:", user);
  document.getElementById("get_value").innerHTML = user.value;
}    

如果您使用的是 Axios,您可以将 withCredentials 配置设置为 true

axios.get("http://127.0.0.1:5000/set/gggg", {
  withCredentials: true
});