跨域问题是怎样造成的?( 三 )

@RestControllerpublic class UserController {@Resourceprivate UserFacade userFacade;/*** 在方法上加注解*/@GetMapping(ApiConstant.Urls.GET_USER_INFO)@CrossOrigin({"http://127.0.0.1:9528", "http://localhost:9528"})public PojoResult<UserDTO> getUserInfo() {return userFacade.getUserInfo();}}通过CorsRegistry设置全局跨域配置@Configuration@EnableWebMvcpublic class WebConfig extends WebMvcConfigurerAdapter { @Override public void addCorsMappings(CorsRegistry registry) {registry.addMapping("/**").allowedOrigins("http://127.0.0.1:9528", "http://localhost:9528"); }}如果你使用的是 Spring Boot,推荐的做法是只定义一个 WebMvcConfigurer 的Bean:
@Configurationpublic class MyConfiguration {@Beanpublic WebMvcConfigurer corsConfigurer() {return new WebMvcConfigurerAdapter() {@Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/**").allowedOrigins("http://127.0.0.1:9528", "http://localhost:9528");}};}}以上两种方式在没有定义拦截器(Interceptor)的时候,使用一切正常,但是如果你有一个全局的拦截器用来检测用户的登录态,例如下面的简易代码:
public class AuthenticationInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {// 从 http 请求头中取出 tokenString token = httpServletRequest.getHeader("token");// 检查是否登录if (token == null) {throw new InvalidTokenException(ResultCode.INVALID_TOKEN.getCode(), "登录态失效,请重新登录");}return true;}}当自定义拦截器返回true时,一切正常,但是当拦截器抛出异常(或者返回false)时,后续的CORS设置将不会生效 。
为什么拦截器抛出异常时,CORS不生效呢?可以看下这个issue:
when interceptor preHandler throw exception, the cors is broken
有个人提交了一个issue,说明如果在自定义拦截器的preHandler方法中抛出异常的话,通过 CorsRegistry 设置的全局 CORS 配置就失效了,但是Spring Boot 的成员不认为这是一个Bug 。
然后提交者举了个具体的例子:
他先定义了CorsRegistry,并添加了一个自定义的拦截器,拦截器中抛出了异常

跨域问题是怎样造成的?

文章插图
 
然后他发现AbstractHandlerMapping在添加CorsInterceptor的时候,是将 Cors 的拦截器加在拦截器链的最后:
跨域问题是怎样造成的?

文章插图
 
那就会造成上面说的问题,在自定义拦截器中抛出异常之后,CorsInterceptor 拦截器就没有机会执行向 response 中设置 CORS 相关响应头了 。
issue的提交者也给出了解决的方案,就是将用来处理 Cors 的拦截器 CorsInterceptor 加在拦截器链的第一个位置:
跨域问题是怎样造成的?

文章插图
 
这样的话请求来了之后,第一个就会为 response 设置相应的 CORS 响应头,后续如果其他自定义拦截器抛出异常,也不会有影响了 。
感觉是一个可行的解决方案,但是 Spring Boot 的成员认为这不是 Spring Boot 的Bug,而是 Spring Framework 的 Bug,所以将这个issue关闭了 。
通过CorsFilter设置全局跨域配置既然通过拦截器设置全局跨域配置会有问题,那我们还有另外一种方案,通过过滤器 CorsFilter 的方式来设置,代码如下:
@Configurationpublic class MyConfiguration { @Bean public FilterRegistrationBean corsFilter() {UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();CorsConfiguration config = new CorsConfiguration();config.setAllowCredentials(true);config.addAllowedOrigin("http://127.0.0.1:9528");config.addAllowedOrigin("http://localhost:9528");config.addAllowedHeader("*");config.addAllowedMethod("*");source.registerCorsConfiguration("/**", config);FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));bean.setOrder(0);return bean; }}为什么过滤器可以而拦截器不行呢?
因为过滤器依赖于 Servlet 容器,基于函数回调,它可以对几乎所有请求进行过滤 。而拦截器是依赖于 Web 框架(如Spring MVC框架),基于反射通过AOP的方式实现的 。
在触发顺序上如下图所示:
跨域问题是怎样造成的?

文章插图
 
因为过滤器在触发上是先于拦截器的,但是如果有多个过滤器的话,也需要将 CorsFilter 设置为第一个过滤器才行 。


推荐阅读