目前在使用react 进行开发,因为前后端分离,存在跨域问题。

1、首先,对于正常的情况下,在前端的server.js里面需要进行配置:如下跨域部分:

var express = require('express');
var app = express();

// 跨域
app.use(async(ctx, next) => {
    ctx.set("Access-Control-Allow-Origin", "*");
    ctx.set("Access-Control-Allow-Credentials", true);
    ctx.set("Access-Control-Allow-Methods", "OPTIONS, GET, PUT, POST, DELETE");
    ctx.set("Access-Control-Allow-Headers", "x-requested-with, accept, origin, content-type");
    ctx.set("X-Powered-By", ' 3.2.1');
    ctx.set("Content-Type", "application/json;charset=utf-8");
    if (ctx.request.method == "OPTIONS") {
        ctx.response.status = 200
    }
    await next();
});

app.all('*',
    function (req, res, next) {
        res.header("Access-Control-Allow-Origin", "*");
        //用于判断request来自ajax还是传统请求
        // res.header("Access-Control-Allow-Headers", "X-Requested-With");
        res.header("Access-Control-Allow-Headers", " Origin, X-Requested-With, Content-Type, Accept");
        //允许访问的方式
        res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
        res.header("X-Powered-By", ' 3.2.1');
        res.header("Content-Type", "application/json;charset=utf-8");
        next();
    });


var server = app.listen
(8081, function
    () {
    var host = server.address().address
    var port = server.address().port
    console.log("应用实例,访问地址为 http://%s:%s", host, port)
})

 2、后端也是需要进行跨域配置,

package com.struum.auth.filter;

import org.apache.http.HttpStatus;
import org.springframework.core.annotation.Order;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @ 解决跨域问题
 * @createTime:2021/9/7 11:06
 */
@Order(1)
//@WebFilter(filterName = "corsFilter")
public class CorsFilter implements Filter{
    @Override
    public void destroy() {

    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse res,
                         FilterChain chain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) res;
        HttpServletRequest hreq = (HttpServletRequest) req;
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "x-requested-with, accept, origin, content-type,自定义,自定义");
     
        chain.doFilter(req, res);

    }

    @Override
    public void init(FilterConfig arg0) throws ServletException {

    }

}

3、headers自定义头信息跨域问题

如果是普通的问题,就不需要管其他了,但是我们可能在headers加一些自定义信息。此时,浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。非简单请求是那种对服务器有特殊要求的请求,比如请求方式是 PUT、DELETE,或者 Content-Type 字段类型是 application/json,像我们这里一样,HTTP头信息中加了自定义的header。非简单请求的 CORS请求,会在正式通信之前,增加一次 HTTP 查询请求,称为预检请求(preflight),预检请求用的请求方法是 OPTIONS。文章开头的那张图记录的就是一个预检请求。浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。因此,我们需要在跨域过滤器修改doFilter。如下:

@Override
    public void doFilter(ServletRequest req, ServletResponse res,
                         FilterChain chain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) res;
        HttpServletRequest hreq = (HttpServletRequest) req;
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "x-requested-with, accept, origin, content-type");
        // 浏览器是会先发一次options请求,如果请求通过,则继续发送正式的post请求..因为header加了自定义参数
        // 配置options的请求返回
        if (hreq.getMethod().equals("OPTIONS")) {
            response.setStatus(HttpStatus.SC_OK);
            response.getWriter().write("OPTIONS returns OK");
            return;
        }
        chain.doFilter(req, res);

    }

4、多个过滤器执行顺序

因为我们的服务器有多个过滤器。毕竟需要解析token。这个时候需要先执行跨域过滤器,再去执行其他的过滤器。保证接口能进来。那么,多个过滤器的执行顺序就有所讲究了。一般我们很实用注解@order(1)  @order(2)的这种,但是发现根本没用。依然是按照内部执行顺序。这时候我们就需要加一个配置来指定他们的执行顺序。如下:

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author: 添加这个是因为,需要先执行 跨域过滤器,再去执行 header的过滤器,因为headers加了一些自定义信息
 * @createTime:2021/9/8 14:00
 */
@Configuration
public class WebConfig {
    @Bean
    public FilterRegistrationBean corsFilter() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();

        CorsFilter corsFilter = new CorsFilter();

        filterRegistrationBean.setFilter(corsFilter);
        //配置过滤规则
        filterRegistrationBean.addUrlPatterns("/*");
        //设置init参数
        filterRegistrationBean.addInitParameter("name", "hahahhhaa");
        //设置过滤器名称
        filterRegistrationBean.setName("corsFilter");
        //执行次序
        filterRegistrationBean.setOrder(1);

        return filterRegistrationBean;
    }

    @Bean
    public FilterRegistrationBean AuthFilter() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();

        AuthFilter authFilter = new AuthFilter();

        filterRegistrationBean.setFilter(authFilter);

        //配置多个过滤规则
//        List<String> urls = new ArrayList<>();
//        urls.add("/order/*");
//        urls.add("/user/*");
//        filterRegistrationBean.setUrlPatterns(urls);

        //配置过滤规则
        filterRegistrationBean.addUrlPatterns("/*");
        //设置过滤器名称
        filterRegistrationBean.setName("authFilter");
        //执行次序
        filterRegistrationBean.setOrder(2);

        return filterRegistrationBean;
    }
}

通过以上代码,基本上可以解决跨域问题了。

5、@Autowired 在Listener和Filter中注入为null情况

通过上面的执行顺序,可以解决跨域问题。但是我的Filter可以需要读取配置文件或者需要@Autowired注解注入类。这个时候他们可能就是null 值了。这是因为因为Filter和Listener加载顺序优先于spring容器初始化实例,所以使用@Autowired肯定为null了。

可以使用用ApplicationContext根据bean名称(注意名称为实现类而不是接口)去获取bean,写个工具类即可

package com.struum.utils;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
 * @author:fxm
 * @createTime:2021/9/8 19:13
 */
@Component
public class SpringBeanUtil implements ApplicationContextAware {
    //ApplicationContext对象是Spring开源框架的上下文对象实例,在项目运行时自动装载Handler内的所有信息到内存。
    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if (SpringBeanUtil.applicationContext == null) {
            SpringBeanUtil.applicationContext = applicationContext;
        }
    }

    //获取applicationContext
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    //通过name获取 Bean.
    public static Object getBean(String name) {
        return getApplicationContext().getBean(name);
    }

    //通过class获取Bean.
    public static <T> T getBean(Class<T> clazz) {
        return getApplicationContext().getBean(clazz);
    }

    //通过name,以及Clazz返回指定的Bean
    public static <T> T getBean(String name, Class<T> clazz) {
        return getApplicationContext().getBean(name, clazz);
    }
}

通过 Decrypt decrypt = SpringBeanUtil.getBean(Decrypt.class);即可获取bean。

6、配置文件的参数值,使用Autowired注入配置类一直为null

6.1 在init方法中使用filterConfig参数。

ServletContext context = filterConfig.getServletContext();
        ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(context);
        LoginProperties loginProperties =  ctx.getBean(LoginProperties.class);

6.2 如果在dofilter方法中也可以使用request参数。

ServletContext context = request.getServletContext();
        ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(context);
        Bean  = ctx.getBean(Bean.class);