Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get custom UserDetailService Object in Resource Server in spring-security-oauth2?

I have separate authorization server and resource server. Authorization server is pointing to a separate database. I haves used CustomUserDetailService for user related information. I have used CustomTokenEnhancer to have additional information apart from the token in the response.

@Configuration
public class OAuth2Configuration {


    @Configuration
    @EnableAuthorizationServer
    protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter implements EnvironmentAware {

        private static final String ENV_OAUTH = "authentication.oauth.";
        private static final String PROP_CLIENTID = "clientid";
        private static final String PROP_SECRET = "secret";
        private static final String PROP_TOKEN_VALIDITY_SECONDS = "tokenValidityInSeconds";

        private RelaxedPropertyResolver propertyResolver;

        @Autowired
        private DataSource dataSource;

        @Autowired
        private CustomUserDetailService userDetailsService;

        @Bean
        public TokenStore tokenStore() {
            return new CustomTokenStore(dataSource);
        }

        @Autowired
        @Qualifier("authenticationManagerBean")
        private AuthenticationManager authenticationManager;

        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints)
                throws Exception {
            endpoints
                    .tokenStore(tokenStore())
                    .userDetailsService(userDetailsService)

                    .tokenEnhancer(tokenEnhancer())
                    .accessTokenConverter(accessTokenConverter())

                    .authenticationManager(authenticationManager);
        }

        @Bean
        public TokenEnhancer tokenEnhancer() {
           return new CustomTokenEnhancer();
        }

        @Bean
        public DefaultAccessTokenConverter accessTokenConverter() {
            return new DefaultAccessTokenConverter();
        }


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

}

CustomUserDetailService Class:

@Service
public class CustomUserDetailService implements UserDetailsService {


    @Autowired
    private AccountRepository accountRepository;

    @Override
    public UserDetails loadUserByUsername(String username) {

        Account account = accountRepository.getByEmail(username);

        if(account == null) {
            throw new UsernameNotFoundException(username);
        }

        return new MyUserPrincipal(account);

    }
}

CustomTokenEnhancer Class:

public class CustomTokenEnhancer implements TokenEnhancer {

    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
        MyUserPrincipal user = (MyUserPrincipal) authentication.getPrincipal();
        final Map<String, Object> additionalInfo = new HashMap<>();

        additionalInfo.put("user_information", user.getAccount());

        ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);

        return accessToken;
    }

}

Request/Response

http://localhost:9191/authserver/oauth/token


{
    "access_token": "fddb571e-224e-4cd7-974e-65104dd24b41",
    "token_type": "bearer",
    "refresh_token": "eb412b00-9e4e-4d6c-86d8-324d999b5f08",
    "expires_in": 100,
    "scope": "read write",
    "account_information": {
        "id": 14,
        "firstname": "name",
        "lastname": "lastname",

    }
}

At resource server side, I have used RemoteTokenSerice to verify the the token presented by user is valid or not.

@Configuration
@EnableResourceServer
public class OAuthResourceConfig extends ResourceServerConfigurerAdapter {

    private TokenExtractor tokenExtractor = new BearerTokenExtractor();

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.addFilterAfter(new OncePerRequestFilter() {
            @Override
            protected void doFilterInternal(HttpServletRequest request,
                    HttpServletResponse response, FilterChain filterChain)
                    throws ServletException, IOException {
                if (tokenExtractor.extract(request) == null) {
                    SecurityContextHolder.clearContext();
                }
                filterChain.doFilter(request, response);
            }
        }, AbstractPreAuthenticatedProcessingFilter.class);
        http.csrf().disable();
        http.authorizeRequests().anyRequest().authenticated();
    }

    @Bean
    public AccessTokenConverter accessTokenConverter() {
        return new DefaultAccessTokenConverter();
    }

    @Bean
    @Primary
    public RemoteTokenServices remoteTokenServices(final @Value("${auth.server.url}") String checkTokenUrl,
            final @Value("${auth.server.clientId}") String clientId,
            final @Value("${auth.server.clientsecret}") String clientSecret) {


        final RemoteTokenServices remoteTokenServices = new RemoteTokenServices();
        remoteTokenServices.setCheckTokenEndpointUrl(checkTokenUrl+"?name=value");
        remoteTokenServices.setClientId(clientId);
        remoteTokenServices.setClientSecret(clientSecret);
        remoteTokenServices.setAccessTokenConverter(accessTokenConverter());
        return remoteTokenServices;
    }

    }

So It is working properly and when I make a request to resource server with token, it processes the request if the token is valid. My question is I want to get Account object in resource server. I tried with below:

 Account account = (Account)SecurityContextHolder.getContext().getAuthentication().getPrincipal()

But it gives string and not the complete user define object and hence it throws the exception.How to get Account object in any controller in Resource server?

{
    "timestamp": 1499334657703,
    "status": 500,
    "error": "Internal Server Error",
    "exception": "java.lang.ClassCastException",
    "message": "java.lang.String cannot be cast to Account",
    "path": "/secure"
    }

I tried with link but is it possible to inject two token services, RemoteTokenService and CustomUserInfoTokenServices both?

Also I think here spring makes internal call from Resource Server to Authorization Server (http://localhost:9191/authserver/oauth/check_token?token=d8dae984-7bd8-4aab-9990-a2c916dfe667) to validate the token.

Is there any way I can get those information in controller without calling this endpoint again.

Response:

{
    "exp": 1499333294,
    "account_information": {
        "accountid": 14,
        "firstname": "fname",
        "lastname": "lname",

    },
    "user_name": "[email protected]",
    "client_id": "clientId",
    "scope": [
        "read",
        "write"
    ]
}
like image 504
027 Avatar asked Oct 29 '22 05:10

027


1 Answers

I have overridden below method and added some logic.

public class CustomAccessTokenConverter extends DefaultAccessTokenConverter{

    private UserAuthenticationConverter userTokenConverter = new DefaultUserAuthenticationConverter();

    @Override
    public OAuth2Authentication extractAuthentication(Map<String, ?> map) {
        Map<String, String> parameters = new HashMap<String, String>();
        @SuppressWarnings("unchecked")
        Set<String> scope = new LinkedHashSet<String>(map.containsKey(SCOPE) ? (Collection<String>) map.get(SCOPE)
                : Collections.<String>emptySet());
        Authentication user = userTokenConverter.extractAuthentication(map);
        String clientId = (String) map.get(CLIENT_ID);
        parameters.put(CLIENT_ID, clientId);
        parameters.put("account_information", String.valueOf((((Map) map.get("account_information")).get("accountid"))));
        @SuppressWarnings("unchecked")
        Set<String> resourceIds = new LinkedHashSet<String>(map.containsKey(AUD) ? (Collection<String>) map.get(AUD)
                : Collections.<String>emptySet());

        Map<String, Serializable> extensions = new HashMap<String, Serializable>();
        extensions.put("account_information", (HashMap) map.get("account_information"));

        OAuth2Request request = new OAuth2Request(parameters, clientId, null, true, scope, resourceIds, null, null,
                extensions);
        return new OAuth2Authentication(request, user);
    }

}

Resource Server Class

@Bean
    public AccessTokenConverter accessTokenConverter() {
        //return new DefaultAccessTokenConverter();
        return new CustomAccessTokenConverter();
    }

    @Bean
    @Primary
    public RemoteTokenServices remoteTokenServices(final @Value("${auth.server.url}") String checkTokenUrl,
            final @Value("${auth.server.clientId}") String clientId,
            final @Value("${auth.server.clientsecret}") String clientSecret) {


        final RemoteTokenServices remoteTokenServices = new RemoteTokenServices();
        remoteTokenServices.setCheckTokenEndpointUrl(checkTokenUrl+"?name=value");
        remoteTokenServices.setClientId(clientId);
        remoteTokenServices.setClientSecret(clientSecret);
        remoteTokenServices.setAccessTokenConverter(accessTokenConverter());
        return remoteTokenServices;
    }

Now I can get additional information in controller.

OAuth2Authentication authentication = (OAuth2Authentication)SecurityContextHolder.getContext().getAuthentication();

        Map<String, Serializable> map = authentication.getOAuth2Request().getExtensions();
like image 60
027 Avatar answered Jan 02 '23 20:01

027