在OAuth 2中模仿DefaultTokenServices写一个新的tokenServices来提供个性化服务
这样写有几个好处:
- 不需要使用拦截器来让设备异地登录失效,大大提升吞吐量
- 每次登录都刷新了access_token,并且加满了过期时间,不会出现过期时间到了要重新登录的问题。
以下是DefaultTokenServices的源代码
/*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
package org.springframework.security.oauth2.provider.token;
import java.util.Date;
import java.util.Set;
import java.util.UUID;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.common.DefaultExpiringOAuth2RefreshToken;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.DefaultOAuth2RefreshToken;
import org.springframework.security.oauth2.common.ExpiringOAuth2RefreshToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2RefreshToken;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.security.oauth2.common.exceptions.InvalidScopeException;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.ClientRegistrationException;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.TokenRequest;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
* Base implementation for token services using random UUID values for the access token and refresh token values. The
* main extension point for customizations is the {@link TokenEnhancer} which will be called after the access and
* refresh tokens have been generated but before they are stored.
* Persistence is delegated to a {@code TokenStore} implementation and customization of the access token to a
* {@link TokenEnhancer}.
* @author Ryan Heaton
* @author Luke Taylor
* @author Dave Syer
public class DefaultTokenServices implements AuthorizationServerTokenServices, ResourceServerTokenServices,
ConsumerTokenServices, InitializingBean {
private int refreshTokenValiditySeconds = 60 * 60 * 24 * 30; // default 30 days.
private int accessTokenValiditySeconds = 60 * 60 * 12; // default 12 hours.
private boolean supportRefreshToken = false;
private boolean reuseRefreshToken = true;
private TokenStore tokenStore;
private ClientDetailsService clientDetailsService;
private TokenEnhancer accessTokenEnhancer;
private AuthenticationManager authenticationManager;
* Initialize these token services. If no random generator is set, one will be created.
public void afterPropertiesSet() throws Exception {
Assert.notNull(tokenStore, "tokenStore must be set");
@Transactional
public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {
OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication);
OAuth2RefreshToken refreshToken = null;
if (existingAccessToken != null) {
if (existingAccessToken.isExpired()) {
if (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);
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;
@Transactional(noRollbackFor={InvalidTokenException.class, InvalidGrantException.class})
public OAuth2AccessToken refreshAccessToken(String refreshTokenValue, TokenRequest tokenRequest)
throws AuthenticationException {
if (!supportRefreshToken) {
throw new InvalidGrantException("Invalid refresh token: " + refreshTokenValue);
OAuth2RefreshToken refreshToken = tokenStore.readRefreshToken(refreshTokenValue);
if (refreshToken == null) {
throw new InvalidGrantException("Invalid refresh token: " + refreshTokenValue);
OAuth2Authentication authentication = tokenStore.readAuthenticationForRefreshToken(refreshToken);
if (this.authenticationManager != null && !authentication.isClientOnly()) {
// The client has already been authenticated, but the user authentication might be old now, so give it a
// chance to re-authenticate.
Authentication user = new PreAuthenticatedAuthenticationToken(authentication.getUserAuthentication(), "", authentication.getAuthorities());
user = authenticationManager.authenticate(user);
Object details = authentication.getDetails();
authentication = new OAuth2Authentication(authentication.getOAuth2Request(), user);
authentication.setDetails(details);
String clientId = authentication.getOAuth2Request().getClientId();
if (clientId == null || !clientId.equals(tokenRequest.getClientId())) {
throw new InvalidGrantException("Wrong client for this refresh token: " + refreshTokenValue);
// clear out any access tokens already associated with the refresh
// token.
tokenStore.removeAccessTokenUsingRefreshToken(refreshToken);
if (isExpired(refreshToken)) {
tokenStore.removeRefreshToken(refreshToken);
throw new InvalidTokenException("Invalid refresh token (expired): " + refreshToken);
authentication = createRefreshedAuthentication(authentication, tokenRequest);
if (!reuseRefreshToken) {
tokenStore.removeRefreshToken(refreshToken);
refreshToken = createRefreshToken(authentication);
OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);
tokenStore.storeAccessToken(accessToken, authentication);
if (!reuseRefreshToken) {
tokenStore.storeRefreshToken(accessToken.getRefreshToken(), authentication);
return accessToken;
public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) {
return tokenStore.getAccessToken(authentication);
* Create a refreshed authentication.
* @param authentication The authentication.
* @param request The scope for the refreshed token.
* @return The refreshed authentication.
* @throws InvalidScopeException If the scope requested is invalid or wider than the original scope.
private OAuth2Authentication createRefreshedAuthentication(OAuth2Authentication authentication, TokenRequest request) {
OAuth2Authentication narrowed = authentication;
Set<String> scope = request.getScope();
OAuth2Request clientAuth = authentication.getOAuth2Request().refresh(request);
if (scope != null && !scope.isEmpty()) {
Set<String> originalScope = clientAuth.getScope();
if (originalScope == null || !originalScope.containsAll(scope)) {
throw new InvalidScopeException("Unable to narrow the scope of the client authentication to " + scope
+ ".", originalScope);
else {
clientAuth = clientAuth.narrowScope(scope);
narrowed = new OAuth2Authentication(clientAuth, authentication.getUserAuthentication());
return narrowed;
protected boolean isExpired(OAuth2RefreshToken refreshToken) {
if (refreshToken instanceof ExpiringOAuth2RefreshToken) {
ExpiringOAuth2RefreshToken expiringToken = (ExpiringOAuth2RefreshToken) refreshToken;
return expiringToken.getExpiration() == null
|| System.currentTimeMillis() > expiringToken.getExpiration().getTime();
return false;
public OAuth2AccessToken readAccessToken(String accessToken) {
return tokenStore.readAccessToken(accessToken);
public OAuth2Authentication loadAuthentication(String accessTokenValue) throws AuthenticationException,
InvalidTokenException {
OAuth2AccessToken accessToken = tokenStore.readAccessToken(accessTokenValue);
if (accessToken == null) {
throw new InvalidTokenException("Invalid access token: " + accessTokenValue);
else if (accessToken.isExpired()) {
tokenStore.removeAccessToken(accessToken);
throw new InvalidTokenException("Access token expired: " + accessTokenValue);
OAuth2Authentication result = tokenStore.readAuthentication(accessToken);
if (result == null) {
// in case of race condition
throw new InvalidTokenException("Invalid access token: " + accessTokenValue);
if (clientDetailsService != null) {
String clientId = result.getOAuth2Request().getClientId();
try {
clientDetailsService.loadClientByClientId(clientId);
catch (ClientRegistrationException e) {
throw new InvalidTokenException("Client not valid: " + clientId, e);
return result;
public String getClientId(String tokenValue) {
OAuth2Authentication authentication = tokenStore.readAuthentication(tokenValue);
if (authentication == null) {
throw new InvalidTokenException("Invalid access token: " + tokenValue);
OAuth2Request clientAuth = authentication.getOAuth2Request();
if (clientAuth == null) {
throw new InvalidTokenException("Invalid access token (no client id): " + tokenValue);
return clientAuth.getClientId();
public boolean revokeToken(String tokenValue) {
OAuth2AccessToken accessToken = tokenStore.readAccessToken(tokenValue);
if (accessToken == null) {
return false;
if (accessToken.getRefreshToken() != null) {
tokenStore.removeRefreshToken(accessToken.getRefreshToken());
tokenStore.removeAccessToken(accessToken);
return true;
private OAuth2RefreshToken createRefreshToken(OAuth2Authentication authentication) {
if (!isSupportRefreshToken(authentication.getOAuth2Request())) {
return null;
int validitySeconds = getRefreshTokenValiditySeconds(authentication.getOAuth2Request());
String value = UUID.randomUUID().toString();
if (validitySeconds > 0) {
return new DefaultExpiringOAuth2RefreshToken(value, new Date(System.currentTimeMillis()
+ (validitySeconds * 1000L)));
return new DefaultOAuth2RefreshToken(value);
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;
* The access token validity period in seconds
* @param clientAuth the current authorization request
* @return the access token validity period in seconds
protected int getAccessTokenValiditySeconds(OAuth2Request clientAuth) {
if (clientDetailsService != null) {
ClientDetails client = clientDetailsService.loadClientByClientId(clientAuth.getClientId());
Integer validity = client.getAccessTokenValiditySeconds();
if (validity != null) {
return validity;
return accessTokenValiditySeconds;
* The refresh token validity period in seconds
* @param clientAuth the current authorization request
* @return the refresh token validity period in seconds
protected int getRefreshTokenValiditySeconds(OAuth2Request clientAuth) {
if (clientDetailsService != null) {
ClientDetails client = clientDetailsService.loadClientByClientId(clientAuth.getClientId());
Integer validity = client.getRefreshTokenValiditySeconds();
if (validity != null) {
return validity;
return refreshTokenValiditySeconds;
* Is a refresh token supported for this client (or the global setting if
* {@link #setClientDetailsService(ClientDetailsService) clientDetailsService} is not set.
* @param clientAuth the current authorization request
* @return boolean to indicate if refresh token is supported
protected boolean isSupportRefreshToken(OAuth2Request clientAuth) {
if (clientDetailsService != null) {
ClientDetails client = clientDetailsService.loadClientByClientId(clientAuth.getClientId());
return client.getAuthorizedGrantTypes().contains("refresh_token");
return this.supportRefreshToken;
* An access token enhancer that will be applied to a new token before it is saved in the token store.
* @param accessTokenEnhancer the access token enhancer to set
public void setTokenEnhancer(TokenEnhancer accessTokenEnhancer) {
this.accessTokenEnhancer = accessTokenEnhancer;
* The validity (in seconds) of the refresh token. If less than or equal to zero then the tokens will be
* non-expiring.
* @param refreshTokenValiditySeconds The validity (in seconds) of the refresh token.
public void setRefreshTokenValiditySeconds(int refreshTokenValiditySeconds) {
this.refreshTokenValiditySeconds = refreshTokenValiditySeconds;
* The default validity (in seconds) of the access token. Zero or negative for non-expiring tokens. If a client
* details service is set the validity period will be read from the client, defaulting to this value if not defined
* by the client.
* @param accessTokenValiditySeconds The validity (in seconds) of the access token.
public void setAccessTokenValiditySeconds(int accessTokenValiditySeconds) {
this.accessTokenValiditySeconds = accessTokenValiditySeconds;
* Whether to support the refresh token.
* @param supportRefreshToken Whether to support the refresh token.
public void setSupportRefreshToken(boolean supportRefreshToken) {
this.supportRefreshToken = supportRefreshToken;
* Whether to reuse refresh tokens (until expired).
* @param reuseRefreshToken Whether to reuse refresh tokens (until expired).
public void setReuseRefreshToken(boolean reuseRefreshToken) {
this.reuseRefreshToken = reuseRefreshToken;
* The persistence strategy for token storage.
* @param tokenStore the store for access and refresh tokens.
public void setTokenStore(TokenStore tokenStore) {
this.tokenStore = tokenStore;
* An authentication manager that will be used (if provided) to check the user authentication when a token is
* refreshed.
* @param authenticationManager the authenticationManager to set
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
* The client details service to use for looking up clients (if necessary). Optional if the access token expiry is
* set globally via {@link #setAccessTokenValiditySeconds(int)}.
* @param clientDetailsService the client details service
public void setClientDetailsService(ClientDetailsService clientDetailsService) {
this.clientDetailsService = clientDetailsService;
}
我们把这些代码考出来,起一个新的名字,比如叫SingleTokenServices
所有的代码保留,唯独要修改的是createAccessToken这个方法,我们不在判断redis中,该access_token是否还未过期而继续使用,而是直接删除,使用新的access_token.
@Transactional
public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {
OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication);
OAuth2RefreshToken refreshToken = null;
if (existingAccessToken != null) {
if (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);
//every time get new token
tokenStore.removeAccessToken(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);
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;
}
最后在AuthorizationServerConfig增加如下内容,其中endpoints.tokenServices(tokenServices(endpoints));就是把我们新写的SingleTokenServices给配置进来。
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenServices(tokenServices(endpoints));
endpoints.authenticationManager(this.authenticationManager);
endpoints.tokenStore(tokenStore());
// 授权码模式下,code存储
// endpoints.authorizationCodeServices(new JdbcAuthorizationCodeServices(dataSource));
endpoints.authorizationCodeServices(redisAuthorizationCodeServices);
if (storeWithJwt) {
endpoints.accessTokenConverter(accessTokenConverter());
private SingleTokenServices tokenServices(AuthorizationServerEndpointsConfigurer endpoints) {
SingleTokenServices tokenServices = new SingleTokenServices();
tokenServices.setTokenStore(tokenStore());
tokenServices.setSupportRefreshToken(true);//支持刷新token
tokenServices.setReuseRefreshToken(true);
tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
addUserDetailsService(tokenServices, this.userDetailsService);
return tokenServices;
private void addUserDetailsService(SingleTokenServices tokenServices, UserDetailsService userDetailsService) {
if (userDetailsService != null) {