目录现象原因浏览器同源策略导致结果:解决方案跨源资源共享(CORS)各个端解决方法:后端:方式1:重载WebMvcConfigurer方法方式2:配置监听CorsFilter方式3:相关类上加注解@CrossOrigin注意事项:Nginx解决:情况1:前端解决:
现象
本人身份:后端
今天部署线上环境前端代码时,发生了如下报错:
AccesstoXMLHttpRequestat'http://192.168.1.11:8081/api/v1/sys/auth/login'fromorigin'http://192.168.1.8:8101'hasbeenblockedbyCORSpolicy:Responsetopreflightrequestdoesn'tpassaccesscontrolcheck:No'Access-Control-Allow-Origin'headerispresentontherequestedresource.
经典的跨域问题,=探究一下这个现象的本质以及从3个端(后端、代理、前端)分别该怎么处理这个问题
这个问题其实在开发环境是不会出现的,当把前端代码打包好后部署到线上环境,用Nginx跑起来后,发生了这个问题,发生这个问题的根本原因是由于浏览器的同源策略
原因
浏览器同源策略
同源策略(SameOriginPolicy)是一种安全策略,它是浏览器最核心也是最基本的安全功能。同源策略会阻止一个域的javascrip脚本和另一个域的内容进行交互,是用于隔离潜在恶意文件的关键安全机制;关于这一点我们后面会举例说明。如果缺少了同源策略浏览器的安全使用会受到很大的影响。可以说web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。
同源的三个条件:
域名、协议、端口都相同
假设URLA:http://192.168.1.8:8080/
下列URL与A是否同源比较
B | https://192.168.1.8:8080/
| 不同源,协议不同,一个是http,一个是https |
C | http://192.168.1.8:8081/ | 不同源,端口号不同 |
D | http://192.168.1.9:8080/ | 不同源,IP不同 |
E | http://192.168.1.8:8080/api | 同源 |
导致结果:
- 不能获取不同源的cookie,LocalStorage和indexDB
- 不能获取不同源的DOM()
- 不能发送不同源的ajax请求(可以向不同源的服务器发起请求,但是返回的数据会被浏览器拦截)
上述的现象就是因为ajax请求其实已经到了后端,后端也已经处理,但是因为浏览器的同源策略导致拒绝接收数据,所以其实这个现象在移动端不会出现,只会在浏览器出现
解决方案
其他方案如前端jsonp之类的方法,都有或大或小的弊端,看了比较久,介绍下面的方法
跨源资源共享(CORS)
Cross-OriginResourceSharing跨资源共享,作为W3C的标准,是一种基于HTTP头的机制,该机制通过允许服务器标示除了它自己以外的其它origin(域,协议和端口),使得浏览器允许这些origin访问加载自己的资源。跨源资源共享还通过一种机制来检查服务器是否会允许要发送的真实请求,该机制通过浏览器发起一个到服务器托管的跨源资源的"预检"请求。在预检中,浏览器发送的头中标示有HTTP方法和真实请求中会用到的头。是跨域ajax的根本解决方法,允许任何类型的请求。
客户端和服务器之间使用CORS首部字段来处理权限:
先看一个http的请求头和响应头
请求头:
GET/resources/public-data/HTTP/1.1Host:bar.otherUser-Agent:Mozilla/5.0(Macintosh;IntelMacOSX10.14;rv:71.0)Gecko/20100101Firefox/71.0Accept:text/html,application/xhtmlxml,application/xml;q=0.9,*/*;q=0.8Accept-Language:en-us,en;q=0.5Accept-Encoding:gzip,deflateConnection:keep-aliveOrigin:https://foo.example请求首部字段Origin表明该请求来源于http://foo.example
。而现在的VUE项目中都会默认带上这个请求头。
响应头:
HTTP/1.1200OKDate:Mon,01Dec200800:23:53GMTServer:Apache/2Access-Control-Allow-Origin:*Keep-Alive:timeout=2,max=100Connection:Keep-AliveTransfer-Encoding:chunkedContent-Type:application/xml服务端返回的Access-Control-Allow-Origin:*
表明,该资源可以被任意外域访问,当然,不建议配置*,当响应的是附带身份凭证的请求时,即withCredentials为true时,服务端必须明确Access-Control-Allow-Origin
的值,而不能使用通配符“*
”。
各个端解决方法:
后端:
后端是基于Springboot开发,提供了这么几种方式来配置cros
为了灵活配置,先做一个配置文件:application-security.yml
lc:cors:allow-credential:trueallow-mapping:'/**'allow-method:GET,HEAD,POST,PUT,DELETE,OPTIONS,PATCHallow-origin-pattern:http://192.168.1.11:8101,http://localhost:3002allow-header:'*'max-age:1800properties文件
@Data@ConfigurationProperties(prefix="lc.cors")publicclassCorsProperties{@NotNullprivateBooleanenable=true;privateStringallowMapping;privateList<String>allowOriginPattern;privateList<String>allowMethod;privateList<String>allowHeader;@ApiModelProperty("单位:秒")privateLongmaxAge;privatebooleanallowCredential=true;}方式1:重载WebMvcConfigurer方法
这种我感觉是最合适的
@Configuration@EnableConfigurationProperties(CorsProperties.class)publicclassCorsConfig{@AutowiredprivateCorsPropertiescorsProperties;@BeanpublicWebMvcConfigurercorsConfigurer(){returnnewWebMvcConfigurer(){@OverridepublicvoidaddCorsMappings(CorsRegistryregistry){registry.addMapping(corsProperties.getAllowMapping()).allowedOriginPatterns(corsProperties.getAllowOriginPattern().toArray(newString[0])).allowCredentials(corsProperties.isAllowCredential()).allowedMethods(corsProperties.getAllowMethod().toArray(newString[0])).allowedHeaders(corsProperties.getAllowHeader().toArray(newString[0])).maxAge(corsProperties.getMaxAge());}};}}方式2:配置监听CorsFilter
@BeanpublicCorsFiltercorsFilter(){UrlBasedCorsConfigurationSourcesource=newUrlBasedCorsConfigurationSource();source.registerCorsConfiguration("/**",buildConfig());returnnewCorsFilter(source);}privateCorsConfigurationbuildConfig(){CorsConfigurationcorsConfiguration=newCorsConfiguration();corsConfiguration.addAllowedOriginPattern("*");corsConfiguration.addAllowedHeader("*");corsConfiguration.addAllowedMethod("*");corsConfiguration.setAllowCredentials(true);returncorsConfiguration;}方式3:相关类上加注解@CrossOrigin
@CrossOrigin@Controller@RequestMapping("/problem")@ResponseBodypublicclassProblemControllerimplementsSerializable{}注意事项:
allow-origin-pattern这个属性配置时,尽量不要配置*,也许考虑到联调和线上环境的ip都要添加上
Nginx解决:
nginx解决主要是这个思路,nginx的实际原理就是配置一个代理路径替换实际的访问路径,使得浏览器认为访问的资源都是属于相同协议,域名和端口的,而实际访问的并不是代理路径,而是通过代理路径找到实际路径进行访问,所以,不妨将nginx看作是给浏览器的一种障眼法
情况1:
因为首先访问的是nginx的index.html,这种情况是不会产生跨域问题的,但是访问接口时,因为是调用的后端的接口,所以会产生跨域问题,这时候,我们将所有的接口配置一个代理,转发到实际的后端接口地址。
比如我们nginx配置的前端访问地址为:http://192.168.1.11:8080/api/
后端的接口地址为:http://192.168.1.11:8081/api/
下面是nginx的配置文件nginx.conf,其实就是访问首页时,访问的还是index.html,但是访问具体的url时,nginx帮我们做一层转发,这样子就可以了。也不用加什么【add_headerAccess-Control-Allow-Origin*;】这样的响应头,因为此时nginx的代理会让浏览器认为是同源的路径
server{listen8101;client_max_body_size20m;location/{root/usr/share/nginx/html;indexindex.htmlindex.htm;}location/api/{proxy_passhttp://192.168.1.11:8081/api/;}}前端解决: