一. 启用 Spring Security 的 CORS 支持
1. 普通的跨域
方式 1:在接口方法上利用 @CrossOrigin 注解解决跨域问题
@RestController
public class IndexController {
@CrossOrigin(value = "http://localhost:8082")
@GetMapping("/hello")
public String hello() {
return "get hello";
}
@CrossOrigin(value = "http://localhost:8082")
@PostMapping("/hello")
public String hello2() {
return "post hello";
}
}
方式 2:通过实现 WebMvcConfigurer 接口来解决跨域问题
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:8082")
.allowedMethods("*")
.allowedHeaders("*");
}
}
二. Spring Security 环境下的跨域问题解决
通过上面的配置,我们已经解决了 Ajax 的跨域请求问题,但是这个案例中也有潜在的威胁存在,常见的就是 CSRF(Cross-site request forgery) 跨站请求伪造。跨站请求伪造也被称为 one-click attack 或者 session riding,通常缩写为 CSRF 或者 XSRF,是一种挟制用户在当前已登录的 Web 应用程序上执行非本意的操作的攻击方法。
所以为了提高网站的安全性,我在上面 Spring Boot 项目的基础之上,添加 Spring Security 的依赖包,但是暂时不进行任何别的操作。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>
2. 解决 Spring Security 环境下跨域问题的 4 种方案
通过实验可知,如果使用了 Spring Security,上面的跨域配置会失效,因为请求会被 Spring Security 拦截。那么在 Spring Security 环境中,如何解决跨域问题呢?这里我们有 3 种方式可以开启 Spring Security 对跨域的支持。
2.1 方式一:开启 cors 方法
我们在上面的案例之上,编写一个 SecurityConfig 配置类,在 configure 方法中,利用 cors() 开启 Spring Security 对 CORS 的支持:
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest()
.permitAll()
.and()
.formLogin()
.permitAll()
.and()
.httpBasic()
.and()
//支持跨域访问
.cors()
.and()
.csrf()
.disable();
}
}
2.2 方式二:进行全局配置
第二种方式是去除上面的跨域配置,直接在 Spring Security 中做全局配置,如下:
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest()
.permitAll()
.and()
.formLogin()
.permitAll()
.and()
.httpBasic()
.and()
//支持跨域访问
.cors()
.configurationSource(corsConfigurationSource())
.and()
.csrf()
.disable();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowCredentials(true);
configuration.setAllowedOrigins(Collections.singletonList("*"));
configuration.setAllowedMethods(Collections.singletonList("*"));
configuration.setAllowedHeaders(Collections.singletonList("*"));
configuration.setMaxAge(Duration.ofHours(1));
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
以上 2 个方法,都可以实现在 Spring Security 环境下的跨域访问。
2.3 方式三:支持 OAuth2 的跨域访问
我们开发时,还有一种情况就是支持 OAuth2 相关接口的跨域,比如用户要访问 OAuth2 中的 /oauth/token 等接口。我们可以配置一个全局的 CorsFilter 跨域过滤器类,核心代码如下:
/**
* 跨域配置方式3:定义全局跨域过滤器
**/
@Configuration
public class GlobalCorsConfiguration {
@Bean
public CorsFilter corsFilter() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowCredentials(true);
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
return new CorsFilter(urlBasedCorsConfigurationSource);
}
}
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//跨域方式3:
http.requestMatchers()
.antMatchers(HttpMethod.OPTIONS, "/oauth/**")
.and()
.csrf()
.disable()
.formLogin()
.and()
.cors();
}
}
2.4 方式 4 :自定义一个全局跨域处理 Filter
public class CorsFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String orignalHeader = StringUtils.defaultIfBlank(request.getHeader("Origin"), "*");
// 指定本次预检请求的有效期
response.setHeader("Access-Control-Max-Age", "3600");
// 服务器支持的所有头信息字段
response.setHeader("Access-Control-Allow-Headers", request.getHeader("Access-Control-Request-Headers"));
response.setHeader("Access-Control-Allow-Origin", orignalHeader);
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT");
filterChain.doFilter(request, response);
}
}
public class WebMvcConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(new CorsFilter (), WebAsyncManagerIntegrationFilter.class);
}
}