mirror of
http://git.pig4cloud.com/pig/pigx.git
synced 2026-11-08 20:49:00 +08:00
✨ Introducing new features. #3612 增加客户端同时登录在线数量控制功能
This commit is contained in:
1 parent
5fdbdf4998
commit
1be210d78b
5 files changed
+75
-55
No files matched your search
+5
@@ -110,4 +110,9 @@ public interface CommonConstants {
|
||||
*/
|
||||
String ENC_FLAG = "enc_flag";
|
||||
|
||||
/**
|
||||
* 客户端允许同时在线数量
|
||||
*/
|
||||
String ONLINE_QUANTITY = "online_quantity";
|
||||
|
||||
}
|
||||
+11
@@ -95,6 +95,12 @@ public interface SecurityConstants {
|
||||
*/
|
||||
String GRANT_MOBILE = "mobile";
|
||||
|
||||
/**
|
||||
* QQ获取token
|
||||
*/
|
||||
String QQ_AUTHORIZATION_CODE_URL = "https://graph.qq.com/oauth2.0/token?grant_type="
|
||||
+ "authorization_code&code=%S&client_id=%s&redirect_uri=" + "%s&client_secret=%s";
|
||||
|
||||
/**
|
||||
* 微信获取OPENID
|
||||
*/
|
||||
@@ -118,6 +124,11 @@ public interface SecurityConstants {
|
||||
*/
|
||||
String OSC_AUTHORIZATION_CODE_URL = "https://www.oschina.net/action/openapi/token";
|
||||
|
||||
/**
|
||||
* QQ获取用户信息
|
||||
*/
|
||||
String QQ_USER_INFO_URL = "https://graph.qq.com/oauth2.0/me?access_token=%s";
|
||||
|
||||
/**
|
||||
* 码云获取用户信息
|
||||
*/
|
||||
|
||||
+53
-45
@@ -1,7 +1,8 @@
|
||||
package com.pig4cloud.pigx.common.security.service;
|
||||
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import com.pig4cloud.pigx.common.core.constant.SecurityConstants;
|
||||
import com.pig4cloud.pigx.common.core.constant.CommonConstants;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.core.Authentication;
|
||||
@@ -16,10 +17,7 @@ import org.springframework.security.web.authentication.preauth.PreAuthenticatedA
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 自定义 token 放发处理逻辑
|
||||
@@ -27,6 +25,7 @@ import java.util.UUID;
|
||||
* @author lengleng
|
||||
* @date 2021/10/15
|
||||
*/
|
||||
@Slf4j
|
||||
public class PigxCustomTokenServices implements AuthorizationServerTokenServices, ResourceServerTokenServices,
|
||||
ConsumerTokenServices, InitializingBean {
|
||||
|
||||
@@ -34,6 +33,8 @@ public class PigxCustomTokenServices implements AuthorizationServerTokenServices
|
||||
|
||||
private int accessTokenValiditySeconds = 60 * 60 * 12; // default 12 hours.
|
||||
|
||||
private int defaultOnlineQuantity = 0; // 客户端默认同时在线数量
|
||||
|
||||
private boolean supportRefreshToken = false;
|
||||
|
||||
private boolean reuseRefreshToken = true;
|
||||
@@ -58,50 +59,44 @@ public class PigxCustomTokenServices implements AuthorizationServerTokenServices
|
||||
public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {
|
||||
OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication);
|
||||
OAuth2RefreshToken refreshToken = null;
|
||||
|
||||
// 根据clientId 查询客户端信息,判断客户端是否是每次生成新的令牌 recreate
|
||||
String clientId = authentication.getOAuth2Request().getClientId();
|
||||
ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
|
||||
Map<String, Object> extMap = clientDetails.getAdditionalInformation();
|
||||
Boolean recreate = MapUtil.getBool(extMap, SecurityConstants.CLIENT_RECREATE, Boolean.TRUE);
|
||||
|
||||
// 若已产生token , 过期时删除相关token,执行下边的重新生成逻辑
|
||||
if (existingAccessToken != null && recreate) {
|
||||
tokenStore.removeAccessToken(existingAccessToken);
|
||||
if (existingAccessToken.getRefreshToken() != null) {
|
||||
refreshToken = existingAccessToken.getRefreshToken();
|
||||
tokenStore.removeRefreshToken(refreshToken);
|
||||
}
|
||||
}
|
||||
else 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);
|
||||
if (existingAccessToken != null) {
|
||||
int onlineQuantity = getOnlineQuantity(authentication.getOAuth2Request());
|
||||
// (0)默认情况下支持同时在线
|
||||
if (onlineQuantity == defaultOnlineQuantity) {
|
||||
if (existingAccessToken.isExpired()) {
|
||||
if (existingAccessToken.getRefreshToken() != null) {
|
||||
refreshToken = existingAccessToken.getRefreshToken();
|
||||
tokenStore.removeRefreshToken(refreshToken);
|
||||
}
|
||||
tokenStore.removeAccessToken(existingAccessToken);
|
||||
}
|
||||
else {
|
||||
// Re-store the access token in case the authentication has changed
|
||||
tokenStore.storeAccessToken(existingAccessToken, authentication);
|
||||
return existingAccessToken;
|
||||
}
|
||||
tokenStore.removeAccessToken(existingAccessToken);
|
||||
}
|
||||
else {
|
||||
// Re-store the access token in case the authentication has changed
|
||||
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);
|
||||
|
||||
// 增加允许同时在线数量逻辑
|
||||
Collection<OAuth2AccessToken> currentTokenList = tokenStore.findTokensByClientIdAndUserName(
|
||||
authentication.getOAuth2Request().getClientId(), authentication.getName());
|
||||
if (currentTokenList.size() >= getOnlineQuantity(authentication.getOAuth2Request())) {
|
||||
Optional<OAuth2AccessToken> oldestTokenOpt = currentTokenList.stream()
|
||||
.min(Comparator.comparing(OAuth2AccessToken::getExpiration));
|
||||
if (oldestTokenOpt.isPresent()) {
|
||||
OAuth2AccessToken oldestToken = oldestTokenOpt.get();
|
||||
log.info("online quantity reaches the upper limit");
|
||||
tokenStore.removeAccessToken(oldestToken);
|
||||
if (oldestToken.getRefreshToken() != null) {
|
||||
refreshToken = oldestToken.getRefreshToken();
|
||||
tokenStore.removeRefreshToken(refreshToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
refreshToken = createRefreshToken(authentication);
|
||||
OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);
|
||||
tokenStore.storeAccessToken(accessToken, authentication);
|
||||
|
||||
@@ -296,7 +291,7 @@ public class PigxCustomTokenServices implements AuthorizationServerTokenServices
|
||||
if (clientDetailsService != null) {
|
||||
ClientDetails client = clientDetailsService.loadClientByClientId(clientAuth.getClientId());
|
||||
Integer validity = client.getAccessTokenValiditySeconds();
|
||||
if (validity != null) {
|
||||
if (validity != null && validity > 0) {
|
||||
return validity;
|
||||
}
|
||||
}
|
||||
@@ -312,13 +307,26 @@ public class PigxCustomTokenServices implements AuthorizationServerTokenServices
|
||||
if (clientDetailsService != null) {
|
||||
ClientDetails client = clientDetailsService.loadClientByClientId(clientAuth.getClientId());
|
||||
Integer validity = client.getRefreshTokenValiditySeconds();
|
||||
if (validity != null) {
|
||||
if (validity != null && validity > 0) {
|
||||
return validity;
|
||||
}
|
||||
}
|
||||
return refreshTokenValiditySeconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* the client online quantity
|
||||
* @param clientAuth the current authorization request
|
||||
* @return the client online quantity
|
||||
*/
|
||||
protected int getOnlineQuantity(OAuth2Request clientAuth) {
|
||||
if (clientDetailsService != null) {
|
||||
ClientDetails client = clientDetailsService.loadClientByClientId(clientAuth.getClientId());
|
||||
return MapUtil.getInt(client.getAdditionalInformation(), CommonConstants.ONLINE_QUANTITY, -1);
|
||||
}
|
||||
return defaultOnlineQuantity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is a refresh token supported for this client (or the global setting if
|
||||
* {@link #setClientDetailsService(ClientDetailsService) clientDetailsService} is not
|
||||
|
||||
+2
-2
@@ -23,8 +23,8 @@ public class SysOauthClientDetailsDTO extends SysOauthClientDetails {
|
||||
private String encFlag;
|
||||
|
||||
/**
|
||||
* 令牌是否重新生成
|
||||
* 客户端允许同时在线数量
|
||||
*/
|
||||
private String recreateFlag;
|
||||
private String onlineQuantity;
|
||||
|
||||
}
|
||||
+4
-8
@@ -31,13 +31,11 @@ import com.pig4cloud.pigx.admin.mapper.SysOauthClientDetailsMapper;
|
||||
import com.pig4cloud.pigx.admin.service.SysOauthClientDetailsService;
|
||||
import com.pig4cloud.pigx.common.core.constant.CacheConstants;
|
||||
import com.pig4cloud.pigx.common.core.constant.CommonConstants;
|
||||
import com.pig4cloud.pigx.common.core.constant.SecurityConstants;
|
||||
import com.pig4cloud.pigx.common.core.util.R;
|
||||
import com.pig4cloud.pigx.common.core.util.SpringContextHolder;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@@ -90,8 +88,7 @@ public class SysOauthClientDetailsServiceImpl extends ServiceImpl<SysOauthClient
|
||||
JSONObject informationObj = JSONUtil.parseObj(information)
|
||||
.set(CommonConstants.CAPTCHA_FLAG, clientDetailsDTO.getCaptchaFlag())
|
||||
.set(CommonConstants.ENC_FLAG, clientDetailsDTO.getEncFlag())
|
||||
.set(SecurityConstants.CLIENT_RECREATE, clientDetailsDTO.getRecreateFlag());
|
||||
|
||||
.set(CommonConstants.ONLINE_QUANTITY, clientDetailsDTO.getOnlineQuantity());
|
||||
clientDetails.setAdditionalInformation(informationObj.toString());
|
||||
|
||||
// 更新数据库
|
||||
@@ -117,7 +114,7 @@ public class SysOauthClientDetailsServiceImpl extends ServiceImpl<SysOauthClient
|
||||
String information = clientDetailsDTO.getAdditionalInformation();
|
||||
JSONUtil.parseObj(information).set(CommonConstants.CAPTCHA_FLAG, clientDetailsDTO.getCaptchaFlag())
|
||||
.set(CommonConstants.ENC_FLAG, clientDetailsDTO.getEncFlag())
|
||||
.set(SecurityConstants.CLIENT_RECREATE, clientDetailsDTO.getRecreateFlag());
|
||||
.set(CommonConstants.ONLINE_QUANTITY, clientDetailsDTO.getOnlineQuantity());
|
||||
|
||||
// 插入数据
|
||||
this.baseMapper.insert(clientDetails);
|
||||
@@ -141,13 +138,12 @@ public class SysOauthClientDetailsServiceImpl extends ServiceImpl<SysOauthClient
|
||||
String information = details.getAdditionalInformation();
|
||||
String captchaFlag = JSONUtil.parseObj(information).getStr(CommonConstants.CAPTCHA_FLAG);
|
||||
String encFlag = JSONUtil.parseObj(information).getStr(CommonConstants.ENC_FLAG);
|
||||
String recreateFlag = JSONUtil.parseObj(information).getStr(SecurityConstants.CLIENT_RECREATE);
|
||||
|
||||
String onlineQuantity = JSONUtil.parseObj(information).getStr(CommonConstants.ONLINE_QUANTITY);
|
||||
SysOauthClientDetailsDTO dto = new SysOauthClientDetailsDTO();
|
||||
BeanUtils.copyProperties(details, dto);
|
||||
dto.setCaptchaFlag(captchaFlag);
|
||||
dto.setEncFlag(encFlag);
|
||||
dto.setRecreateFlag(recreateFlag);
|
||||
dto.setOnlineQuantity(onlineQuantity);
|
||||
return dto;
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
|
||||
Reference in new issue
Block a user