How to implement login and mutual kick in spring security oauth2

Posted by cliffboss on Wed, 27 May 2020 10:37:55 +0200

Background

An account can only be logged in at one place. Similar business requirements are very common in existing post management systems. However, in the original spring security oauth2 token method process (so-called login) can not meet similar needs.

Let's first look at TokenEndpoint's method flow

The client accesses the / oauth/token interface with parameters, and finally calls tokengrater

TokenGranter obtains user authentication information and calls TokenServices to generate tokens according to different authorization types

Re TokenService

  • Rewrite the issuance logic createAccessToken. When the user managed token exists, it will be deleted and recreated. This will cause the token obtained by previous login to be invalid, and it will be eliminated as a matter of course.

    @Transactional
    public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {

        OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication);
        OAuth2RefreshToken refreshToken = null;
        // Rewrite here to delete the original token when the token associated with the user exists
        if (existingAccessToken != null) {
            tokenStore.removeAccessToken(existingAccessToken);
        }
        else if (refreshToken instanceof ExpiringOAuth2RefreshToken) {
            ExpiringOAuth2RefreshToken expiring = (ExpiringOAuth2RefreshToken) refreshToken;
            if (System.currentTimeMillis() > expiring.getExpiration().getTime()) {
                refreshToken = createRefreshToken(authentication);
            }
        }

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

Rewrite Token key generation logic

  • As shown in the above code, we can realize the unique login of a single user terminal. What is a single terminal? We can compare QQ login to mobile and PC login at the same time, but mobile and mobile terminals cannot be online at the same time.
  • How to achieve unique login in different clients?

First, let's look at the above source code ` OAuth2AccessToken existingAccessToken=tokenStore.getAccessToken(authentication);
`
How to judge the existence of token based on user information?

public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) {
        String key = authenticationKeyGenerator.extractKey(authentication);
          // redis query logic according to key
        return accessToken;
    
}
  • The authenticationkeygenerator key value generator generates a unique token by default based on the combination of the username/clientId/scope parameter
public String extractKey(OAuth2Authentication authentication) {
    Map<String, String> values = new LinkedHashMap<String, String>();
    OAuth2Request authorizationRequest = authentication.getOAuth2Request();
    if (!authentication.isClientOnly()) {
        values.put(USERNAME, authentication.getName());
    }
    values.put(CLIENT_ID, authorizationRequest.getClientId());
    if (authorizationRequest.getScope() != null) {
        values.put(SCOPE, OAuth2Utils.formatParameterList(new TreeSet<String>(authorizationRequest.getScope())));
    }
    return generateKey(values);
}
  • If you want to realize the unique login of multiple terminals, you only need to make the token generated by the same user in multiple terminals consistent. In addition to the above-mentioned createToken modification logic, you can remove the clientId condition of extractKey and do not distinguish between terminals
public String extractKey(OAuth2Authentication authentication) {
    Map<String, String> values = new LinkedHashMap<String, String>();
    OAuth2Request authorizationRequest = authentication.getOAuth2Request();
    if (!authentication.isClientOnly()) {
        values.put(USERNAME, authentication.getName());
    }
    if (authorizationRequest.getScope() != null) {
        values.put(SCOPE, OAuth2Utils.formatParameterList(new TreeSet<String>(authorizationRequest.getScope())));
    }
    return generateKey(values);
}
  • Finally, inject a new TokenService into authserver

Topics: Web Server Mobile Spring Redis