Spring Cloud Oauth token generation source code analysis

Spring Cloud Oauth core class parsing

What is oAUTH2.0 protocol is what can read Ruan Yifeng teacher oAuth2.0 explanation

The following diagram is the core class that generates the token source code (Password mode)

The entry point of the process is from TokenEndpoint until the access_token is returned

  • First, the front-end request accesses the corresponding endpoint in TokenEndpoint directly through the /oauth/token path. TokenEndpoint obtains the request parameter client_id. The ClientDetail object is obtained based on the client_ID through the ClientDetailsService interface

  • The TokenRequest object is then constructed from the front-end request parameters (scope,grant_type,client_id, etc.) and ClientDetail objects

  • Then through TokenGranter token who co-signed generate token operation, TokenGranter will according to different grant_type perform different logic token is generated

  • Either way it generates a token it generates an Authentication object and an OAuth2Request object, Authentication contains the user information. OAuth2Request contains the TokenRequest and ClientDetail information The information in OAuth2Request is summarized to construct an OAuth2Authentication object

  • The final pass OAuth2Authentication object to AuthorizationServerTokenServices implementation class (the default is: DefaultTokenServices) to generate the token is returned to the front

Spring Cloud OAuth Token generation process

  • Obtain the front-end request parameter clientId, and query the ClientDetails object corresponding to clientId through ClientDetailsService interface.
  • The TokenRequest object is built from ClientDetails with the front-end request parameters
  • Verify ClientDetails information
  • Call different token authorizers according to grant_type to generate token information
@RequestMapping(value = "/oauth/token", method=RequestMethod.POST) public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, @RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException { if (! (principal instanceof Authentication)) { throw new InsufficientAuthenticationException( "There is no client authentication. Try adding an appropriate authentication filter."); } // Get the request header clientId parameter String clientId = getClientId(Principal); // Use clientId to query client information. Return ClientDetails object ClientDetails authenticatedClient = getClientDetailsService().loadClientByCliEntid (clientId); TokenRequest TokenRequest = TokenRequest = TokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient); // Verify clientId if (clientId! = null && ! clientId.equals("")) { // Only validate the client details if a client authenticated during this // request. if (! clientId.equals(tokenRequest.getClientId())) { // double check to make sure that the client ID in the token request is the same as that in the // authenticated client throw new InvalidClientException("Given client ID does not match authenticated client"); }} Scope if (authenticatedClient! = null) { oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient); } // Verify grant_type if (! StringUtils.hasText(tokenRequest.getGrantType())) { throw new InvalidRequestException("Missing grant type"); } / / simple model can directly generate token returned, do not come this far, so when grant_type = implicit directly thrown exception if (tokenRequest. GetGrantType (.) the equals (" implicit ")) {throw new InvalidGrantException("Implicit grant type not supported from token endpoint"); } // The scope is defined when the license code is obtained. If (isAuthCodeRequest(parameters)) {// The scope was requested or determined during The authorization step if (! tokenRequest.getScope().isEmpty()) { logger.debug("Clearing scope of incoming token request"); tokenRequest.setScope(Collections.<String> emptySet()); Scope if (isRefreshTokenRequest(parameters)) {A refresh Token has its own default scopes, so we should ignore any added by the factory here. tokenRequest.setScope(OAuth2Utils.parseParameterList(parameters.get(OAuth2Utils.SCOPE))); } / / call the token grantee generated token OAuth2AccessToken token = getTokenGranter () grant (tokenRequest. GetGrantType (), tokenRequest); if (token == null) { throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType()); Return getResponse(token);} return getResponse(token); }Copy the code

TokenGranter process parsing

  • Through the getTokenGranter (). Grant (tokenRequest getGrantType into CompositeTokenGranter ()
  • The CompositeTokenGranter has a collection of tokenGranters that contains four authorization modes and a refresh token operation
  • Traverse tokenGranters collection, called AbstractTokenGranter. Grant () to judge whether the current mode and grant_type match, does not match the returns null, match again to verify the client information, the last call getAccessToken () to generate a token
Public class CompositeTokenGranter implements TokenGranter {private Final List<TokenGranter> tokenGranters; public CompositeTokenGranter(List<TokenGranter> tokenGranters) { this.tokenGranters = new ArrayList<TokenGranter>(tokenGranters); } / / traverse tokenGranters collection, called AbstractTokenGranter. Grant () / / whether the current mode and grant_type match, does not match the returns null, match again to verify the client information,  public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) { for (TokenGranter granter : tokenGranters) { OAuth2AccessToken grant = granter.grant(grantType, tokenRequest); if (grant! =null) { return grant; } } return null; } public void addTokenGranter(TokenGranter tokenGranter) { if (tokenGranter == null) { throw new IllegalArgumentException("Token granter is null"); } tokenGranters.add(tokenGranter); }}Copy the code
AbstractTokenGranter public OAuth2AccessToken Grant (String grantType, TokenRequest TokenRequest) {// Check whether the current mode matches grant_type. Null if (! this.grantType.equals(grantType)) { return null; } String clientId = TokenRequest.getCliEntid (); ClientDetails client = clientDetailsService.loadClientByClientId(clientId); validateGrantType(grantType, client); if (logger.isDebugEnabled()) { logger.debug("Getting access token for: " + clientId); } // Generate token return getAccessToken(client, tokenRequest); } / / call AuthorizationServerTokenServices interface generated token / / getOAuth2Authentication (client, TokenRequest) generates an OAuth2Authentication object based on the matching pattern. Protected OAuth2AccessToken getAccessToken TokenRequest tokenRequest) { return tokenServices.createAccessToken(getOAuth2Authentication(client, tokenRequest)); } protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) { OAuth2Request storedOAuth2Request = requestFactory.createOAuth2Request(client, tokenRequest); return new OAuth2Authentication(storedOAuth2Request, null); } protected AuthorizationServerTokenServices getTokenServices() { return tokenServices; } protected OAuth2RequestFactory getRequestFactory() { return requestFactory; }Copy the code

Here is the most common password mode to parse, grant_type matches the operation

  • To get the account password to construct UsernamePasswordAuthenticationToken front-end request
  • By the authenticationManager. Authenticate user information, In the authenticationManager. Authenticate is ultimately is to call UserDetailService loadUserByUsername () method of the query to the user information
  • Build the OAuth2Request object from ClientDetails and tokenRequest information
  • Finally, the summary of Authentication and OAuth2Request information is constructed into OAuth2Authentication object. At this time, OAuth2Authentication contains request parameters, client information, user information and other information
@Override protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest TokenRequest) {Map<String, String> parameters = new LinkedHashMap<String, String>(tokenRequest.getRequestParameters()); String username = parameters.get("username"); String password = parameters.get("password"); // Protect from downstream leaks of password parameters.remove("password"); / / build UsernamePasswordAuthenticationToken Authentication userAuth = new UsernamePasswordAuthenticationToken (username, password); ((AbstractAuthenticationToken) userAuth).setDetails(parameters); Try {/ / get the user information, you will call to the UserDetailService loadUserByUsername () method userAuth = the authenticationManager. Authenticate (userAuth); } catch (AccountStatusException ase) {// Covers expired, locked, disabled cases (mentioned in section 5.2, draft 31) throw new InvalidGrantException(ase.getMessage()); } catch (BadCredentialsException e) { // If the username/password are wrong the spec says we should send 400/invalid grant throw new InvalidGrantException(e.getMessage()); } if (userAuth == null || ! userAuth.isAuthenticated()) { throw new InvalidGrantException("Could not authenticate user: " + username); } // Construct OAuth2Request object OAuth2Request storedOAuth2Request = according to ClientDetails and tokenRequest information getRequestFactory().createOAuth2Request(client, tokenRequest); OAuth2Authentication {//OAuth2Authentication {//OAuth2Authentication {//OAuth2Authentication {//OAuth2Authentication {//OAuth2Authentication {//OAuth2Authentication {//OAuth2Authentication {//OAuth2Authentication {//OAuth2Authentication {//OAuth2Authentication {//OAuth2Authentication} User information Return new OAuth2Authentication(storedOAuth2Request, userAuth); }Copy the code

Finally resolve the tokenServices. CreateAccessToken method process

  • AuthorizationServerTokenServices interface, the default implementation class is DefaultTokenServices,
  • Check the token in tokenStore according to authentication and determine whether the token exists. If the token exists, the user has applied for the token before. In this case, the user needs to verify whether the token has not expired. If the new token does not exist, the user applies for the token for the first time and returns the new token
@Transactional public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {// Query token OAuth2AccessToken existingAccessToken = in tokenStore according to authentication tokenStore.getAccessToken(authentication); OAuth2RefreshToken refreshToken = null; // Check whether token exists if (existingAccessToken! Whether = null) {/ / token expired, expired if are removed (existingAccessToken. IsExpired ()) {/ / refresh token is late, Expired if are removed (existingAccessToken getRefreshToken ()! = null) { refreshToken = existingAccessToken.getRefreshToken(); // The token store could remove the refresh token when the // access token is removed, but we want to // be sure... tokenStore.removeRefreshToken(refreshToken); } tokenStore.removeAccessToken(existingAccessToken); Else {// re-store the access token in case the authentication has changed tokenStore.storeAccessToken(existingAccessToken, authentication); return existingAccessToken; } } // Only create a new refresh token if there wasn't an existing one // associated with an expired access token. // Clients might be holding existing refresh tokens, so we re-use it in // the case that the old access token // expired. if (refreshToken == null) { refreshToken = createRefreshToken(authentication); } // But the refresh token itself might need to be re-issued if it has // expired. else if (refreshToken instanceof ExpiringOAuth2RefreshToken) { ExpiringOAuth2RefreshToken expiring = (ExpiringOAuth2RefreshToken) refreshToken; if (System.currentTimeMillis() > expiring.getExpiration().getTime()) { refreshToken = createRefreshToken(authentication); }} // If the token exists, the token must be created again. Because the authorization mode is different OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken); tokenStore.storeAccessToken(accessToken, authentication); // In case it was modified refreshToken = accessToken.getRefreshToken(); if (refreshToken ! = null) { tokenStore.storeRefreshToken(refreshToken, authentication); } return accessToken;Copy the code

As a bonus

  • When the token is created, it will finally determine whether there is a TokenEnhancer(token enhancement), in which case it will perform the token enhancement
private OAuth2AccessToken createAccessToken(OAuth2Authentication authentication, OAuth2RefreshToken refreshToken) { DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(UUID.randomUUID().toString()); int validitySeconds = getAccessTokenValiditySeconds(authentication.getOAuth2Request()); if (validitySeconds > 0) { token.setExpiration(new Date(System.currentTimeMillis() + (validitySeconds * 1000L))); } token.setRefreshToken(refreshToken); token.setScope(authentication.getOAuth2Request().getScope()); return accessTokenEnhancer ! = null ? accessTokenEnhancer.enhance(token, authentication) : token; }Copy the code