高级配置
本站(springdoc.cn)中的内容来源于 spring.io ,原始版权归属于 spring.io。由 springdoc.cn 进行翻译,整理。可供个人学习、研究,未经许可,不得进行任何转载、商用或与之相关的行为。 商标声明:Spring 是 Pivotal Software, Inc. 在美国以及其他国家的商标。 |
HttpSecurity.oauth2Login()
为定制OAuth 2.0登录提供了大量的配置选项。主要的配置选项被分组到它们的协议端点对应处。
例如,oauth2Login().authorizationEndpoint()
允许配置授权端点,而 oauth2Login().tokenEndpoint()
允许配置令牌端点。
下面的代码是一个例子。
-
Java
-
Kotlin
@Configuration
@EnableWebSecurity
public class OAuth2LoginSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.oauth2Login(oauth2 -> oauth2
.authorizationEndpoint(authorization -> authorization
...
)
.redirectionEndpoint(redirection -> redirection
...
)
.tokenEndpoint(token -> token
...
)
.userInfoEndpoint(userInfo -> userInfo
...
)
);
return http.build();
}
}
@Configuration
@EnableWebSecurity
class OAuth2LoginSecurityConfig {
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
oauth2Login {
authorizationEndpoint {
...
}
redirectionEndpoint {
...
}
tokenEndpoint {
...
}
userInfoEndpoint {
...
}
}
}
return http.build()
}
}
oauth2Login()
DSL的主要目标是与规范中定义的命名紧密结合。
OAuth 2.0授权框架对 协议端点 的定义如下。
授权过程使用两个授权服务器端点(HTTP资源)。
-
授权端点。由客户端使用,通过用户代理重定向从资源所有者那里获得授权。
-
令牌端点。由客户端使用,以交换授权许可的访问令牌,通常是客户端认证。
授权过程也使用一个客户端点。
-
重定向端点。由授权服务器使用,通过资源所有者的用户代理将包含授权凭证的响应返回给客户端。
OpenID Connect Core 1.0 规范对 UserInfo端点 的定义如下。
UserInfo端点是一个OAuth 2.0保护资源,用于返回关于已认证最终用户的请求。为了获得关于最终用户的请求,客户端通过使用通过OpenID连接认证获得的访问令牌向UserInfo端点提出请求。这些请求通常由一个JSON对象表示,该对象包含请求的key/value对的集合。
下面的代码显示了 oauth2Login()
DSL可用的完整配置选项。
-
Java
-
Kotlin
@Configuration
@EnableWebSecurity
public class OAuth2LoginSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.oauth2Login(oauth2 -> oauth2
.clientRegistrationRepository(this.clientRegistrationRepository())
.authorizedClientRepository(this.authorizedClientRepository())
.authorizedClientService(this.authorizedClientService())
.loginPage("/login")
.authorizationEndpoint(authorization -> authorization
.baseUri(this.authorizationRequestBaseUri())
.authorizationRequestRepository(this.authorizationRequestRepository())
.authorizationRequestResolver(this.authorizationRequestResolver())
)
.redirectionEndpoint(redirection -> redirection
.baseUri(this.authorizationResponseBaseUri())
)
.tokenEndpoint(token -> token
.accessTokenResponseClient(this.accessTokenResponseClient())
)
.userInfoEndpoint(userInfo -> userInfo
.userAuthoritiesMapper(this.userAuthoritiesMapper())
.userService(this.oauth2UserService())
.oidcUserService(this.oidcUserService())
)
);
return http.build();
}
}
@Configuration
@EnableWebSecurity
class OAuth2LoginSecurityConfig {
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
oauth2Login {
clientRegistrationRepository = clientRegistrationRepository()
authorizedClientRepository = authorizedClientRepository()
authorizedClientService = authorizedClientService()
loginPage = "/login"
authorizationEndpoint {
baseUri = authorizationRequestBaseUri()
authorizationRequestRepository = authorizationRequestRepository()
authorizationRequestResolver = authorizationRequestResolver()
}
redirectionEndpoint {
baseUri = authorizationResponseBaseUri()
}
tokenEndpoint {
accessTokenResponseClient = accessTokenResponseClient()
}
userInfoEndpoint {
userAuthoritiesMapper = userAuthoritiesMapper()
userService = oauth2UserService()
oidcUserService = oidcUserService()
}
}
}
return http.build()
}
}
除了 oauth2Login()
DSL,还支持XML配置。
下面的代码显示了 security namespace 中可用的完整配置选项。
<http>
<oauth2-login client-registration-repository-ref="clientRegistrationRepository"
authorized-client-repository-ref="authorizedClientRepository"
authorized-client-service-ref="authorizedClientService"
authorization-request-repository-ref="authorizationRequestRepository"
authorization-request-resolver-ref="authorizationRequestResolver"
access-token-response-client-ref="accessTokenResponseClient"
user-authorities-mapper-ref="userAuthoritiesMapper"
user-service-ref="oauth2UserService"
oidc-user-service-ref="oidcUserService"
login-processing-url="/login/oauth2/code/*"
login-page="/login"
authentication-success-handler-ref="authenticationSuccessHandler"
authentication-failure-handler-ref="authenticationFailureHandler"
jwt-decoder-factory-ref="jwtDecoderFactory"/>
</http>
下面几节将更详细地介绍每个可用的配置选项。
OAuth 2.0 登录页面
默认情况下,OAuth 2.0登录页面是由 DefaultLoginPageGeneratingFilter
自动生成的。默认的登录页面显示每个配置的OAuth客户端,其 ClientRegistration.clientName
为链接,能够启动授权请求(或OAuth 2.0登录)。
对于 |
每个OAuth客户端的链接的目的地默认为以下内容。
OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI + "/{registrationId}"
下面一行是一个例子。
<a href="/oauth2/authorization/google">Google</a>
要覆盖默认的登录页面,请配置 oauth2Login().loginPage()
和(可选) oauth2Login().authorizationEndpoint().baseUri()
。
下面的列表显示了一个例子。
-
Java
-
Kotlin
-
Xml
@Configuration
@EnableWebSecurity
public class OAuth2LoginSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.oauth2Login(oauth2 -> oauth2
.loginPage("/login/oauth2")
...
.authorizationEndpoint(authorization -> authorization
.baseUri("/login/oauth2/authorization")
...
)
);
return http.build();
}
}
@Configuration
@EnableWebSecurity
class OAuth2LoginSecurityConfig {
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
oauth2Login {
loginPage = "/login/oauth2"
authorizationEndpoint {
baseUri = "/login/oauth2/authorization"
}
}
}
return http.build()
}
}
<http>
<oauth2-login login-page="/login/oauth2"
...
/>
</http>
你需要提供一个带有 |
如前所述,配置 下面一行显示了一个例子。
|
重定向端点
重定向端点由授权服务器使用,用于通过资源所有者用户代理向客户返回授权响应(包含授权凭证)
OAuth 2.0登录利用的是授权码授予。因此,授权凭证就是授权码。 |
默认的授权响应 baseUri
(重定向端点)是 /login/oauth2/code/*
,这在 OAuth2LoginAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI
中定义。
如果你想定制授权响应 baseUri
,请按以下方式配置。
-
Java
-
Kotlin
-
Xml
@Configuration
@EnableWebSecurity
public class OAuth2LoginSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.oauth2Login(oauth2 -> oauth2
.redirectionEndpoint(redirection -> redirection
.baseUri("/login/oauth2/callback/*")
...
)
);
return http.build();
}
}
@Configuration
@EnableWebSecurity
class OAuth2LoginSecurityConfig {
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
oauth2Login {
redirectionEndpoint {
baseUri = "/login/oauth2/callback/*"
}
}
}
return http.build()
}
}
<http>
<oauth2-login login-processing-url="/login/oauth2/callback/*"
...
/>
</http>
你还需要确保 下面的列表显示了一个例子。
|
UserInfo端点
UserInfo端点包括一些配置选项,在下面的小节中描述。
映射用户权限
在用户成功通过 OAuth 2.0 提供商的认证后,OAuth2User.getAuthorities()
(或 OidcUser.getAuthorities()
)包含一个由 OAuth2UserRequest.getAccessToken().getScopes()
填充的授予权限列表,其前缀为 SCOPE_
。这些授权可以被映射到一组新的 GrantedAuthority
实例中,在完成认证时提供给 OAuth2AuthenticationToken
。
OAuth2AuthenticationToken.getAuthorities() 用于授权请求,如 hasRole('USER') 或 hasRole('ADMIN') 中。
|
在映射用户权限时,有几个选项可以选择。
使用 GrantedAuthoritiesMapper
GrantedAuthoritiesMapper
被赋予一个授予权限的列表,其中包含一个类型为 OAuth2UserAuthority
和权限字符串 OAUTH2_USER
(或 OidcUserAuthority
和权限字符串 OIDC_USER
)的特殊权限。
提供一个 GrantedAuthoritiesMapper
的实现并对其进行配置,如下所示。
-
Java
-
Kotlin
-
Xml
@Configuration
@EnableWebSecurity
public class OAuth2LoginSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.oauth2Login(oauth2 -> oauth2
.userInfoEndpoint(userInfo -> userInfo
.userAuthoritiesMapper(this.userAuthoritiesMapper())
...
)
);
return http.build();
}
private GrantedAuthoritiesMapper userAuthoritiesMapper() {
return (authorities) -> {
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
authorities.forEach(authority -> {
if (OidcUserAuthority.class.isInstance(authority)) {
OidcUserAuthority oidcUserAuthority = (OidcUserAuthority)authority;
OidcIdToken idToken = oidcUserAuthority.getIdToken();
OidcUserInfo userInfo = oidcUserAuthority.getUserInfo();
// Map the claims found in idToken and/or userInfo
// to one or more GrantedAuthority's and add it to mappedAuthorities
} else if (OAuth2UserAuthority.class.isInstance(authority)) {
OAuth2UserAuthority oauth2UserAuthority = (OAuth2UserAuthority)authority;
Map<String, Object> userAttributes = oauth2UserAuthority.getAttributes();
// Map the attributes found in userAttributes
// to one or more GrantedAuthority's and add it to mappedAuthorities
}
});
return mappedAuthorities;
};
}
}
@Configuration
@EnableWebSecurity
class OAuth2LoginSecurityConfig {
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
oauth2Login {
userInfoEndpoint {
userAuthoritiesMapper = userAuthoritiesMapper()
}
}
}
return http.build()
}
private fun userAuthoritiesMapper(): GrantedAuthoritiesMapper = GrantedAuthoritiesMapper { authorities: Collection<GrantedAuthority> ->
val mappedAuthorities = emptySet<GrantedAuthority>()
authorities.forEach { authority ->
if (authority is OidcUserAuthority) {
val idToken = authority.idToken
val userInfo = authority.userInfo
// Map the claims found in idToken and/or userInfo
// to one or more GrantedAuthority's and add it to mappedAuthorities
} else if (authority is OAuth2UserAuthority) {
val userAttributes = authority.attributes
// Map the attributes found in userAttributes
// to one or more GrantedAuthority's and add it to mappedAuthorities
}
}
mappedAuthorities
}
}
<http>
<oauth2-login user-authorities-mapper-ref="userAuthoritiesMapper"
...
/>
</http>
或者,你可以注册一个 GrantedAuthoritiesMapper
@Bean
,让它自动应用于配置,如下所示。
-
Java
-
Kotlin
@Configuration
@EnableWebSecurity
public class OAuth2LoginSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.oauth2Login(withDefaults());
return http.build();
}
@Bean
public GrantedAuthoritiesMapper userAuthoritiesMapper() {
...
}
}
@Configuration
@EnableWebSecurity
class OAuth2LoginSecurityConfig {
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
oauth2Login { }
}
return http.build()
}
@Bean
fun userAuthoritiesMapper(): GrantedAuthoritiesMapper {
...
}
}
基于Delegation的策略与OAuth2UserService
与使用 GrantedAuthoritiesMapper
相比,这种策略是先进的。然而,它也更灵活,因为它让你可以访问 OAuth2UserRequest
和 OAuth2User
(当使用OAuth 2.0 UserService)或 OidcUserRequest
和 OidcUser
(当使用 OpenID Connect 1.0 UserService)。
OAuth2UserRequest
(和 OidcUserRequest
)为你提供了对相关 OAuth2AccessToken
的访问,这在 delegator 需要从受保护的资源中获取权限信息,然后再为用户映射自定义权限的情况下非常有用。
下面的例子显示了如何使用OpenID Connect 1.0 UserService实现和配置基于delegation的策略。
-
Java
-
Kotlin
-
Xml
@Configuration
@EnableWebSecurity
public class OAuth2LoginSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.oauth2Login(oauth2 -> oauth2
.userInfoEndpoint(userInfo -> userInfo
.oidcUserService(this.oidcUserService())
...
)
);
return http.build();
}
private OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
final OidcUserService delegate = new OidcUserService();
return (userRequest) -> {
// Delegate to the default implementation for loading a user
OidcUser oidcUser = delegate.loadUser(userRequest);
OAuth2AccessToken accessToken = userRequest.getAccessToken();
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
// TODO
// 1) Fetch the authority information from the protected resource using accessToken
// 2) Map the authority information to one or more GrantedAuthority's and add it to mappedAuthorities
// 3) Create a copy of oidcUser but use the mappedAuthorities instead
oidcUser = new DefaultOidcUser(mappedAuthorities, oidcUser.getIdToken(), oidcUser.getUserInfo());
return oidcUser;
};
}
}
@Configuration
@EnableWebSecurity
class OAuth2LoginSecurityConfig {
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
oauth2Login {
userInfoEndpoint {
oidcUserService = oidcUserService()
}
}
}
return http.build()
}
@Bean
fun oidcUserService(): OAuth2UserService<OidcUserRequest, OidcUser> {
val delegate = OidcUserService()
return OAuth2UserService { userRequest ->
// Delegate to the default implementation for loading a user
var oidcUser = delegate.loadUser(userRequest)
val accessToken = userRequest.accessToken
val mappedAuthorities = HashSet<GrantedAuthority>()
// TODO
// 1) Fetch the authority information from the protected resource using accessToken
// 2) Map the authority information to one or more GrantedAuthority's and add it to mappedAuthorities
// 3) Create a copy of oidcUser but use the mappedAuthorities instead
oidcUser = DefaultOidcUser(mappedAuthorities, oidcUser.idToken, oidcUser.userInfo)
oidcUser
}
}
}
<http>
<oauth2-login oidc-user-service-ref="oidcUserService"
...
/>
</http>
OAuth 2.0 UserService
DefaultOAuth2UserService
是一个 OAuth2UserService
的实现,支持标准的OAuth2.0提供者。
|
DefaultOAuth2UserService
在UserInfo端点请求用户属性时使用 RestOperations
实例。
如果你需要定制UserInfo请求的预处理,你可以为 DefaultOAuth2UserService.setRequestEntityConverter()
提供一个自定义的 Converter<OAuth2UserRequest, RequestEntity<?>>
。默认的实现 OAuth2UserRequestEntityConverter
建立了一个UserInfo请求的 RequestEntity
表示,默认在授权header中设置 OAuth2AccessToken
。
在另一端,如果你需要定制 UserInfo 响应的后处理,你需要为 DefaultOAuth2UserService.setRestOperations()
提供一个自定义配置的 RestOperations
。默认的 RestOperations
配置如下。
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
OAuth2ErrorResponseErrorHandler
是一个 ResponseErrorHandler
,可以处理OAuth2.0错误(400 Bad Request)。它使用一个 OAuth2ErrorHttpMessageConverter
将 OAuth2.0 错误参数转换为 OAuth2Error
。
无论你是定制 DefaultOAuth2UserService
还是提供你自己的 OAuth2UserService
的实现,你都需要按以下方式进行配置。
-
Java
-
Kotlin
@Configuration
@EnableWebSecurity
public class OAuth2LoginSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.oauth2Login(oauth2 -> oauth2
.userInfoEndpoint(userInfo -> userInfo
.userService(this.oauth2UserService())
...
)
);
return http.build();
}
private OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService() {
...
}
}
@Configuration
@EnableWebSecurity
class OAuth2LoginSecurityConfig {
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
oauth2Login {
userInfoEndpoint {
userService = oauth2UserService()
// ...
}
}
}
return http.build()
}
private fun oauth2UserService(): OAuth2UserService<OAuth2UserRequest, OAuth2User> {
// ...
}
}
OpenID Connect 1.0 UserService
OidcUserService
是一个 OAuth2UserService
的实现,支持OpenID Connect 1.0提供者。
OidcUserService
在 UserInfo 端点请求用户属性时利用了 DefaultOAuth2UserService
。
如果你需要定制UserInfo请求的预处理或UserInfo响应的后处理,你需要向 OidcUserService.setOauth2UserService()
提供一个自定义配置的 DefaultOAuth2UserService
。
无论你是定制 OidcUserService
还是为 OpenID Connect 1.0 提供商提供自己的 OAuth2UserService
实现,你都需要按以下方式配置它。
-
Java
-
Kotlin
@Configuration
@EnableWebSecurity
public class OAuth2LoginSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.oauth2Login(oauth2 -> oauth2
.userInfoEndpoint(userInfo -> userInfo
.oidcUserService(this.oidcUserService())
...
)
);
return http.build();
}
private OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
...
}
}
@Configuration
@EnableWebSecurity
class OAuth2LoginSecurityConfig {
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
oauth2Login {
userInfoEndpoint {
oidcUserService = oidcUserService()
// ...
}
}
}
return http.build()
}
private fun oidcUserService(): OAuth2UserService<OidcUserRequest, OidcUser> {
// ...
}
}
ID Token 签名验证
OpenID Connect 1.0认证引入了 ID Token,它是一个安全令牌,包含授权服务器在客户端使用时对终端用户进行认证的要求。
ID令牌被表示为 JSON Web Token(JWT),并且必须使用 JSON Web Signature (JWS)进行签名。
OidcIdTokenDecoderFactory
提供一个用于 OidcIdToken
签名验证的 JwtDecoder
。默认算法是 RS256
,但在客户端注册时分配的算法可能不同。对于这些情况,你可以配置解析器以返回为特定客户分配的预期 JWS 算法。
JWS算法解析器是一个 Function
,它接受 ClientRegistration
并返回客户端预期的 JwsAlgorithm
,例如 SignatureAlgorithm.RS256
或 MacAlgorithm.HS256
。
下面的代码显示了如何配置 OidcIdTokenDecoderFactory
@Bean
为所有 ClientRegistration
实例默认为 MacAlgorithm.HS256
。
-
Java
-
Kotlin
@Bean
public JwtDecoderFactory<ClientRegistration> idTokenDecoderFactory() {
OidcIdTokenDecoderFactory idTokenDecoderFactory = new OidcIdTokenDecoderFactory();
idTokenDecoderFactory.setJwsAlgorithmResolver(clientRegistration -> MacAlgorithm.HS256);
return idTokenDecoderFactory;
}
@Bean
fun idTokenDecoderFactory(): JwtDecoderFactory<ClientRegistration?> {
val idTokenDecoderFactory = OidcIdTokenDecoderFactory()
idTokenDecoderFactory.setJwsAlgorithmResolver { MacAlgorithm.HS256 }
return idTokenDecoderFactory
}
对于基于MAC的算法(如 |
如果为 OpenID Connect 1.0 认证配置了一个以上的 |
OpenID Connect 1.0 注销
OpenID Connect会话管理1.0允许通过使用客户端在提供方注销终端用户的能力。其中一个可用的策略是 RP发起的注销。
如果OpenID提供商同时支持会话管理和 Discovery,客户端可以从OpenID提供商的 Discovery Metadata 中获得 end_session_endpoint
URL。你可以通过在 ClientRegistration
中配置 issuer-uri
来做到这一点,如下所示。
spring:
security:
oauth2:
client:
registration:
okta:
client-id: okta-client-id
client-secret: okta-client-secret
...
provider:
okta:
issuer-uri: https://dev-1234.oktapreview.com
另外,你可以配置 OidcClientInitiatedLogoutSuccessHandler
,它实现了RP发起的注销,如下所示。
-
Java
-
Kotlin
@Configuration
@EnableWebSecurity
public class OAuth2LoginSecurityConfig {
@Autowired
private ClientRegistrationRepository clientRegistrationRepository;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
)
.oauth2Login(withDefaults())
.logout(logout -> logout
.logoutSuccessHandler(oidcLogoutSuccessHandler())
);
return http.build();
}
private LogoutSuccessHandler oidcLogoutSuccessHandler() {
OidcClientInitiatedLogoutSuccessHandler oidcLogoutSuccessHandler =
new OidcClientInitiatedLogoutSuccessHandler(this.clientRegistrationRepository);
// Sets the location that the End-User's User Agent will be redirected to
// after the logout has been performed at the Provider
oidcLogoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}");
return oidcLogoutSuccessHandler;
}
}
@Configuration
@EnableWebSecurity
class OAuth2LoginSecurityConfig {
@Autowired
private lateinit var clientRegistrationRepository: ClientRegistrationRepository
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
authorizeRequests {
authorize(anyRequest, authenticated)
}
oauth2Login { }
logout {
logoutSuccessHandler = oidcLogoutSuccessHandler()
}
}
return http.build()
}
private fun oidcLogoutSuccessHandler(): LogoutSuccessHandler {
val oidcLogoutSuccessHandler = OidcClientInitiatedLogoutSuccessHandler(clientRegistrationRepository)
// Sets the location that the End-User's User Agent will be redirected to
// after the logout has been performed at the Provider
oidcLogoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}")
return oidcLogoutSuccessHandler
}
}
|