如何在 Spring Boot + Spring Security 应用程序中配置 CORS?

IT技术 javascript spring spring-security spring-boot cors
2021-02-02 22:21:47

我使用带有 Spring Security 和 Cors 支持的 Spring Boot。

如果我执行以下代码

url = 'http://localhost:5000/api/token'
xmlhttp = new XMLHttpRequest
xmlhttp.onreadystatechange = ->
    if xmlhttp.readyState is 4
        console.log xmlhttp.status
xmlhttp.open "GET", url, true
# xmlhttp.setRequestHeader "X-Requested-With", "XMLHttpRequest"
xmlhttp.setRequestHeader 'Authorization', 'Basic ' + btoa 'a:a'
do xmlhttp.send

结果我得到

200

如果我使用错误的凭据进行测试,例如

url = 'http://localhost:5000/api/token'
xmlhttp = new XMLHttpRequest
xmlhttp.onreadystatechange = ->
    if xmlhttp.readyState is 4
        console.log xmlhttp.status
xmlhttp.open "GET", url, true
# xmlhttp.setRequestHeader "X-Requested-With", "XMLHttpRequest"
xmlhttp.setRequestHeader 'Authorization', 'Basic ' + btoa 'a:aa'
do xmlhttp.send

我得到的不是 401(这是 Spring Security 中错误认证的标准代码)

0

带有以下浏览器通知:

获取http://localhost:5000/api/token

XMLHttpRequest 无法加载http://localhost:5000请求的资源上不存在“Access-Control-Allow-Origin”标头。因此,不允许访问Origin ' http://localhost:3000 '。响应的 HTTP 状态代码为 401。

我正在开发需要来自服务器响应的有用 http 状态代码来处理这种情况的前端代码。我需要比 0 更有用的东西。而且响应体是空的。我不知道我的配置是错误的,还是软件错误,我也不知道在哪里,是铬(使用 arch linux)还是 spring 安全。

我的弹簧配置是:

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

@RestController
@RequestMapping("api")
public class Controller {
    @RequestMapping("token")
    @CrossOrigin
    Map<String, String> token(HttpSession session) {
        return Collections.singletonMap("token", session.getId());
    }
}

@EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("a").password("a").roles("USER");
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .requestMatchers(CorsUtils::isPreFlightRequest).permitAll()
                .anyRequest().authenticated()
                .and().httpBasic();
    }
}

如果我使用 curl 测试一切正常,我认为是因为不需要 CORS 支持,但我尝试使用 OPTION 请求模拟 CORS,结果也不错。

$ curl -v localhost:5000/api/token -H "Authorization: Basic YTpha"
*   Trying ::1...
* Connected to localhost (::1) port 5000 (#0)
> GET /api/token HTTP/1.1
> Host: localhost:5000
> User-Agent: curl/7.48.0
> Accept: */*
> Authorization: Basic YTpha
> 
< HTTP/1.1 200 OK
< Server: Apache-Coyote/1.1
< X-Content-Type-Options: nosniff
< X-XSS-Protection: 1; mode=block
< Cache-Control: no-cache, no-store, max-age=0, must-revalidate
< Pragma: no-cache
< Expires: 0
< X-Frame-Options: DENY
< Access-Control-Allow-Origin: http://localhost:3000
< Access-Control-Allow-Methods: POST,GET,OPTIONS,DELETE
< Access-Control-Max-Age: 3600
< Access-Control-Allow-Credentials: true
< Access-Control-Allow-Headers: Origin,Accept,X-Requested-    With,Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers,Authorization
< x-auth-token: 58e4cca9-7719-46c8-9180-2fc16aec8dff
< Content-Type: application/json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Sun, 01 May 2016 16:15:44 GMT
< 
* Connection #0 to host localhost left intact
{"token":"58e4cca9-7719-46c8-9180-2fc16aec8dff"}

并且凭据错误:

$ curl -v localhost:5000/api/token -H "Authorization: Basic YTp"
*   Trying ::1...
* Connected to localhost (::1) port 5000 (#0)
> GET /api/token HTTP/1.1
> Host: localhost:5000
> User-Agent: curl/7.48.0
> Accept: */*
> Authorization: Basic YTp
> 
< HTTP/1.1 401 Unauthorized
< Server: Apache-Coyote/1.1
< X-Content-Type-Options: nosniff
< X-XSS-Protection: 1; mode=block
< Cache-Control: no-cache, no-store, max-age=0, must-revalidate
< Pragma: no-cache
< Expires: 0
< X-Frame-Options: DENY
< WWW-Authenticate: Basic realm="Realm"
< Content-Type: application/json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Sun, 01 May 2016 16:16:15 GMT
< 
* Connection #0 to host localhost left intact
{"timestamp":1462119375041,"status":401,"error":"Unauthorized","message":"Failed to decode basic authentication token","path":"/api/token"}

编辑:为了避免误解。我使用 1.3.3 Spring Boot。博文写道:

CORS 支持将在即将发布的 Spring Boot 1.3 版本中提供,并且已经在 1.3.0.BUILD-SNAPSHOT 版本中提供。

在 Spring Boot 应用程序中使用带有 @CrossOrigin 注释的控制器方法 CORS 配置不需要任何特定配置。

可以通过使用自定义的 addCorsMappings(CorsRegistry) 方法注册 WebMvcConfigurer bean 来定义全局 CORS 配置:

我添加了以下代码以启用全局 cors 支持。实际上我以前试过这个,但结果是一样的。我最近又试了一次,结果是一样的。

@Configuration
public class MyConfiguration {

    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurerAdapter() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**");
            }
        };
    }
}

问题来自授权过程之间的重定向的想法很有趣。如何将重定向更改为任何资源以避免这种冲突?

编辑:

我想我更接近解决方案了。我已经通过向所有请求添加 Access-Control-Allow-Origin: * 来测试我的 nodejs 服务器,该服务器支持 cors 没有问题。

就像 Stefan Isele 已经提到的那样,spring security 似乎重定向或不添加 CORS 标头,因此这就是请求似乎被破坏的原因。因此,当 Spring Security 正在检查身份验证时,它必须添加正确的标头。

有谁知道该怎么做?

编辑:

我找到了一个解决方法,这似乎很难看。我已经开始了 Spring Boot 的 github 问题,我在其中描述了解决方法:https : //github.com/spring-projects/spring-boot/issues/5834

6个回答

Spring Security 现在可以利用我写的这篇博文中描述的 Spring MVC CORS 支持

要使其工作,您需要在 Spring Security 级别显式启用 CORS 支持,如下所示,否则启用 CORS 的请求可能会在到达 Spring MVC 之前被 Spring Security 阻止。

如果您使用控制器级别的@CrossOrigin注解,您只需要启用 Spring Security CORS 支持,它将利用 Spring MVC 配置:

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and()...
    }
}

如果你更喜欢使用 CORS 全局配置,你可以声明一个CorsConfigurationSourcebean,如下所示:

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and()...
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
        return source;
    }
}

这种方法取代了之前推荐的基于过滤器的方法

您可以在Spring Security 文档专用 CORS 部分找到更多详细信息

补充一点,如果您使用@RepositoryRestResource,则必须以另一种方式专门启用它:stackoverflow.com/questions/31724994/...
2021-03-12 22:21:47
嗨,我正在尝试第二种方式,但是 Springs 将每个 JWT 令牌解码为“anonymousUser”。
2021-03-25 22:21:47
我使用CorsFilterbean使 CORS+Spring Security 工作由于某种原因,FilterRegistrationBean 没有被选中。
2021-04-10 22:21:47
嗨,我有这个: <mvc:cors> <mvc:mapping path="/rest/client/**" allowed-headers=" " allowed-methods="GET,PUT,HEAD,OPTIONS,POST,DELETE,PATCH “ allowed-origins=" " allow-credentials="true" /> </mvc:cors> 在我的调度程序 servlet 中。这个: <cors /> 在我的 security.xml 中。但是当我打开身份验证时它不起作用。没有日志记录。有什么线索吗?
2021-04-10 22:21:47

如果您使用 JDK 8+,则有一个单行 lambda 解决方案:

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.cors().configurationSource(request -> new CorsConfiguration().applyPermitDefaultValues());
}
它究竟有什么作用?javascript 客户端请求将不再抱怨“被 CORS 策略阻止”?
2021-03-13 22:21:47
解决了我在使用 Axios 执行 POST 请求时遇到相同 CORS 异常的问题。
2021-04-04 22:21:47

如果您使用的是Spring Security,您可以执行以下操作以确保首先处理 CORS 请求:

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            // by default uses a Bean by the name of corsConfigurationSource
            .cors().and()
            ...
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("https://example.com"));
        configuration.setAllowedMethods(Arrays.asList("GET","POST"));
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}

有关更多信息,请参阅Spring 4.2.x CORS

如果没有Spring Security,这将起作用:

@Bean
public WebMvcConfigurer corsConfigurer() {
    return new WebMvcConfigurer() {
        @Override
        public void addCorsMappings(CorsRegistry registry) {
            registry.addMapping("/**")
                    .allowedOrigins("*")
                    .allowedMethods("GET", "PUT", "POST", "PATCH", "DELETE", "OPTIONS");
        }
    };
}
这些都不适合我。我不断收到 401 与选项请求。
2021-03-18 22:21:47
我在我的 MainApplicationFile 中复制了名为“Without Spring Security”的代码解决方案,但它不起作用:(
2021-03-27 22:21:47
完美运行!重要的是要记住(如您所说),如果您使用的是 Spring Security,您应该使用第一个代码片段来确保首先处理 CORS 请求......这就是我的情况的关键。太感谢了!
2021-04-04 22:21:47

如何解决 Spring Boot 2.3+ 上的 CORS

概括

如果您遇到此 CORS 问题,请不要担心。这是每个后端开发人员第一次尝试与前端微服务集成时都会遇到的常见问题。这是浏览器严格应用的某种安全策略,以确保用户的安全,这就是为什么当您通过 Postman/Swagger 或 cURL 尝试您的 API 时您没有面对它。

解决方案

  • 客户端绕过(仅限开发人员)

以下解决方案仅用于开发目的,您绝对需要为您的生产环境永久解决此 CORS 问题。您可以使用以下浏览器扩展来绕过针对 CORS 错误的浏览器策略,但如果它们无法正常工作,请不要感到惊讶。

  1. CORS 取消阻止Firefox - Chrome
  2. CORS 无处不在Firefox
  • 生产解决方案

有多种方法可以在应用程序上配置 CORS 策略,这完全基于您的部署架构。例如,如果您的应用程序将通过反向代理(如 Nginx)、API 网关(Kong)、服务网格 Sidecar 代理(即 Envoy)、Kubernetes NGINX Ingress 等公开,最佳实践是处理 CORS Edge 层上的配置,因为有时他们不考虑较低层的标头并覆盖它们,您仍然会收到来自浏览器的 CORS 错误。我在下面列出了用于配置边缘层的有用链接

但是,如果您要通过 SprintBoot 的内置 Web 服务器部署和公开您的 API,则可以使用接下来的说明。

全局启用 CORS 的说明 - Spring Boot 应用程序

如果您没有任何 WebSecurityConfig 实现,只需轻松执行以下步骤:

  1. 将以下依赖项 [ spring-boot-starter-security ] 添加到您的 pom.xml
<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-security</artifactId>
</dependency>
  1. 在您的配置包中创建一个扩展 WebSecurityConfig 的新类(即“SecurityConfig”)
  2. 将以下代码放入创建的文件中:
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.web.cors.CorsConfiguration;

import java.util.List;

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter
{


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.setAllowedHeaders(List.of("Authorization", "Cache-Control", "Content-Type"));
        corsConfiguration.setAllowedOrigins(List.of("*"));
        corsConfiguration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PUT","OPTIONS","PATCH", "DELETE"));
        corsConfiguration.setAllowCredentials(true);
        corsConfiguration.setExposedHeaders(List.of("Authorization"));
        
        // You can customize the following part based on your project, it's only a sample
        http.authorizeRequests().antMatchers("/**").permitAll().anyRequest()
                .authenticated().and().csrf().disable().cors().configurationSource(request -> corsConfiguration);

    }
}
  1. 现在您需要根据需要自定义 CORS 配置:

    • setAllowedHeaders -> 你必须指定哪些参数允许通过前端应用发送到后端服务,例如,如果你使用的是 Bearer/Basic Token Authorization 方法,则需要通过“授权”标题。因此,您需要确保 backed 会相应地接受此数据,为此,您必须将“授权”放在 Allowed-Headers 列表中。

    • setAllowedMethods -> 不要忘记将“OPTIONS”方法放在 Pre-flight 过程的列表中。别担心,在这里阅读更多!

    • setAllowCredentials -> 如果您使用 Authorization 标头,请将其设置为 True。

    • setExposedHeaders -> 如果你通过响应头返回数据,你需要在这里指定它们。例如,某些 API 旨在通过响应标头在成功/身份验证后返回授权令牌。因此,需要相应地公开相关的标头。

    • setAllowedOrigins -> 您必须指定有资格向后端应用程序发送请求的域。例如,如果您的应用程序托管在https://penguin.com 上,而您的 API 位于https://api.penguin.com 上,则您需要允许“https://penguing.com”向您的后端发送请求. 此外,您可以传递通配符 (*) 以允许任何域向您的后端发送请求。但建议不要使用“any”,除非您提供公共 API 或在非生产环境中部署。

    对于可能认为 CORS 可以避免其他平台滥用 API(即网络钓鱼目的)的人来说,这是一个重要的误解。这不是真的,CORS 策略是基于浏览器的策略,可以通过代理轻松绕过,因此它只会使误用过程变得更加困难,但并不会产生免疫力。

  2. 构建/运行您的应用程序,测试您的 API,然后休息(每个人都知道 CORS 头痛)

替代解决方案

您可以使用以下链接:

Spring.io | 为 RESTful Web 服务启用跨域请求

Bealdung | 带弹簧的 CORS

@Soroosh Khodami 在同一个域上有没有严格的方法,但对于端口,例如:www.corscheck.com:8081 www.corscheck.com:8056 端口可能会改变,但域将保持不变,所以我怎么能只限制为检查域(域是事先不知道的,它们可以根据客户端进行更改)
2021-03-11 22:21:47
这节省了我几个小时,谢谢!一个对我完全有用的小改动,将此行 corsConfiguration.setAllowedOrigins 更改为 corsConfiguration.setAllowedOriginPatterns
2021-03-20 22:21:47
只有14谢谢。我想原因是因为谷歌没有显示在第一个。这个答案是我找到的最好的答案,非常感谢。
2021-04-06 22:21:47
@Shamitha Silva 我认为 HTTP 请求标头中没有源端口的迹象。所以它应该在防火墙中进行管理。
2021-04-07 22:21:47

跨源保护是浏览器的一项功能。正如您所推测的那样,Curl 不关心 CORS。这解释了为什么您的卷发成功,而浏览器请求却没有。

如果您使用错误的凭据发送浏览器请求,spring 将尝试将客户端转发到登录页面。此响应(在登录页面之外)不包含标题“Access-Control-Allow-Origin”,浏览器会按照您的描述做出react。

您必须使 spring 包含此登录响应的 haeder,并且可能包含其他响应,例如错误页面等。

这可以这样做:

    @Configuration
    @EnableWebMvc
    public class WebConfig extends WebMvcConfigurerAdapter {

            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/api/**")
                    .allowedOrigins("http://domain2.com")
                    .allowedMethods("PUT", "DELETE")
                    .allowedHeaders("header1", "header2", "header3")
                    .exposedHeaders("header1", "header2")
                    .allowCredentials(false).maxAge(3600);
            }
     }

这是从cors-support-in-spring-framework复制的

我将首先为所有资源添加 cors 映射:

registry.addMapping("/**")

并且还允许所有方法标头..一旦它起作用,您可以开始再次将其减少到所需的最小值。

请注意,CORS 配置随 4.2 版而更改。

如果这不能解决您的问题,请发布您从失败的 ajax 请求中获得的响应。