跨域是实际应用开发中一个非常常见的需求,在Spring 框架中对于跨域问题的处理方案有好几种,引入了Spring Security之后,跨域问题的处理方案又增加了。
CORS
CORS就是由W3C制定的一种跨域资源共享技术标准,其目的就是为了解决前端的跨域请求。在JavaEE开发中,最常见的前端跨域请求解决方案是JSONP,但是JSONP只支持GET请求,这是一个很大的缺陷,而CORS支持多种HTTP请求方法,也是目前主流的跨域解决方案
CORS中新增了一组HTTP请求字段,通过这些字段,服务器告诉浏览器,哪些网站通过浏览器有权限访问哪些资源,同时规定,对哪些可能修改服务器数据的HTTP请求方法(如GET以外的HTTP请求),浏览器必须首先使用OPTIONS方法发起一个预检请求(preflightrequest),预检请求的目的是查看服务端是否支持即将发起的跨域请求,如果服务端允许,才能发起实际的HTTP请求。在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证(如Cookie,HTTP认证信息等)
Spring处理方案
Spring中关于跨域的处理一共有三种
@CrossOrigin
Spring中第一种跨域的方式是通过@CrossOrign注解来标记支持跨域,该注解可以添加在方法上,也可以添加在Controller上,当添加在Controller上时,表示Controller中所有接口都支持跨域
例:
@RestController
public class HelloController{
@CrossOrign(orign="http://localhost:8081")
@PostMapping("/post")
public String post(){
return "hello post";
}
}
@CrossOrign注解各属性含义如下:
allowCredentials:浏览器是否应当发送凭证信息,如Cookie等
allowedHeaders:请求被允许的请求头字段,表示所有字段
exposedHeaders:哪些相应头可以作为相应的一部分暴露出来,注意,这里只能一一列举,通配符无效
maxAge:预检请求的有效期,有效期内不必再次发送预检请求,默认位1800秒
methods:允许的请求方法,表示允许所有方法
origins:允许的域,表示允许所有的域
addCoresMappings
@CrossOrigin注解需要添加在不同的Controller上,所以还有一种全局的配置方法。就是重写WebMvcConfigurerComposite的addCoresMappings方法
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowCredentials(false)
.allowedMethods("*")
.allowedOrigins("*")
.exposedHeaders("")
.allowedHeaders("*")
.maxAge(3600);
}
}
addMapping表示要处理的请求地址,接下来的方法含义和@CrossOrigin注解中属性的含义都一一对应,这里不再赘述。
CorsFilter
Spring Security处理方案
当我们为项目添加Spring Security依赖后,发现上面三种跨域方式失效了!
原因很简单。我们的预检请求由于不携带认证信息,所以被Spring Security 过滤器拦截了!
第一种方案
我们仍使用Spring的Cors方法,只需要对预检请求放行即可!
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
//注意顺序!
.requestMatchers(CorsUtils::isPreFlightRequest).permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
}
}
第二种方案
我们使用Spring Security的Cors方案
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
FindByIndexNameSessionRepository sessionRepository;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.and()
.cors()
.configurationSource(corsConfigurationSource())
.and()
//关掉csrf防御,方便测试!
.csrf().disable();
}
@Bean
CorsConfigurationSource corsConfigurationSource(){
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);
return source;
}
}
首先需要提供一个CoresConfigurationSource实例,将跨域的各项配置都填充进去,然后在configure(HttpSecurity)方法中,通过cors()开启跨域配置,并将一开始配置好的CoresConfigurationSource实例设置进去。
cors()方法的实质就是获取一个CorsFilter 并添加在Spring Security过滤器链中(位置在HeaderWriterFilter之后,CsrfFilter之前),此时还没有到认证过滤器。