✨ Introducing new features. #3612 增加客户端同时登录在线数量控制功能

This commit is contained in:
qraddx committed 2022-02-18 10:24:53 +08:00
1 parent 5fdbdf4998
commit 1be210d78b
5 files changed
+75 -55

No files matched your search

@@ -110,4 +110,9 @@ public interface CommonConstants {
*/
String ENC_FLAG = "enc_flag";
/**
* 客户端允许同时在线数量
*/
String ONLINE_QUANTITY = "online_quantity";
}
@@ -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";
/**
* 码云获取用户信息
*/
@@ -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
@@ -23,8 +23,8 @@ public class SysOauthClientDetailsDTO extends SysOauthClientDetails {
private String encFlag;
/**
* 令牌是否重新生成
* 客户端允许同时在线数量
*/
private String recreateFlag;
private String onlineQuantity;
}
@@ -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());