Flask蓝图中@login_required导致JS Fetch请求返回401问题
问题:Flask前后端分离下@login_required拦截已登录请求(Session ID不一致)
问题详情
采用前后端分离架构,后端基于Flask开发,前端通过JS的fetch请求与后端交互。后端使用Flask-Login实现认证,部分接口通过@login_required装饰器保护。
已成功登录(返回200状态码),但通过JS调用受保护的fund_request_bp蓝图接口时,@login_required触发401未授权错误。经排查发现:
- 登录后的Session ID与
fund_request_bp蓝图接口获取到的Session ID不一致 fund_request_bp蓝图下所有接口的Session ID均相同- Postman测试无此问题
期望请求能正常进入受保护接口,不被@login_required拦截。
相关代码
登录接口代码
from config import app, db, login_manager, default_password from models import User from flask import Blueprint, request, jsonify, session from flask_login import login_user, login_required, current_user user_bp = Blueprint("user", __name__) @user_bp.route('/login', methods=['POST']) def login(): data = request.form user = User.query.filter_by(username=data.get('username')).first() if (user and user.check_password(data.get('password')) and data.get('password')==default_password): return jsonify({"message": "Redirect To Change Password !"}), 303 elif (user and user.check_password(data.get('password'))): login_user(user) session['test'] = 'valuetest' print(session.sid) return jsonify({"message": "Login successfully"}), 200 else: return jsonify({"error": "Invalid username or password."}), 404
受保护蓝图接口代码
fund_request_bp = Blueprint("fund_request", __name__, url_prefix="/fund_requests") @fund_request_bp.route("/", methods=["GET"]) @login_required def get_all_fund_request(): fund_requests = FundRequest.query.filter_by(approval_level=current_user.user_level.level) if fund_requests.first() is None: return jsonify({"message": "No Funds request found."}), 200 fund_requests_serialized = [fund_request.serialize() for fund_request in fund_requests] return jsonify(fund_requests_serialized), 200
前端Fetch请求代码
fetch(api_base+"/fund_requests", { credentials: 'include' }) .then(response => response.json()) .then(data => { console.log(data) }) .catch(error => { console.error("Error fetching data:", error); });
Flask配置代码
app = Flask(__name__) app.config['SECRET_KEY'] = 'some_secret_key' app.config['SESSION_TYPE'] = 'filesystem' app.config['SESSION_COOKIE_PATH'] = '/' login_manager = LoginManager(app) CORS(app, supports_credentials=True) Session(app)
解决方案
1. 完善CORS配置,指定允许的前端源
前后端分离场景下,浏览器会严格校验跨域请求的来源,仅开启supports_credentials=True不够,需要明确指定前端的域名(开发环境可临时用"*",生产环境必须指定具体域名):
# 替换成你的前端实际地址,比如http://localhost:3000 CORS(app, supports_credentials=True, origins=["http://your-frontend-domain.com"])
2. 调整Session Cookie的SameSite属性
跨域场景下,默认的SameSite=Lax会阻止浏览器携带Session Cookie,需修改为SameSite=None,同时根据环境设置Secure属性:
# 开发环境(HTTP)可设为False,生产环境(HTTPS)必须设为True app.config['SESSION_COOKIE_SECURE'] = False app.config['SESSION_COOKIE_SAMESITE'] = 'None'
3. 确保登录请求也携带Credentials
前端登录的Fetch请求同样需要添加credentials: 'include',否则登录时的Session Cookie不会被浏览器保存:
// 示例登录请求代码 fetch(api_base+"/user/login", { method: 'POST', credentials: 'include', body: new FormData(document.getElementById('login-form')) }) .then(response => response.json()) .then(data => { console.log(data); // 登录成功后再发起受保护接口请求 }) .catch(error => { console.error("Login error:", error); });
4. 检查蓝图注册顺序
确保所有蓝图在Flask-Session初始化之后注册到app,避免Session上下文异常:
# 先初始化Session Session(app) # 再注册蓝图 app.register_blueprint(user_bp) app.register_blueprint(fund_request_bp)
5. 验证Session一致性
在受保护接口中打印Session ID,确认是否与登录时的ID一致,辅助排查:
@fund_request_bp.route("/", methods=["GET"]) @login_required def get_all_fund_request(): print("Fund Request Session ID:", session.sid) # 剩余业务代码...
排查思路总结
Postman测试正常但前端异常,说明问题出在浏览器的跨域Cookie策略上:
- 浏览器会对跨域请求的Cookie做严格校验,Postman不受此限制
- 核心是确保Session Cookie能被浏览器正确携带到跨域请求中,需从CORS配置、Cookie属性、请求Credentials设置三个维度入手
内容的提问来源于stack exchange,提问作者magloire touh
相关产品推荐
相关产品推荐

