概述
同源策略
同源策略(Sameoriginpolicy)是一种约定,它是 浏览器 最核心、最基本 的安全功能。
因此 跨域问题 仅仅存在于 浏览器,走出浏览器 例如 curl、postman 就不存在跨域了。
所谓同源(即指在同一个域)就是两个页面具有相同的 协议(protocol)
,主机(host)
和端口号(port)
。
跨域问题就是当一个请求url的协议、域名、端口三者之间任意一个与 当前页面url
不一致时出现的问题。
跨域脚本攻击
浏览器的同源策略 主要是为了防止 跨域脚本攻击
(Cross Site Scripting,简写作XSS
)。
跨域脚本攻击 是 Web应用程序 在将 数据输出 展示到网页的时候 存在问题,导致攻击者可以将对网站的正常功能造成影响甚至窃取或篡改用户个人信息,其诱发的主要原因 是没有 针对用 户输入来源的数据
做安全过滤处理。
跨域脚本攻击的危害性很大,因此开发人员需要注意对用户输入进行严格的过滤和转义,同时浏览器的同源策略也是一种防范跨域脚本攻击的重要手段。
例如,如果一个网站的输入字段没有进行适当的过滤和验证,攻击者可以在表单中插入 JavaScript 代码,当其他用户访问包含这些代码的页面时,该代码就会执行并实施攻击者的恶意意图,比如盗取用户的 cookie 信息、修改页面内容等。
两种请求
浏览器将CORS请求分成两类:简单请求
(simple request)和 非简单请求
(not-so-simple request)
简单请求
只要同时满足以下两大条件,就属于 简单请求。
1、 请求方法是以下三种方法之一:HEAD、GET、POST
2、 HTTP的头信息不超出以下几种字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限于以下三个值:application/x-www-form-urlencodedmultipart/form-datatext/plain
非简单请求
只要是不同时满足上面 两个条件,就属 非简单请求。
“预检” 请求
非简单请求 的CORS请求,会在正式通信之前,增加一次HTTP查询请求
,称为 “预检” 请求
(preflight)。
“预检” 请求 的 请求方法 是 OPTIONS
“预检” 请求 的 本质就是 浏览器 询问 服务器:
- 当前网页所在的
域名
是否在服务器的许可名单之中 - 当前网页 发出请求的
请求的方法
是否在服务器的许可名单之中 - 当前网页 发出请求的
请求头字段
是否在服务器的许可名单之中
只有都得到 服务器的 肯定答复,浏览器才会发出正式的XMLHttpRequest请求。
原理讲解
Access-Control-Allow-Origin
该配置是 服务器 回答 客户端 允许 哪些域名
可以跨域访问的。
网上大多在此处的写的是 *
,即 允许所有源,这样不安全。
可以指定自己前端的源,例:http://192.121.23.234:3157
Access-Control-Allow-Methods
该配置是 服务器 回答 客户端 允许 哪些请求方法
可以跨域访问的。
可以写常用的方法: GET, POST, PUT, DELETE, PATCH, OPTIONS。
没有写进来的请求方法会出现跨域。
Access-Control-Allow-Headers
该配置是 服务器 回答 客户端 允许 请求头中的哪些字段
可以跨域访问的。
因此需要注意,在前后端交互过程中 如果需要 自定义的请求头
,则需在此报备,否则 会报 跨域。
Access-Control-Max-Age
该配置 用来指定 当次 “预检” 请求 的 有效期
。
-
Access-Control-Max-Age 0
表示 每次异步请求 都发起 预检请求。 -
Access-Control-Max-Age 60
表示 该次异步请求 发起 预检请求 后的 60秒内,后续的 非简单请求 都不必在 二次发起 “预检” 请求。
OPTIONS
204
给 OPTIONS 请求 添加 204的返回,是为了处理在发送 非简单请求 时 出现拒绝访问的错误,所以服务器需要允许该方法。
Access-Control-Expose-Headers
该配置 指明 哪些 响应头字段
是可以 让 跨域请求 的 客户端看到的。
默认情况下,使用跨域访问时,浏览器只会返回 默认 6个 响应头字段:
- Cache-Control
- Content-Language
- Content-Type
- Expires
- Last-Modified
- Pragma
如果想要 客户端 能够访问其他的请求头,则必须使用 Access-Control-Expose-Headers 列出他们。
Access-Control-Allow-Credentials
该配置 设置是否可以允许发送 cookie
。
true表示 cookie 包含在请求中,false则相反,默认为false
, 也就是会说跨域的请求模式是不会自动携带cookie的。
如果项目需要用到 cookie 就需要设置该字段。CORS请求默认不发送Cookie和HTTP认证信息的,所以在此基础上,也需要在前端设置,这里以axios为例:
axios.defaults.withCredentials = true
具体实现
Nginx 中的跨域处理
# add_header Access-Control-Allow-Origin *;
add_header 'Access-Control-Allow-Origin' $http_origin;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, DELETE, PUT';
add_header 'Access-Control-Allow-Headers' 'Origin, x-requested-with, Content-Type, Accept, Authorization';
add_header 'Access-Control-Expose-Headers' 'Cache-Control, Content-Language, Content-Type, Expires, Last-Modified, Pragma';
add_header 'Access-Control-Allow-Credentials' 'true';if ($request_method = 'OPTIONS') {add_header 'Access-Control-Max-Age' 1728000;add_header 'Content-Type' 'text/plain; charset=utf-8';add_header 'Content-Length' 0;return 204;
}
Gin 中的跨域处理
package middlewareimport ("net/http""github.com/gin-gonic/gin"
)// 跨域中间件
func Cors() gin.HandlerFunc {return func(context *gin.Context) {method := context.Request.Methodcontext.Header("Access-Control-Allow-Origin", "*")context.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS")context.Header("Access-Control-Allow-Headers", "Content-Type, AccessToken, X-CSRF-Token, Authorization, Token, Wtt")context.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type")context.Header("Access-Control-Allow-Credentials", "true")if method == "OPTIONS" {context.AbortWithStatus(http.StatusNoContent)}context.Next()}
}
Flask 中的跨域处理
@app.after_request
def func_res(resp): res = make_response(resp)res.headers['Access-Control-Allow-Origin'] = '*'res.headers['Access-Control-Allow-Methods'] = 'GET,POST'res.headers['Access-Control-Allow-Headers'] = 'x-requested-with,content-type'return res
SpringBoot
public class CorsFilter implements Filter{@Overridepublic boolean doFilterInternal(HttpServletRequest request, HttpServletResponse response, final FilterChainfilterChain) throws ServletException, IOException{response.setHeader("Access-Control-Allow-Origin", "*");response.setHeader("Access-Control-Allow-Headers", "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,account_id,Origin,Accept,Authorization");response.setHeader("Access-Control-Allow-Credentials", "true");response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, HEAD");response.setHeader("Access-Control-Max-Age", "1728000");filterChain.doFilter(request, response);}
}
接下来说一下 其他角度 的 跨域处理办法
Vue 反向代理
/*
注意:1、如果使用的 uniapp 自带的网络请求框架, 配置代理则需要在 manifest.json中的 h5 选项中配置,这里 使用的是 第三方网络请求框架 flyio , 配置代理 需要在 vite.config.js 中配置。2、代理 捕获的是 本地项目启动 同源的 请求,例如: npm run dev 启动监听的是 localhost:5612, 那么代理获取的请求的范围 就是 请求地址是 localhost:5612, 然后在通过路径进行 具体捕获。proxy: {'/h5_api': { // 匹配 本地 请求路径 localhost:5173/h5_apitarget: 'http://192.168.104.60:8668', // 代理的目标地址changeOrigin: true, // 开发模式, 默认的origin是 真实的 secure: false, // 是否https接口ws: false, // 是否带来websockets// localhost:5173/h5_api/test ==> http://192.168.104.60:8663/wtt/testrewrite: (path) => path.replace('/h5_api', '/wtt'),},},*/proxy: {'/api_default': {target: 'http://121.4.225.175:8000', changeOrigin: true,secure: false,rewrite: (path) => path.replace('/api_default', ''),},},},
Nginx 的重定向
# /api_defaul/aaa/bbb ====> http://localhost:8033/aaa/bbb
location /api_defaul{proxy_set_header Host $proxy_host;proxy_set_header Real-IP $remote_addr;proxy_set_header name bbb;rewrite ^/api_default/(.*)$ /$1 break; proxy_pass http://localhost:8033;
}
Jsonp
最早的解决方案,利用script标签可以跨域的原理实现。
限制:
- 需要服务的支持
- 只能发起GET请求
原理:
Jsonp其实就是一个跨域解决方案。Js跨域请求数据是不可以的,但是js跨域请求js脚本是可以的。可以把数据封装成一个js语句,做一个方法的调用。跨域请求js脚本可以得到此脚本。得到js脚本之后会立即执行。可以把数据做为参数传递到方法中。就可以获得数据。从而解决跨域问题。