目录
方案一:控制器加特定请求头【Access-Control-Allow-Origin】
方案三:拦截器加特定请求头【Access-Control-Allow-Origin】
*方案三有一个简化方法,就是直接在【spring-mvc.xml】配置cors,如方案四
(2)如果需要携带凭证(如Cookie),那么除了前端需要做相应配置外,后端也要做修改
*配置cors后,若因业务需要再增加拦截器(如令牌检验)后,跨域问题会重新出现
跨域
前后端分离项目时,首先浏览器就会报如下错误
Access to XMLHttpRequest at 'http://localhost:54321/projectName' from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
一、概念
-
违反同源策略现象被称为跨域
二、同源策略
浏览器的一种保护措施,主要用于预防浏览器通过JS代码进行跨站点的访问操作。
三、违反规则
-
链接(url)三要素其中一个不同均为违反同源策略
-
url: 协议://主机地址:端口
协议://主机地址:端口 例: http://localhost:8080 http://localhost:8080 与 https://localhost:8080 --协议不同 http://localhost:8080 与 http://127.168.0.2:8080 --主机地址不同 http://localhost:8080 与 http://localhost:8088 --端口不同
四、解决方案
-
JSONP(JSON with Padding):一些标签比如
<scripti>
,<link>
,<a>
,<img>
,这样的获取静态资源的标签是没有跨域限制的 -
CORS(Cross-Origin Resource Sharing):即跨域资源共享。是一种基于HTTP头的机制,该机制通过允许服务器标示除了它自己以外的其他源域、协议或端口),使得浏览器允许这些源访问加载自己的资源
跨域问题CORS解决方案
方案一:控制器加特定请求头【Access-Control-Allow-Origin】
@RequestMapping("/pathDemo")
public String methodDemo(HttpServletResponse response){
response.setHeader("Access-Control-Allow-Origin","*");
//*表示所有人都可以,也可以设置成固定站点
return "success";
}
方案二:注解@CrossOrigin
/**
* @CorssOrigin 注解相当于自动给每个响应加上方案一的特定请求头
* 该注解只能用在Controller中
*/
@RestController
@CrossOrigin //加在此处,整个控制器所有方法都生效
public class TestController{
@RequestMapping("/pathDemo")
@CrossOrigin //加在此处,当前方法生效
public String methodDemo(){
return "success";
}
}
*方案一和方案二的优缺点
-
优点:随用随加,使用上比较灵活
-
缺点:解决跨域问题的范围太小
方案三:拦截器加特定请求头【Access-Control-Allow-Origin】
-
在interceptor包中创建类
/**
* 拦截器类
*/
public class CorsInterceptor implements handlerInterceptor{
@Override
public boolean preHandle(HttpServletRequest requst, HttpServletResponse response, Object Handler)throws Exception {
response.setHeader("Access-Control-Allow-Origin","*");
}
}
-
在【spring-mvc.xml】中配置创建的拦截器
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<!-- /**代表所有路径,及其下的所有子路径-->
<mvc:exclude-mapping path="/log"/>
<!-- 使用exclude-mapping 排除登录请求 -->
<bean id="corsInterceptor" class="org.w.interceptor.CorsInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
*方案三有一个简化方法,就是直接在【spring-mvc.xml】配置cors,如方案四
方案四:配置【mvc:cors】
(1)跨域请求默认无法携带凭证,不需要携带凭证的情况下
<!-- 全局配置 -->
<!-- 其中:
allowed-origins 为允许的访问源地址(*为所有请求)
allowed-methods 为允许访问方式
allowed-headers 为允许携带的请求头
-->
<mvc:cors>
<mvc:mapping path="/**"
allowed-origins="*"
allowed-methods="*"
allowed-headers="*"
/>
</mvc:cors>
(2)如果需要携带凭证(如Cookie),那么除了前端需要做相应配置外,后端也要做修改
<!-- 全局配置 -->
<!-- 其中:
allowed-origins 为允许的访问源地址(此处不能使用*,而是"")
allowed-origin-patterns 为自动将访问源地址加入允许列表中(即响应请求头中)
allow-credentials 为允许携带凭证(如cookies)(允许时origins不能为*)
-->
<mvc:cors>
<mvc:mapping path="/**"
allowed-origin-patterns="*"
allow-credentials="true"
allowed-origins=""
allowed-headers="*"
/>
</mvc:cors>
*配置cors后,若因业务需要再增加拦截器(如令牌检验)后,跨域问题会重新出现
Spring加拦截器后重新出现的跨域问题
案例一:拦截器验证请求头(如Token)
出现问题
-
登录请求可以正常访问,而其他被拦截的请求全部报错
-
拦截器收到的请求头header里token为null
-
在拦截器处理token为null时不会返回非200状态码时,前端浏览器仍然返回CORS:ERR错误
分析
-
查看浏览器发送的请求,会发现浏览器发送了两个请求,其中一个是不带token的OPTIONS请求
-
查看CORS介绍后发现,原来CROS复杂请求时会首先发送一个OPTIONS请求做嗅探,来测试服务器是否支持本次请求,请求成功后才会发送真实的请求;
-
而OPTIONS请求不会携带任何数据,导致这个请求不符合我们拦截器的校验规则被拦截了,直接返回了状态码,响应头中也没携带解决跨域需要的头部信息,进而出现了跨域问题
-
将拦截器先关闭,查看正常情况下(即仅仅配置了mvc:cors)发出的预检请求如下:
Accept: */*
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: zh-CN,zh;q=0.9
Access-Control-Request-Headers: token
Access-Control-Request-Method: GET
Cache-Control: no-cache
Connection: keep-alive
Host: localhost:54321
Origin: http://localhost:8080
Pragma: no-cache
Referer: http://localhost:8080/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36
-
后端配置的cors跨域会自动回复响应如下:
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: token
Access-Control-Allow-Methods: GET,HEAD,POST
Access-Control-Allow-Origin: http://localhost:8080
Access-Control-Max-Age: 1800
Allow: GET, HEAD, POST, PUT, DELETE, OPTIONS, PATCH
Connection: keep-alive
Content-Length: 0
Date: Sat, 09 Mar 2024 15:13:36 GMT
Keep-Alive: timeout=20
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
-
由此可以看出,CORS的预检请求是通过请求中携带了一些特定的请求头(如Access-Control-Request-Headers,Access-Control-Request-Method,Origin)来告知服务器,在正式请求中,将会携带的请求头、请求方式、以及请求源。
-
而响应中,必须根据特定请求头做出响应,例如上述的Access-Control-Allow-Headers、Access-Control-Allow-Methods、Access-Control-Allow-Origin。当前端浏览器接收到这些特定的响应头后,这次预检请求才算成功。
-
而加了拦截器后,拦截器会将预检请求当作正常请求对待,回复的自然不包含以上这些CORS专属请求头了。那么浏览器在接收到这个响应后,会认为服务器不接收本次跨域请求,那么正式请求就不会发送,自然会报CORS:ERR错误了。此时后端只收到了一个不带token的预检请求,根本没有进行后续操作,所以后端数据自然也到不了前端。
解决方案
-
拦截器将OPTIONS方法的响应增加允许跨域的响应头
package org.w.interceptor;
/**
* 店铺员工拦截器
*/
public class StaffInterceptor implements HandlerInterceptor {
/**
* 前置拦截器(解析请求头token)
* */
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (request.getMethod().equals("OPTIONS")){
System.err.println("request method is OPTIONS");
response.setHeader("Access-Control-Allow-Origin","*");//允许所有访问源
response.setHeader("Access-Control-Allow-Headers","*");//允许携带所有请求头
response.setHeader("Access-Control-Max-Age","1800");//可选,在1800秒内同源请求无须预检
return false;//若拦截器下控制器接口中没有使用OPTIONS方法,则可以直接将其拦截
}
String token = request.getHeader("token");
//后续代码省略
return HandlerInterceptor.super.preHandle(request, response, handler);
}
}
-
springmvc.xml文件配置拦截器和CORS
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/staff/**"/>
<bean id="staffInterceptor" class="org.w.interceptor.StaffInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
<!-- 全局配置 -->
<!-- 其中:
allowed-origins 为允许的访问源地址(*为所有请求)
allowed-methods 为允许访问方式
allowed-headers 为允许携带的请求头
-->
<mvc:cors>
<mvc:mapping path="/**"
allowed-origins="*"
allowed-methods="*"
allowed-headers="*"
/>
</mvc:cors>
经测试,前端响应恢复正常
案例二:在案例一基础上,需要携带凭证(如Cookie)
-
前端axios请求需要进行相应默认设置
axios.defaults.withCredentials = true; //开启凭证携带功能
-
后端拦截器配置(需要注意的是,前端携带凭证设置开启后,后端无法再用【
*
】号配置允许的访问源和允许携带的请求头了)
package org.w.interceptor;
/**
* 店铺员工拦截器
*/
public class StaffInterceptor implements HandlerInterceptor {
/**
* 前置拦截器(解析请求头token)
* */
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (request.getMethod().equals("OPTIONS")){
System.err.println("staff interceptor --- >request method is OPTIONS");
String origin = request.getHeader("Origin");
System.out.println("origin="+origin);
response.setHeader("Access-Control-Allow-Origin",origin);//允许预检请求的访问源
response.setHeader("Access-Control-Allow-Credentials","true");//允许携带凭证
response.setHeader("Access-Control-Allow-Headers","token,Content-Type");//允许token,Content-Type请求头
response.setHeader("Access-Control-Max-Age","1800");//可选,在1800秒内同源请求无须预检
return false;//若拦截器下控制器接口中没有使用OPTIONS方法,则可以直接将其拦截
}
String token = request.getHeader("token");
//后续代码省略
return HandlerInterceptor.super.preHandle(request, response, handler);
}
}
-
springmvc.xml文件配置
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/staff/**"/>
<bean id="staffInterceptor" class="org.w.interceptor.StaffInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
<!-- 全局配置 -->
<!-- 其中:
allowed-origins 为允许的访问源地址(此处不能使用*,而是"")
allowed-origin-patterns 为自动将访问源地址加入允许列表中(即响应请求头中)
allow-credentials 为允许携带凭证(如cookies)(允许时origins不能为*)
-->
<mvc:cors>
<mvc:mapping path="/**"
allowed-origin-patterns="*"
allow-credentials="true"
allowed-origins=""
allowed-headers="*"
/>
</mvc:cors>
总结
由此可以看出,springmvc中,拦截器和CORS配置非前后关系,而是平行关系,即通过拦截器进入控制层的,不会再通过CORS配置。所以若一定要使用拦截器,可以将所有CORS的配置都放在对应拦截器中进行配置。此时只要针对预检请求进行对应的响应请求头的设置即可
跨域预检请求相关请求头
请求头 | 说明 |
---|---|
Origin | 请求的源URI,无论是否跨域都必然会发送 |
Access-Control-Request-Method | 实际请求方法 |
Access-Control-Request-Headers | 实际请求携带的请求头 |
跨域预检请求相关响应头
响应头 | 说明 |
---|---|
Access-Control-Allow-Origin | 允许发个文该资源的外域URI,若允许携带凭证,则不可为【* 】 |
Access-Control-Expose-Headers | 指定XMLHttpRequest的getResponseHeader可以访问到的响应头(即响应暴露的响应头) |
Access-Control-Max-Age | 指定预检请求缓存时间,之后同源请求在缓存时间内无需预检 |
Access-Control-Allow-Credentials | 是否允许浏览器读取response的内容,即是否允许携带凭证(如Cookie) |
Access-Control-Allow-Methods | 实际请求允许使用的HTTP方法 |
Access-Control-Allow-Headers | 实际请求允许携带的请求头 |
参考资料
学会Spring Mvc 跨域你只需要看完这一篇-腾讯云开发者社区
Access-Control-Allow- 设置跨域资源共享CORS详解-CSDN博客
SpringBoot加了拦截器后出现的跨域问题解析 - 掘金 (juejin.cn)
【http】2、http request header Origin 属性、跨域 CORS、同源、nginx 反向代理、预检请求_requesthead 中的 origin-CSDN博客