跨域是實際應用開發中一個非常常見的需求,在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之前),此時還沒有到認證過濾器。