目前在使用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);