如何自定义Spring 授权服务器的 UserInfo 端点

时间:2022-12-06 11:37:41

如何自定义Spring 授权服务器的 UserInfo 端点

本指南展示了如何自定义Spring 授权服务器的 UserInfo 端点。 本指南的目的是演示如何启用终结点并使用可用的自定义选项生成自定义响应。

  • 启用用户信息终结点
  • 自定义用户信息响应

启用用户信息终结点

OpenID Connect 1.0​UserInfo 端点是受 OAuth2 保护的资源,它需要在UserInfo 请求中将访问令牌作为持有者令牌发送。


根据OAuth 2.0 持有者令牌用法 [RFC6750] 的第 2 节,从 OpenID Connect 身份验证请求获取的访问令牌必须作为持有者令牌发送。


在自定义响应之前,需要启用 UserInfo 终结点。 以下清单显示了如何启用OAuth2 资源服务器配置。

@Configuration(proxyBeanMethods = false)
public class EnableUserInfoSecurityConfig {

@Bean
@Order(1)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
.oidc(Customizer.withDefaults()); // Enable OpenID Connect 1.0
http
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
.exceptionHandling((exceptions) -> exceptions
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))
);

return http.build();
}

@Bean
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
}

}

单击上面代码示例中的“展开折叠文本”图标以显示完整示例。

此配置提供以下内容:

协议端点的 Spring 安全过滤器链。

资源服务器支持,允许使用访问令牌对用户信息请求进行身份验证。

用于验证访问令牌的实例。​​JwtDecoder​

自定义用户信息响应

以下部分介绍用于自定义用户信息响应的一些选项。

  • 自定义 ID 令牌
  • 自定义用户信息映射器

自定义 ID 令牌

默认情况下,用户信息响应是使用随令牌响应返回的声明生成的。 使用默认策略,标准声明仅返回基于授权期间请求的范围的用户信息响应。​​id_token​

自定义用户信息响应的首选方法是向中添加标准声明。 下面的清单演示如何向 添加声明。​​id_token​​​​id_token​

@Configuration
public class IdTokenCustomizerConfig {

@Bean
public OAuth2TokenCustomizer<JwtEncodingContext> tokenCustomizer(
OidcUserInfoService userInfoService) {
return (context) -> {
if (OidcParameterNames.ID_TOKEN.equals(context.getTokenType().getValue())) {
OidcUserInfo userInfo = userInfoService.loadUser(
context.getPrincipal().getName());
context.getClaims().claims(claims ->
claims.putAll(userInfo.getClaims()));
}
};
}

}

此配置提供以下内容:

用于自定义的OAuth2TokenCustomizer​的实例。​​id_token​

用于以特定于域的方式获取用户信息的自定义服务。

以下清单显示了用于以特定于域的方式查找用户信息的自定义服务:

/**
* Example service to perform lookup of user info for customizing an {@code id_token}.
*/
@Service
public class OidcUserInfoService {

private final UserInfoRepository userInfoRepository = new UserInfoRepository();

public OidcUserInfo loadUser(String username) {
return new OidcUserInfo(this.userInfoRepository.findByUsername(username));
}

static class UserInfoRepository {

private final Map<String, Map<String, Object>> userInfo = new HashMap<>();

public UserInfoRepository() {
this.userInfo.put("user1", createUser("user1"));
this.userInfo.put("user2", createUser("user2"));
}

public Map<String, Object> findByUsername(String username) {
return this.userInfo.get(username);
}

private static Map<String, Object> createUser(String username) {
return OidcUserInfo.builder()
.subject(username)
.name("First Last")
.givenName("First")
.familyName("Last")
.middleName("Middle")
.nickname("User")
.preferredUsername(username)
.profile("https://example.com/" + username)
.picture("https://example.com/" + username + ".jpg")
.website("https://example.com")
.email(username + "@example.com")
.emailVerified(true)
.gender("female")
.birthdate("1970-01-01")
.zoneinfo("Europe/Paris")
.locale("en-US")
.phoneNumber("+1 (604) 555-1234;ext=5678")
.phoneNumberVerified(false)
.claim("address", Collections.singletonMap("formatted", "Champ de Mars\n5 Av. Anatole France\n75007 Paris\nFrance"))
.updatedAt("1970-01-01T00:00:00Z")
.build()
.getClaims();
}
}

}

自定义用户信息映射器

要完全自定义用户信息响应,您可以提供一个自定义用户信息映射器,该映射器能够生成用于呈现响应的对象,该对象是 Spring 安全性中的类的实例。 映射器实现接收有关当前请求的信息的实例,包括OAuth2Authorization。​​OidcUserInfo​​​​OidcUserInfoAuthenticationContext​

下面的清单显示了如何使用在直接使用 时可用的自定义选项。​​OAuth2AuthorizationServerConfigurer​

@Configuration(proxyBeanMethods = false)
public class JwtUserInfoMapperSecurityConfig {

@Bean
@Order(1)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer();
RequestMatcher endpointsMatcher = authorizationServerConfigurer
.getEndpointsMatcher();

Function<OidcUserInfoAuthenticationContext, OidcUserInfo> userInfoMapper = (context) -> {
OidcUserInfoAuthenticationToken authentication = context.getAuthentication();
JwtAuthenticationToken principal = (JwtAuthenticationToken) authentication.getPrincipal();

return new OidcUserInfo(principal.getToken().getClaims());
};

authorizationServerConfigurer
.oidc((oidc) -> oidc
.userInfoEndpoint((userInfo) -> userInfo
.userInfoMapper(userInfoMapper)
)
);
http
.securityMatcher(endpointsMatcher)
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher))
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
.exceptionHandling((exceptions) -> exceptions
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))
)
.apply(authorizationServerConfigurer);

return http.build();
}

}

此配置映射来自访问令牌(使用入门配置时为 JWT)的声明以填充用户信息响应,并提供以下内容:

协议端点的 Spring 安全过滤器链。

以特定于域的方式映射声明的用户信息映射器。

显示用于自定义用户信息映射器的配置选项的示例。

资源服务器支持,允许使用访问令牌对用户信息请求进行身份验证。

一个示例,显示如何将 Spring 安全配置应用于 Spring 安全性配置。​​OAuth2AuthorizationServerConfigurer​

用户信息映射器不限于映射来自 JWT 的声明,但这是一个演示自定义选项的简单示例。 与前面所示的示例类似,我们自定义 ID 令牌的声明,您可以提前自定义访问令牌本身的声明,如以下示例所示:

@Configuration
public class JwtTokenCustomizerConfig {

@Bean
public OAuth2TokenCustomizer<JwtEncodingContext> tokenCustomizer() {
return (context) -> {
if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType())) {
context.getClaims().claims((claims) -> {
claims.put("claim-1", "value-1");
claims.put("claim-2", "value-2");
});
}
};
}

}

无论是直接自定义用户信息响应,还是使用此示例并自定义访问令牌,都可以在数据库中查找信息、执行 LDAP 查询、向其他服务发出请求,或使用任何其他方式获取要在用户信息响应中显示的信息。