Enforcing Single-Device Login with Spring Security OAuth2

This article explains how to modify Spring Security OAuth2's token service to enforce a single-device login by customizing token creation and key generation logic, removing existing tokens for the same user and ignoring client identifiers, with code examples and deployment steps.

Java Architecture Diary
Java Architecture Diary
Java Architecture Diary
Enforcing Single-Device Login with Spring Security OAuth2

Background

A user account should be allowed to log in from only one location, a common requirement in back‑office systems. The default Spring Security OAuth2 token flow cannot satisfy this need.

First, consider the TokenEndpoint flow: the client calls /oauth/token, which eventually invokes TokenGranter. TokenGranter obtains authentication information based on the grant type and calls TokenServices to generate a token.

Rewrite TokenService

Override the token issuance logic in createAccessToken. When a token already exists for the user, delete it and create a new one, causing the previous token to become invalid and be displaced.

@Transactional
public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {
    OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication);
    OAuth2RefreshToken refreshToken = null;
    // If a token already exists for the user, remove the original token
    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

The above code achieves single‑device login: a user can log in on mobile and PC simultaneously, but two mobile sessions cannot be active at the same time.

How to achieve unique login across different client types?

First, look at the source code that checks whether a token exists for a user:

public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) {
    String key = authenticationKeyGenerator.extractKey(authentication);
    // Redis query logic based on the key
    return accessToken;
}

The default AuthenticationKeyGenerator builds the key from username, clientId, and scope parameters, producing a unique token per user‑client‑scope combination.

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

To enable single login across multiple terminals, make the token identical for the same user regardless of client. Remove the clientId condition from extractKey so that the generated key does not differentiate 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 the customized TokenService into the authorization server configuration.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaOAuth2spring-securitySingle Sign-OnToken Service
Java Architecture Diary
Written by

Java Architecture Diary

Committed to sharing original, high‑quality technical articles; no fluff or promotional content.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.