CORS跨域处理与安全头设置:前后端分离的安全基石
发布时间:2026/8/16 16:08:12 作者:尧图编辑部 阅读量:1,286

# CORS跨域处理与安全头设置:前后端分离的安全基石摘要: 本篇从CORS预检请求原理讲起用Gin实现CORS中间件和常用安全响应头CSP、HSTS、X-Frame-Options等分享OPTIONS预检请求被认证中间件拦截导致跨域失效的踩坑经历对比gin-contrib/cors与手写中间件的取舍。开篇故事去年我们组前后端分离重构前端跑在localhost:3000后端API在localhost:8080。前端刚调第一个接口就炸了浏览器控制台报Access-Control-Allow-Origin缺失。前端同事跑过来说你这后端是不是坏了。我当时对CORS理解不深以为加个Access-Control-Allow-Origin: *就行。加上之后GET请求通了但POST请求还是报错。查了半天才知道有预检请求这回事浏览器对非简单请求会先发一个OPTIONS请求探路服务端返回允许的方法和头部后才发真正的请求。我的接口根本没处理OPTIONS方法预检请求直接404了。前端的POST自然就发不出去。这个坑折腾了我大半天今天就把CORS和安全头一起聊清楚。一、CORS原理与预检请求CORS全叫跨域资源共享。浏览器有个同源策略协议、域名、端口任一不同就算跨域。跨域请求分两种。简单请求直接发浏览器在响应头里找Access-Control-Allow-Origin。非简单请求比如POST JSON、带自定义头会先发OPTIONS预检服务端确认允许后才发真实请求。// 简单请求: GET, 无自定义头, Content-Type为text/plain // 非简单请求: PUT/DELETE, 或Content-Type为application/json // 非简单请求触发预检, 流程如下: // 1. 浏览器发 OPTIONS 请求 // 2. 服务端返回允许的方法、头部、是否带凭证 // 3. 浏览器检查通过后发真实请求二、Gin CORS中间件手写一个CORS中间件处理预检请求和真实请求的响应头。packagemainimport(net/httpgithub.com/gin-gonic/gin)// CorsMiddleware CORS跨域中间件funcCorsMiddleware()gin.HandlerFunc{returnfunc(c*gin.Context){// 允许的源, 生产环境用具体域名origin:c.Request.Header.Get(Origin)allowOrigin:http://localhost:3000// 设置CORS响应头c.Header(Access-Control-Allow-Origin,allowOrigin)// 允许携带Cookiec.Header(Access-Control-Allow-Credentials,true)// 允许的请求方法c.Header(Access-Control-Allow-Methods,GET, POST, PUT, DELETE, OPTIONS)// 允许的请求头c.Header(Access-Control-Allow-Headers,Content-Type, Authorization, X-Request-ID)// 预检请求缓存时间, 12小时内不重复预检c.Header(Access-Control-Max-Age,43200)_origin// 实际项目根据origin白名单动态设置// 预检请求直接返回204ifc.Request.Methodhttp.MethodOptions{c.AbortWithStatus(http.StatusNoContent)return}c.Next()}}funcmain(){r:gin.Default()// CORS中间件必须在路由前注册r.Use(CorsMiddleware())// 全局生效r.GET(/api/data,func(c*gin.Context){c.JSON(http.StatusOK,gin.H{data:hello})// GET响应})r.POST(/api/data,func(c*gin.Context){c.JSON(http.StatusOK,gin.H{message:created})// POST响应})r.Run(:8080)// 启动服务}用gin-contrib/cors库可以少写代码配置更灵活。import(timegithub.com/gin-contrib/corsgithub.com/gin-gonic/gin)funcmain(){r:gin.Default()// 使用gin-contrib/cors库, 配置更简洁r.Use(cors.New(cors.Config{AllowOrigins:[]string{http://localhost:3000},AllowMethods:[]string{GET,POST,PUT,DELETE},AllowHeaders:[]string{Content-Type,Authorization},AllowCredentials:true,MaxAge:12*time.Hour,// 预检缓存}))r.Run(:8080)}三、安全响应头设置除了CORS还有一组安全头要加。这些头告诉浏览器执行安全策略防XSS、防点击劫持、强制HTTPS。// SecurityHeaders 安全响应头中间件funcSecurityHeaders()gin.HandlerFunc{returnfunc(c*gin.Context){// 防止点击劫持, 禁止页面被iframe嵌套c.Header(X-Frame-Options,DENY)// 防MIME类型嗅探, 浏览器不猜测Content-Typec.Header(X-Content-Type-Options,nosniff)// XSS过滤, 检测到攻击时阻止页面渲染c.Header(X-XSS-Protection,1; modeblock)// 强制HTTPS, 1年内所有请求走HTTPSc.Header(Strict-Transport-Security,max-age31536000; includeSubDomains)// 内容安全策略, 只允许加载同源资源c.Header(Content-Security-Policy,default-src self)// Referer策略, 只发源不发完整路径c.Header(Referrer-Policy,strict-origin-when-cross-origin)c.Next()}}funcmain(){r:gin.Default()r.Use(CorsMiddleware())// CORS跨域r.Use(SecurityHeaders())// 安全头中间件// 业务路由, 内联handlerr.GET(/api/data,func(c*gin.Context){c.JSON(http.StatusOK,gin.H{data:ok})// 返回数据})r.Run(:8080)// 启动服务}逐个解释一下这些头的作用。X-Frame-Options设为DENY别人就没法用iframe把你的页面嵌进去做点击劫持。Strict-Transport-Security让浏览器记住这个域名必须走HTTPS即使用户输入http也会自动跳https。Content-Security-Policy是最强的一个default-src self表示只允许加载同源资源外部CDN、内联脚本全被拦。四、独家踩坑:预检请求被认证中间件拦截说一个我踩过的坑。有一次我把CORS中间件和JWT认证中间件都挂上了结果前端还是报跨域错误。浏览器Network里看到OPTIONS请求返回401。原因是中间件注册顺序的问题。我的JWT中间件对所有请求做认证检查包括OPTIONS预检请求。预检请求不带Authorization头所以直接被认证中间件拦返回401。浏览器拿到401认为预检失败真实请求就不发了。// 错误写法: 认证中间件拦截了OPTIONSfuncmain(){r:gin.Default()r.Use(CorsMiddleware())auth:r.Group(/api)auth.Use(JWTAuth())// OPTIONS请求也走认证, 返回401auth.GET(/data,func(c*gin.Context){c.JSON(http.StatusOK,gin.H{data:ok})})auth.POST(/data,func(c*gin.Context){c.JSON(http.StatusOK,gin.H{msg:created})})r.Run(:8080)}修复方法是在认证中间件里放行OPTIONS请求或者在CORS中间件里直接Abort掉OPTIONS。// 修复: 认证中间件放行OPTIONS预检funcJWTAuth()gin.HandlerFunc{returnfunc(c*gin.Context){// 预检请求直接放行ifc.Request.Methodhttp.MethodOptions{c.Next()return}// 正常的认证逻辑...c.Next()}}另一个方案更干净把CORS中间件放在所有路由组之前在CORS中间件里Abort掉OPTIONS请求。这样预检请求不会进入认证中间件。但要注意Access-Control-Allow-Origin不能用*通配符配合Allow-Credentials: true浏览器会拒绝。必须用具体的origin或者动态读取请求头里的Origin。五、对比分析与总结方案优点缺点适用场景手写CORS中间件完全可控理解原理配置繁琐容易漏头学习理解gin-contrib/cors配置简洁功能完善额外依赖生产推荐Nginx代理同源后端零改动运维配置已有NginxCORS和安全头是前后端分离项目的安全基石。CORS解决跨域访问安全头防御XSS和点击劫持。核心原则是CORS中间件最早注册认证中间件放行OPTIONS预检请求。安全头里CSP最强也最复杂生产环境建议从宽松策略开始逐步收紧。下篇我们聊Swagger/OpenAPI文档自动生成让API文档永远跟代码同步。