Angular17调用Go Gorilla/Mux API遇CORS错误:响应无HTTP成功状态
CORS错误排查与解决方案:预检请求无HTTP OK状态
错误信息
Access to XMLHttpRequest at 'http://localhost:8082/login' from origin 'http://localhost:4200' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.
问题原因
1. Go后端未正确处理OPTIONS预检请求
浏览器发送跨域请求前会先发送OPTIONS预检请求验证权限,但当前Go中间件仅设置CORS响应头,未专门处理OPTIONS请求:
- 当路由未定义OPTIONS方法的处理器时,Gorilla/Mux会返回404状态码
- 预检请求要求必须返回200/204的OK状态,否则浏览器会拦截后续请求
2. Angular拦截器存在两处问题
- 错误设置请求头:在请求中添加
Access-Control-Allow-Origin,该字段是服务器响应头,前端无需设置,浏览器会忽略该请求头,甚至可能干扰预检逻辑 - 逻辑漏洞:
- 无token时,
router.navigate后未终止请求流,后续代码仍会执行并发送无效请求 - token过期时,使用
subscribe处理刷新token的异步操作,无法正确将更新后的请求流返回给拦截器,导致后续代码覆盖Authorization头,原请求会提前发送
- 无token时,
解决方案
针对Go后端:修改CORS中间件处理OPTIONS请求
修改中间件,检测到OPTIONS请求时直接返回200 OK,无需传递到后续路由处理器:
func jsonContentTypeMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // 设置CORS响应头 w.Header().Set("Content-Type", "application/json") w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, DELETE") // 移除重复的Access-Control-Allow-Headers,仅保留允许的请求头 w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With") // 专门处理OPTIONS预检请求,直接返回200状态 if r.Method == http.MethodOptions { w.WriteHeader(http.StatusOK) return } next.ServeHTTP(w, r) }) }
针对Angular拦截器:修复逻辑与请求头设置
移除无效请求头,修复异步逻辑,确保请求流正确返回:
@Injectable() export class HttpInterceptor implements HttpInterceptor { constructor(private router: Router, private authService: AuthService) {} intercept(req: HttpRequest<any>, nextHandler: HttpHandler): Observable<HttpEvent<any>> { const authToken = StorageUtil.getAuthToken(); let request = req.clone({ setHeaders: { "Content-Type": "application/json" // 移除Access-Control-Allow-Origin,该字段属于响应头,前端无需设置 } }); // 无有效token时,跳转登录并终止请求流 if (!authToken) { this.router.navigate(['/auth/login']); return throwError(() => new Error('Você precisa estar logado')); } // token过期时,通过switchMap处理异步刷新逻辑,确保请求流正确传递 if (StorageUtil.isTokenExpired()) { return this.authService.refreshToken().pipe( switchMap((resp) => { const accessToken = resp.data.accessToken as string; const updatedRequest = request.clone({ setHeaders: { Authorization: `Bearer ${accessToken}` } }); return nextHandler.handle(updatedRequest); }), catchError(() => { // 刷新失败时跳转登录 this.router.navigate(['/auth/login']); return throwError(() => new Error('Token过期且刷新失败')); }) ); } // token有效,添加Authorization头后发送请求 request = request.clone({ setHeaders: { Authorization: `Bearer ${authToken}` } }); return nextHandler.handle(request); } }
内容的提问来源于stack exchange,提问作者rafaelpadu
相关产品推荐
相关产品推荐

