1 Spring Security + OAuth establishes the authorization system

There is a lot of fragmented knowledge online. In reality, Spring Security and OAuth are often used together. They exist in the same system and play different roles.

The role of OAuth is to authorize. Security provides the basis for authorization.

It’s like you need to go into a strict neighborhood right now. The property of the community is OAuth, and Security provides a list of all users in the community who meet the requirements and can enter the community. Property management will check the user’s access card at the entrance of the community. If the name on the access card matches the list, the user is allowed to enter the unit marked on the access card. There will also be people whose access cards have more units, who can go not only to unit 2, but also to unit 3. This access card is accessToken issued by Oauth. Its function is to mark the user’s identity (whether can enter the community) and mark the user’s authority (after entering the community can go to the community where).

Back to the business, our application is the cell, the interface is the cell, and the visiting user is the HTTP request.


2 Start by requesting OAuth

Take Oauth of wechat Ecology for example. After users log in to wechat ecology and obtain user identification information through silent authorization or webpage authorization, they often need to establish binding relationship with basic users of the business system. Consider the package user’s unionid as an indication, which should theoretically not be exposed. So you need encryption. This is a demonstration of the whole link process of oauth request in wechat ecosystem from request initiation to security identification and authority to final return of accessToken.

The curl request:

curl --location --request POST 'http://xxx.com/api/auth/oauth/token?unionId=QHhpYW96YW8%3D&grant_type=wechat&scope=wechat --header 'Authorization: Basic eGlhb3phb193ZWNoYXQ******2VjcmV0'
Copy the code

3 TokenEndPoint Look at the overall process

This request will be sent to TokenEndPoint (org. Springframework. Security. Oauth2. The provider. The endpoint)

Only the core code is retained as follows. It can also be seen that OAuth guides the entire authorization process and plays a good image of an honest and honest community security guard

Public class TokenEndpoint extends AbstractEndpoint {public class TokenEndpoint extends AbstractEndpoint { @RequestMapping(value ="/oauth/token", method=RequestMethod.POST) public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, @RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException { // 1. String clientId = getClientId(Principal); String clientId = getClientId(Principal); ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId); TokenRequest TokenRequest = TokenRequest = TokenRequest = TokenRequest = TokenRequest = TokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient); // 3. Verify client informationif(clientId ! = null && ! clientId.equals("")) {
            if(! Clientid. equals(tokenRequest.getClientid ())) { Throw new InvalidClientException("Given client ID does not match authenticated client"); }} // 4. Set TokenRequest scope according to grantType. // There are password mode, authorization_code mode, refresh_token mode, client_credentials mode, and implicit modeif(! StringUtils.hasText(tokenRequest.getGrantType())) { throw new InvalidRequestException("Missing grant type");
        }
        if (tokenRequest.getGrantType().equals("implicit")) {
            throw new InvalidGrantException("Implicit grant type not supported from token endpoint"); } // In authorization mode, empty scope. Because the authorization request process determines the scope, there is no need to passif (isAuthCodeRequest(parameters)) {
            if(! tokenRequest.getScope().isEmpty()) { logger.debug("Clearing scope of incoming token request"); tokenRequest.setScope(Collections.<String> emptySet()); }} // If the Token mode is refreshed, parse and set scopeif(isRefreshTokenRequest(parameters)) { tokenRequest.setScope(OAuth2Utils.parseParameterList(parameters.get(OAuth2Utils.SCOPE))); } / / 5. Through the token authorizes access token OAuth2AccessToken token = getTokenGranter () grant (tokenRequest. GetGrantType (), tokenRequest);if (token == null) {
            throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType());
        }

        return getResponse(token);
    }

Copy the code

The most important logic that we need to fill in is the following statement. We need to implement an accessToken granter.

OAuth2AccessToken token = this.getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
Copy the code

4 composite TokenGranter

TokenGranter has different types of various, such as AuthorizationCodeTokenGranter authorization code mode, different client ClientCredentialsTokenGranter mode and so on in detail. They can exist in combinations.

public static TokenGranter getTokenGranter(final AuthenticationManager authenticationManager, final AuthorizationServerEndpointsConfigurer endpoints, RedisUtil redisUtil, LdapTemplate ldapTemplate, SmsTemplate SmsTemplate) {// Default tokenGranter set List< tokenGranter > granters = new ArrayList<>(Collections.singletonList(endpoints.getTokenGranter())); / / add captcha model granters. Add (new CaptchaTokenGranter (the authenticationManager, endpoints. GetTokenServices (), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory(), redisUtil)); / / phone verification code mode granters. Add (new MobileTokenGranter (the authenticationManager, endpoints. GetTokenServices (), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory(), smsTemplate)); / / Ldap authentication model granters. Add (new LdapTokenGranter (the authenticationManager, endpoints. GetTokenServices (), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory(), ldapTemplate)); / / wechat authentication model granters. Add (new WechatTokenGranter (the authenticationManager, endpoints. GetTokenServices (), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory(), smsTemplate)); // Combine tokenGranter setreturn new CompositeTokenGranter(granters);
    }
Copy the code

Implement a concrete Granter: WxTokenGranter

In the case of wxToken, only the core code remains:

/** * WxTokenGranter ** @author Yanghaolei * @date 2020/03/31 PM 17:41 */ public class WechatTokenGranter extends AbstractTokenGranter { private final AuthenticationManager authenticationManager; @Override protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) { HttpServletRequest request = WebUtil.getRequest(); // 1 synthesize userName [Spring Security] String unionId = Request.getParameter (tokenUtil.wechat_header_unionID); String userName = String.format(USERNAME_FORMAT, unionId, mobile); UserDetail [Spring Security] Map<String, String> parameters = new LinkedHashMap<String, String>(tokenRequest.getRequestParameters()); / / 3 build Authentication object Authentication Authentication userAuth = new UsernamePasswordAuthenticationToken (userName, TokenUtil.DEFAULT_PASSWROD); ((AbstractAuthenticationToken) userAuth).setDetails(parameters); try { userAuth = authenticationManager.authenticate(userAuth); } catch (AccountStatusException | BadCredentialsException ase) { throw new BusinessException(AuthErrorCode.AUTH_CHECK_ERROR); }if(userAuth == null || ! userAuth.isAuthenticated()) { throw new BusinessException(AuthErrorCode.AUTH_CHECK_ERROR); } OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);returnnew OAuth2Authentication(storedOAuth2Request, userAuth); }}Copy the code

There is also one of the most important lines of code, and this is where Spring Security is embedded.

            userAuth = authenticationManager.authenticate(userAuth);
Copy the code

Earlier we talked about security keeping the identity of the users who can enter the cell and the permissions of where those users can go once they enter the cell. We request the identity information brought by oauth (encrypted unionId). Here, we wrap it into the form of security cognition (userDetails), and then compare it with the list saved by security (many userDetails) to confirm the composition of the visitor. Finally get userDetail object to issue access control card (construct accessToken). It’s the last step in the process.


6 AuthenticationManager The manager has issued the access card

Let’s just keep the core logic for now and look at the logic of the Manager:

public Authentication authenticate(Authentication authentication) throws AuthenticationException { // 1 : before construction was obtained from the authentication userName String userName = authentication. GetPrincipal () = = null?"NONE_PROVIDED": authentication.getName(); // 2: Check whether the same user exists in the cache as username Boolean cacheWasUsed =true; UserDetails user = this.userCache.getUserFromCache(username); / / 3: RetrieveUser [Spring Security implementation part loadUserByUserName method] user = this.retrieveuser (username, (UsernamePasswordAuthenticationToken) // 4: Issued token enclosing createSuccessAuthentication (principalToReturn, authentication, user); }Copy the code

The third step is where security is implemented. Configure the user-defined user userDetail object to cooperate with the database to realize user storage. The core is to implement the loadUserByUsername() method in the UserDetailsService interface. This method returns accessToken to construct the required user.

Finally, the fourth step constructs with userDetail and returns accessToken information.

If you’re interested in SpringSecurity, check out my next article about SpringSecurity

{
    "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.."."token_type": "bearer"."refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cC.."."expires_in": 35999,
    "user_id": "15"."role_id": "3",}Copy the code