CORS是Web浏览器为了防止跨站脚本攻击而设置的一种安全保护措施,但前后端分离开发流行后,浏览器的这种保护措施某种程度上反而成了绊脚石。
跨域问题如何解决?Springboot解决CORS我们弄明白这两个点就行:
1)浏览器发送跨站请求时,在发送POST请求之前它会先发送一个OPTION请求试探一下服务器的反应。如果服务器支持CORS请求,那服务器返回的响应头(headers)中就会包含浏览器所需的一些header信息(Access-Control-Allow-Origin、Access-Control-Allow-Methods、Access-Control-Allow-Headers、Access-Control-Allow-Credentials),然后浏览器就发送POST请求;如果服务器不支持CORS的话就不会返回那些header信息,收不到所需的header浏览器终止发送POST请求,然后报出CORS error。
2)Springboot处理客户端请求的流程如图所示:请求从浏览器发出,最先通过过滤器链,然后通过Dispatcher Servlet,然后通过拦截器,最后才到达处理请求的控制器上。我们只要让Springboot在受到OPTIONS试探请求时,过滤器或者在拦截器上给浏览器返回一下它需要的那几个header信息就可以了。
通过上面两段介绍,对CORS有了解决问题的思路,有了思路那这个问题的解决方案就很多了,平时比较常用的两种方式如下:(请注意:这是没有Spring Security的情况下)
第一种:在控制器上添加注解CrossOrigin让CorsInterceptor拦截器来处理:
@RestController
//写在类头上的话,对所有方法有效
@CrossOrign(origins = "允许跨域的域名",allowCredentials = "true",allowedHeaders = "*")
public class TestController {
//写在方法头上的话,对当前方法有效
@CrossOrign(origins = "允许跨域的域名",allowCredentials = "true",allowedHeaders = "*")
@RequestMapping(value = "/api/test",method= RequestMethod.POST)
public ResponseEntity test(){
return ResponseEntity.ok("test");
}
}
控制器加上这个注解后,Springboot就会激活CorsInterceptor拦截器来处理Cors问题。如果每个控制器上一个一个的加注解觉得麻烦的话,可以在MvcConfigurer上一次性设置一下,让其全局有效就可以了:
@Configuration
public class MyMvcConfig implements WebMvcConfigurer{
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") //对那些请求路径有效
.allowedOrigins("*")
.allowedHeaders("*")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(10000);
}
}
第二种:在第一种解决方式中我们用interceptor解决问题,接下来我们用Filter解决这个问题。我们Filters过滤器链中手动添加一个处理CORS的Filter就可以,代码如下:
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class RestCorsFilter implements Filter {
public RestCorsFilter() {
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
HttpServletRequest request = (HttpServletRequest) req;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Credentials","true");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
response.setHeader("Access-Control-Max-Age", "31536000");
response.setHeader("Access-Control-Allow-Headers", "*");
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
} else {
chain.doFilter(req, res);
}
}
@Override
public void init(FilterConfig filterConfig) {
}
@Override
public void destroy() {
}
}
这里比较重要的注解有两个@Component和@Order,其中Component大家应该都懂不用多说,那Order是干什么的呢?因为过滤器链中有很多过滤器,为了防止爱管闲事的某个过滤器拦截CORS请求,所以我们用Order(Ordered.HIGHEST_PRECEDENCE)来把我们的过滤器排到最前面,Ordered.HIGHEST_PRECEDENCE是过滤器链的最高等级排位,有这个注解的过滤器会排在最前面。
好了,接下来我们聊聊如果有Spring Security的时候会是什么样的情况
Spring Security由一堆验证授权过滤器组成,他们会以SecurityFilterChain的方式加入到我们的过滤器链:
如图所示,Spring Security加入后情况有了些变化(红色部分),我们刚才的第一种解决方式在这种情况下会失效,因为Spring Security会在过滤器链拦截CORS试探请求,请求无法到达Interceptor拦截器那边。而我们的第二种解决方式倒是可以继续用,因为我们给过滤器设置了最高等级的排位,他会排在在Spring Security的过滤器之前处理CORS请求。
其实Spring Security也提供了它的处理CORS的方式,而且使用起来比前面两个方式显得更优雅更专业,下面看看具体怎么处理。
第三种:Spring Security提供了非常便捷的方式让我们处理CORS,我们只要在Security Adapter 中配置一下cors就可以:
@Configuration
@EnableWebSecurity
public class SecurityTestConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().configurationSource(corsConfigurationSource()) // 这里
.and()
...
}
CorsConfigurationSource corsConfigurationSource(){
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowCredentials(true);
corsConfiguration.setAllowedHeaders(Arrays.asList("*"));
corsConfiguration.setAllowedMethods(Arrays.asList("*"));
corsConfiguration.setAllowedOrigins(Arrays.asList("*"));
corsConfiguration.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**",corsConfiguration);
return source;
}
}
我们在http security config方法中添加.cors(), 这会让Spring Security全局搜索可用的CORS配置,接着我们给他configurationSource让他直接用我们给的CORS配置。
这样我们的CORS就处理完了。