mirror of
http://git.pig4cloud.com/pig/pigx.git
synced 2026-11-10 21:19:00 +08:00
重写DefaultTokenServices 实现单设备登陆
This commit is contained in:
1 parent
a380a996b6
commit
7787126f42
2 files changed
+416
-1
No files matched your search
+19
-1
@@ -21,13 +21,16 @@ package com.pig4cloud.pigx.auth.config;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.pig4cloud.pigx.auth.service.PigxDefaultTokenServices;
|
||||
import com.pig4cloud.pigx.auth.service.PigxRedisTokenStore;
|
||||
import com.pig4cloud.pigx.common.core.constant.SecurityConstants;
|
||||
import com.pig4cloud.pigx.common.data.tenant.TenantContextHolder;
|
||||
import com.pig4cloud.pigx.common.security.component.PigxCommenceAuthExceptionEntryPoint;
|
||||
import com.pig4cloud.pigx.common.security.component.PigxWebResponseExceptionTranslator;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
@@ -40,6 +43,7 @@ import org.springframework.security.oauth2.config.annotation.web.configurers.Aut
|
||||
import org.springframework.security.oauth2.provider.ClientDetailsService;
|
||||
import org.springframework.security.oauth2.provider.OAuth2Authentication;
|
||||
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
|
||||
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
|
||||
import org.springframework.security.oauth2.provider.token.DefaultAuthenticationKeyGenerator;
|
||||
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
|
||||
|
||||
@@ -48,7 +52,7 @@ import org.springframework.security.oauth2.provider.token.TokenEnhancer;
|
||||
* @date 2018/6/22 认证服务器配置
|
||||
*/
|
||||
@Configuration
|
||||
@AllArgsConstructor
|
||||
@RequiredArgsConstructor
|
||||
@EnableAuthorizationServer
|
||||
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
|
||||
|
||||
@@ -66,6 +70,9 @@ public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdap
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
@Value("${pigx.one.login.enable:true}")
|
||||
private Boolean oneLogin;
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void configure(ClientDetailsServiceConfigurer clients) {
|
||||
@@ -95,8 +102,19 @@ public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdap
|
||||
endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST).tokenStore(redisTokenStore)
|
||||
.tokenEnhancer(pigxTokenEnhancer).userDetailsService(pigxUserDetailsService)
|
||||
.authorizationCodeServices(authorizationCodeServices).authenticationManager(authenticationManagerBean)
|
||||
.tokenServices(pigxDefaultTokenServices())
|
||||
.reuseRefreshTokens(false).pathMapping("/oauth/confirm_access", "/token/confirm_access")
|
||||
.exceptionTranslator(new PigxWebResponseExceptionTranslator());
|
||||
}
|
||||
|
||||
public AuthorizationServerTokenServices pigxDefaultTokenServices() {
|
||||
PigxDefaultTokenServices pigxDefaultTokenServices = new PigxDefaultTokenServices();
|
||||
pigxDefaultTokenServices.setTokenEnhancer(pigxTokenEnhancer);
|
||||
pigxDefaultTokenServices.setTokenStore(redisTokenStore);
|
||||
pigxDefaultTokenServices.setAuthenticationManager(authenticationManagerBean);
|
||||
pigxDefaultTokenServices.setClientDetailsService(pigxClientDetailsServiceImpl);
|
||||
pigxDefaultTokenServices.setOneLogin(oneLogin);
|
||||
return pigxDefaultTokenServices;
|
||||
}
|
||||
|
||||
}
|
||||
+397
@@ -0,0 +1,397 @@
|
||||
package com.pig4cloud.pigx.auth.service;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import lombok.Setter;
|
||||
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.*;
|
||||
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.*;
|
||||
import org.springframework.security.oauth2.provider.token.*;
|
||||
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @program: pigx
|
||||
* @description: 实现 一个设备登陆
|
||||
* @author: Sinda
|
||||
* @create: 2020/9/29 4:53 下午
|
||||
*/
|
||||
public class PigxDefaultTokenServices 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;
|
||||
|
||||
@Setter
|
||||
private Boolean oneLogin;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
OAuth2RefreshToken refreshToken = null;
|
||||
OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication);
|
||||
|
||||
if (ObjectUtil.isNotEmpty(existingAccessToken)) {
|
||||
refreshToken = existingAccessToken.getRefreshToken();
|
||||
|
||||
if (oneLogin) {
|
||||
// 单设备登陆
|
||||
tokenStore.removeAccessToken(existingAccessToken);
|
||||
tokenStore.removeRefreshToken(refreshToken);
|
||||
} else {
|
||||
if (existingAccessToken.isExpired()) {
|
||||
tokenStore.removeAccessToken(existingAccessToken);
|
||||
if (existingAccessToken.getRefreshToken() != null) {
|
||||
tokenStore.removeRefreshToken(refreshToken);
|
||||
}
|
||||
}
|
||||
tokenStore.storeAccessToken(existingAccessToken, authentication);
|
||||
return existingAccessToken;
|
||||
}
|
||||
}
|
||||
|
||||
if (refreshToken == null) {
|
||||
refreshToken = createRefreshToken(authentication);
|
||||
} 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;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user