过滤器filter实现跨域,简单请求、非简单请求携带cookie(关键勿动,多个文章引用)

说明

新版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);
})

原文出处:/show_1IX46QGBvoNw.html