mirror of
https://gitee.com/huangge1199_admin/vue-pro.git
synced 2024-11-23 07:41:53 +08:00
增加 refresh token 接口,并接入到前端项目
This commit is contained in:
parent
86e6c04e07
commit
a3687132b6
@ -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.common.util.collection.SetUtils;
|
||||||
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
|
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
|
||||||
import cn.iocoder.yudao.framework.security.config.SecurityProperties;
|
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.controller.admin.auth.vo.auth.*;
|
||||||
import cn.iocoder.yudao.module.system.convert.auth.AuthConvert;
|
import cn.iocoder.yudao.module.system.convert.auth.AuthConvert;
|
||||||
import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
|
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.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.getLoginUserId;
|
||||||
|
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.obtainAuthorization;
|
||||||
import static java.util.Collections.singleton;
|
import static java.util.Collections.singleton;
|
||||||
|
|
||||||
@Api(tags = "管理后台 - 认证")
|
@Api(tags = "管理后台 - 认证")
|
||||||
@ -68,13 +68,20 @@ public class AuthController {
|
|||||||
@ApiOperation("登出系统")
|
@ApiOperation("登出系统")
|
||||||
@OperateLog(enable = false) // 避免 Post 请求被记录操作日志
|
@OperateLog(enable = false) // 避免 Post 请求被记录操作日志
|
||||||
public CommonResult<Boolean> logout(HttpServletRequest request) {
|
public CommonResult<Boolean> logout(HttpServletRequest request) {
|
||||||
String token = SecurityFrameworkUtils.obtainAuthorization(request, securityProperties.getTokenHeader());
|
String token = obtainAuthorization(request, securityProperties.getTokenHeader());
|
||||||
if (StrUtil.isNotBlank(token)) {
|
if (StrUtil.isNotBlank(token)) {
|
||||||
authService.logout(token);
|
authService.logout(token);
|
||||||
}
|
}
|
||||||
return success(true);
|
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")
|
@GetMapping("/get-permission-info")
|
||||||
@ApiOperation("获取登录用户的权限信息")
|
@ApiOperation("获取登录用户的权限信息")
|
||||||
public CommonResult<AuthPermissionInfoRespVO> getPermissionInfo() {
|
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 cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO;
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@Mapper
|
@Mapper
|
||||||
public interface OAuth2AccessTokenMapper extends BaseMapperX<OAuth2AccessTokenDO> {
|
public interface OAuth2AccessTokenMapper extends BaseMapperX<OAuth2AccessTokenDO> {
|
||||||
|
|
||||||
@ -11,22 +13,8 @@ public interface OAuth2AccessTokenMapper extends BaseMapperX<OAuth2AccessTokenDO
|
|||||||
return selectOne(OAuth2AccessTokenDO::getAccessToken, accessToken);
|
return selectOne(OAuth2AccessTokenDO::getAccessToken, accessToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
// default OAuth2AccessTokenDO selectByUserIdAndUserType(Integer userId, Integer userType) {
|
default List<OAuth2AccessTokenDO> selectListByRefreshToken(String refreshToken) {
|
||||||
// return selectOne(new QueryWrapper<OAuth2AccessTokenDO>()
|
return selectList(OAuth2AccessTokenDO::getRefreshToken, refreshToken);
|
||||||
// .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));
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,20 @@
|
|||||||
package cn.iocoder.yudao.module.system.dal.mysql.auth;
|
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.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||||
import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2RefreshTokenDO;
|
import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2RefreshTokenDO;
|
||||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
@Mapper
|
@Mapper
|
||||||
public interface OAuth2RefreshTokenMapper extends BaseMapper<OAuth2RefreshTokenDO> {
|
public interface OAuth2RefreshTokenMapper extends BaseMapperX<OAuth2RefreshTokenDO> {
|
||||||
|
|
||||||
default int deleteByRefreshToken(String refreshToken) {
|
default int deleteByRefreshToken(String refreshToken) {
|
||||||
return delete(new LambdaQueryWrapperX<OAuth2RefreshTokenDO>()
|
return delete(new LambdaQueryWrapperX<OAuth2RefreshTokenDO>()
|
||||||
.eq(OAuth2RefreshTokenDO::getRefreshToken, refreshToken));
|
.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;
|
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.framework.common.util.json.JsonUtils;
|
||||||
import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO;
|
import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO;
|
||||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import static cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants.OAUTH2_ACCESS_TOKEN;
|
import static cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants.OAUTH2_ACCESS_TOKEN;
|
||||||
@ -39,6 +42,11 @@ public class OAuth2AccessTokenRedisDAO {
|
|||||||
stringRedisTemplate.delete(redisKey);
|
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) {
|
private static String formatKey(String accessToken) {
|
||||||
return String.format(OAUTH2_ACCESS_TOKEN.getKeyTemplate(), 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/login")).permitAll();
|
||||||
registry.antMatchers(buildAdminApi("/system/auth/logout")).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-auth-redirect")).permitAll();
|
||||||
registry.antMatchers(buildAdminApi("/system/auth/social-quick-login")).permitAll();
|
registry.antMatchers(buildAdminApi("/system/auth/social-quick-login")).permitAll();
|
||||||
|
@ -59,4 +59,12 @@ public interface AdminAuthService {
|
|||||||
*/
|
*/
|
||||||
AuthLoginRespVO socialBindLogin(@Valid AuthSocialBindLoginReqVO reqVO);
|
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);
|
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) {
|
private AuthLoginRespVO createTokenAfterLoginSuccess(Long userId, String username, LoginLogTypeEnum logType) {
|
||||||
// 插入登陆日志
|
// 插入登陆日志
|
||||||
createLoginLog(userId, username, logType, LoginResultEnum.SUCCESS);
|
createLoginLog(userId, username, logType, LoginResultEnum.SUCCESS);
|
||||||
|
@ -30,9 +30,10 @@ public interface OAuth2TokenService {
|
|||||||
* 参考 DefaultTokenServices 的 refreshAccessToken 方法
|
* 参考 DefaultTokenServices 的 refreshAccessToken 方法
|
||||||
*
|
*
|
||||||
* @param refreshToken 刷新令牌
|
* @param refreshToken 刷新令牌
|
||||||
|
* @param clientId 客户端编号
|
||||||
* @return 访问令牌的信息
|
* @return 访问令牌的信息
|
||||||
*/
|
*/
|
||||||
OAuth2AccessTokenDO refreshAccessToken(String refreshToken);
|
OAuth2AccessTokenDO refreshAccessToken(String refreshToken, Long clientId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获得访问令牌
|
* 获得访问令牌
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package cn.iocoder.yudao.module.system.service.auth;
|
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.IdUtil;
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
|
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
|
||||||
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
|
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
|
||||||
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
|
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
|
||||||
@ -15,8 +17,10 @@ import org.springframework.transaction.annotation.Transactional;
|
|||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import java.util.Calendar;
|
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.exception.util.ServiceExceptionUtil.exception;
|
||||||
|
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OAuth2.0 Token Service 实现类
|
* OAuth2.0 Token Service 实现类
|
||||||
@ -44,15 +48,38 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService {
|
|||||||
// 创建刷新令牌
|
// 创建刷新令牌
|
||||||
OAuth2RefreshTokenDO refreshTokenDO = createOAuth2RefreshToken(userId, userType, clientDO);
|
OAuth2RefreshTokenDO refreshTokenDO = createOAuth2RefreshToken(userId, userType, clientDO);
|
||||||
// 创建访问令牌
|
// 创建访问令牌
|
||||||
OAuth2AccessTokenDO accessTokenDO = createOAuth2AccessToken(refreshTokenDO, clientDO);
|
return createOAuth2AccessToken(refreshTokenDO, clientDO);
|
||||||
// 记录到 Redis 中
|
|
||||||
oauth2AccessTokenRedisDAO.set(accessTokenDO);
|
|
||||||
return accessTokenDO;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OAuth2AccessTokenDO refreshAccessToken(String refreshToken) {
|
public OAuth2AccessTokenDO refreshAccessToken(String refreshToken, Long clientId) {
|
||||||
return null;
|
// 查询访问令牌
|
||||||
|
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
|
@Override
|
||||||
@ -76,10 +103,10 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService {
|
|||||||
public OAuth2AccessTokenDO checkAccessToken(String accessToken) {
|
public OAuth2AccessTokenDO checkAccessToken(String accessToken) {
|
||||||
OAuth2AccessTokenDO accessTokenDO = getAccessToken(accessToken);
|
OAuth2AccessTokenDO accessTokenDO = getAccessToken(accessToken);
|
||||||
if (accessTokenDO == null) {
|
if (accessTokenDO == null) {
|
||||||
throw exception(GlobalErrorCodeConstants.UNAUTHORIZED, "Token 不存在");
|
throw exception(GlobalErrorCodeConstants.UNAUTHORIZED, "访问令牌不存在");
|
||||||
}
|
}
|
||||||
if (DateUtils.isExpired(accessTokenDO.getExpiresTime())) {
|
if (DateUtils.isExpired(accessTokenDO.getExpiresTime())) {
|
||||||
throw exception(GlobalErrorCodeConstants.UNAUTHORIZED, "Token 已过期");
|
throw exception(GlobalErrorCodeConstants.UNAUTHORIZED, "访问令牌已过期");
|
||||||
}
|
}
|
||||||
return accessTokenDO;
|
return accessTokenDO;
|
||||||
}
|
}
|
||||||
@ -98,38 +125,16 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService {
|
|||||||
return accessTokenDO;
|
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) {
|
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())
|
.setUserId(refreshTokenDO.getUserId()).setUserType(refreshTokenDO.getUserType()).setClientId(clientDO.getId())
|
||||||
.setRefreshToken(refreshTokenDO.getRefreshToken())
|
.setRefreshToken(refreshTokenDO.getRefreshToken())
|
||||||
.setExpiresTime(DateUtils.addDate(Calendar.SECOND, clientDO.getAccessTokenValiditySeconds()));
|
.setExpiresTime(DateUtils.addDate(Calendar.SECOND, clientDO.getAccessTokenValiditySeconds()));
|
||||||
accessToken.setTenantId(TenantContextHolder.getTenantId()); // 手动设置租户编号,避免缓存到 Redis 的时候,无对应的租户编号
|
accessTokenDO.setTenantId(TenantContextHolder.getTenantId()); // 手动设置租户编号,避免缓存到 Redis 的时候,无对应的租户编号
|
||||||
oauth2AccessTokenMapper.insert(accessToken);
|
oauth2AccessTokenMapper.insert(accessTokenDO);
|
||||||
return accessToken;
|
// 记录到 Redis 中
|
||||||
|
oauth2AccessTokenRedisDAO.set(accessTokenDO);
|
||||||
|
return accessTokenDO;
|
||||||
}
|
}
|
||||||
|
|
||||||
private OAuth2RefreshTokenDO createOAuth2RefreshToken(Long userId, Integer userType, OAuth2ClientDO clientDO) {
|
private OAuth2RefreshTokenDO createOAuth2RefreshToken(Long userId, Integer userType, OAuth2ClientDO clientDO) {
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import request from '@/utils/request'
|
import request from '@/utils/request'
|
||||||
|
import {getRefreshToken} from "@/utils/auth";
|
||||||
|
import service from "@/utils/request";
|
||||||
|
|
||||||
// 登录方法
|
// 登录方法
|
||||||
export function login(username, password, code, uuid) {
|
export function login(username, password, code, uuid) {
|
||||||
@ -99,3 +101,11 @@ export function smsLogin(mobile, code) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 刷新访问令牌
|
||||||
|
export function refreshToken() {
|
||||||
|
return service({
|
||||||
|
url: '/system/auth/refresh-token?refreshToken=' + getRefreshToken(),
|
||||||
|
method: 'post'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -3,8 +3,6 @@ import {getAccessToken, setToken, removeToken, getRefreshToken} from '@/utils/au
|
|||||||
|
|
||||||
const user = {
|
const user = {
|
||||||
state: {
|
state: {
|
||||||
accessToken: getAccessToken(),
|
|
||||||
refreshToken: getRefreshToken(),
|
|
||||||
id: 0, // 用户编号
|
id: 0, // 用户编号
|
||||||
name: '',
|
name: '',
|
||||||
avatar: '',
|
avatar: '',
|
||||||
@ -16,10 +14,6 @@ const user = {
|
|||||||
SET_ID: (state, id) => {
|
SET_ID: (state, id) => {
|
||||||
state.id = id
|
state.id = id
|
||||||
},
|
},
|
||||||
SET_TOKEN: (state, token) => {
|
|
||||||
state.accessToken = token.accessToken
|
|
||||||
state.refreshToken = token.refreshToken
|
|
||||||
},
|
|
||||||
SET_NAME: (state, name) => {
|
SET_NAME: (state, name) => {
|
||||||
state.name = name
|
state.name = name
|
||||||
},
|
},
|
||||||
@ -46,7 +40,6 @@ const user = {
|
|||||||
res = res.data;
|
res = res.data;
|
||||||
// 设置 token
|
// 设置 token
|
||||||
setToken(res)
|
setToken(res)
|
||||||
commit('SET_TOKEN', res)
|
|
||||||
resolve()
|
resolve()
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
reject(error)
|
reject(error)
|
||||||
@ -64,7 +57,6 @@ const user = {
|
|||||||
res = res.data;
|
res = res.data;
|
||||||
// 设置 token
|
// 设置 token
|
||||||
setToken(res)
|
setToken(res)
|
||||||
commit('SET_TOKEN', res)
|
|
||||||
resolve()
|
resolve()
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
reject(error)
|
reject(error)
|
||||||
@ -84,7 +76,6 @@ const user = {
|
|||||||
res = res.data;
|
res = res.data;
|
||||||
// 设置 token
|
// 设置 token
|
||||||
setToken(res)
|
setToken(res)
|
||||||
commit('SET_TOKEN', res)
|
|
||||||
resolve()
|
resolve()
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
reject(error)
|
reject(error)
|
||||||
@ -100,7 +91,6 @@ const user = {
|
|||||||
res = res.data;
|
res = res.data;
|
||||||
// 设置 token
|
// 设置 token
|
||||||
setToken(res)
|
setToken(res)
|
||||||
commit('SET_TOKEN', res)
|
|
||||||
resolve()
|
resolve()
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
reject(error)
|
reject(error)
|
||||||
@ -148,7 +138,6 @@ const user = {
|
|||||||
LogOut({ commit, state }) {
|
LogOut({ commit, state }) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
logout(state.token).then(() => {
|
logout(state.token).then(() => {
|
||||||
commit('SET_TOKEN', '')
|
|
||||||
commit('SET_ROLES', [])
|
commit('SET_ROLES', [])
|
||||||
commit('SET_PERMISSIONS', [])
|
commit('SET_PERMISSIONS', [])
|
||||||
removeToken()
|
removeToken()
|
||||||
|
@ -8,7 +8,7 @@ export function getAccessToken() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getRefreshToken() {
|
export function getRefreshToken() {
|
||||||
return Cookies.get(AccessTokenKey)
|
return Cookies.get(RefreshTokenKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setToken(token) {
|
export function setToken(token) {
|
||||||
|
@ -1,13 +1,19 @@
|
|||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { Notification, MessageBox, Message } from 'element-ui'
|
import { Notification, MessageBox, Message } from 'element-ui'
|
||||||
import store from '@/store'
|
import store from '@/store'
|
||||||
import { getAccessToken } from '@/utils/auth'
|
import {getAccessToken, getRefreshToken, setToken} from '@/utils/auth'
|
||||||
import errorCode from '@/utils/errorCode'
|
import errorCode from '@/utils/errorCode'
|
||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
import {getPath, getTenantEnable} from "@/utils/ruoyi";
|
import {getPath, getTenantEnable} from "@/utils/ruoyi";
|
||||||
|
import {refreshToken} from "@/api/login";
|
||||||
|
|
||||||
// 是否显示重新登录
|
// 是否显示重新登录
|
||||||
export let isRelogin = { show: false };
|
export let isRelogin = { show: false };
|
||||||
|
// Axios 无感知刷新令牌,参考 https://www.dashingdog.cn/article/11 实现
|
||||||
|
// 请求队列
|
||||||
|
let requestList = []
|
||||||
|
// 是否正在刷新中
|
||||||
|
let isRefreshToken = false
|
||||||
|
|
||||||
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
|
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
|
||||||
// 创建axios实例
|
// 创建axios实例
|
||||||
@ -60,29 +66,43 @@ service.interceptors.request.use(config => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// 响应拦截器
|
// 响应拦截器
|
||||||
service.interceptors.response.use(res => {
|
service.interceptors.response.use( async res => {
|
||||||
// 未设置状态码则默认成功状态
|
// 未设置状态码则默认成功状态
|
||||||
const code = res.data.code || 200;
|
const code = res.data.code || 200;
|
||||||
// 获取错误信息
|
// 获取错误信息
|
||||||
const msg = errorCode[code] || res.data.msg || errorCode['default']
|
const msg = errorCode[code] || res.data.msg || errorCode['default']
|
||||||
if (code === 401) {
|
if (code === 401) {
|
||||||
if (!isRelogin.show) {
|
// 如果未认证,并且未进行刷新令牌,说明可能是访问令牌过期了
|
||||||
isRelogin.show = true;
|
if (!isRefreshToken) {
|
||||||
MessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', {
|
isRefreshToken = true;
|
||||||
confirmButtonText: '重新登录',
|
// 1. 如果获取不到刷新令牌,则只能执行登出操作
|
||||||
cancelButtonText: '取消',
|
if (!getRefreshToken()) {
|
||||||
type: 'warning'
|
return handleAuthorized();
|
||||||
}
|
}
|
||||||
).then(() => {
|
// 2. 进行刷新访问令牌
|
||||||
isRelogin.show = false;
|
try {
|
||||||
store.dispatch('LogOut').then(() => {
|
const refreshTokenRes = await refreshToken()
|
||||||
location.href = getPath('/index');
|
// 2.1 刷新成功,则回放队列的请求 + 当前请求
|
||||||
|
setToken(refreshTokenRes.data)
|
||||||
|
requestList.forEach(cb => cb())
|
||||||
|
return service(res.config)
|
||||||
|
} catch (e) {
|
||||||
|
// 2.1 刷新失败,则只能执行登出操作
|
||||||
|
return handleAuthorized();
|
||||||
|
} finally {
|
||||||
|
requestList = []
|
||||||
|
isRefreshToken = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 添加到队列,等待刷新获取到新的令牌
|
||||||
|
return new Promise(resolve => {
|
||||||
|
requestList.push(() => {
|
||||||
|
config.headers['Authorization'] = 'Bearer ' + getAccessToken() // 让每个请求携带自定义token 请根据实际情况自行修改
|
||||||
|
resolve(service(config))
|
||||||
})
|
})
|
||||||
}).catch(() => {
|
})
|
||||||
isRelogin.show = false;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return Promise.reject('无效的会话,或者会话已过期,请重新登录。')
|
return handleAuthorized();
|
||||||
} else if (code === 500) {
|
} else if (code === 500) {
|
||||||
Message({
|
Message({
|
||||||
message: msg,
|
message: msg,
|
||||||
@ -138,4 +158,24 @@ export function getBaseHeader() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleAuthorized() {
|
||||||
|
if (!isRelogin.show) {
|
||||||
|
isRelogin.show = true;
|
||||||
|
MessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', {
|
||||||
|
confirmButtonText: '重新登录',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}
|
||||||
|
).then(() => {
|
||||||
|
isRelogin.show = false;
|
||||||
|
store.dispatch('LogOut').then(() => {
|
||||||
|
location.href = getPath('/index');
|
||||||
|
})
|
||||||
|
}).catch(() => {
|
||||||
|
isRelogin.show = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Promise.reject('无效的会话,或者会话已过期,请重新登录。')
|
||||||
|
}
|
||||||
|
|
||||||
export default service
|
export default service
|
||||||
|
Loading…
Reference in New Issue
Block a user