Mapping user roles to oauth2 scopes/authorities

Stackee007 picture Stackee007 · Jul 10, 2015 · Viewed 7k times · Source

We have a entitlements database which has application id, roles and users mapped to roles per application. Following the advice on thread how do I map user roles to oauth2 scopes/authorities based on resourceId?

Ignoring the entitlements database I mentioned above do I map roles "USER", "READER", "WRITER" to oauth2 scopes/authorities based on user and resourceId in below code?

User Authentication/Authorization Config

@Configuration
@Order(-10)
protected static class LoginConfig extends WebSecurityConfigurerAdapter {

    ....

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // auth.parentAuthenticationManager(authenticationManager);
        // @formatter:off
        auth.inMemoryAuthentication()
            .withUser("admin").password("admin")
                .roles("ADMIN", "USER", "READER", "WRITER")
            .and()
            .withUser("user").password("password")
                .roles("USER")
            .and()
            .withUser("audit").password("audit")
                .roles("USER", "ADMIN", "READER");
        // @formatter:on
    }
}

OAuth2 Config

@Configuration
@EnableAuthorizationServer
protected static class OAuth2Config extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // @formatter:off
        clients.inMemory()
            .withClient("acme").secret("acmesecret")
                .authorizedGrantTypes("authorization_code", "refresh_token", "password")
                .scopes("openid")
            .and()
            .withClient("trusted").secret("shuush")
                .authorizedGrantTypes("client_credentials")
                .scopes("openid");
        // @formatter:on
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager);
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
        oauthServer.checkTokenAccess("isAuthenticated()");
    }
}

Update 1:

Introduced a custom OAuth2RequestFactory into the config to set checkUserScopes to true. While this setup works for "client_credentails", it fails for "code" grants. For "code" grants, DefaultOAuth2RequestFactory tries to maps authorities for client (acme) rather than user during the authorization step. Other thought is to implement ClientDetailsService that add authorities of client (acme) based on the logged-in user (admin/user) but not sure on how to grab logged-in user from SecurityContext since it is overwritten with client (acme) during authorization step. Any ideas?

public class ScopeMappingOAuth2RequestFactory extends DefaultOAuth2RequestFactory {

    private SecurityContextAccessor securityContextAccessor = new DefaultSecurityContextAccessor();

    public ScopeMappingOAuth2RequestFactory(ClientDetailsService clientDetailsService) {
        super(clientDetailsService);
        super.setCheckUserScopes(true);
    }

    /**
     * @param securityContextAccessor the security context accessor to set
     */
    @Override
    public void setSecurityContextAccessor(SecurityContextAccessor securityContextAccessor) {
        this.securityContextAccessor = securityContextAccessor;
        super.setSecurityContextAccessor(securityContextAccessor);
    }

    @Override
    public AuthorizationRequest createAuthorizationRequest(Map<String, String> authorizationParameters) {
        AuthorizationRequest request = super.createAuthorizationRequest(authorizationParameters);

        if (securityContextAccessor.isUser()) {
            request.setAuthorities(securityContextAccessor.getAuthorities());
        }

        return request;
    }

}

and updated related code to

@EnableAuthorizationServer
protected static class OAuth2Config extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;

    private InMemoryClientDetailsService clientDetailsService;

    private Map<String, ClientDetails> clientDetailsStore;

    public InMemoryClientDetailsService clientDetailsService() {
        if (clientDetailsService == null) {
            clientDetailsStore = new HashMap<String, ClientDetails>();
            InMemoryClientDetailsService m = new InMemoryClientDetailsService() {

                @Override
                public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException {
                    ClientDetails details = clientDetailsStore.get(clientId);
                    if (details == null) {
                        throw new NoSuchClientException("No client with requested id: " + clientId);
                    }
                    return details;
                }

            };
            clientDetailsService = m;
        }
        return clientDetailsService;
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        InMemoryClientDetailsServiceBuilder builder = new InMemoryClientDetailsServiceBuilder() {

            @Override
            protected void addClient(String clientId, ClientDetails value) {
                clientDetailsStore.put(clientId, value);
            }

            @Override
            protected ClientDetailsService performBuild() {
                return clientDetailsService();
            }
        };
        clients.setBuilder(builder);

        // @formatter:off
        builder
            .withClient("acme").secret("acmesecret")
                .authorizedGrantTypes("authorization_code", "refresh_token", "password")
                .scopes("openid", "apim.read", "apim.write")
            .and()
            .withClient("trusted").secret("shuush")
                .authorizedGrantTypes("client_credentials")
                .scopes("openid", "apim.read", "apim.write")
                .authorities("openid", "apim.read", "apim.write");
        // @formatter:on
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager);
        endpoints.requestFactory(new ScopeMappingOAuth2RequestFactory(clientDetailsService()));
    }

... }

LoginConfig

Configuration
@Order(-10)
protected static class LoginConfig extends WebSecurityConfigurerAdapter {

....

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // auth.parentAuthenticationManager(authenticationManager);
        // @formatter:off
        auth.inMemoryAuthentication()
            .withUser("admin").password("admin")
                .roles("APIM.READ", "APIM.WRITE")
            .and()
            .withUser("user").password("password")
                .roles("APIM.READ")
            .and()
            .withUser("audit").password("audit")
                .roles("APIM.READ");
        // @formatter:on
    }
}

Answer

Sebastiaan van den Broek picture Sebastiaan van den Broek · Jun 21, 2018

I ran into the same problem and I also noticed the code was running the checkUserScopes method twice. I found out that what is missing is that both the user AND the CLIENT need to have the authorities that you want to return.

So define your client in a way like this (adjust roles to your own):

    @Bean
    public ClientDetailsService clientDetailsService() {
        Map<String, ClientDetails> clientDetailsStore = new HashMap<>();

        Collection<String> scope = new HashSet<>();
        scope.add("user");
        scope.add("admin");

        Collection<GrantedAuthority> authorities = new HashSet<>();
        authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
        authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));

        Collection<String> authorizedGrantTypes = new HashSet<>();
        authorizedGrantTypes.add("authorization_code");

        BaseClientDetails clientDetails = new BaseClientDetails();
        clientDetails.setClientId("clientid");
        clientDetails.setClientSecret("{noop}secret"); //noop for Spring Security 5
        clientDetails.setScope(scope);
        clientDetails.setAuthorities(authorities);
        clientDetails.setAuthorizedGrantTypes(authorizedGrantTypes);

        clientDetailsStore.put("clientid", clientDetails);

        InMemoryClientDetailsService clientDetailsService = new InMemoryClientDetailsService();
        clientDetailsService.setClientDetailsStore(clientDetailsStore);

        return clientDetailsService;
    }

Now the client has the required authorities user and admin.

And configure your request factory:

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {

        DefaultOAuth2RequestFactory defaultOAuth2RequestFactory = new DefaultOAuth2RequestFactory(clientDetailsService());
        defaultOAuth2RequestFactory.setCheckUserScopes(true);
        endpoints.requestFactory(defaultOAuth2RequestFactory);
    }