19

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
        }
    }
rynmrtn
  • 3,371
  • 5
  • 28
  • 44
Stackee007
  • 3,196
  • 1
  • 26
  • 39
  • 5
    Any pointers @DaveSyer? – Stackee007 Jul 20 '15 at 16:23
  • I think you have to comment on one of his other answers in order for him to see your message. But even then, he probably will not take the time to respond to your question. And the result is that google searches for all these key words just result in a bunch of unanswered questions. And the end result is that people do not implement Spring OAuth2 because it is not supported by Spring on Stack Overflow. – CodeMed May 13 '16 at 20:08
  • The defect is open here: https://github.com/spring-projects/spring-security-oauth/issues/547 – Cody May 27 '16 at 21:43

1 Answers1

0

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);
    }
Sebastiaan van den Broek
  • 5,818
  • 7
  • 40
  • 73