Description
Describe the bug
When using the Delegation-based Strategy with OidcUserService
as documented at https://docs.spring.io/spring-security/reference/6.0/servlet/oauth2/login/advanced.html#oauth2login-advanced-map-authorities-oauth2userservice, and the spring.security.oauth2.client.provider.<provider>.user-name-attribute
is set, the username retrieved by SecurityContextHolder.getContext().getAuthentication().getName()
is wrong.
This is because DefaultOidcUser
has the following constructors:
DefaultOidcUser(Collection<? extends GrantedAuthority> authorities, OidcIdToken idToken, OidcUserInfo userInfo)
DefaultOidcUser(Collection<? extends GrantedAuthority> authorities, OidcIdToken idToken, OidcUserInfo userInfo, String nameAttributeKey)
where the first calls the second, with thenameAtrributeKey
defaulting tosub
.
The authority mapping replaces the authorities, but copies over the existing idToken
and userInfo
. It is however unable to copy over the nameAttributeKey
because the OidcUser interface does not have a getter for nameAttributeKey
.
Perhaps a builder could be added to DefaultOidcUser
that copies the values over from an existing OidcUser
(including the nameAttributeKey
). This avoids having to change the OidcUser
interface.
To Reproduce
- Configure an OIDC client with
spring-boot-starter-oauth2-client
. - Configure a custom
OAuth2UserService
to map authorities. For the simplest example to reproduce this issue:
@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) -> {
OidcUser oidcUser = delegate.loadUser(userRequest);
return new DefaultOidcUser(oidcUser.getAuthorities(), oidcUser.getIdToken(), oidcUser.getUserInfo());
};
}
}
- Configure
spring.security.oauth2.client.provider.<provider>.user-name-attribute
, e.g.
spring:
security:
oauth2:
client:
provider:
keycloak:
user-name-attribute: preferred_username
- Call
SecurityContextHolder.getContext().getAuthentication().getName()
.
Expected behavior
SecurityContextHolder.getContext().getAuthentication().getName()
should return the username from the configured username attribute (e.g. preferred_username
from the example given above).
Sample
WIP - Will provide one soon