此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Authorization Server 1.4.3! |
作方法:使用 Social Login 进行身份验证
本指南展示了如何使用社交登录提供程序(例如 Google、GitHub 等)配置 Spring Authorization Server 进行身份验证。 本指南的目的是演示如何将表单登录替换为 OAuth 2.0 登录。
Spring Authorization Server 基于 Spring Security 构建,我们将在本指南中使用 Spring Security 概念。 |
向 Social Login Provider 注册
首先,您需要使用您选择的社交登录提供商设置应用程序。 常见的提供商包括:
按照提供程序的步骤作,直到系统要求您指定 Redirect URI。
要设置 Redirect URI,请选择registrationId
(例如google
,my-client
或您希望的任何其他唯一标识符),您将使用它来配置 Spring Security 和您的提供程序。
这registrationId 是ClientRegistration 在 Spring Security 中。默认的重定向 URI 模板是{baseUrl}/login/oauth2/code/{registrationId} .有关更多信息,请参见 Spring Security 参考中的设置重定向 URI。 |
例如,在端口上本地测试9000 替换为registrationId 之google ,则重定向 URI 将为localhost:9000/login/oauth2/code/google .在向提供商设置应用程序时,将此值输入为 Redirect URI。 |
使用社交登录提供商完成设置过程后,您应该已获得凭据(客户端 ID 和客户端密钥)。 此外,您还需要参考提供商的文档并记下以下值:
-
授权 URI:用于启动
authorization_code
流向提供者。 -
Token URI:用于将
authorization_code
对于access_token
以及可选的id_token
. -
JWK 设置 URI:用于获取验证 JWT 签名的密钥的终端节点,当
id_token
可用。 -
User Info URI:用于获取用户信息的终端节点,当
id_token
不可用。 -
User Name 属性:在
id_token
或包含用户用户名的 User Info Response。
配置 OAuth 2.0 登录
向社交登录提供程序注册后,您可以继续为 OAuth 2.0 登录配置 Spring Security。
添加 OAuth2 客户端依赖项
首先,添加以下依赖项:
-
Maven
-
Gradle
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
implementation "org.springframework.boot:spring-boot-starter-oauth2-client"
注册客户端
接下来,配置ClientRegistration
替换为之前获得的值。
以 Okta 为例,配置以下属性:
okta:
base-url: ${OKTA_BASE_URL}
spring:
security:
oauth2:
client:
registration:
my-client:
provider: okta
client-id: ${OKTA_CLIENT_ID}
client-secret: ${OKTA_CLIENT_SECRET}
scope:
- openid
- profile
- email
provider:
okta:
authorization-uri: ${okta.base-url}/oauth2/v1/authorize
token-uri: ${okta.base-url}/oauth2/v1/token
user-info-uri: ${okta.base-url}/oauth2/v1/userinfo
jwk-set-uri: ${okta.base-url}/oauth2/v1/keys
user-name-attribute: sub
这registrationId 在上面的例子中是my-client . |
上面的示例演示了使用环境变量 (OKTA_BASE_URL ,OKTA_CLIENT_ID 和OKTA_CLIENT_SECRET ).有关更多信息,请参见 Spring Boot 参考中的外部化配置。 |
这个简单的示例演示了一个典型的配置,但某些提供商需要额外的配置。
有关配置ClientRegistration
,请参阅 Spring Security 参考中的 Spring Boot Property Mappings。
配置身份验证
最后,要将 Spring Authorization Server 配置为使用社交登录提供程序进行身份验证,您可以使用oauth2Login()
而不是formLogin()
.
您还可以通过配置exceptionHandling()
替换为AuthenticationEntryPoint
.
继续前面的示例,使用@Configuration
如以下示例所示:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean (1)
@Order(1)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
throws Exception {
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
OAuth2AuthorizationServerConfigurer.authorizationServer();
http
.securityMatcher(authorizationServerConfigurer.getEndpointsMatcher())
.with(authorizationServerConfigurer, (authorizationServer) ->
authorizationServer
.oidc(Customizer.withDefaults()) // Enable OpenID Connect 1.0
)
.authorizeHttpRequests((authorize) ->
authorize
.anyRequest().authenticated()
)
// Redirect to the OAuth 2.0 Login endpoint when not authenticated
// from the authorization endpoint
.exceptionHandling((exceptions) -> exceptions
.defaultAuthenticationEntryPointFor( (2)
new LoginUrlAuthenticationEntryPoint("/oauth2/authorization/my-client"),
new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
)
);
return http.build();
}
@Bean (3)
@Order(2)
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http)
throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
// OAuth2 Login handles the redirect to the OAuth 2.0 Login endpoint
// from the authorization server filter chain
.oauth2Login(Customizer.withDefaults()); (4)
return http.build();
}
}
1
A Spring Security filter chain for the Protocol Endpoints.
2
Configure an AuthenticationEntryPoint
for redirecting to the OAuth 2.0 Login endpoint.
3
A Spring Security filter chain for authentication.
4
Configure OAuth 2.0 Login for authentication.
If you configured a UserDetailsService
when getting started, you can remove it now.
Advanced Use Cases
The demo authorization server sample demonstrates advanced configuration options for federating identity providers.
Select from the following use cases to see an example of each:
-
I want to Capture Users in a Database
-
I want to Map Claims to an ID Token
Capture Users in a Database
The following example AuthenticationSuccessHandler
uses a custom component to capture users in a local database when they first log in:
FederatedIdentityAuthenticationSuccessHandler
import java.io.IOException;
import java.util.function.Consumer;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
public final class FederatedIdentityAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private final AuthenticationSuccessHandler delegate = new SavedRequestAwareAuthenticationSuccessHandler();
private Consumer<OAuth2User> oauth2UserHandler = (user) -> {};
private Consumer<OidcUser> oidcUserHandler = (user) -> this.oauth2UserHandler.accept(user);
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
if (authentication instanceof OAuth2AuthenticationToken) {
if (authentication.getPrincipal() instanceof OidcUser oidcUser) {
this.oidcUserHandler.accept(oidcUser);
} else if (authentication.getPrincipal() instanceof OAuth2User oauth2User) {
this.oauth2UserHandler.accept(oauth2User);
}
}
this.delegate.onAuthenticationSuccess(request, response, authentication);
}
public void setOAuth2UserHandler(Consumer<OAuth2User> oauth2UserHandler) {
this.oauth2UserHandler = oauth2UserHandler;
}
public void setOidcUserHandler(Consumer<OidcUser> oidcUserHandler) {
this.oidcUserHandler = oidcUserHandler;
}
}
Using the AuthenticationSuccessHandler
above, you can plug in your own Consumer<OAuth2User>
that can capture users in a database or other data store for concepts like Federated Account Linking or JIT Account Provisioning.
Here is an example that simply stores users in-memory:
UserRepositoryOAuth2UserHandler
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import org.springframework.security.oauth2.core.user.OAuth2User;
public final class UserRepositoryOAuth2UserHandler implements Consumer<OAuth2User> {
private final UserRepository userRepository = new UserRepository();
@Override
public void accept(OAuth2User user) {
// Capture user in a local data store on first authentication
if (this.userRepository.findByName(user.getName()) == null) {
System.out.println("Saving first-time user: name=" + user.getName() + ", claims=" + user.getAttributes() + ", authorities=" + user.getAuthorities());
this.userRepository.save(user);
}
}
static class UserRepository {
private final Map<String, OAuth2User> userCache = new ConcurrentHashMap<>();
public OAuth2User findByName(String name) {
return this.userCache.get(name);
}
public void save(OAuth2User oauth2User) {
this.userCache.put(oauth2User.getName(), oauth2User);
}
}
}
Map Claims to an ID Token
The following example OAuth2TokenCustomizer
maps a user’s claims from an authentication provider to the id_token
produced by Spring Authorization Server:
FederatedIdentityIdTokenCustomizer
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
public final class FederatedIdentityIdTokenCustomizer implements OAuth2TokenCustomizer<JwtEncodingContext> {
private static final Set<String> ID_TOKEN_CLAIMS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
IdTokenClaimNames.ISS,
IdTokenClaimNames.SUB,
IdTokenClaimNames.AUD,
IdTokenClaimNames.EXP,
IdTokenClaimNames.IAT,
IdTokenClaimNames.AUTH_TIME,
IdTokenClaimNames.NONCE,
IdTokenClaimNames.ACR,
IdTokenClaimNames.AMR,
IdTokenClaimNames.AZP,
IdTokenClaimNames.AT_HASH,
IdTokenClaimNames.C_HASH
)));
@Override
public void customize(JwtEncodingContext context) {
if (OidcParameterNames.ID_TOKEN.equals(context.getTokenType().getValue())) {
Map<String, Object> thirdPartyClaims = extractClaims(context.getPrincipal());
context.getClaims().claims(existingClaims -> {
// Remove conflicting claims set by this authorization server
existingClaims.keySet().forEach(thirdPartyClaims::remove);
// Remove standard id_token claims that could cause problems with clients
ID_TOKEN_CLAIMS.forEach(thirdPartyClaims::remove);
// Add all other claims directly to id_token
existingClaims.putAll(thirdPartyClaims);
});
}
}
private Map<String, Object> extractClaims(Authentication principal) {
Map<String, Object> claims;
if (principal.getPrincipal() instanceof OidcUser oidcUser) {
OidcIdToken idToken = oidcUser.getIdToken();
claims = idToken.getClaims();
} else if (principal.getPrincipal() instanceof OAuth2User oauth2User) {
claims = oauth2User.getAttributes();
} else {
claims = Collections.emptyMap();
}
return new HashMap<>(claims);
}
}
You can configure Spring Authorization Server to use this customizer by publishing it as a @Bean
as in the following example:
Configure FederatedIdentityIdTokenCustomizer
@Bean
public OAuth2TokenCustomizer<JwtEncodingContext> idTokenCustomizer() {
return new FederatedIdentityIdTokenCustomizer();
}