React+FastAPI集成Microsoft单点登录的CORS跨域问题求助
问题背景
我用React和FastAPI开发Microsoft单点登录(SSO)功能,一直被CORS跨域问题困扰,尝试过中间件配置、请求头设置等方案都无效。直接访问后端/login路由能正常跳转到Microsoft登录页面,说明Azure凭据配置正确,但前端调用http://localhost:5050/login时被CORS策略拦截,提示:
'http://localhost:5050/login' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
另外,不配置Starlette的SessionMiddleware会触发错误:"session" in self.scope AssertionError: SessionMiddleware must be installed to access request.session,同时启用两个中间件后问题仍未解决。
前端代码
import React, { useState } from 'react'; import './App.css'; function App() { const handleSubmit = (event) => { event.preventDefault(); // 阻止表单提交时刷新页面 const url = 'http://localhost:5050/login'; fetch(url, {method: 'GET'}) .then((response) => { if (!response.ok) { throw new Error('请求出错'); } window.location.href = response.json() return response.json(); }) .then((data) => { console.log('服务器响应:', data); }) .catch((error) => { console.error('错误:', error); }); }; return ( <div className="login-container"> <h2>Login</h2> <form onSubmit={handleSubmit}> <button type="submit" className="login-button">登录</button> </form> </div> ); } export default App;
后端代码
from authlib.integrations.starlette_client import OAuth import uvicorn import logging from fastapi import FastAPI, Request from starlette.middleware.sessions import SessionMiddleware from fastapi.middleware.cors import CORSMiddleware app = FastAPI() logger = logging.getLogger('uvicorn.error') app.add_middleware( SessionMiddleware, secret_key="?", # 需替换为强随机密钥 ) app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) oauth = OAuth() oauth.register( name='azure', client_id='SEU_CLIENT_ID', client_secret='SEU_CLIENT_SECRET', authorize_url='https://login.microsoftonline.com/SEU_TENANT_ID/oauth2/v2.0/authorize', token_url='https://login.microsoftonline.com/SEU_TENANT_ID/oauth2/v2.0/token', client_kwargs={'scope': 'openid email profile User.Read'} ) @app.api_route('/login') async def login(request: Request): redirect_uri = request.url_for('auth') return await oauth.azure.authorize_redirect(request, redirect_uri) @app.get("/callback") async def auth(request: Request): token = await oauth.azure.authorize_access_token(request) user = await oauth.azure.parse_id_token(request, token) return {"token": token, "user_info": user} @app.get("/") def hello_world_root(): return {"Hello": "World"} if __name__ == '__main__': uvicorn.run(app, port=5050)
问题根源与修复方案
1. 核心问题:错误的请求方式
SSO登录流程的本质是页面跳转,而非AJAX请求。你用fetch发起跨域AJAX请求获取重定向地址,浏览器会严格拦截这类跨域重定向请求,即使配置了CORS也无法绕过。
前端修复:直接跳转登录地址
把handleSubmit改成直接跳转到后端登录路由,完全绕开AJAX的CORS限制:
const handleSubmit = (event) => { event.preventDefault(); window.location.href = 'http://localhost:5050/login'; };
2. 后端CORS配置优化
- 调整中间件顺序:将
CORSMiddleware放在SessionMiddleware之前添加,确保CORS头能被正确注入到所有响应(包括重定向):# 先添加CORS中间件 app.add_middleware( CORSMiddleware, allow_origins=["http://localhost:3000"], # 不要用*,明确指定前端地址,配合credentials=True allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # 再添加SessionMiddleware app.add_middleware( SessionMiddleware, secret_key="your-strong-random-secret-key", # 替换为随机强密钥 ) - 避免使用通配符
*:当allow_credentials=True时,浏览器不允许allow_origins设为*,必须指定具体的前端域名。
3. 验证Azure回调地址
确保Azure应用注册中的重定向URI设置为http://localhost:5050/callback,与后端request.url_for('auth')生成的地址完全一致。
内容的提问来源于stack exchange,提问作者Al777

