前两天,看了看前端小伙伴的代码,发现他在访问API的时候,并不是直接访问API域名的,而是访问前端域名下的/api路径,然后在部署的服务器上,用nginx把/api路径的请求,转发到API域名那里。我感觉很奇怪,为什么要这样做?本来可以直接访问的东西,为啥还要再绕一圈?
后来搜了搜,发现了同源策略(SOP, Same-origin policy )这种东西。默认情况下,一个网站只能访问自己的域名下的接口,准确的说,只有协议(http,https),域名,端口全都一致的情况下,才可以访问。正是因为这个原因,前端小伙伴才选择将API请求通过nginx转发到真实的API服务器。
我们知道浏览器是个公共场所,用户会访问很多网站。假如没有同源策略,A网站就可以访问B网站的接口或者网页,因为访问B网站的时候会带上B的Cookie信息,所以可以获取到几乎所有的用户数据,这时B网站就毫无安全可言了,所以需要用同源策略来隔离。同源策略只适用于JS代码,你在网页的HTML里还是可以通过img,script等标签获取静态资源的(比如你放在CDN上的js或图片),或者通过form标签提交表单。
我还想跨域怎么办?跨域资源共享( CORS ) 是这样一种可以实现跨域的机制,大部分浏览器都支持。API服务器通过返回特别的HTTP Header,来告诉浏览器,谁可以访问这个服务器。
原来跨域是通过服务器进行控制的:scream:,也就是说,你只有请求了之后,才能知道是否被允许。CORS将请求分为两种,一种是简单请求,一种是非简单请求,他俩的区别你可以通过 这里 查看。对于非简单请求,浏览器会用HTTP OPTIONS方法先发出一个预检请求(preflight request),服务器同意了之后,再发出实际的请求。而简单请求则没有这一过程。
服务器返回的Header大致有以下几种:
Access-Control-Allow-Origin
值写成 http://example.com
这种形式指明谁可以访问,或者传 *
表示谁都可以 Access-Control-Expose-Headers
哪些Header能在响应中列出,默认只有 6个Header 可以。服务器其实可以返回很多Header,但是浏览器暴露给js的Header是经过筛选的 Access-Control-Allow-Credentials
有点复杂,我还不太懂:joy: Access-Control-Max-Age
预检请求的结果的缓存时间,单位为秒 Access-Control-Allow-Methods
预检请求中指明允许的HTTP方法 Access-Control-Allow-Headers
预检请求中指明允许的Header 如果你仔细研究协议的内容,会晕掉的,别管那么多,毕竟大多数情况下,我们只关心 Access-Control-Allow-Origin
,而且没必要控制的那么细。接下来看看Spring Boot中是怎么支持CORS的。
你可以创建一个 WebMvcConfigurer
类型的Bean,或者让 @Configuration
配置类实现 WebMvcConfigurer
接口,来配置全局的CORS支持:
默认情况下,上图的配置,会允许所有的网站访问,允许所有的header,允许GET、POST和HEAD请求,maxAge设置为30分钟。当然你可以自定义很多参数:
通常我会把 allowedOrigins
设置为前端服务器的域名(包括http和https),会把 allowedMethods
设置为允许所有方法。简单易懂好控制。
Spring还支持使用 @CrossOrigin
注解来更精细地控制跨域的粒度,比如加在 @Controller
类上,控制对应路径的跨域设置,更可以添加到具体的控制器方法上,如果你有需要,可以研究研究。Spring还包含了 CorsFilter
,用来实现跟全局配置CORS一样的效果,Spring Security也使用了 CorsFilter
完成跨域的需求,使用时请注意。
CORS的作用范围也只是浏览器,如果你用curl或者postman或者普通程序都是可以访问的。所以我在想,到底CORS有没有意义,毕竟别人知道了你的API之后,随便写个程序都可以调用了,还用在乎你CORS不CORS?如果是在浏览器端,你的用户token存在LocalStorage里的,就算别的网站可以访问你的API,也获取不到你的LocalStorage里的东西呀,也是安全的。后来我想了想,别人可以写个网站调用你的接口来提供服务,这样流量就是别人的了。所以 allowedOrigins
还是设置一下比较好。
我在后端代码里配置完CORS以后,前端小伙伴就不用再做转发的工作了。项目的工作逻辑清晰度+1,开发和部署环境的设置复杂度-1。你或者你的后端小伙伴看了本文还是不懂?那就看看Spring的文档吧: Spring-MVC-CORS