mirror of
				https://gitee.com/hhyykk/ipms-sjy.git
				synced 2025-11-04 04:08:43 +08:00 
			
		
		
		
	增加 refresh token 接口,并接入到前端项目
This commit is contained in:
		@@ -6,7 +6,6 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 | 
			
		||||
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
 | 
			
		||||
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
 | 
			
		||||
import cn.iocoder.yudao.framework.security.config.SecurityProperties;
 | 
			
		||||
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
 | 
			
		||||
import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.*;
 | 
			
		||||
import cn.iocoder.yudao.module.system.convert.auth.AuthConvert;
 | 
			
		||||
import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
 | 
			
		||||
@@ -34,6 +33,7 @@ import java.util.Set;
 | 
			
		||||
 | 
			
		||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
 | 
			
		||||
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
 | 
			
		||||
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.obtainAuthorization;
 | 
			
		||||
import static java.util.Collections.singleton;
 | 
			
		||||
 | 
			
		||||
@Api(tags = "管理后台 - 认证")
 | 
			
		||||
@@ -68,13 +68,20 @@ public class AuthController {
 | 
			
		||||
    @ApiOperation("登出系统")
 | 
			
		||||
    @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
 | 
			
		||||
    public CommonResult<Boolean> logout(HttpServletRequest request) {
 | 
			
		||||
        String token = SecurityFrameworkUtils.obtainAuthorization(request, securityProperties.getTokenHeader());
 | 
			
		||||
        String token = obtainAuthorization(request, securityProperties.getTokenHeader());
 | 
			
		||||
        if (StrUtil.isNotBlank(token)) {
 | 
			
		||||
            authService.logout(token);
 | 
			
		||||
        }
 | 
			
		||||
        return success(true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @PostMapping("/refresh-token")
 | 
			
		||||
    @ApiOperation("刷新令牌")
 | 
			
		||||
    @OperateLog(enable = false) // 避免 Post 请求被记录操作日志 TODO 接口文档
 | 
			
		||||
    public CommonResult<AuthLoginRespVO> refreshToken(@RequestParam("refreshToken") String refreshToken) {
 | 
			
		||||
        return success(authService.refreshToken(refreshToken));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @GetMapping("/get-permission-info")
 | 
			
		||||
    @ApiOperation("获取登录用户的权限信息")
 | 
			
		||||
    public CommonResult<AuthPermissionInfoRespVO> getPermissionInfo() {
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,8 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 | 
			
		||||
import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO;
 | 
			
		||||
import org.apache.ibatis.annotations.Mapper;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
@Mapper
 | 
			
		||||
public interface OAuth2AccessTokenMapper extends BaseMapperX<OAuth2AccessTokenDO> {
 | 
			
		||||
 | 
			
		||||
@@ -11,22 +13,8 @@ public interface OAuth2AccessTokenMapper extends BaseMapperX<OAuth2AccessTokenDO
 | 
			
		||||
        return selectOne(OAuth2AccessTokenDO::getAccessToken, accessToken);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
//    default OAuth2AccessTokenDO selectByUserIdAndUserType(Integer userId, Integer userType) {
 | 
			
		||||
//        return selectOne(new QueryWrapper<OAuth2AccessTokenDO>()
 | 
			
		||||
//                .eq("user_id", userId).eq("user_type", userType));
 | 
			
		||||
//    }
 | 
			
		||||
//
 | 
			
		||||
//    default int deleteByUserIdAndUserType(Integer userId, Integer userType) {
 | 
			
		||||
//        return delete(new QueryWrapper<OAuth2AccessTokenDO>()
 | 
			
		||||
//                .eq("user_id", userId).eq("user_type", userType));
 | 
			
		||||
//    }
 | 
			
		||||
//
 | 
			
		||||
//    default int deleteByRefreshToken(String refreshToken) {
 | 
			
		||||
//        return delete(new QueryWrapper<OAuth2AccessTokenDO>().eq("refresh_token", refreshToken));
 | 
			
		||||
//    }
 | 
			
		||||
//
 | 
			
		||||
//    default List<OAuth2AccessTokenDO> selectListByRefreshToken(String refreshToken) {
 | 
			
		||||
//        return selectList(new QueryWrapper<OAuth2AccessTokenDO>().eq("refresh_token", refreshToken));
 | 
			
		||||
//    }
 | 
			
		||||
    default List<OAuth2AccessTokenDO> selectListByRefreshToken(String refreshToken) {
 | 
			
		||||
        return selectList(OAuth2AccessTokenDO::getRefreshToken, refreshToken);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,16 +1,20 @@
 | 
			
		||||
package cn.iocoder.yudao.module.system.dal.mysql.auth;
 | 
			
		||||
 | 
			
		||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 | 
			
		||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 | 
			
		||||
import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2RefreshTokenDO;
 | 
			
		||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 | 
			
		||||
import org.apache.ibatis.annotations.Mapper;
 | 
			
		||||
 | 
			
		||||
@Mapper
 | 
			
		||||
public interface OAuth2RefreshTokenMapper extends BaseMapper<OAuth2RefreshTokenDO> {
 | 
			
		||||
public interface OAuth2RefreshTokenMapper extends BaseMapperX<OAuth2RefreshTokenDO> {
 | 
			
		||||
 | 
			
		||||
    default int deleteByRefreshToken(String refreshToken) {
 | 
			
		||||
        return delete(new LambdaQueryWrapperX<OAuth2RefreshTokenDO>()
 | 
			
		||||
                .eq(OAuth2RefreshTokenDO::getRefreshToken, refreshToken));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    default OAuth2RefreshTokenDO selectByRefreshToken(String refreshToken) {
 | 
			
		||||
        return selectOne(OAuth2RefreshTokenDO::getRefreshToken, refreshToken);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,14 @@
 | 
			
		||||
package cn.iocoder.yudao.module.system.dal.redis.auth;
 | 
			
		||||
 | 
			
		||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 | 
			
		||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
 | 
			
		||||
import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO;
 | 
			
		||||
import org.springframework.data.redis.core.StringRedisTemplate;
 | 
			
		||||
import org.springframework.stereotype.Repository;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Resource;
 | 
			
		||||
import java.util.Collection;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.concurrent.TimeUnit;
 | 
			
		||||
 | 
			
		||||
import static cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants.OAUTH2_ACCESS_TOKEN;
 | 
			
		||||
@@ -39,6 +42,11 @@ public class OAuth2AccessTokenRedisDAO {
 | 
			
		||||
        stringRedisTemplate.delete(redisKey);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void deleteList(Collection<String> accessTokens) {
 | 
			
		||||
        List<String> redisKeys = CollectionUtils.convertList(accessTokens, OAuth2AccessTokenRedisDAO::formatKey);
 | 
			
		||||
        stringRedisTemplate.delete(redisKeys);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static String formatKey(String accessToken) {
 | 
			
		||||
        return String.format(OAUTH2_ACCESS_TOKEN.getKeyTemplate(), accessToken);
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,7 @@ public class SecurityConfiguration {
 | 
			
		||||
                // 登录的接口
 | 
			
		||||
                registry.antMatchers(buildAdminApi("/system/auth/login")).permitAll();
 | 
			
		||||
                registry.antMatchers(buildAdminApi("/system/auth/logout")).permitAll();
 | 
			
		||||
                registry.antMatchers(buildAdminApi("/system/auth/refresh-token")).permitAll();
 | 
			
		||||
                // 社交登陆的接口
 | 
			
		||||
                registry.antMatchers(buildAdminApi("/system/auth/social-auth-redirect")).permitAll();
 | 
			
		||||
                registry.antMatchers(buildAdminApi("/system/auth/social-quick-login")).permitAll();
 | 
			
		||||
 
 | 
			
		||||
@@ -59,4 +59,12 @@ public interface AdminAuthService {
 | 
			
		||||
     */
 | 
			
		||||
    AuthLoginRespVO socialBindLogin(@Valid AuthSocialBindLoginReqVO reqVO);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 刷新访问令牌
 | 
			
		||||
     *
 | 
			
		||||
     * @param refreshToken 刷新令牌
 | 
			
		||||
     * @return 登录结果
 | 
			
		||||
     */
 | 
			
		||||
    AuthLoginRespVO refreshToken(String refreshToken);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -192,6 +192,12 @@ public class AdminAuthServiceImpl implements AdminAuthService {
 | 
			
		||||
        return createTokenAfterLoginSuccess(user.getId(), reqVO.getUsername(), LoginLogTypeEnum.LOGIN_SOCIAL);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public AuthLoginRespVO refreshToken(String refreshToken) {
 | 
			
		||||
        OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.refreshAccessToken(refreshToken, OAuth2ClientIdEnum.DEFAULT.getId());
 | 
			
		||||
        return AuthConvert.INSTANCE.convert(accessTokenDO);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private AuthLoginRespVO createTokenAfterLoginSuccess(Long userId, String username, LoginLogTypeEnum logType) {
 | 
			
		||||
        // 插入登陆日志
 | 
			
		||||
        createLoginLog(userId, username, logType, LoginResultEnum.SUCCESS);
 | 
			
		||||
 
 | 
			
		||||
@@ -30,9 +30,10 @@ public interface OAuth2TokenService {
 | 
			
		||||
     * 参考 DefaultTokenServices 的 refreshAccessToken 方法
 | 
			
		||||
     *
 | 
			
		||||
     * @param refreshToken 刷新令牌
 | 
			
		||||
     * @param clientId 客户端编号
 | 
			
		||||
     * @return 访问令牌的信息
 | 
			
		||||
     */
 | 
			
		||||
    OAuth2AccessTokenDO refreshAccessToken(String refreshToken);
 | 
			
		||||
    OAuth2AccessTokenDO refreshAccessToken(String refreshToken, Long clientId);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获得访问令牌
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,8 @@
 | 
			
		||||
package cn.iocoder.yudao.module.system.service.auth;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.collection.CollUtil;
 | 
			
		||||
import cn.hutool.core.util.IdUtil;
 | 
			
		||||
import cn.hutool.core.util.ObjectUtil;
 | 
			
		||||
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
 | 
			
		||||
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
 | 
			
		||||
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
 | 
			
		||||
@@ -15,8 +17,10 @@ import org.springframework.transaction.annotation.Transactional;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Resource;
 | 
			
		||||
import java.util.Calendar;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 | 
			
		||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * OAuth2.0 Token Service 实现类
 | 
			
		||||
@@ -44,15 +48,38 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService {
 | 
			
		||||
        // 创建刷新令牌
 | 
			
		||||
        OAuth2RefreshTokenDO refreshTokenDO = createOAuth2RefreshToken(userId, userType, clientDO);
 | 
			
		||||
        // 创建访问令牌
 | 
			
		||||
        OAuth2AccessTokenDO accessTokenDO = createOAuth2AccessToken(refreshTokenDO, clientDO);
 | 
			
		||||
        // 记录到 Redis 中
 | 
			
		||||
        oauth2AccessTokenRedisDAO.set(accessTokenDO);
 | 
			
		||||
        return accessTokenDO;
 | 
			
		||||
        return createOAuth2AccessToken(refreshTokenDO, clientDO);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public OAuth2AccessTokenDO refreshAccessToken(String refreshToken) {
 | 
			
		||||
        return null;
 | 
			
		||||
    public OAuth2AccessTokenDO refreshAccessToken(String refreshToken, Long clientId) {
 | 
			
		||||
        // 查询访问令牌
 | 
			
		||||
        OAuth2RefreshTokenDO refreshTokenDO = oauth2RefreshTokenMapper.selectByRefreshToken(refreshToken);
 | 
			
		||||
        if (refreshTokenDO == null) {
 | 
			
		||||
            throw exception(GlobalErrorCodeConstants.BAD_REQUEST, "无效的刷新令牌");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 校验 Client 匹配
 | 
			
		||||
        OAuth2ClientDO clientDO = oauth2ClientService.validOAuthClientFromCache(clientId);
 | 
			
		||||
        if (ObjectUtil.notEqual(clientId, refreshTokenDO.getClientId())) {
 | 
			
		||||
            throw exception(GlobalErrorCodeConstants.BAD_REQUEST, "刷新令牌的客户端编号不正确");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 移除相关的访问令牌
 | 
			
		||||
        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())) {
 | 
			
		||||
            oauth2AccessTokenMapper.deleteById(refreshTokenDO.getId());
 | 
			
		||||
            throw exception(GlobalErrorCodeConstants.UNAUTHORIZED, "刷新令牌已过期");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 创建访问令牌
 | 
			
		||||
        return createOAuth2AccessToken(refreshTokenDO, clientDO);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
@@ -76,10 +103,10 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService {
 | 
			
		||||
    public OAuth2AccessTokenDO checkAccessToken(String accessToken) {
 | 
			
		||||
        OAuth2AccessTokenDO accessTokenDO = getAccessToken(accessToken);
 | 
			
		||||
        if (accessTokenDO == null) {
 | 
			
		||||
            throw exception(GlobalErrorCodeConstants.UNAUTHORIZED, "Token 不存在");
 | 
			
		||||
            throw exception(GlobalErrorCodeConstants.UNAUTHORIZED, "访问令牌不存在");
 | 
			
		||||
        }
 | 
			
		||||
        if (DateUtils.isExpired(accessTokenDO.getExpiresTime())) {
 | 
			
		||||
            throw exception(GlobalErrorCodeConstants.UNAUTHORIZED, "Token 已过期");
 | 
			
		||||
            throw exception(GlobalErrorCodeConstants.UNAUTHORIZED, "访问令牌已过期");
 | 
			
		||||
        }
 | 
			
		||||
        return accessTokenDO;
 | 
			
		||||
    }
 | 
			
		||||
@@ -98,38 +125,16 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService {
 | 
			
		||||
        return accessTokenDO;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//    @Override
 | 
			
		||||
//    @Transactional
 | 
			
		||||
//    public OAuth2AccessTokenRespDTO refreshAccessToken(OAuth2RefreshAccessTokenReqDTO refreshAccessTokenDTO) {
 | 
			
		||||
//        OAuth2RefreshTokenDO refreshTokenDO = oauth2RefreshTokenMapper.selectById(refreshAccessTokenDTO.getRefreshToken());
 | 
			
		||||
//        // 校验刷新令牌是否合法
 | 
			
		||||
//        if (refreshTokenDO == null) { // 不存在
 | 
			
		||||
//            throw ServiceExceptionUtil.exception(OAUTH2_REFRESH_TOKEN_NOT_FOUND);
 | 
			
		||||
//        }
 | 
			
		||||
//        if (refreshTokenDO.getExpiresTime().getTime() < System.currentTimeMillis()) { // 已过期
 | 
			
		||||
//            throw ServiceExceptionUtil.exception(OAUTH_REFRESH_TOKEN_EXPIRED);
 | 
			
		||||
//        }
 | 
			
		||||
//
 | 
			
		||||
//        // 标记 refreshToken 对应的 accessToken 都不合法
 | 
			
		||||
//        // 这块的实现,参考了 Spring Security OAuth2 的代码
 | 
			
		||||
//        List<OAuth2AccessTokenDO> accessTokenDOs = oauth2AccessTokenMapper.selectListByRefreshToken(refreshAccessTokenDTO.getRefreshToken());
 | 
			
		||||
//        accessTokenDOs.forEach(accessTokenDO -> deleteOAuth2AccessToken(accessTokenDO.getId()));
 | 
			
		||||
//
 | 
			
		||||
//        // 创建访问令牌
 | 
			
		||||
//        OAuth2AccessTokenDO oauth2AccessTokenDO = createOAuth2AccessToken(refreshTokenDO, refreshAccessTokenDTO.getCreateIp());
 | 
			
		||||
//        // 返回访问令牌
 | 
			
		||||
//        return OAuth2Convert.INSTANCE.convert(oauth2AccessTokenDO);
 | 
			
		||||
//    }
 | 
			
		||||
 | 
			
		||||
    private OAuth2AccessTokenDO createOAuth2AccessToken(OAuth2RefreshTokenDO refreshTokenDO, OAuth2ClientDO clientDO) {
 | 
			
		||||
        OAuth2AccessTokenDO accessToken = new OAuth2AccessTokenDO().setAccessToken(generateAccessToken())
 | 
			
		||||
        OAuth2AccessTokenDO accessTokenDO = new OAuth2AccessTokenDO().setAccessToken(generateAccessToken())
 | 
			
		||||
                .setUserId(refreshTokenDO.getUserId()).setUserType(refreshTokenDO.getUserType()).setClientId(clientDO.getId())
 | 
			
		||||
                .setRefreshToken(refreshTokenDO.getRefreshToken())
 | 
			
		||||
                .setExpiresTime(DateUtils.addDate(Calendar.SECOND, clientDO.getAccessTokenValiditySeconds()));
 | 
			
		||||
        accessToken.setTenantId(TenantContextHolder.getTenantId()); // 手动设置租户编号,避免缓存到 Redis 的时候,无对应的租户编号
 | 
			
		||||
        oauth2AccessTokenMapper.insert(accessToken);
 | 
			
		||||
        return accessToken;
 | 
			
		||||
        accessTokenDO.setTenantId(TenantContextHolder.getTenantId()); // 手动设置租户编号,避免缓存到 Redis 的时候,无对应的租户编号
 | 
			
		||||
        oauth2AccessTokenMapper.insert(accessTokenDO);
 | 
			
		||||
        // 记录到 Redis 中
 | 
			
		||||
        oauth2AccessTokenRedisDAO.set(accessTokenDO);
 | 
			
		||||
        return accessTokenDO;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private OAuth2RefreshTokenDO createOAuth2RefreshToken(Long userId, Integer userType, OAuth2ClientDO clientDO) {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user