Spring处理方案
首先回顾一下 Spring 中关于跨域的处理方案,这有助于我们理解 Spring Security 中的跨域。Spring 中对于跨域的处理一共有三种不同的方案,我们分别来看。
@CrossOrigin
Spring 中第一种处理跨域的方式是通过 @CrossOrigin 注解来标记支持跨域,该注解可以添加在方法上,也可以添加在 Controller 上。当添加在 Controller 上时,表示 Controller 中的所有接口都支持跨域,具体配置如下:
@RestController
public class HelloController {
@PostMapping("/post")
public String post() {
return "hello post";
}
}
@CrossOrigin 注解各属性含义如下:
-
allowCredentials:浏览器是否应当发送凭证信息,如 Cookie。
-
allowedHeaders:请求被允许的请求头字段,* 表示所有字段。
-
exposedHeaders:哪些响应头可以作为响应的一部分暴露出来。注意,这里只可以一一列举,通配符 * 在这里是无效的。
-
maxAge:预检请求的有效期,有效期内不必再次发送预检请求,默认是 1800 秒。
-
methods:允许的请求方法,* 表示允许所有方法。
-
origins:允许的域,* 表示允许所有域。
该注解的实现原理属于 Spring 范畴,这里不做过多介绍,本书仅和大家简单梳理一下 @CrossOrigin 注解执行过程,这有助于我们理解后面的内容(读者可按照下面的叙述自行 Debug 以加深理解)。
-
@CrossOrigin 注解在 AbstractHandlerMethodMapping 的内部类 MappingRegistry 的 register 方法中完成解析的,@CrossOrigin 注解中的内容会被解析成一个配置对象 CorsCon figuration。
-
将 @CrossOrigin 所标记的请求方法对象 HandlerMethod 和 CorsConfiguration 一一对应存入一个名为 corsLookup 的 Map 集合中。
-
当请求到达 DispatcherServlet#doDispatch 方法之后,调用 AbstractHandlerMapping#getHandler 方法获取执行链 HandlerExecutionChain 时,会从 corsLookup 集合中获取到 CorsConfiguration 对象。
-
根据获取到的 CorsConfiguration 对象构建一个 CorsInterceptor 拦截器。
-
在 CorsInterceptor 拦截器中触发对 DefaultCorsProcessor#processRequest 的调用,跨域请求的校验工作将在该方法中完成。
addCorsMappings
@CrossOrigin 注解需要添加在不同的 Controller 上。所以还有一种全局的配置方法,就是通过重写 WebMvcConfigurerComposite#addCorsMappings 方法来实现,具体配置如下:
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedMethods("*")
.allowedOrigins("*")
.allowedHeaders("*")
.allowCredentials(false)
.exposedHeaders("")
.maxAge(3600);
}
}
addMapping 表示要处理的请求地址,接下来的方法含义和 @CrossOrigin 注解中属性的含义都一一对应,这里不再赘述。
这种配置方式最终的处理方式和 @CrossOrigin 注解相同,都是在 CorsInterceptor 拦截器中触发对 DefaultCorsProcessor#processRequest 的调用,并最终在该方法中完成对跨域请求的校验工作。不过在源码执行过程中略有差异。
-
registry.addMapping("/**") 方法配置了一个 CorsRegistration 对象,该对象中包含了一个路径拦截规则,拦截规则的值就是 addMapping 方法的参数,同时 CorsRegistration 中还包含了一个 CorsConfiguration 配置对象,该对象用来保存这里跨域相关的配置。
-
在 WebMvcConfigurationSupport#requestMappingHandlerMapping 方法中触发了 addCorsMappings 方法的执行,将获取到的 CorsRegistration 对象重新组装成一个 UrlBasedCorsConfigurationSource 对象,该对象中定义了一个 corsConfigurations 变量(Map<String,CorsConfiguration>),该变量保存了拦截规则和 CorsConfiguration 对象的映射关系。
-
将新建的 UrlBasedCorsConfigurationSource 对象赋值给 AbstractHandlerMapping#corsConfigurationSource 属性。
-
当请求到达时的处理方法和 @CrossOrigin 注解处理流程的第 3 步一样,都是在 AbstractHandlerMapping#getHandler 方法中进行处理,不同的是,这里是从 corsConfigurationSource 中获取 CorsConfiguration 配置对象,而 @CrossOrigin 注解则从 corsLookup 集合中获取到 CorsConfiguration 配置对象。如果两处都可以获取到 CorsConfiguration 对象,则对获取到的对象属性值进行合并。
-
根据获取到的 CorsConfiguration 对象构建一个 CorsInterceptor 拦截器。
-
在 CorsInterceptor 拦截器中触发对 DefaultCorsProcessor#processRequest 的调用,跨域请求的校验工作将在该方法中完成。
这两种跨域配置方式殊途同归,最终目的都是配置了一个 CorsConfiguration 对象,并根据该对象创建 CorsInterceptor 拦截器,然后在 CorsInterceptor 拦截器中触发 DefaultCorsProcessor#processRequest 方法的执行,完成跨域的校验。另外还需要注意的是,这里的跨域校验是由 DispatcherServlet 中的方法触发的,而 DispatcherServlet 的执行是在 Filter 之后,这一点需要牢记,我们后面将会用到。
CorsFilter
CorsFilter 是 Spring Web 中提供的一个处理跨域的过滤器,开发者也可以通过该过滤器处理跨域:
@Configuration
public class WebMvcConfig {
@Bean
FilterRegistrationBean<CorsFilter> corsFilter() {
FilterRegistrationBean<CorsFilter> registrationBean = new FilterRegistrationBean<>();
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowedHeaders(Arrays.asList("*"));
corsConfiguration.setAllowedMethods(Arrays.asList("*"));
corsConfiguration.setAllowedOrigins(Arrays.asList("http://localhost:8081"));
corsConfiguration.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", corsConfiguration);
registrationBean.setFilter(new CorsFilter(source));
registrationBean.setOrder(-1);
return registrationBean;
}
}
CorsFilter 过滤器的配置也很容易:
-
由于是在 Spring Boot 项目中,这里通过 FilterRegistrationBean 来配置一个过滤器,这种配置方式既可以设置拦截规则,又可以为配置的过滤器设置优先级。
-
在这里依然离不开 CorsConfiguration 对象,不同的是我们自己手动创建该对象,并逐个设置跨域的各项处理规则。
-
我们还需要创建一个 UrlBasedCorsConfigurationSource 对象,将过滤器的拦截规则和 CorsConfiguration 对象之间的映射关系由 UrlBasedCorsConfigurationSource 中的 corsConfigurations 变量保存起来。
-
最后创建一个 CorsFilter,并为其配置一个优先级。
在 CorsFilter 过滤器的 doFilterlnternal 方法中,触发对 DefaultCorsProcessor#processRequest 的调用,进而完成跨域请求的校验。
和前面两种方式不同的是,CorsFilter 是在过滤器中处理跨域的,而前面两种方案则是在 DispatcherServlet 中触发跨域处理,从处理时间上来说,CorsFilter 对于跨域的处理时机要早于前面两种。
这就是 Spring 中为我们提供的三种不同的跨域解决方案,三种方式都能解决问题,选择其中任意一种即可。需要说明的是:
-
@CrossOrigin 注解+重写 addCorsMappings 方法同时配置,这两种方式中关于跨域的配置会自动合并,跨域在 CorsInterceptor 中只处理了一次。
-
@CrossOrigin 注解 + CorsFilter 同时配置,或者重写 addCorsMappings 方法 + CorsFilter 同时配置,都会导致跨域在 CorsInterceptor 和 CorsFilter 中各处理了一次,降低程序运行效率,这种组合不可取。