一次印象深刻的跨域请求问题解决过程,写的有点罗嗦。◑﹏◐

在我的项目中使用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 配置出现了冲突。