前端调用Flask后端API登出时遇CORS跨域权限问题求助
错误信息
Access to XMLHttpRequest at 'http://localhost:8000/api/auth/logout' from origin 'http://localhost:5173' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Logout error: Network Error
后端相关代码
/api/auth.py 文件内容
class UserLogout(Resource): @JWTManager.token_required def post(self, public_id): """Logout authenticated user by invalidating JWT refresh token""" current_user = User.find_by_public_id(public_id) if current_user: # Get token from HTTPOnly cookies refresh_token = request.cookies.get('refresh_token') print("refresh_token") if not refresh_token: return {'message': 'Refresh token not provided.'}, 401 # Revoke refresh token try: JWTManager.revoke_token(refresh_token) response = make_response({'message': 'User logged out successfully.'}, 200) JWTManager.delete_token_cookies(response) response.delete_cookie('csrfToken', path='/', samesite='Strict') return response except TokenInvalidError: return {'message': 'Invalid token: Please login again'}, 401 except Exception as e: return {'message': f'Error occurred during logout.', 'errors': e}, 500 else: return {'message': 'User not found.'}, 404
Flask服务器初始化文件内容
cors = CORS() def create_app(): app = Flask(__name__) # Set up general app configurations app.config['SECRET_KEY'] = os.getenv('SECRET_KEY') app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///{DB_NAME}' app.config['SOCKETIO_SECRET_KEY'] = os.getenv('SOCKETIO_SECRET_KEY') # Set up Redis key-value db store configurations app.config['SESSION_TYPE'] = 'redis' app.config['SESSION_PERMANENT'] = False app.config['SESSION_USE_SIGNER'] = True app.config['SESSION_REDIS'] = Redis( host=os.getenv('REDIS_HOST'), port= os.getenv('REDIS_PORT'), # password=os.getenv('REDIS_PASSWORD'), --> USE PASSWORD IN PROD. db=os.getenv('REDIS_DB'), decode_responses=False ) app.redis_client = app.config['SESSION_REDIS'] db.init_app(app) csrf.init_app(app) socketio.init_app(app, logger=True, engineio_logger=True, cors_allowed_origins="*") cors.init_app(app, supports_credentials=True, allow_headers=[ 'Content-Type', 'X-CSRF-Token', 'Authorization', 'Access-Control-Allow-Origin', 'Access-Control-Allow-Credentials' ], resources={r"/api/*": {"origins": "http://localhost:5173/*"}}) from .auth import auth from website.views import views from .api import api_bp, initialize_routes # Intialize api routes initialize_routes() app.register_blueprint(views, url_prefix='/') app.register_blueprint(auth, url_prefix='/') app.register_blueprint(api_bp, url_prefix='/api') if not database_exists(): create_database(app) return app def database_exists(): return path.exists('website/' + DB_NAME) def create_database(app): with app.app_context(): db.create_all()
前端登出代码
const dispatch = useDispatch(); const navigate = useNavigate(); const csrfToken = useSelector((state: RootState) => state.csrfToken.value); console.log(csrfToken) // Handle logout const logout = async () => { try { await axiosInstance.post( '/auth/logout', {}, { withCredentials: true, headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrfToken, }, } ); console.log("post work") // Clear CSRF token from Redux after successful logout dispatch(clearCsrfToken()); // Redirect to login page upon successful logout navigate('/login'); } catch (error) { if (axios.isAxiosError(error)) { console.error('Logout error:', error.message); if (error.response) { console.error('Server response:', error.response.data); } } else if (error instanceof Error) { console.error('Unexpected error:', error.message); } } }; return logout; };
解决步骤
修正CORS源配置格式
当前resources中origins写了http://localhost:5173/*,这是错误格式,源不需要加/*,改成精确的源地址:resources={r"/api/*": {"origins": "http://localhost:5173"}}清理请求头配置
allow_headers中不需要包含Access-Control-Allow-Origin和Access-Control-Allow-Credentials,这两个是响应头,不是请求头,修改后:cors.init_app(app, supports_credentials=True, allow_headers=['Content-Type', 'X-CSRF-Token', 'Authorization'], resources={r"/api/*": {"origins": "http://localhost:5173"}})确保CORS中间件优先生效
检查cors.init_app的调用顺序,必须在蓝图注册之前执行,否则蓝图路由不会应用CORS配置。处理JWT装饰器的拦截响应
如果@JWTManager.token_required验证失败时直接返回响应,可能会绕过CORS中间件。可以在全局添加CORS中间件,或者确保所有错误响应都手动添加CORS头:# 示例:在错误响应中添加CORS头 response = make_response({'message': 'Invalid token: Please login again'}, 401) response.headers['Access-Control-Allow-Origin'] = 'http://localhost:5173' response.headers['Access-Control-Allow-Credentials'] = 'true' return response验证预检请求处理
浏览器会先发OPTIONS预检请求,确保Flask-CORS自动处理OPTIONS请求,或者手动添加OPTIONS方法支持。
内容的提问来源于stack exchange,提问作者user23776560

