diff --git a/pigx-common/pigx-common-core/src/main/java/com/pig4cloud/pigx/common/core/constant/CommonConstants.java b/pigx-common/pigx-common-core/src/main/java/com/pig4cloud/pigx/common/core/constant/CommonConstants.java index 55d2aa8c2..7926ece5a 100644 --- a/pigx-common/pigx-common-core/src/main/java/com/pig4cloud/pigx/common/core/constant/CommonConstants.java +++ b/pigx-common/pigx-common-core/src/main/java/com/pig4cloud/pigx/common/core/constant/CommonConstants.java @@ -110,4 +110,9 @@ public interface CommonConstants { */ String ENC_FLAG = "enc_flag"; + /** + * 客户端允许同时在线数量 + */ + String ONLINE_QUANTITY = "online_quantity"; + } diff --git a/pigx-common/pigx-common-core/src/main/java/com/pig4cloud/pigx/common/core/constant/SecurityConstants.java b/pigx-common/pigx-common-core/src/main/java/com/pig4cloud/pigx/common/core/constant/SecurityConstants.java index 38f9dffe3..e7f4cda77 100644 --- a/pigx-common/pigx-common-core/src/main/java/com/pig4cloud/pigx/common/core/constant/SecurityConstants.java +++ b/pigx-common/pigx-common-core/src/main/java/com/pig4cloud/pigx/common/core/constant/SecurityConstants.java @@ -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"; + /** * 码云获取用户信息 */ diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigxCustomTokenServices.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigxCustomTokenServices.java index fe397e9c0..34134c4aa 100644 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigxCustomTokenServices.java +++ b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigxCustomTokenServices.java @@ -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 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 currentTokenList = tokenStore.findTokensByClientIdAndUserName( + authentication.getOAuth2Request().getClientId(), authentication.getName()); + if (currentTokenList.size() >= getOnlineQuantity(authentication.getOAuth2Request())) { + Optional 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 diff --git a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/dto/SysOauthClientDetailsDTO.java b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/dto/SysOauthClientDetailsDTO.java index c1dc2b99d..7638db5b9 100644 --- a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/dto/SysOauthClientDetailsDTO.java +++ b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/dto/SysOauthClientDetailsDTO.java @@ -23,8 +23,8 @@ public class SysOauthClientDetailsDTO extends SysOauthClientDetails { private String encFlag; /** - * 令牌是否重新生成 + * 客户端允许同时在线数量 */ - private String recreateFlag; + private String onlineQuantity; } diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysOauthClientDetailsServiceImpl.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysOauthClientDetailsServiceImpl.java index a05be3817..fca40e2c3 100644 --- a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysOauthClientDetailsServiceImpl.java +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysOauthClientDetailsServiceImpl.java @@ -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