Spring OAuth2 - Manually creating an access token

2019-01-21 10:07发布

问题:

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
private DefaultTokenServices defaultTokenServices;

... 

OAuth2Authentication auth = xxx;
OAuth2AccessToken  token = defaultTokenServices.createAccessToken(auth);

The only problem is that I am not sure how to create the OAuth2Authentication (in my code the part with xxx). I have the user & client info and I know which Authorities I want to grant this token.

回答1:

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);


回答2:

Here is how to generate a Token using the TokenEndpoint interface (used to expose REST service) :

@Inject
private TokenEndpoint tokenEndpoint;

public ResponseEntity<?> getToken(Principal principal) {

        HashMap<String, String> parameters = new HashMap<String, String>();
        parameters.put("client_id", "appid");
        parameters.put("client_secret", "myOAuthSecret");
        parameters.put("grant_type", "password");
        parameters.put("password", myUser.getPassword());
        parameters.put("scope", "read write");
        parameters.put("username", myUser.getLogin());

        return tokenEndpoint.getAccessToken(principal, parameters);
}


回答3:

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();
}


回答4:

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/



回答5:

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();
}


回答6:

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