说明
新版Chrome浏览器导致 cookie 跨域失败,详见:cookie跨域失败-新版chrome浏览器SameSite属性
适用场景
本文适用 简单请求 携带 cookie,非简单请求 携带 cookie
原因详见 链接
filter 代码
提示:类名是 A0CORSFilter,下面有解释
package org.springframework.web.filter;
import top.malaoshi.web.utils.CorsUtils;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 处理跨域的过滤器
*/
public class A0CORSFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse) res;
// 判断是否跨域
if(CorsUtils.isCorsRequest(request)){
configCors(request,response);
}
chain.doFilter(req, res);
}
/**
* 配置跨域请求头
* @param request
* @param response
*/
protected void configCors(HttpServletRequest request,HttpServletResponse response){
setCorsHeader(request,response,
"*","*","*",true,3600);
}
/**
* 设置跨域的头信息
* @param request
* @param response
* @param allowOrigin
* @param allowedHeader
* @param allowMethods
* @param allowCredentials
* @param maxAge
*/
protected void setCorsHeader(HttpServletRequest request, HttpServletResponse response,
String allowOrigin,
String allowedHeader,
String allowMethods,
boolean allowCredentials,
long maxAge){
/*
允许可访问的域列表。携带cookie时,不能是 *,必须填域名,而且末尾不能有符号: /
跨域必须设置
如果是 *,表示允许任意前端服务器 跨域(不安全)
实现原理:跨域时,前端发送请求头中,一定包含 Origin,也就是前端域名,允许该前端域名
*/
if("*".equals(allowOrigin)) {
allowOrigin = request.getHeader("Origin");
}
response.setHeader("Access-Control-Allow-Origin", allowOrigin);
//如果是 Preflight 请求
if(CorsUtils.isPreFlightRequest(request)) {
/*
允许携带请求头。携带cookie时,不能是 *
如果是 *,表示允许任意请求头
实现原理:跨域时,前端发送非简单请求时,会先发送 Preflight 请求,该请求头中,包含 Access-Control-Request-Headers 头,是正式请求携带的头,允许该请求头
提示:跨域时,发送简单请求头中,不包含 Access-Control-Request-Headers,此时可以不设置该项
*/
if("*".equals(allowedHeader)) {
allowedHeader = request.getHeader("Access-Control-Request-Headers");
}
if(allowedHeader !=null && !"".equals(allowedHeader)) {
response.setHeader("Access-Control-Allow-Headers", allowedHeader);
}
/*
允许跨域的请求方法
如果是 *,表示允许任意请求方法
实现原理:跨域时,前端发送非简单请求时,会先发送 Preflight 请求,该请求头中,包含 Access-Control-Request-Method 头,是正式请求的方法,允许该方法
提示:跨域时,发送简单请求头中,不包含 Access-Control-Request-Method,此时可以不设置该项
*/
if("*".equals(allowMethods)){
allowMethods = request.getHeader("Access-Control-Request-Method");
}
if(allowMethods !=null && !"".equals(allowMethods)) {
response.setHeader("Access-Control-Allow-Methods", allowMethods);
}
// 设置 Preflight 请求缓存多长时间(以秒为单位)
response.setHeader("Access-Control-Max-Age", maxAge + "");
//允许浏览器发送 cookie
response.setHeader("Access-Control-Allow-Credentials", allowCredentials+"");
}
}
public void init(FilterConfig filterConfig) {}
public void destroy() {}
}
解释
Access-Control-Allow-Origin 头
允许可访问的前端服务器列表,* 表示允许任意服务器
携带cookie时,不能是 *,必须前端服务器域名
注意:末尾不能有符号: /,如下:
response.setHeader("Access-Control-Allow-Origin", "http://localhost:9528");
Access-Control-Allow-Headers 头
用于 preflight request(预检请求)
告诉前端,发请求时,允许携带的请求头。
(关键)如果前端发请求时,携带了不允许的请求头,则无响应,且不容易找到错误
携带了不被允许的 uid,错误如下:
关于 cookie:
- 不携带cookie时,可以是
*,表示允许所有请求头 - 携带cookie时,不能是
*,必须指明允许哪些请求头
如下:
response.setHeader("Access-Control-Allow-Headers", "access-control-allow-origin, authority, content-type, version-info, X-Requested-With");
解释:
X-Requested-With:前端发请求可以携带X-Requested-With,后端用来判断是否 ajax 请求。如果前端使用 axios,默认不携带该头,需要额外指定,详见 链接
Access-Control-Allow-Methods 头
用于 preflight request(预检请求)
表示允许请求方法,如:POST, GET, OPTIONS, DELETE, HEAD, PUT
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, HEAD, PUT");
Access-Control-Allow-Credentials 头
true 表示允许携带 cookie,还需要ajax配合
response.setHeader("Access-Control-Allow-Credentials", "true");
设置 Preflight 请求缓存时间
非简单请求,先发送 OPTIONS 请求,该请求称为:Preflight 预检请求,然后才发送正式请求
该设置表示:在一段时间内,不再发送Preflight 预检请求,以秒为单位
response.setHeader("Access-Control-Max-Age", "3600");
提示:由于本文适用 简单请求,所以被设置不写也可以
更多参见:
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Access-Control-Max-Age
注解配置
在类名上,加下面注解:
@WebFilter("/*")
类名
类名必须是 A0CORSFilter,即:以 A0开头
因为使用 @WebFilter 注解,无法指定过滤器的执行顺序,只会根据 类名 的 ascii码 的顺序,作为 执行顺序,所以为了保证 最先执行该跨域过滤器,类名以 A0开头
web.xml 配置
如果使用 web.xml 配置启用过滤器,加上下面配置,放在其他过滤器的上面:
<filter>
<filter-name>CORSFilter</filter-name>
<filter-class>top.malaoshi.filter.CORSFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CORSFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
ajax
jquery
$.ajax({
url:baseUrl+'goods/list',
type:'post',
dataType:'json',//服务器端返回的数据格式是json
xhrFields: { //带上cookie
withCredentials: true
},
data: {
"status":1
},//发给服务器端的数据
success:function(res){ //data:服务器端返回给浏览器端的数据
if(res.code==0){
console.log(res)
}else{
alert(res.msg);
}
},
error:function (XMLHttpRequest, textStatus, errorThrown) {
alert(XMLHttpRequest);
alert(textStatus);
alert(errorThrown);
}
});
axios 非简单请求
axios 发送post请求,默认是 application/json ,所以是 非简单请求
// 携带 cookie
axios.defaults.withCredentials = true;
// 携带 X-Requested-With
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
axios.post('http://localhost:8080/login',
{
"username":this.loginForm.username,
"password":this.loginForm.password
}
)
.then((res) => {
console.log(res);
this.loading = false
})
.catch(function (error) {// 请求失败处理
console.log(error);
});
axios 简单请求
get 请求携带 cookie:
axios.defaults.withCredentials = true;//带cookie
axios.get(baseUrl+'goods/list?status=1').then(function(res){
console.log(res.data);
})
get 请求携带 cookie 的另一种方式:
axios.get(
baseUrl+'goods/list?status=1',
{withCredentials: true}//带cookie
).then(function(res){
console.log(res.data);
})
