package com.iailab.module.system.service.oauth2; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import com.iailab.framework.common.enums.UserTypeEnum; import com.iailab.framework.common.exception.enums.GlobalErrorCodeConstants; import com.iailab.framework.common.pojo.PageResult; import com.iailab.framework.common.util.date.DateUtils; import com.iailab.framework.security.core.LoginUser; import com.iailab.framework.tenant.core.context.TenantContextHolder; import com.iailab.module.system.controller.admin.oauth2.vo.token.OAuth2AccessTokenPageReqVO; import com.iailab.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; import com.iailab.module.system.dal.dataobject.oauth2.OAuth2ClientDO; import com.iailab.module.system.dal.dataobject.oauth2.OAuth2RefreshTokenDO; import com.iailab.module.system.dal.dataobject.user.AdminUserDO; import com.iailab.module.system.dal.mysql.oauth2.OAuth2AccessTokenMapper; import com.iailab.module.system.dal.mysql.oauth2.OAuth2RefreshTokenMapper; import com.iailab.module.system.dal.redis.oauth2.OAuth2AccessTokenRedisDAO; import com.iailab.module.system.service.user.AdminUserService; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.time.LocalDateTime; import java.util.Collections; import java.util.List; import java.util.Map; import static com.iailab.framework.common.exception.util.ServiceExceptionUtil.exception0; import static com.iailab.framework.common.util.collection.CollectionUtils.convertSet; /** * OAuth2.0 Token Service 实现类 * * @author iailab */ @Service public class OAuth2TokenServiceImpl implements OAuth2TokenService { @Resource private OAuth2AccessTokenMapper oauth2AccessTokenMapper; @Resource private OAuth2RefreshTokenMapper oauth2RefreshTokenMapper; @Resource private OAuth2AccessTokenRedisDAO oauth2AccessTokenRedisDAO; @Resource private OAuth2ClientService oauth2ClientService; @Resource @Lazy // æ‡’åŠ è½½ï¼Œé¿å…循环ä¾èµ– private AdminUserService adminUserService; @Override @Transactional public OAuth2AccessTokenDO createAccessToken(Long userId, Integer userType, String clientId, List<String> scopes) { OAuth2ClientDO clientDO = oauth2ClientService.validOAuthClientFromCache(clientId); // 创建刷新令牌 OAuth2RefreshTokenDO refreshTokenDO = createOAuth2RefreshToken(userId, userType, clientDO, scopes); // 创建访问令牌 return createOAuth2AccessToken(refreshTokenDO, clientDO); } @Override public OAuth2AccessTokenDO refreshAccessToken(String refreshToken, String clientId) { // 查询访问令牌 OAuth2RefreshTokenDO refreshTokenDO = oauth2RefreshTokenMapper.selectByRefreshToken(refreshToken); if (refreshTokenDO == null) { throw exception0(GlobalErrorCodeConstants.BAD_REQUEST.getCode(), "æ— æ•ˆçš„åˆ·æ–°ä»¤ç‰Œ"); } // æ ¡éªŒ Client åŒ¹é… OAuth2ClientDO clientDO = oauth2ClientService.validOAuthClientFromCache(clientId); if (ObjectUtil.notEqual(clientId, refreshTokenDO.getClientId())) { throw exception0(GlobalErrorCodeConstants.BAD_REQUEST.getCode(), "刷新令牌的客户端编å·ä¸æ£ç¡®"); } // 移除相关的访问令牌 List<OAuth2AccessTokenDO> accessTokenDOs = oauth2AccessTokenMapper.selectListByRefreshToken(refreshToken); if (CollUtil.isNotEmpty(accessTokenDOs)) { oauth2AccessTokenMapper.deleteBatchIds(convertSet(accessTokenDOs, OAuth2AccessTokenDO::getId)); oauth2AccessTokenRedisDAO.deleteList(convertSet(accessTokenDOs, OAuth2AccessTokenDO::getAccessToken)); } // å·²è¿‡æœŸçš„æƒ…å†µä¸‹ï¼Œåˆ é™¤åˆ·æ–°ä»¤ç‰Œ if (DateUtils.isExpired(refreshTokenDO.getExpiresTime())) { oauth2RefreshTokenMapper.deleteById(refreshTokenDO.getId()); throw exception0(GlobalErrorCodeConstants.UNAUTHORIZED.getCode(), "刷新令牌已过期"); } // 创建访问令牌 return createOAuth2AccessToken(refreshTokenDO, clientDO); } @Override public OAuth2AccessTokenDO getAccessToken(String accessToken) { // 优先从 Redis ä¸èŽ·å– OAuth2AccessTokenDO accessTokenDO = oauth2AccessTokenRedisDAO.get(accessToken); if (accessTokenDO != null) { return accessTokenDO; } // 获å–ä¸åˆ°ï¼Œä»Ž MySQL ä¸èŽ·å– accessTokenDO = oauth2AccessTokenMapper.selectByAccessToken(accessToken); // 如果在 MySQL å˜åœ¨ï¼Œåˆ™å¾€ Redis ä¸å†™å…¥ if (accessTokenDO != null && !DateUtils.isExpired(accessTokenDO.getExpiresTime())) { oauth2AccessTokenRedisDAO.set(accessTokenDO); } return accessTokenDO; } @Override public OAuth2AccessTokenDO checkAccessToken(String accessToken) { OAuth2AccessTokenDO accessTokenDO = getAccessToken(accessToken); if (accessTokenDO == null) { throw exception0(GlobalErrorCodeConstants.UNAUTHORIZED.getCode(), "访问令牌ä¸å˜åœ¨"); } if (DateUtils.isExpired(accessTokenDO.getExpiresTime())) { throw exception0(GlobalErrorCodeConstants.UNAUTHORIZED.getCode(), "访问令牌已过期"); } return accessTokenDO; } @Override public OAuth2AccessTokenDO removeAccessToken(String accessToken) { // åˆ é™¤è®¿é—®ä»¤ç‰Œ OAuth2AccessTokenDO accessTokenDO = oauth2AccessTokenMapper.selectByAccessToken(accessToken); if (accessTokenDO == null) { return null; } oauth2AccessTokenMapper.deleteById(accessTokenDO.getId()); oauth2AccessTokenRedisDAO.delete(accessToken); // åˆ é™¤åˆ·æ–°ä»¤ç‰Œ oauth2RefreshTokenMapper.deleteByRefreshToken(accessTokenDO.getRefreshToken()); return accessTokenDO; } @Override public PageResult<OAuth2AccessTokenDO> getAccessTokenPage(OAuth2AccessTokenPageReqVO reqVO) { return oauth2AccessTokenMapper.selectPage(reqVO); } private OAuth2AccessTokenDO createOAuth2AccessToken(OAuth2RefreshTokenDO refreshTokenDO, OAuth2ClientDO clientDO) { OAuth2AccessTokenDO accessTokenDO = new OAuth2AccessTokenDO().setAccessToken(generateAccessToken()) .setUserId(refreshTokenDO.getUserId()).setUserType(refreshTokenDO.getUserType()) .setUserInfo(buildUserInfo(refreshTokenDO.getUserId(), refreshTokenDO.getUserType())) .setClientId(clientDO.getClientId()).setScopes(refreshTokenDO.getScopes()) .setRefreshToken(refreshTokenDO.getRefreshToken()) .setExpiresTime(LocalDateTime.now().plusSeconds(clientDO.getAccessTokenValiditySeconds())); accessTokenDO.setTenantId(TenantContextHolder.getTenantId()); // 手动设置租户编å·ï¼Œé¿å…缓å˜åˆ° Redis çš„æ—¶å€™ï¼Œæ— å¯¹åº”çš„ç§Ÿæˆ·ç¼–å· oauth2AccessTokenMapper.insert(accessTokenDO); // 记录到 Redis ä¸ oauth2AccessTokenRedisDAO.set(accessTokenDO); return accessTokenDO; } private OAuth2RefreshTokenDO createOAuth2RefreshToken(Long userId, Integer userType, OAuth2ClientDO clientDO, List<String> scopes) { OAuth2RefreshTokenDO refreshToken = new OAuth2RefreshTokenDO().setRefreshToken(generateRefreshToken()) .setUserId(userId).setUserType(userType) .setClientId(clientDO.getClientId()).setScopes(scopes) .setExpiresTime(LocalDateTime.now().plusSeconds(clientDO.getRefreshTokenValiditySeconds())); oauth2RefreshTokenMapper.insert(refreshToken); return refreshToken; } /** * åŠ è½½ç”¨æˆ·ä¿¡æ¯ï¼Œæ–¹ä¾¿ {@link com.iailab.framework.security.core.LoginUser} 获å–到昵称ã€éƒ¨é—¨ç‰ä¿¡æ¯ * * @param userId ç”¨æˆ·ç¼–å· * @param userType 用户类型 * @return ç”¨æˆ·ä¿¡æ¯ */ private Map<String, String> buildUserInfo(Long userId, Integer userType) { if (userType.equals(UserTypeEnum.ADMIN.getValue())) { AdminUserDO user = adminUserService.getUser(userId); return MapUtil.builder(LoginUser.INFO_KEY_NICKNAME, user.getNickname()) .put(LoginUser.INFO_KEY_DEPT_ID, StrUtil.toStringOrNull(user.getDeptId())).build(); } else if (userType.equals(UserTypeEnum.MEMBER.getValue())) { // 注æ„ï¼šç›®å‰ Member æš‚æ—¶ä¸è¯»å–,å¯ä»¥æŒ‰éœ€å®žçŽ° return Collections.emptyMap(); } return null; } private static String generateAccessToken() { return IdUtil.fastSimpleUUID(); } private static String generateRefreshToken() { return IdUtil.fastSimpleUUID(); } }