Spring OAuth2 - Manually creating an access token in the token store

后端 未结 7 931
天涯浪人
天涯浪人 2020-12-07 17:00

I have a situation where I would like to create an access token myself (so not through the usual process). I have come up with something like this:

@Inject
p         


        
相关标签:
7条回答
  • 2020-12-07 17:31

    I based my solution on Mop So's answer but instead of using:

    return tokenEndpoint.getAccessToken(principal, parameters);
    

    I used:

    tokenEndpoint.postAccessToken(principal, parameters);
    

    Why? Because if you use tokenEndpoint.getAccessToken(principal, parameters) the endpoing will throw you a HttpRequestMethodNotSupportedException because it has not been called with a GET method. At least, this is what happened to me with spring-security-oauth2-2.0.13.RELEASE

    public OAuth2AccessToken getAccessToken() throws HttpRequestMethodNotSupportedException {
        HashMap<String, String> parameters = new HashMap<>();
        parameters.put("client_id", CLIENT_ID);
        parameters.put("client_secret", CLIENT_SECRET);
        parameters.put("grant_type", "client_credentials");
    
        ClientDetails clientDetails = clientDetailsStore.get(CLIENT_ID);
    
        // Create principal and auth token
        User userPrincipal = new User(CLIENT_ID, CLIENT_SECRET, true, true, true, true, clientDetails.getAuthorities());
    
        UsernamePasswordAuthenticationToken principal = new UsernamePasswordAuthenticationToken(userPrincipal, CLIENT_SECRET,
                clientDetails.getAuthorities());
    
        ResponseEntity<OAuth2AccessToken> accessToken = tokenEndpoint.postAccessToken(principal, parameters);
    
        return accessToken.getBody();
    }
    
    0 讨论(0)
  • 2020-12-07 17:44

    This has worked for me:

    @Override public OAuth2AccessToken getToken(String username, String password) {
        HashMap<String, String> parameters = new HashMap<String, String>();
        parameters.put("client_id", clientid);
        parameters.put("grant_type", "password");
        parameters.put("password", username);
        parameters.put("scope", scope);
        parameters.put("username", password);
    
        AuthorizationRequest authorizationRequest = defaultOAuth2RequestFactory.createAuthorizationRequest(parameters);
        authorizationRequest.setApproved(true);
    
        OAuth2Request oauth2Request = defaultOAuth2RequestFactory.createOAuth2Request(authorizationRequest);
        // Create principal and auth token
        final UsernamePasswordAuthenticationToken loginToken = new UsernamePasswordAuthenticationToken(
                username, password);
        Authentication authentication = authenticationManager.authenticate(loginToken);
    
        OAuth2Authentication authenticationRequest = new OAuth2Authentication(oauth2Request, authentication);
        authenticationRequest.setAuthenticated(true);
    
        OAuth2AccessToken accessToken = tokenServices.createAccessToken(authenticationRequest);
    
        return accessToken;
    }
    

    In the Oauth2Configuration:

    @Bean
        DefaultOAuth2RequestFactory defaultOAuth2RequestFactory() {
        return new DefaultOAuth2RequestFactory(clientDetailsService);
    }
    

    The rest of the Oauth2Configuration should look like in the article:

    http://stytex.de/blog/2016/02/01/spring-cloud-security-with-oauth2/

    0 讨论(0)
  • 2020-12-07 17:47

    Here it is, your use case may differ slightly based on the flow you are using. This is what works for a password grant flow. There are a few custom class like token store, token enhancer ect. but that is really just extended versions of the spring classes modified for our own needs.

            HashMap<String, String> authorizationParameters = new HashMap<String, String>();
            authorizationParameters.put("scope", "read");
            authorizationParameters.put("username", "mobile_client");
            authorizationParameters.put("client_id", "mobile-client");
            authorizationParameters.put("grant", "password");
    
            DefaultAuthorizationRequest authorizationRequest = new DefaultAuthorizationRequest(authorizationParameters);
            authorizationRequest.setApproved(true);
    
            Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
            authorities.add(new SimpleGrantedAuthority("ROLE_UNTRUSTED_CLIENT"));
            authorizationRequest.setAuthorities(authorities);
    
            HashSet<String> resourceIds = new HashSet<String>();
            resourceIds.add("mobile-public");
            authorizationRequest.setResourceIds(resourceIds);
    
            // Create principal and auth token
            User userPrincipal = new User(user.getUserID(), "", true, true, true, true, authorities);
    
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userPrincipal, null, authorities) ;
    
            OAuth2Authentication authenticationRequest = new OAuth2Authentication(authorizationRequest, authenticationToken);
            authenticationRequest.setAuthenticated(true);
    
            CustomTokenStore tokenStore = new CustomTokenStore();
    
            // Token Enhancer
            CustomTokenEnhancer tokenEnhancer = new CustomTokenEnhancer(user.getUserID());
    
            CustomTokenServices tokenServices = new CustomTokenServices();
            tokenServices.setTokenEnhancer(tokenEnhancer);
            tokenServices.setSupportRefreshToken(true);
            tokenServices.setTokenStore(tokenStore);
    
            OAuth2AccessToken accessToken = tokenServices.createAccessTokenForUser(authenticationRequest, user);
    
    0 讨论(0)
  • 2020-12-07 17:48

    Other way, to manually generate an OAuth2 Accesss Token we can use an instance of TokenService

    @Autowired
    private AuthorizationServerEndpointsConfiguration configuration;
    
    @Override
    public String generateOAuth2AccessToken(User user, List<Role> roles, List<String> scopes) {
    
        Map<String, String> requestParameters = new HashMap<String, String>();
        Map<String, Serializable> extensionProperties = new HashMap<String, Serializable>();
    
        boolean approved = true;
        Set<String> responseTypes = new HashSet<String>();
        responseTypes.add("code");
    
        // Authorities
        List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
        for(Role role: roles)
            authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getName()));
    
        OAuth2Request oauth2Request = new OAuth2Request(requestParameters, "clientIdTest", authorities, approved, new HashSet<String>(scopes), new HashSet<String>(Arrays.asList("resourceIdTest")), null, responseTypes, extensionProperties);
    
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUsername(), "N/A", authorities);
    
        OAuth2Authentication auth = new OAuth2Authentication(oauth2Request, authenticationToken);
    
        AuthorizationServerTokenServices tokenService = configuration.getEndpointsConfigurer().getTokenServices();
    
        OAuth2AccessToken token = tokenService.createAccessToken(auth);
    
        return token.getValue();
    }
    
    0 讨论(0)
  • 2020-12-07 17:48

    Problem

    I had problems with all the implementations listed here, so I finally managed to get my own with a stateless server, oauth2 and google social. Its just the last part of the tutorial that is missing here

    The problem for me is that after executing the google oauth, I need to exchange a 10 second duration token for a long lived token. In order to do that I need to generate a JWT token and exchange it with a real access token generated by myself.

    Implementation

    @Service
    class SocialTokenVerificationService {
    
        @Autowired
        private lateinit var jwsTokenService: JWSTokenService
        @Autowired
        private lateinit var clientDetailsService: ClientDetailsService
        @Autowired
        private lateinit var userService: UserService
        @Autowired
        private lateinit var tokenServices: DefaultTokenServices
        @Autowired
        private lateinit var tokenRequestFactory: OAuth2RequestFactory
    
        fun verifyToken(token: String): OAuth2AccessToken? {
            val claimSet = jwsTokenService.parseToken(token)
            val userDetails = userService.loadUserByUsername(claimSet.subject)
    
            val client = clientDetailsService.loadClientByClientId(DEFAULT_SERVER_CLIENT)
            val parameters = HashMap<String, String>()
            val authentication = UsernamePasswordAuthenticationToken(userDetails, null, userDetails.authorities)
            return tokenServices.createAccessToken(OAuth2Authentication(
                    tokenRequestFactory.createOAuth2Request(client, TokenRequest(parameters, client.clientId, listOf("read", "write"), "password")),
                    authentication
            ))
        }
    }
    
    • JWSTokenService: its a self implemented class that encodes and decodes the exchanging token between google oauth and mine.
    • ClientDetailsService: bean declared as as part of the authorization server. Comes from my database

      override fun configure(clients: ClientDetailsServiceConfigurer) { clients.jdbc(datasource) }

    • UserService: just a user service that extends UserDetailsService to obtain my users from the database

    • DefaultTokenServices: implemented as a primary bean as follows

      @Bean
      @Primary
      fun tokenServices(): DefaultTokenServices {
          val defaultTokenServices = DefaultTokenServices()
          defaultTokenServices.setTokenStore(tokenStore())
          defaultTokenServices.setSupportRefreshToken(true)
          defaultTokenServices.setTokenEnhancer(jwtAccessTokenConverter())
          return defaultTokenServices
      }
      
    • OAuth2RequestFactory: implemented as a bean as follows

      @Bean
      fun oauthRequestFactory(clientsDetails: ClientDetailsService): OAuth2RequestFactory {
          return DefaultOAuth2RequestFactory(clientsDetails)
      }
      

    With all this dependencies, what I need to do to generate a token that gets stored into the database and follows the same flows as the other ones without providing a password is:

    1. Parse the jws token and verify its validity
    2. Load the user that was authenticated with google
    3. Generate an Authentication using the UsernamePasswordAuthenticationToken class. This is the key part, call DefaultTokenServices#createAccessToken to obtain a new token. It needs some arguments to execute the request:
      • OAuth2Request: it can be created with the OAuth2RequestFactory
      • The Authentication created previously
      • We need to generate a TokenRequest with the client that is triggering this token request. In my case I have that hardcoded

    Summary

    So to recap how to create a token manually:

    • We need to ask the token services to give us a token
    • For that we need to provide the authentication details and a client who does the request
    • With those 2 we can obtain a new token and serve it normally
    0 讨论(0)
  • 2020-12-07 17:53

    In a spring boot 2.2.2 project I'm using the following code to do a pasword flow server side: I had to specify authorizedClientManager.setContextAttributesMapper since PasswordOAuth2AuthorizedClientProvider is expecting specific attributes in the context. Hope that helps.

    Config (application.yaml):

    spring:
      security:
        oauth2:
          client:
            provider:
              yourOauthProvider:
                user-info-uri: ...
                authorization-uri: ...
                token-uri: ...
    
            registration:
              regId:
                clientId: ...
                clientSecret: ...
                provider: yourOauthProvider
                authorization-grant-type: password
                redirect-uri-template: "{baseUrl}/login/oauth2/code/{registrationId}"
                scope:
    

    Wiring:

    @Configuration
    public class Oauth2ClientConfig {
    
        @Bean
        public OAuth2AuthorizedClientManager authorizedClientManager(
                ClientRegistrationRepository clientRegistrationRepository,
                OAuth2AuthorizedClientRepository authorizedClientRepository) {
    
            OAuth2AuthorizedClientProvider authorizedClientProvider =
                    OAuth2AuthorizedClientProviderBuilder.builder()
                            .password()
                            .build();
    
            DefaultOAuth2AuthorizedClientManager authorizedClientManager =
                    new DefaultOAuth2AuthorizedClientManager(
                            clientRegistrationRepository, authorizedClientRepository);
            authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
            authorizedClientManager.setContextAttributesMapper(r -> {
                Map<String, Object> m = new HashMap<>();
                m.put(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, r.getPrincipal().getPrincipal());
                m.put(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, r.getPrincipal().getCredentials());
                return m;
            });
    
            return authorizedClientManager;
        }
    }
    

    Service:

    class AuthService {
        @Autowired
        private OAuth2AuthorizedClientManager authorizedClientManager;
        public OAuth2AccessToken authenticate(String user, String password) {
    
            Authentication principal = new UsernamePasswordAuthenticationToken(
                    user,
                    password);
    
            OAuth2AuthorizeRequest authorizeRequest = 
                OAuth2AuthorizeRequest.withClientRegistrationId("regId")
                    .principal(principal)
                    .build();
    
            OAuth2AuthorizedClient authorizedClient =
                this.authorizedClientManager.authorize(authorizeRequest);
    
            return authorizedClient.getAccessToken();
        }
    }
    
    0 讨论(0)
提交回复
热议问题