Skip to content

Access to accessToken in GrantedAuthoritiesMapper #14461

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Xyaren opened this issue Jan 15, 2024 · 6 comments
Closed

Access to accessToken in GrantedAuthoritiesMapper #14461

Xyaren opened this issue Jan 15, 2024 · 6 comments
Assignees
Labels
in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) status: duplicate A duplicate of another issue type: enhancement A general enhancement

Comments

@Xyaren
Copy link

Xyaren commented Jan 15, 2024

Expected Behavior

When using oauth2login it is should be possible to use the access token in GrantedAuthoritiesMapper in order to map access token claims to authorities for use in hasRole/hasAuthority.
This is espacially usefull when some claims do only appear in the access token.

Current Behavior
GrantedAuthoritiesMapper has currently no access to the accessToken.

Context
https://docs.spring.io/spring-security/reference/reactive/oauth2/login/advanced.html#webflux-oauth2-login-advanced-map-authorities-grantedauthoritiesmapper

My (dirty) workaround involves extending the OidcUserService and returning a new OidcUser Object.

@Xyaren Xyaren added status: waiting-for-triage An issue we've not yet triaged type: enhancement A general enhancement labels Jan 15, 2024
@jzheaux
Copy link
Contributor

jzheaux commented Jan 18, 2024

Hi, @Xyaren, thanks for reaching out. What is making you feel like extending OidcUserService is a dirty workaround? I'm thinking you might do something like this:

@Component
public class MyOidcUserService implements OAuth2UserService<OidcUserRequest, OidcUser> {
    private final OidcUserService delegate = new OidcUserService();

    @Override
    public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
        OidcUser user = this.delegate.loadUser(userRequest);
        Collection<GrantedAuthority> authorities = new ArrayList<>(user.getAuthorities());
        // ... add custom authorities
        return new DefaultOidcUser(authorities, user.getIdToken(), user.getUserInfo());
    }
}

which doesn't seem dirty to me. I want to make sure I'm not missing something; can you clarify please?

@jzheaux jzheaux added status: waiting-for-feedback We need additional information before we can continue in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) and removed status: waiting-for-triage An issue we've not yet triaged labels Jan 18, 2024
@Xyaren
Copy link
Author

Xyaren commented Jan 18, 2024

Hi, thanks for the quick response!
I do have a similar workaround, that I'll attach at the end.

It feels odd wrapping the original service, tear apart the response and stitch it together again in a new instance of the same object. I also was unsure about "reconstructing" the OidcUser due to no access to nameAttributeKey which is required for the all-arg constructor. Therefore I went for extending the default implementation and wrapping the result.
It there anything speaking against having the Access token available within the OidcUser ?
This would allow users to use a GrantedAuthoritiesMapper to map from any of the sources.

import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;

import packages.from.my.company.that.i.dont.want.to.name.JwtToAuthorityExtractor;
import lombok.AllArgsConstructor;
import lombok.experimental.Delegate;
import org.jetbrains.annotations.NotNull;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.oauth2.jwt.SupplierJwtDecoder;

@AllArgsConstructor
public class ExtendedOidcUserService extends OidcUserService {

    private SupplierJwtDecoder supplierJwtDecoder;

    @Override
    public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
        var oidcUser = super.loadUser(userRequest);
        var accessToken = userRequest.getAccessToken();

        var authorityExtractor = new JwtToAuthorityExtractor(
            userRequest.getClientRegistration().getClientId(),
            Set.of(),
            false);
        var jwt = supplierJwtDecoder.decode(accessToken.getTokenValue());
        Set<GrantedAuthority> additionalAuthorities = authorityExtractor.authorities(jwt).collect(Collectors.toSet());
        return new ExtendedOidcUser(oidcUser, additionalAuthorities);
    }

    static class ExtendedOidcUser implements OidcUser {
        @Delegate
        private final OidcUser oidcUser;
        private final Collection<? extends GrantedAuthority> authorities;

        public ExtendedOidcUser(OidcUser oidcUser, Collection<? extends GrantedAuthority> additionalAuthorities) {
            this.oidcUser = oidcUser;
            this.authorities = joinAuthorities(oidcUser, additionalAuthorities);
        }

        @SuppressWarnings({"LombokGetterMayBeUsed", "RedundantSuppression"})
        public Collection<? extends GrantedAuthority> getAuthorities() {
            return authorities;
        }

        @NotNull
        private static HashSet<GrantedAuthority> joinAuthorities(
            OidcUser oidcUser,
            Collection<? extends GrantedAuthority> additionalAuthorities) {

            HashSet<GrantedAuthority> totalAuthorities = new HashSet<>(
                oidcUser.getAuthorities().size() + additionalAuthorities.size());

            totalAuthorities.addAll(oidcUser.getAuthorities());
            totalAuthorities.addAll(additionalAuthorities);
            return totalAuthorities;
        }
    }
}

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Jan 18, 2024
@jzheaux
Copy link
Contributor

jzheaux commented Jan 27, 2025

Thanks for reporting this, @Xyaren, and sorry for the delay. setOidcUserMapper was added to OidcUserService in 6.3.

You should now instead be able to do the following:

@Bean 
OidcUserService oidcUserService() {
    OidcUserService service = new OidcUserService();
    service.setOidcUserMapper((request, userInfo) -> {
        OAuth2AccessToken accessToken = userRequest.getAccessToken();
        Collection<GrantedAuthority> authorities = authorityExtractor.authorities().collect(Collectors.toSet());
        String userNameAttributeName = "preferred_username";
        return new DefaultOidcUser(authorities, userRequest.getIdToken(), 
                userInfo, userNameAttributeName);
    });
    return service;
}

Does this allow you to remove your wrapper class and service extension? If so, I think we'll close this issue and guide people to use that method to get the access token.

@jzheaux jzheaux added status: waiting-for-feedback We need additional information before we can continue and removed status: feedback-provided Feedback has been provided labels Jan 27, 2025
@spring-projects-issues
Copy link

If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.

@spring-projects-issues spring-projects-issues added the status: feedback-reminder We've sent a reminder that we need additional information before we can continue label Feb 3, 2025
@Xyaren
Copy link
Author

Xyaren commented Feb 4, 2025

I got it working using the mapper, thank you very much :)

@Xyaren Xyaren closed this as completed Feb 4, 2025
@jzheaux jzheaux added status: duplicate A duplicate of another issue and removed status: waiting-for-feedback We need additional information before we can continue status: feedback-reminder We've sent a reminder that we need additional information before we can continue labels Feb 18, 2025
@jzheaux jzheaux self-assigned this Feb 18, 2025
@jzheaux
Copy link
Contributor

jzheaux commented Feb 18, 2025

Superceded by #14672, which added setOidcUserMapper.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) status: duplicate A duplicate of another issue type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

3 participants