记录一次跨域请求问题的解决过程
一次印象深刻的跨域请求问题解决过程,写的有点罗嗦。◑﹏◐
在我的项目中使用shiro作为认证授权的管理框架,在结合前端页面进行测试的时候出现了跨域问题。
为了解决跨域问题,我编写了WebConfig实现了WebMvcConfigurer接口,但是测试的是登录接口,并没有再出现跨域问题。
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") // 允许跨域请求的所有路径
.allowedOrigins("http://localhost:8081") // 允许的源
.allowedMethods("GET", "POST", "PUT", "DELETE") // 允许的请求方法
.allowedHeaders("*") // 允许的请求头
.allowCredentials(true) // 是否允许发送Cookie
.exposedHeaders("Authorization"); // 指定暴露的响应头,必须指定字段,不能为*
}
}
但是当我测试到角色相关接口时,又开始出现了跨域问题。我仔细分析了一下,问题只可能出现在shiro配置类上,因为在其中国,登录接口配置的过滤器时 anon
,而角色接口(或者说需要认证的接口)都配置的是自定义过滤器JWTFilter。
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
factoryBean.setSecurityManager(securityManager);
// 注册自定义 Filter Map<String, Filter> filters = new HashMap<>();
filters.put("JWTFilter", new JWTFilter());
factoryBean.setFilters(filters);
// 定义 Filter 的拦截规则
Map<String, String> filterChainDefinitionMap = new HashMap<>();
filterChainDefinitionMap.put("/admin/login", "anon");
......
filterChainDefinitionMap.put("/**", "JWTFilter");
factoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return factoryBean;
}
上网查了博客,又问了Copilot,让我尝试创建一个CorsFilter类实现Filter接口,并将它放在前JWTFilter前,用于处理需要认证接口的跨域问题。它确实做到了,只不过还有问题!
@Component
public class CorsFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse.setHeader("Access-Control-Allow-Origin", httpServletRequest.getHeader("Origin"));
httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
httpServletResponse.setHeader("Access-Control-Allow-Credentials", "true");
if ("OPTIONS".equalsIgnoreCase(httpServletRequest.getMethod())) {
httpServletResponse.setStatus(HttpServletResponse.SC_OK);
return; }
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
filterChainDefinitionMap.put("/**", "CorsFilter, JWTFilter");
但是就在这时新的问题又出现了!因为我的登录是采用双token(即Access Token和Refresh Token),需要从header中取出Authorization。由于跨域问题,在后端配置时,必须明确暴漏的响应头:.exposedHeaders("Authorization");
,前端才能拿到。
但是这个我在WebConfig中有配置,但是在CorsFilter中却没有。
所以,前端登录会提示登录成功,但是由于没有拿到token,不会跳转到/home页面。前端访问角色接口,一切正常(当时没退出登录)。WTF!这不就意味着WebConfig没生效吗?
当时就以为着,不需要认证的请求应该会走WebConfig的跨域处理,需要认证的应该会走CorsFilter的跨域处理,一顿乱搞,也没解决问题。
直到我把CorsFilter上面的@Component注解给删了。然后一切就正常了!!!!
所以,整个问题的逻辑是,因为@Component注解导致CorsFilter被Springboot管理,由于它已经处理了跨域,所以WebConfig的配置没有生效???
问了GPT,原因应该就是如此。
@Component
注解会使得 CorsFilter
成为 Spring 容器管理的一个 Bean,而 Shiro 的过滤器配置是通过 filterChainDefinitionMap
来进行配置的。Spring 容器会将 CorsFilter
注册为一个标准的 Servlet 过滤器,而 Shiro 的 filterChainDefinitionMap
配置实际上是在 Shiro 的过滤器链中注入的,导致 Shiro 对 CorsFilter
的管理和 WebConfig 配置出现了冲突。