跳到主要内容
版本:3.5.10

Spring Security

QWen Max 中英对照 Spring Security

如果 Spring Security 在 classpath 中,则 Web 应用程序默认是受保护的。这包括对 Spring Boot 的 /error 端点进行安全保护。Spring Boot 依赖于 Spring Security 的内容协商(content-negotiation)策略来决定使用 httpBasic 还是 formLogin。若要为 Web 应用程序添加方法级安全控制,还可以添加 @EnableMethodSecurity 并配置所需的设置。更多详细信息请参阅 Spring Security 参考指南

默认的 UserDetailsService 只包含一个用户。用户名为 user,密码是随机生成的,并在应用程序启动时以 WARN 级别打印出来,如下例所示:

Using generated security password: 78fa095d-3f4c-48b1-ad50-e24c31d5cf35

This generated password is for development use only. Your security configuration must be updated before running your application in production.
备注

如果你微调了日志配置,请确保将 org.springframework.boot.autoconfigure.security 类别设置为记录 WARN 级别的消息。否则,默认密码不会被打印出来。

你可以通过提供 spring.security.user.namespring.security.user.password 来更改用户名和密码。

Web 应用程序默认提供的基本功能包括:

你可以通过添加一个 bean 来提供一个不同的 AuthenticationEventPublisher

MVC Security

默认的安全配置在 SecurityAutoConfigurationUserDetailsServiceAutoConfiguration 中实现。SecurityAutoConfiguration 为 Web 安全导入了 SpringBootWebSecurityConfiguration,并为身份验证导入了 UserDetailsServiceAutoConfiguration

要完全关闭默认的 Web 应用安全配置(包括 Actuator 安全),或组合多个 Spring Security 组件(例如 OAuth2 Client 和 Resource Server),请添加一个类型为 SecurityFilterChain 的 Bean(这样做不会禁用 UserDetailsService 配置)。若还需关闭 UserDetailsService 配置,请添加一个类型为 UserDetailsServiceAuthenticationProviderAuthenticationManager 的 Bean。

当以下任一 Spring Security 模块位于 classpath 中时,UserDetailsService 的自动配置也会退避:

  • spring-security-oauth2-client

  • spring-security-oauth2-resource-server

  • spring-security-saml2-service-provider

要使用 UserDetailsService 以及上述一个或多个依赖项,请定义你自己的 InMemoryUserDetailsManager bean。

可以通过添加自定义的 SecurityFilterChain Bean 来覆盖访问规则。Spring Boot 提供了一些便捷方法,可用于覆盖对 Actuator 端点和静态资源的访问规则。EndpointRequest 可用于创建一个基于 management.endpoints.web.base-path 属性的 RequestMatcherPathRequest 可用于为常用位置中的资源创建一个 RequestMatcher

WebFlux Security

与 Spring MVC 应用程序类似,你可以通过添加 spring-boot-starter-security 依赖来保护你的 WebFlux 应用程序。默认的安全配置在 ReactiveSecurityAutoConfigurationReactiveUserDetailsServiceAutoConfiguration 中实现。除了响应式 Web 应用程序外,当使用 RSocket 时,后者也会被自动配置。ReactiveSecurityAutoConfiguration 会导入 WebFluxSecurityConfiguration 以提供 Web 安全支持。ReactiveUserDetailsServiceAutoConfiguration 则负责自动配置身份验证。

要完全关闭默认的 Web 应用安全配置(包括 Actuator 安全),请添加一个类型为 WebFilterChainProxy 的 Bean(这样做不会禁用 ReactiveUserDetailsService 的配置)。若还需关闭 ReactiveUserDetailsService 的配置,请添加一个类型为 ReactiveUserDetailsServiceReactiveAuthenticationManager 的 Bean。

当以下任意 Spring Security 模块位于 classpath 中时,自动配置也会退避:

  • spring-security-oauth2-client

  • spring-security-oauth2-resource-server

要使用 ReactiveUserDetailsService,除了这些依赖项中的一个或多个之外,还需定义你自己的 MapReactiveUserDetailsService bean。

可以通过添加自定义的 SecurityWebFilterChain Bean 来配置访问规则以及使用多个 Spring Security 组件(例如 OAuth 2 Client 和 Resource Server)。Spring Boot 提供了一些便捷方法,可用于覆盖 actuator 端点和静态资源的访问规则。EndpointRequest 可用于创建一个基于 management.endpoints.web.base-path 属性的 ServerWebExchangeMatcher

PathRequest 可用于为常用位置中的资源创建一个 ServerWebExchangeMatcher

例如,你可以通过添加类似以下内容来自定义你的安全配置:

import org.springframework.boot.autoconfigure.security.reactive.PathRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;

import static org.springframework.security.config.Customizer.withDefaults;

@Configuration(proxyBeanMethods = false)
public class MyWebFluxSecurityConfiguration {

@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http.authorizeExchange((exchange) -> {
exchange.matchers(PathRequest.toStaticResources().atCommonLocations()).permitAll();
exchange.pathMatchers("/foo", "/bar").authenticated();
});
http.formLogin(withDefaults());
return http.build();
}

}

OAuth2

OAuth2 是一个被 Spring 支持的广泛使用的授权框架。

Client

如果你的 classpath 中包含 spring-security-oauth2-client,你可以利用一些自动配置来设置 OAuth2/Open ID Connect 客户端。该配置使用了 OAuth2ClientProperties 下的属性。这些属性同时适用于 Servlet 和响应式(reactive)应用程序。

你可以在 spring.security.oauth2.client 前缀下注册多个 OAuth2 客户端和提供商,如下例所示:

spring.security.oauth2.client.registration.my-login-client.client-id=abcd
spring.security.oauth2.client.registration.my-login-client.client-secret=password
spring.security.oauth2.client.registration.my-login-client.client-name=Client for OpenID Connect
spring.security.oauth2.client.registration.my-login-client.provider=my-oauth-provider
spring.security.oauth2.client.registration.my-login-client.scope=openid,profile,email,phone,address
spring.security.oauth2.client.registration.my-login-client.redirect-uri={baseUrl}/login/oauth2/code/{registrationId}
spring.security.oauth2.client.registration.my-login-client.client-authentication-method=client_secret_basic
spring.security.oauth2.client.registration.my-login-client.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.my-client-1.client-id=abcd
spring.security.oauth2.client.registration.my-client-1.client-secret=password
spring.security.oauth2.client.registration.my-client-1.client-name=Client for user scope
spring.security.oauth2.client.registration.my-client-1.provider=my-oauth-provider
spring.security.oauth2.client.registration.my-client-1.scope=user
spring.security.oauth2.client.registration.my-client-1.redirect-uri={baseUrl}/authorized/user
spring.security.oauth2.client.registration.my-client-1.client-authentication-method=client_secret_basic
spring.security.oauth2.client.registration.my-client-1.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.my-client-2.client-id=abcd
spring.security.oauth2.client.registration.my-client-2.client-secret=password
spring.security.oauth2.client.registration.my-client-2.client-name=Client for email scope
spring.security.oauth2.client.registration.my-client-2.provider=my-oauth-provider
spring.security.oauth2.client.registration.my-client-2.scope=email
spring.security.oauth2.client.registration.my-client-2.redirect-uri={baseUrl}/authorized/email
spring.security.oauth2.client.registration.my-client-2.client-authentication-method=client_secret_basic
spring.security.oauth2.client.registration.my-client-2.authorization-grant-type=authorization_code
spring.security.oauth2.client.provider.my-oauth-provider.authorization-uri=https://my-auth-server.com/oauth2/authorize
spring.security.oauth2.client.provider.my-oauth-provider.token-uri=https://my-auth-server.com/oauth2/token
spring.security.oauth2.client.provider.my-oauth-provider.user-info-uri=https://my-auth-server.com/userinfo
spring.security.oauth2.client.provider.my-oauth-provider.user-info-authentication-method=header
spring.security.oauth2.client.provider.my-oauth-provider.jwk-set-uri=https://my-auth-server.com/oauth2/jwks
spring.security.oauth2.client.provider.my-oauth-provider.user-name-attribute=name

对于支持 OpenID Connect discovery 的 OpenID Connect 提供商,配置可以进一步简化。需要为该提供商配置一个 issuer-uri,即其声明为 Issuer Identifier 的 URI。例如,如果提供的 issuer-uri 是 "https://example.com",那么将向 "https://example.com/.well-known/openid-configuration" 发起一个 "OpenID Provider Configuration Request"。预期结果是一个 "OpenID Provider Configuration Response"。以下示例展示了如何使用 issuer-uri 配置一个 OpenID Connect Provider:

spring.security.oauth2.client.provider.oidc-provider.issuer-uri=https://dev-123456.oktapreview.com/oauth2/default/

默认情况下,Spring Security 的 OAuth2LoginAuthenticationFilter 仅处理匹配 /login/oauth2/code/* 的 URL。如果你想自定义 redirect-uri 以使用不同的模式,则需要提供相应的配置来处理该自定义模式。例如,对于 Servlet 应用程序,你可以添加自己的 SecurityFilterChain,如下所示:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration(proxyBeanMethods = false)
@EnableWebSecurity
public class MyOAuthClientConfiguration {

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((requests) -> requests
.anyRequest().authenticated()
)
.oauth2Login((login) -> login
.redirectionEndpoint((endpoint) -> endpoint
.baseUri("/login/oauth2/callback/*")
)
);
return http.build();
}

}
提示

Spring Boot 会自动配置一个 InMemoryOAuth2AuthorizedClientService,Spring Security 使用它来管理客户端注册信息。InMemoryOAuth2AuthorizedClientService 的功能有限,我们建议仅在开发环境中使用。对于生产环境,请考虑使用 JdbcOAuth2AuthorizedClientService,或者自行实现 OAuth2AuthorizedClientService

常见提供者的 OAuth2 客户端注册

对于常见的 OAuth2 和 OpenID 提供商,包括 Google、Github、Facebook 和 Okta,我们提供了一组提供商默认配置(分别为 googlegithubfacebookokta)。

如果你不需要自定义这些 provider,可以将 provider 属性设置为你需要推断默认值的那个 provider。此外,如果客户端注册的 key 与某个默认支持的 provider 匹配,Spring Boot 也会自动推断出该 provider。

换句话说,以下示例中的两种配置都使用了 Google provider:

spring.security.oauth2.client.registration.my-client.client-id=abcd
spring.security.oauth2.client.registration.my-client.client-secret=password
spring.security.oauth2.client.registration.my-client.provider=google
spring.security.oauth2.client.registration.google.client-id=abcd
spring.security.oauth2.client.registration.google.client-secret=password

资源服务器

如果你的 classpath 中包含 spring-security-oauth2-resource-server,Spring Boot 可以自动配置一个 OAuth2 Resource Server。对于 JWT 配置,需要指定一个 JWK Set URI 或 OIDC Issuer URI,如下例所示:

spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://example.com/oauth2/default/v1/keys
spring.security.oauth2.resourceserver.jwt.issuer-uri=https://dev-123456.oktapreview.com/oauth2/default/
备注

如果授权服务器不支持 JWK Set URI,你可以使用用于验证 JWT 签名的公钥来配置资源服务器。这可以通过 spring.security.oauth2.resourceserver.jwt.public-key-location 属性完成,该属性的值需要指向一个包含 PEM 编码 x509 格式公钥的文件。

spring.security.oauth2.resourceserver.jwt.audiences 属性可用于指定 JWT 中 aud 声明的预期值。例如,要要求 JWT 包含值为 my-audienceaud 声明:

spring.security.oauth2.resourceserver.jwt.audiences[0]=my-audience

相同的属性适用于 servlet 和 reactive 应用程序。或者,你可以为 servlet 应用程序定义自己的 JwtDecoder bean,或为 reactive 应用程序定义 ReactiveJwtDecoder

在使用不透明令牌(opaque tokens)而非 JWT 的情况下,你可以配置以下属性,通过内省(introspection)来验证令牌:

spring.security.oauth2.resourceserver.opaquetoken.introspection-uri=https://example.com/check-token
spring.security.oauth2.resourceserver.opaquetoken.client-id=my-client-id
spring.security.oauth2.resourceserver.opaquetoken.client-secret=my-client-secret

同样,这些属性适用于 Servlet 和响应式应用程序。或者,你可以为 Servlet 应用程序定义自己的 OpaqueTokenIntrospector Bean,或为响应式应用程序定义 ReactiveOpaqueTokenIntrospector

Authorization Server

如果你的 classpath 中包含 spring-security-oauth2-authorization-server,你可以利用一些自动配置来设置基于 Servlet 的 OAuth2 授权服务器。

你可以在 spring.security.oauth2.authorizationserver.client 前缀下注册多个 OAuth2 客户端,如下例所示:

spring.security.oauth2.authorizationserver.client.my-client-1.registration.client-id=abcd
spring.security.oauth2.authorizationserver.client.my-client-1.registration.client-secret={noop}secret1
spring.security.oauth2.authorizationserver.client.my-client-1.registration.client-authentication-methods[0]=client_secret_basic
spring.security.oauth2.authorizationserver.client.my-client-1.registration.authorization-grant-types[0]=authorization_code
spring.security.oauth2.authorizationserver.client.my-client-1.registration.authorization-grant-types[1]=refresh_token
spring.security.oauth2.authorizationserver.client.my-client-1.registration.redirect-uris[0]=https://my-client-1.com/login/oauth2/code/abcd
spring.security.oauth2.authorizationserver.client.my-client-1.registration.redirect-uris[1]=https://my-client-1.com/authorized
spring.security.oauth2.authorizationserver.client.my-client-1.registration.scopes[0]=openid
spring.security.oauth2.authorizationserver.client.my-client-1.registration.scopes[1]=profile
spring.security.oauth2.authorizationserver.client.my-client-1.registration.scopes[2]=email
spring.security.oauth2.authorizationserver.client.my-client-1.registration.scopes[3]=phone
spring.security.oauth2.authorizationserver.client.my-client-1.registration.scopes[4]=address
spring.security.oauth2.authorizationserver.client.my-client-1.require-authorization-consent=true
spring.security.oauth2.authorizationserver.client.my-client-1.token.authorization-code-time-to-live=5m
spring.security.oauth2.authorizationserver.client.my-client-1.token.access-token-time-to-live=10m
spring.security.oauth2.authorizationserver.client.my-client-1.token.access-token-format=reference
spring.security.oauth2.authorizationserver.client.my-client-1.token.reuse-refresh-tokens=false
spring.security.oauth2.authorizationserver.client.my-client-1.token.refresh-token-time-to-live=30m
spring.security.oauth2.authorizationserver.client.my-client-2.registration.client-id=efgh
spring.security.oauth2.authorizationserver.client.my-client-2.registration.client-secret={noop}secret2
spring.security.oauth2.authorizationserver.client.my-client-2.registration.client-authentication-methods[0]=client_secret_jwt
spring.security.oauth2.authorizationserver.client.my-client-2.registration.authorization-grant-types[0]=client_credentials
spring.security.oauth2.authorizationserver.client.my-client-2.registration.scopes[0]=user.read
spring.security.oauth2.authorizationserver.client.my-client-2.registration.scopes[1]=user.write
spring.security.oauth2.authorizationserver.client.my-client-2.jwk-set-uri=https://my-client-2.com/jwks
spring.security.oauth2.authorizationserver.client.my-client-2.token-endpoint-authentication-signing-algorithm=RS256
备注

client-secret 属性的格式必须能够被所配置的 PasswordEncoder 匹配。默认的 PasswordEncoder 实例是通过 PasswordEncoderFactories.createDelegatingPasswordEncoder() 创建的。

Spring Boot 为 Spring Authorization Server 提供的自动配置旨在帮助用户快速上手。大多数应用程序都需要进行定制,并希望定义多个 Bean 来覆盖自动配置。

以下组件可定义为 Bean,以覆盖 Spring Authorization Server 特定的自动配置:

提示

Spring Boot 会自动配置一个 InMemoryRegisteredClientRepository,Spring Authorization Server 使用它来管理已注册的客户端。InMemoryRegisteredClientRepository 的功能有限,我们建议仅在开发环境中使用。对于生产环境,请考虑使用 JdbcRegisteredClientRepository,或者自行实现 RegisteredClientRepository

更多信息可参见 Spring Authorization Server 参考指南 中的 Getting Started 章节。

SAML 2.0

Relying Party

如果你的 classpath 中包含 spring-security-saml2-service-provider,你可以利用一些自动配置来设置一个 SAML 2.0 Relying Party。该配置使用了 Saml2RelyingPartyProperties 下的属性。

一个依赖方(Relying Party)注册表示身份提供者(Identity Provider, IDP)和服务提供者(Service Provider, SP)之间的一种配对配置。你可以在 spring.security.saml2.relyingparty 前缀下注册多个依赖方,如下例所示:

spring.security.saml2.relyingparty.registration.my-relying-party1.signing.credentials[0].private-key-location=path-to-private-key
spring.security.saml2.relyingparty.registration.my-relying-party1.signing.credentials[0].certificate-location=path-to-certificate
spring.security.saml2.relyingparty.registration.my-relying-party1.decryption.credentials[0].private-key-location=path-to-private-key
spring.security.saml2.relyingparty.registration.my-relying-party1.decryption.credentials[0].certificate-location=path-to-certificate
spring.security.saml2.relyingparty.registration.my-relying-party1.singlelogout.url=https://myapp/logout/saml2/slo
spring.security.saml2.relyingparty.registration.my-relying-party1.singlelogout.response-url=https://remoteidp2.slo.url
spring.security.saml2.relyingparty.registration.my-relying-party1.singlelogout.binding=POST
spring.security.saml2.relyingparty.registration.my-relying-party1.assertingparty.verification.credentials[0].certificate-location=path-to-verification-cert
spring.security.saml2.relyingparty.registration.my-relying-party1.assertingparty.entity-id=remote-idp-entity-id1
spring.security.saml2.relyingparty.registration.my-relying-party1.assertingparty.sso-url=https://remoteidp1.sso.url
spring.security.saml2.relyingparty.registration.my-relying-party2.signing.credentials[0].private-key-location=path-to-private-key
spring.security.saml2.relyingparty.registration.my-relying-party2.signing.credentials[0].certificate-location=path-to-certificate
spring.security.saml2.relyingparty.registration.my-relying-party2.decryption.credentials[0].private-key-location=path-to-private-key
spring.security.saml2.relyingparty.registration.my-relying-party2.decryption.credentials[0].certificate-location=path-to-certificate
spring.security.saml2.relyingparty.registration.my-relying-party2.assertingparty.verification.credentials[0].certificate-location=path-to-other-verification-cert
spring.security.saml2.relyingparty.registration.my-relying-party2.assertingparty.entity-id=remote-idp-entity-id2
spring.security.saml2.relyingparty.registration.my-relying-party2.assertingparty.sso-url=https://remoteidp2.sso.url
spring.security.saml2.relyingparty.registration.my-relying-party2.assertingparty.singlelogout.url=https://remoteidp2.slo.url
spring.security.saml2.relyingparty.registration.my-relying-party2.assertingparty.singlelogout.response-url=https://myapp/logout/saml2/slo
spring.security.saml2.relyingparty.registration.my-relying-party2.assertingparty.singlelogout.binding=POST

对于 SAML2 注销,默认情况下,Spring Security 的 Saml2LogoutRequestFilterSaml2LogoutResponseFilter 仅处理匹配 /logout/saml2/slo 的 URL。如果你想自定义 AP 发起的注销请求所发送到的 url,或 AP 发送注销响应所使用的 response-url,以使用不同的模式,则需要提供相应的配置来处理该自定义模式。例如,对于 Servlet 应用程序,你可以添加自己的 SecurityFilterChain,如下所示:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;

import static org.springframework.security.config.Customizer.withDefaults;

@Configuration(proxyBeanMethods = false)
public class MySamlRelyingPartyConfiguration {

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests((requests) -> requests.anyRequest().authenticated());
http.saml2Login(withDefaults());
http.saml2Logout((saml2) -> saml2.logoutRequest((request) -> request.logoutUrl("/SLOService.saml2"))
.logoutResponse((response) -> response.logoutUrl("/SLOService.saml2")));
return http.build();
}

}