Spring Boot 2.x->3.x 升级后的 CORS 问题

问题描述 投票:0回答:1

自从从 Spring Boot 2.x 升级到 3.x 以来,我遇到了与 CORS 相关的问题。我在这里将其归结为要点。

RegexCorsConfiguration.java

/**
 * Extend the traditional CORS origin check (equalsIgnoreCase) with a regular expression check.
 *
 * @author Lorent Lempereur <[email protected]>
 * Source: https://github.com/looorent/spring-security-jwt/blob/master/src/main/java/be/looorent/security/jwt/RegexCorsConfiguration.java
 */
public class RegexCorsConfiguration extends CorsConfiguration {
  private static final Logger LOGGER = LoggerFactory.getLogger(RegexCorsConfiguration.class);
  private final List<Pattern> allowedOriginsRegex;

  public RegexCorsConfiguration() {
    allowedOriginsRegex = new ArrayList<>();
  }

  public RegexCorsConfiguration(CorsConfiguration other) {
    this();
    setAllowCredentials(other.getAllowCredentials());
    setAllowedOrigins(other.getAllowedOrigins());
    setAllowedHeaders(other.getAllowedHeaders());
    setAllowedMethods(other.getAllowedMethods());
    setExposedHeaders(other.getExposedHeaders());
    setMaxAge(other.getMaxAge());
  }

  @Override
  public void addAllowedOrigin(String origin) {
    super.addAllowedOrigin(origin);
    try {
      allowedOriginsRegex.add(Pattern.compile(origin));
    } catch (PatternSyntaxException e) {
      LOGGER.warn(
          "Wrong syntax for the origin {} as a regular expression. If this origin is not supposed to be a regular expression, just ignore this error.",
          origin);
    }
  }

  @Override
  public String checkOrigin(String requestOrigin) {
    final String result = super.checkOrigin(requestOrigin);
    return result != null ? result : checkOriginWithRegularExpression(requestOrigin);
  }

  private String checkOriginWithRegularExpression(String requestOrigin) {
    return allowedOriginsRegex.stream()
        .filter(pattern -> pattern.matcher(requestOrigin).matches())
        .map(pattern -> requestOrigin)
        .findFirst()
        .orElse(null);
  }

  @Override
  public CorsConfiguration combine(CorsConfiguration other) {
    if (other == null) {
      return this;
    }
    final CorsConfiguration config = new RegexCorsConfiguration(this);
    config.setAllowedOrigins(combineInternal(this.getAllowedOrigins(), other.getAllowedOrigins()));
    config.setAllowedMethods(combineInternal(this.getAllowedMethods(), other.getAllowedMethods()));
    config.setAllowedHeaders(combineInternal(this.getAllowedHeaders(), other.getAllowedHeaders()));
    config.setExposedHeaders(combineInternal(this.getExposedHeaders(), other.getExposedHeaders()));
    final Boolean allowCredentials = other.getAllowCredentials();
    if (allowCredentials != null) {
      config.setAllowCredentials(allowCredentials);
    }
    final Long maxAge = other.getMaxAge();
    if (maxAge != null) {
      config.setMaxAge(maxAge);
    }
    return config;
  }

  private List<String> combineInternal(List<String> source, List<String> other) {
    if (other == null || other.contains(ALL)) {
      return source;
    }
    if (source == null || source.contains(ALL)) {
      return other;
    }
    final Set<String> combined = new HashSet<>(source);
    combined.addAll(other);
    return new ArrayList<>(combined);
  }
}

示例WebSecurityAutoConfiguration.java

@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
@EnableAspectJAutoProxy
@EnableWebSecurity(debug = true)
public class ExampleWebSecurityAutoConfiguration {

  @Bean(name="com.example.web.security.autoconfigure.ExampleWebSecurityAutoConfiguration")
  @Order(50) // allow other implementations to take precedence
  public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests(request -> request
          .anyRequest().authenticated()
        )
        .csrf(AbstractHttpConfigurer::disable)
        .cors(Customizer.withDefaults());
    return http.build();
  }

  // exclude HandlerMappingIntrospector which implements a default Spring CORS policy. We only want to exclude this
  // configuration when a user includes their own customized CORS policy
  @Bean
  @ConditionalOnMissingBean(ignored = HandlerMappingIntrospector.class)
  public CorsConfigurationSource corsConfigurationSource() {
    final RegexCorsConfiguration config = new RegexCorsConfiguration();
    config.setAllowCredentials(true);
    config.addAllowedOrigin(".*?://(.+\\.)*example\\.com");
    config.addAllowedMethod(CorsConfiguration.ALL);
    config.addAllowedHeader(CorsConfiguration.ALL);
    final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", config);
    return source;
  }
}

示例WebSecurityAutoConfigurationTests.java

@Tag("integration")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = {
    ExampleWebSecurityAutoConfiguration.class
})
@ContextConfiguration(classes = {
    ExampleWebSecurityAutoConfigurationTests.Config.class,
    ExampleWebSecurityAutoConfigurationTests.SecurityConfig.class
})
@DirtiesContext
class ExampleWebSecurityAutoConfigurationTests {

  @Autowired
  private TestRestTemplate restTemplate;

  @Test
  void testCors() {
    final URI uri = restTemplate.getRestTemplate().getUriTemplateHandler().expand("/unauthenticated");
    final ResponseEntity<Void> fromSubdomain = restTemplate.exchange(
        RequestEntity.options(uri)
            .header(HttpHeaders.ORIGIN, "http://stackoverflow.example.com")
            .build(),
        Void.class);
    assertThat(fromSubdomain.getStatusCode())
        .isEqualTo(HttpStatus.OK);

    final ResponseEntity<Void> fromRoot = restTemplate.exchange(
        RequestEntity.options(uri)
            .header(HttpHeaders.ORIGIN, "http://example.com")
            .build(),
        Void.class);
    assertThat(fromRoot.getStatusCode())
        .isEqualTo(HttpStatus.OK);

    final ResponseEntity<Void> fromOther = restTemplate.exchange(
        RequestEntity.options(uri)
            .header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")
            .header(HttpHeaders.ORIGIN, "http://stackoverflow.com")
            .build(),
        Void.class);
    assertThat(fromOther.getStatusCode())
        .isEqualTo(HttpStatus.FORBIDDEN);
  }

  @Configuration(proxyBeanMethods = false)
  @EnableAutoConfiguration
  @EnableMethodSecurity
  static class Config {

    @RestController
    @RequestMapping
    public static class Controller {

      @GetMapping("/unauthenticated")
      public void test() {
      }

    }
  }

  @Configuration(proxyBeanMethods = false)
  @EnableWebSecurity
  @EnableMethodSecurity
  static class SecurityConfig {

    @Bean
    @Order(1)
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
      http
          .securityMatchers(matchers -> matchers.requestMatchers("/unauthenticated"))
          .csrf(AbstractHttpConfigurer::disable)
          .cors(Customizer.withDefaults())
          .authorizeHttpRequests(requests -> requests
            .dispatcherTypeMatchers(DispatcherType.FORWARD, DispatcherType.ERROR).permitAll()
            .requestMatchers("/unauthenticated").permitAll()
          )
          .httpBasic(Customizer.withDefaults());
      return http.build();
    }
  }
}

第三个断言针对 CORS 违规,应返回 FORBIDDEN,但由于返回 OK 而失败。我已经尝试添加一些没有效果的东西(@EnableMethodSecurity、securityMatchers、dispatcherTypeMatchers)。

spring-boot spring-security cors upgrade
1个回答
0
投票

一旦创建了

SecurityFilterChain
,它就会与 cors 配置一起创建,您已将其指定为默认配置。这意味着您在其他地方指定什么并不重要,
SecurityFilterChain
已经使用此 CORS 设置创建了。

而是将其与正确的 CORS 设置一起创建,例如:

@Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
        return httpSecurity
                .cors(c -> {
                    CorsConfigurationSource source = request -> {
                        CorsConfiguration configuration = new CorsConfiguration();
                        configuration.setAllowedOrigins(List.of("http://localhost:4200"));
                        configuration.setAllowedMethods(List.of("GET", "POST"));
                        configuration.setAllowedHeaders(List.of("*"));
                        configuration.setAllowCredentials(true);
                        return configuration;
                    };
                    c.configurationSource(source);
                })
                // add whatever other configuration that you have
                .build();
    }
© www.soinside.com 2019 - 2024. All rights reserved.