过滤器filter实现跨域,简单请求、非简单请求携带cookie(关键勿动,多个文章引用) 作者:马育民 • 2022-09-23 23:15 • 阅读:10088 # 说明 新版Chrome浏览器导致 cookie 跨域失败,详见:[cookie跨域失败-新版chrome浏览器SameSite属性](https://www.malaoshi.top/show_1IX2ncYlXaVN.html "cookie跨域失败-新版chrome浏览器SameSite属性") ### 适用场景 本文适用 **简单请求 携带 cookie**,**非简单请求 携带 cookie** 原因详见 [链接](https://www.malaoshi.top/show_1IXBXRNJyf2.html "链接") # 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`,错误如下: [](/upload/0/0/1IX70WjyPWQM.jpg) 关于 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,默认不携带该头,需要额外指定,详见 [链接](https://www.malaoshi.top/show_1IX46ZsMmg8T.html "链接") #### 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` 配置启用过滤器,加上下面配置,放在其他过滤器的上面: ``` CORSFilter top.malaoshi.filter.CORSFilter CORSFilter /* ``` # 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); }) ``` 原文出处:https://www.malaoshi.top/show_1IX46QGBvoNw.html