跨域请求在前后端分离的项目中也算常规操作了
CORS 是一个W3C标准,全称是“跨域资源共享”(Cross-origin resource sharing)。它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。 由于浏览器同源策略(同源策略,它是由Netscape提出的一个著名的安全策略。现在所有支持JavaScript 的浏览器都会使用这个策略。所谓同源是指,域名,协议,端口相同。),凡是发送请求url的协议、域名、端口三者之间任意一与当前页面地址不同即为跨域。
Tip
src 属性不受同源策略约束,如img
标签,JSONP 就是利用此原理
@Target({ElementType.METHOD, ElementType.TYPE})//可作用于方法,类上 @Retention(RetentionPolicy.RUNTIME) @Documented public @interface CrossOrigin { ... @AliasFor("origins")//允许可访问的域列表 String[] value() default {}; ... long maxAge() default -1L;//准备响应前的缓存持续的最大时间(以秒为单位)。 }
在控制器类或方法上加上@CrossOrigin
注解
@CrossOrigin(origins = "*",maxAge = 3600)
@CrossOrigin(origins = "http://localhost:8080",maxAge = 3600)
/**
* @author Macky
* @Title class WebMvConfig
* @Description: TODO
* @date 2019/8/2 13:52
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("POST", "GET", "PUT", "OPTIONS", "DELETE")
.maxAge(3600)
.allowCredentials(true);
}
}
当时使用拦截器校验自定义请求头时,以上配置依然会报错
浏览器会在发送真正请求之前,先发送一个方法为OPTIONS的预检请求 Preflighted requests 这个请求是用来验证本次请求是否安全的,但是并不是所有请求都会发送,需要符合以下条件:
- 请求方法不是GET/HEAD/POST
- POST请求的Content-Type并非application/x-www-form-urlencoded, multipart/form-data, 或text/plain
- 请求设置了自定义的header字段
对于管理端的接口,我有对接口进行权限校验,每次请求需要在header中携带自定义的字段(token),所以浏览器会多发送一个OPTIONS请求去验证此次请求的安全性。
为何OPTIONS请求是500呢?
OPTIONS请求只会携带自定义的字段,并不会将相应的值带入进去,而后台校验token字段时 token为NULL,所以验证不通过,抛出了一个异常。
Springboot如何优雅的解决ajax+自定义headers的跨域请求 - Java知音号 - 博客园 (cnblogs.com)
解决办法:拦截器放行OPTIONS
方法
package com.config;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import com.common.utils.JwtUserUtils;
import com.common.utils.JwtUtils;
@Component
public class JwtInterceptor implements HandlerInterceptor {
@Autowired
RedisTemplate<String, String> redisTemplate;
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
// 如果是OPTIONS请求则结束
if (HttpMethod.OPTIONS.toString().equals(httpServletRequest.getMethod())) {
return true;
}
...
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) {
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
}
}
Note
由于此情形下跨域请求会发送OPTIONS
请求,自然整个项目不能禁用OPTIONS
请求方式
web.xml
<security-constraint>
<web-resource-collection>
<url-pattern>/*</url-pattern>
<http-method>PUT</http-method>
<http-method>DELETE</http-method>
<http-method>HEAD</http-method>
<http-method>TRACE</http-method>
</web-resource-collection>
<auth-constraint/>
</security-constraint>
<security-constraint>
<web-resource-collection>
<web-resource-name>securedapp</web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
application.yml
spring:
mvc:
dispatch-options-request: true
有时后端已配置跨域,且在控制台访问测试正常,但是项目中的请求报错
The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'.
原因是:请求的 origin 与后端配置的 origin 不一致。
当前端需要带 cookie 请求时,即配置了withCredentials: true
时
// xhr
var xhr = new XMLHttpRequest();
xhr.open('POST', 'https://192.168.3.105/api/notification/list?page=1&limit=2');
// xhr.setRequestHeader("J-Token", "eyJhbGciOiJIUzUxMiJ9.eyJzdW...f4Mm18VPvrztQzTO3yopgdQoKPkh28g");
xhr.withCredentials = true;
xhr.send();
// jquery
$.ajax({
url : 'url',
xhrFields: {
withCredentials: true
},
});
// axios
Axios.defaults.withCredentials = true
后端Access-Control-Allow-Credentials
必须为true,但是需要注意当Access-Control-Allow-Credentials=true
时,Access-Control-Allow-Origin
就不能为” * “ ,必须是明确的域名,当然可以多个域名使用 “,” 分割
一般的接口请求或前后端分离项目是不需要带 cookie 请求的,所以一般不配置withCredentials
,保持它的默认值false
。
Access-Control-Allow-Origin - HTTP | MDN (mozilla.org)
跨域踩坑经验总结(内涵:跨域知识科普) - 凝雨 - Yun (ningyu1.github.io)
动态设置?不知有无必要
跨域通配符与include报错,The value of the 'Access-Control-Allow-Origin' header ''_海贼8023的博客-CSDN博客