Skip to content

Wrong username attribute when mapping authorities with delegate OidcUserService #12275

Closed
@daniel-shuy

Description

@daniel-shuy

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 the nameAtrributeKey defaulting to sub.

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

  1. Configure an OIDC client with spring-boot-starter-oauth2-client.
  2. 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());
		};
	}
}
  1. Configure spring.security.oauth2.client.provider.<provider>.user-name-attribute, e.g.
spring:
  security:
    oauth2:
      client:
        provider:
          keycloak:
            user-name-attribute: preferred_username
  1. 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

Metadata

Metadata

Assignees

Labels

for: stackoverflowA question that's better suited to stackoverflow.com

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions