1. 访问令牌,增加 Redis 作为缓存
2. 删除多余的 order 代码
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
<modules>
|
||||
<module>system-service-api</module>
|
||||
<module>system-service-app</module>
|
||||
<module>system-service-integration-test</module>
|
||||
</modules>
|
||||
|
||||
<dependencyManagement>
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package cn.iocoder.mall.systemservice.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* 系统服务的业务配置项
|
||||
*/
|
||||
@Component
|
||||
@ConfigurationProperties("biz")
|
||||
@Validated
|
||||
@Data
|
||||
public class SystemBizProperties {
|
||||
|
||||
/**
|
||||
* 访问令牌过期时间,单位:毫秒
|
||||
*/
|
||||
@NotNull(message = "访问令牌过期时间不能为空")
|
||||
private int accessTokenExpireTimeMillis;
|
||||
/**
|
||||
* 刷新令牌过期时间,单位:毫秒
|
||||
*/
|
||||
@NotNull(message = "刷新令牌过期时间不能为空")
|
||||
private int refreshTokenExpireTimeMillis;
|
||||
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package cn.iocoder.mall.systemservice.convert.oauth;
|
||||
|
||||
import cn.iocoder.mall.systemservice.dal.mysql.dataobject.oauth.OAuth2AccessTokenDO;
|
||||
import cn.iocoder.mall.systemservice.rpc.oauth.dto.OAuth2AccessTokenRespDTO;
|
||||
import cn.iocoder.mall.systemservice.service.oauth.bo.OAuth2AccessTokenBO;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
@@ -13,8 +12,6 @@ public interface OAuth2Convert {
|
||||
OAuth2Convert INSTANCE = Mappers.getMapper(OAuth2Convert.class);
|
||||
|
||||
@Mapping(source = "id", target = "accessToken")
|
||||
OAuth2AccessTokenBO convert(OAuth2AccessTokenDO bean);
|
||||
|
||||
OAuth2AccessTokenRespDTO convert(OAuth2AccessTokenBO bean);
|
||||
OAuth2AccessTokenRespDTO convert(OAuth2AccessTokenDO bean);
|
||||
|
||||
}
|
||||
|
||||
@@ -5,9 +5,16 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public interface OAuth2AccessTokenMapper extends BaseMapper<OAuth2AccessTokenDO> {
|
||||
|
||||
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));
|
||||
@@ -17,4 +24,8 @@ public interface OAuth2AccessTokenMapper extends BaseMapper<OAuth2AccessTokenDO>
|
||||
return delete(new QueryWrapper<OAuth2AccessTokenDO>().eq("refresh_token", refreshToken));
|
||||
}
|
||||
|
||||
default List<OAuth2AccessTokenDO> selectListByRefreshToken(String refreshToken) {
|
||||
return selectList(new QueryWrapper<OAuth2AccessTokenDO>().eq("refresh_token", refreshToken));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package cn.iocoder.mall.systemservice.dal.redis;
|
||||
|
||||
import cn.iocoder.mall.redis.core.RedisKeyDefine;
|
||||
import cn.iocoder.mall.systemservice.dal.mysql.dataobject.oauth.OAuth2AccessTokenDO;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import static cn.iocoder.mall.redis.core.RedisKeyDefine.KeyTypeEnum.STRING;
|
||||
|
||||
/**
|
||||
* Redis Key 枚举类
|
||||
*
|
||||
* 通过将项目中的 Key 枚举在该类中,方便统一管理。
|
||||
*/
|
||||
public interface RedisKeyConstants {
|
||||
|
||||
/**
|
||||
* {@link OAuth2AccessTokenDO} 的缓存
|
||||
*
|
||||
* key 的 format 的参数是 [{@link OAuth2AccessTokenDO#getId()}]
|
||||
*/
|
||||
RedisKeyDefine OAUTH2_ACCESS_TOKEN = new RedisKeyDefine("oauth2_access_token:%s", STRING, OAuth2AccessTokenDO.class, Duration.ofHours(2));
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package cn.iocoder.mall.systemservice.dal.redis.dao;
|
||||
|
||||
import cn.iocoder.mall.systemservice.dal.mysql.dataobject.oauth.OAuth2AccessTokenDO;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import static cn.iocoder.mall.systemservice.dal.redis.RedisKeyConstants.OAUTH2_ACCESS_TOKEN;
|
||||
|
||||
@Repository
|
||||
public class OAuth2AccessTokenRedisDAO {
|
||||
|
||||
@Autowired
|
||||
private StringRedisTemplate redisTemplate;
|
||||
|
||||
public OAuth2AccessTokenDO get(String accessToken) {
|
||||
String redisKey = formatKey(accessToken);
|
||||
return JSON.parseObject(redisTemplate.opsForValue().get(redisKey), OAuth2AccessTokenDO.class);
|
||||
}
|
||||
|
||||
public void set(OAuth2AccessTokenDO accessTokenDO) {
|
||||
String redisKey = formatKey(accessTokenDO.getId());
|
||||
redisTemplate.opsForValue().set(redisKey, JSON.toJSONString(accessTokenDO), OAUTH2_ACCESS_TOKEN.getTimeout());
|
||||
}
|
||||
|
||||
public void delete(String accessToken) {
|
||||
String redisKey = formatKey(accessToken);
|
||||
redisTemplate.delete(redisKey);
|
||||
}
|
||||
|
||||
private static String formatKey(String accessToken) {
|
||||
return String.format(OAUTH2_ACCESS_TOKEN.getKeyTemplate(), accessToken);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,9 +10,10 @@ import cn.iocoder.mall.systemservice.rpc.admin.dto.AdminPageDTO;
|
||||
import cn.iocoder.mall.systemservice.rpc.admin.dto.AdminUpdateDTO;
|
||||
import cn.iocoder.mall.systemservice.rpc.admin.dto.AdminVerifyPasswordDTO;
|
||||
import cn.iocoder.mall.systemservice.rpc.admin.vo.AdminVO;
|
||||
import cn.iocoder.mall.systemservice.rpc.oauth.dto.OAuth2RemoveTokenByUserReqDTO;
|
||||
import cn.iocoder.mall.systemservice.service.admin.AdminService;
|
||||
import cn.iocoder.mall.systemservice.service.admin.bo.AdminBO;
|
||||
import cn.iocoder.mall.systemservice.service.oauth.OAuth2Service;
|
||||
import cn.iocoder.mall.systemservice.service.oauth.OAuth2ServiceImpl;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@@ -23,7 +24,7 @@ public class AdminManager {
|
||||
@Autowired
|
||||
private AdminService adminService;
|
||||
@Autowired
|
||||
private OAuth2Service oauth2Service;
|
||||
private OAuth2ServiceImpl oauth2Service;
|
||||
|
||||
public AdminVO verifyPassword(AdminVerifyPasswordDTO verifyPasswordDTO) {
|
||||
AdminBO adminBO = adminService.verifyPassword(verifyPasswordDTO.getUsername(),
|
||||
@@ -43,7 +44,7 @@ public class AdminManager {
|
||||
// 如果修改密码,或者禁用管理员
|
||||
if (StringUtils.hasText(updateDTO.getPassword())
|
||||
|| AdminStatusEnum.INACTIVE.getStatus().equals(updateDTO.getStatus())) {
|
||||
oauth2Service.removeToken(updateDTO.getId(), UserTypeEnum.ADMIN.getValue());
|
||||
oauth2Service.removeToken(new OAuth2RemoveTokenByUserReqDTO().setUserId(updateDTO.getId()).setUserType(UserTypeEnum.ADMIN.getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
package cn.iocoder.mall.systemservice.manager.oauth;
|
||||
|
||||
import cn.iocoder.mall.systemservice.convert.oauth.OAuth2Convert;
|
||||
import cn.iocoder.mall.systemservice.rpc.oauth.dto.OAuth2AccessTokenRespDTO;
|
||||
import cn.iocoder.mall.systemservice.rpc.oauth.dto.OAuth2CreateAccessTokenReqDTO;
|
||||
import cn.iocoder.mall.systemservice.rpc.oauth.dto.OAuth2RefreshAccessTokenReqDTO;
|
||||
import cn.iocoder.mall.systemservice.rpc.oauth.dto.OAuth2RemoveTokenByUserReqDTO;
|
||||
import cn.iocoder.mall.systemservice.service.oauth.OAuth2Service;
|
||||
import cn.iocoder.mall.systemservice.service.oauth.bo.OAuth2AccessTokenBO;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
||||
/**
|
||||
* OAuth2.0 Manager
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
public class OAuth2Manager {
|
||||
|
||||
@Autowired
|
||||
private OAuth2Service oauth2Service;
|
||||
|
||||
public OAuth2AccessTokenRespDTO createAccessToken(@Valid OAuth2CreateAccessTokenReqDTO createAccessTokenDTO) {
|
||||
OAuth2AccessTokenBO accessTokenBO = oauth2Service.createAccessToken(createAccessTokenDTO.getUserId(),
|
||||
createAccessTokenDTO.getUserType(), createAccessTokenDTO.getCreateIp());
|
||||
return OAuth2Convert.INSTANCE.convert(accessTokenBO);
|
||||
}
|
||||
|
||||
public OAuth2AccessTokenRespDTO checkAccessToken(String accessToken) {
|
||||
OAuth2AccessTokenBO accessTokenBO = oauth2Service.checkAccessToken(accessToken);
|
||||
return OAuth2Convert.INSTANCE.convert(accessTokenBO);
|
||||
}
|
||||
|
||||
public OAuth2AccessTokenRespDTO refreshAccessToken(@Valid OAuth2RefreshAccessTokenReqDTO refreshAccessTokenDTO) {
|
||||
OAuth2AccessTokenBO accessTokenBO = oauth2Service.refreshAccessToken(refreshAccessTokenDTO.getRefreshToken(),
|
||||
refreshAccessTokenDTO.getCreateIp());
|
||||
return OAuth2Convert.INSTANCE.convert(accessTokenBO);
|
||||
}
|
||||
|
||||
public void removeToken(@Valid OAuth2RemoveTokenByUserReqDTO removeTokenDTO) {
|
||||
oauth2Service.removeToken(removeTokenDTO.getUserId(), removeTokenDTO.getUserType());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,40 +1,40 @@
|
||||
package cn.iocoder.mall.systemservice.rpc.oauth;
|
||||
|
||||
import cn.iocoder.common.framework.vo.CommonResult;
|
||||
import cn.iocoder.mall.systemservice.manager.oauth.OAuth2Manager;
|
||||
import cn.iocoder.mall.systemservice.rpc.oauth.dto.OAuth2AccessTokenRespDTO;
|
||||
import cn.iocoder.mall.systemservice.rpc.oauth.dto.OAuth2CreateAccessTokenReqDTO;
|
||||
import cn.iocoder.mall.systemservice.rpc.oauth.dto.OAuth2RefreshAccessTokenReqDTO;
|
||||
import cn.iocoder.mall.systemservice.rpc.oauth.dto.OAuth2AccessTokenRespDTO;
|
||||
import cn.iocoder.mall.systemservice.rpc.oauth.dto.OAuth2RemoveTokenByUserReqDTO;
|
||||
import org.apache.dubbo.config.annotation.Service;
|
||||
import cn.iocoder.mall.systemservice.service.oauth.OAuth2Service;
|
||||
import org.apache.dubbo.config.annotation.DubboService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import static cn.iocoder.common.framework.vo.CommonResult.success;
|
||||
|
||||
@Service(version = "${dubbo.provider.OAuth2Rpc.version}")
|
||||
@DubboService
|
||||
public class OAuth2RpcImpl implements OAuth2Rpc {
|
||||
|
||||
@Autowired
|
||||
private OAuth2Manager oauth2Manager;
|
||||
private OAuth2Service oAuth2Service;
|
||||
|
||||
@Override
|
||||
public CommonResult<OAuth2AccessTokenRespDTO> createAccessToken(OAuth2CreateAccessTokenReqDTO createAccessTokenDTO) {
|
||||
return success(oauth2Manager.createAccessToken(createAccessTokenDTO));
|
||||
return success(oAuth2Service.createAccessToken(createAccessTokenDTO));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommonResult<OAuth2AccessTokenRespDTO> checkAccessToken(String accessToken) {
|
||||
return success(oauth2Manager.checkAccessToken(accessToken));
|
||||
return success(oAuth2Service.checkAccessToken(accessToken));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommonResult<OAuth2AccessTokenRespDTO> refreshAccessToken(OAuth2RefreshAccessTokenReqDTO refreshAccessTokenDTO) {
|
||||
return success(oauth2Manager.refreshAccessToken(refreshAccessTokenDTO));
|
||||
return success(oAuth2Service.refreshAccessToken(refreshAccessTokenDTO));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommonResult<Boolean> removeToken(OAuth2RemoveTokenByUserReqDTO removeTokenDTO) {
|
||||
oauth2Manager.removeToken(removeTokenDTO);
|
||||
oAuth2Service.removeToken(removeTokenDTO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,118 +1,21 @@
|
||||
package cn.iocoder.mall.systemservice.service.oauth;
|
||||
|
||||
import cn.iocoder.common.framework.exception.util.ServiceExceptionUtil;
|
||||
import cn.iocoder.common.framework.util.StringUtils;
|
||||
import cn.iocoder.mall.systemservice.convert.oauth.OAuth2Convert;
|
||||
import cn.iocoder.mall.systemservice.dal.mysql.dataobject.oauth.OAuth2AccessTokenDO;
|
||||
import cn.iocoder.mall.systemservice.dal.mysql.dataobject.oauth.OAuth2RefreshTokenDO;
|
||||
import cn.iocoder.mall.systemservice.dal.mysql.mapper.oauth.OAuth2AccessTokenMapper;
|
||||
import cn.iocoder.mall.systemservice.dal.mysql.mapper.oauth.OAuth2RefreshTokenMapper;
|
||||
import cn.iocoder.mall.systemservice.service.oauth.bo.OAuth2AccessTokenBO;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import static cn.iocoder.mall.systemservice.enums.SystemErrorCodeConstants.*;
|
||||
import cn.iocoder.mall.systemservice.rpc.oauth.dto.OAuth2AccessTokenRespDTO;
|
||||
import cn.iocoder.mall.systemservice.rpc.oauth.dto.OAuth2CreateAccessTokenReqDTO;
|
||||
import cn.iocoder.mall.systemservice.rpc.oauth.dto.OAuth2RefreshAccessTokenReqDTO;
|
||||
import cn.iocoder.mall.systemservice.rpc.oauth.dto.OAuth2RemoveTokenByUserReqDTO;
|
||||
|
||||
/**
|
||||
* OAuth2.0 Service
|
||||
* OAuth2.0 Service 接口
|
||||
*/
|
||||
@Service
|
||||
public class OAuth2Service {
|
||||
public interface OAuth2Service {
|
||||
|
||||
/**
|
||||
* 访问令牌过期时间,单位:毫秒
|
||||
*/
|
||||
@Value("${modules.oauth2-service.access-token-expire-time-millis}")
|
||||
private int accessTokenExpireTimeMillis;
|
||||
/**
|
||||
* 刷新令牌过期时间,单位:毫秒
|
||||
*/
|
||||
@Value("${modules.oauth2-service.refresh-token-expire-time-millis}")
|
||||
private int refreshTokenExpireTimeMillis;
|
||||
OAuth2AccessTokenRespDTO createAccessToken(OAuth2CreateAccessTokenReqDTO createAccessTokenDTO);
|
||||
|
||||
@Autowired
|
||||
private OAuth2AccessTokenMapper oauth2AccessTokenMapper;
|
||||
@Autowired
|
||||
private OAuth2RefreshTokenMapper oauth2RefreshTokenMapper;
|
||||
OAuth2AccessTokenRespDTO checkAccessToken(String accessToken);
|
||||
|
||||
@Transactional
|
||||
public OAuth2AccessTokenBO createAccessToken(Integer userId, Integer userType, String createIp) {
|
||||
// 创建刷新令牌 + 访问令牌
|
||||
OAuth2RefreshTokenDO refreshTokenDO = createOAuth2RefreshToken(userId, userType, createIp);
|
||||
OAuth2AccessTokenDO accessTokenDO = createOAuth2AccessToken(refreshTokenDO, createIp);
|
||||
// 返回访问令牌
|
||||
return OAuth2Convert.INSTANCE.convert(accessTokenDO);
|
||||
}
|
||||
OAuth2AccessTokenRespDTO refreshAccessToken(OAuth2RefreshAccessTokenReqDTO refreshAccessTokenDTO);
|
||||
|
||||
@Transactional
|
||||
public OAuth2AccessTokenBO checkAccessToken(String accessToken) {
|
||||
OAuth2AccessTokenDO accessTokenDO = oauth2AccessTokenMapper.selectById(accessToken);
|
||||
if (accessTokenDO == null) { // 不存在
|
||||
throw ServiceExceptionUtil.exception(OAUTH2_ACCESS_TOKEN_NOT_FOUND);
|
||||
}
|
||||
if (accessTokenDO.getExpiresTime().getTime() < System.currentTimeMillis()) { // 已过期
|
||||
throw ServiceExceptionUtil.exception(OAUTH2_ACCESS_TOKEN_TOKEN_EXPIRED);
|
||||
}
|
||||
// 返回访问令牌
|
||||
return OAuth2Convert.INSTANCE.convert(accessTokenDO);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public OAuth2AccessTokenBO refreshAccessToken(String refreshToken, String createIp) {
|
||||
OAuth2RefreshTokenDO refreshTokenDO = oauth2RefreshTokenMapper.selectById(refreshToken);
|
||||
// 校验刷新令牌是否合法
|
||||
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 的代码
|
||||
oauth2AccessTokenMapper.deleteByRefreshToken(refreshToken);
|
||||
// 创建访问令牌
|
||||
OAuth2AccessTokenDO oauth2AccessTokenDO = createOAuth2AccessToken(refreshTokenDO, createIp);
|
||||
// 返回访问令牌
|
||||
return OAuth2Convert.INSTANCE.convert(oauth2AccessTokenDO);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void removeToken(Integer userId, Integer userType) {
|
||||
oauth2AccessTokenMapper.deleteByUserIdAndUserType(userId, userType);
|
||||
oauth2RefreshTokenMapper.deleteByUserIdAndUserType(userId, userType);
|
||||
}
|
||||
|
||||
private OAuth2AccessTokenDO createOAuth2AccessToken(OAuth2RefreshTokenDO refreshTokenDO, String createIp) {
|
||||
OAuth2AccessTokenDO accessToken = new OAuth2AccessTokenDO()
|
||||
.setId(generateAccessToken())
|
||||
.setUserId(refreshTokenDO.getUserId()).setUserType(refreshTokenDO.getUserType())
|
||||
.setRefreshToken(refreshTokenDO.getId())
|
||||
.setExpiresTime(new Date(System.currentTimeMillis() + accessTokenExpireTimeMillis))
|
||||
.setCreateIp(createIp);
|
||||
oauth2AccessTokenMapper.insert(accessToken);
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
private OAuth2RefreshTokenDO createOAuth2RefreshToken(Integer userId, Integer userType, String createIp) {
|
||||
OAuth2RefreshTokenDO refreshToken = new OAuth2RefreshTokenDO()
|
||||
.setId(generateRefreshToken())
|
||||
.setUserId(userId).setUserType(userType)
|
||||
.setExpiresTime(new Date(System.currentTimeMillis() + refreshTokenExpireTimeMillis))
|
||||
.setCreateIp(createIp);
|
||||
oauth2RefreshTokenMapper.insert(refreshToken);
|
||||
return refreshToken;
|
||||
}
|
||||
|
||||
private String generateAccessToken() {
|
||||
return StringUtils.uuid(true);
|
||||
}
|
||||
|
||||
private String generateRefreshToken() {
|
||||
return StringUtils.uuid(true);
|
||||
}
|
||||
void removeToken(OAuth2RemoveTokenByUserReqDTO removeTokenDTO);
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
package cn.iocoder.mall.systemservice.service.oauth;
|
||||
|
||||
import cn.iocoder.common.framework.exception.util.ServiceExceptionUtil;
|
||||
import cn.iocoder.common.framework.util.StringUtils;
|
||||
import cn.iocoder.mall.systemservice.config.SystemBizProperties;
|
||||
import cn.iocoder.mall.systemservice.convert.oauth.OAuth2Convert;
|
||||
import cn.iocoder.mall.systemservice.dal.mysql.dataobject.oauth.OAuth2AccessTokenDO;
|
||||
import cn.iocoder.mall.systemservice.dal.mysql.dataobject.oauth.OAuth2RefreshTokenDO;
|
||||
import cn.iocoder.mall.systemservice.dal.mysql.mapper.oauth.OAuth2AccessTokenMapper;
|
||||
import cn.iocoder.mall.systemservice.dal.mysql.mapper.oauth.OAuth2RefreshTokenMapper;
|
||||
import cn.iocoder.mall.systemservice.dal.redis.dao.OAuth2AccessTokenRedisDAO;
|
||||
import cn.iocoder.mall.systemservice.rpc.oauth.dto.OAuth2AccessTokenRespDTO;
|
||||
import cn.iocoder.mall.systemservice.rpc.oauth.dto.OAuth2CreateAccessTokenReqDTO;
|
||||
import cn.iocoder.mall.systemservice.rpc.oauth.dto.OAuth2RefreshAccessTokenReqDTO;
|
||||
import cn.iocoder.mall.systemservice.rpc.oauth.dto.OAuth2RemoveTokenByUserReqDTO;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.mall.systemservice.enums.SystemErrorCodeConstants.*;
|
||||
|
||||
/**
|
||||
* OAuth2.0 Service 实现类
|
||||
*/
|
||||
@Service
|
||||
public class OAuth2ServiceImpl implements OAuth2Service {
|
||||
|
||||
@Autowired
|
||||
private SystemBizProperties systemBizProperties;
|
||||
|
||||
@Autowired
|
||||
private OAuth2AccessTokenMapper oauth2AccessTokenMapper;
|
||||
@Autowired
|
||||
private OAuth2RefreshTokenMapper oauth2RefreshTokenMapper;
|
||||
|
||||
@Autowired
|
||||
private OAuth2AccessTokenRedisDAO oauth2AccessTokenRedisDAO;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public OAuth2AccessTokenRespDTO createAccessToken(OAuth2CreateAccessTokenReqDTO createAccessTokenDTO) {
|
||||
// 创建刷新令牌 + 访问令牌
|
||||
OAuth2RefreshTokenDO refreshTokenDO = createOAuth2RefreshToken(createAccessTokenDTO.getUserId(),
|
||||
createAccessTokenDTO.getUserType(), createAccessTokenDTO.getCreateIp());
|
||||
OAuth2AccessTokenDO accessTokenDO = createOAuth2AccessToken(refreshTokenDO, createAccessTokenDTO.getCreateIp());
|
||||
// 返回访问令牌
|
||||
return OAuth2Convert.INSTANCE.convert(accessTokenDO);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public OAuth2AccessTokenRespDTO checkAccessToken(String accessToken) {
|
||||
OAuth2AccessTokenDO accessTokenDO = this.getOAuth2AccessToken(accessToken);
|
||||
if (accessTokenDO == null) { // 不存在
|
||||
throw ServiceExceptionUtil.exception(OAUTH2_ACCESS_TOKEN_NOT_FOUND);
|
||||
}
|
||||
if (accessTokenDO.getExpiresTime().getTime() < System.currentTimeMillis()) { // 已过期
|
||||
throw ServiceExceptionUtil.exception(OAUTH2_ACCESS_TOKEN_TOKEN_EXPIRED);
|
||||
}
|
||||
// 返回访问令牌
|
||||
return OAuth2Convert.INSTANCE.convert(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);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void removeToken(OAuth2RemoveTokenByUserReqDTO removeTokenDTO) {
|
||||
// 删除 Access Token
|
||||
OAuth2AccessTokenDO accessTokenDO = oauth2AccessTokenMapper.selectByUserIdAndUserType(
|
||||
removeTokenDTO.getUserId(), removeTokenDTO.getUserType());
|
||||
if (accessTokenDO != null) {
|
||||
this.deleteOAuth2AccessToken(accessTokenDO.getId());
|
||||
}
|
||||
|
||||
// 删除 Refresh Token
|
||||
oauth2RefreshTokenMapper.deleteByUserIdAndUserType(removeTokenDTO.getUserId(), removeTokenDTO.getUserType());
|
||||
}
|
||||
|
||||
private OAuth2AccessTokenDO createOAuth2AccessToken(OAuth2RefreshTokenDO refreshTokenDO, String createIp) {
|
||||
OAuth2AccessTokenDO accessToken = new OAuth2AccessTokenDO()
|
||||
.setId(generateAccessToken())
|
||||
.setUserId(refreshTokenDO.getUserId()).setUserType(refreshTokenDO.getUserType())
|
||||
.setRefreshToken(refreshTokenDO.getId())
|
||||
.setExpiresTime(new Date(System.currentTimeMillis() + systemBizProperties.getAccessTokenExpireTimeMillis()))
|
||||
.setCreateIp(createIp);
|
||||
oauth2AccessTokenMapper.insert(accessToken);
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
private OAuth2RefreshTokenDO createOAuth2RefreshToken(Integer userId, Integer userType, String createIp) {
|
||||
OAuth2RefreshTokenDO refreshToken = new OAuth2RefreshTokenDO()
|
||||
.setId(generateRefreshToken())
|
||||
.setUserId(userId).setUserType(userType)
|
||||
.setExpiresTime(new Date(System.currentTimeMillis() + systemBizProperties.getRefreshTokenExpireTimeMillis()))
|
||||
.setCreateIp(createIp);
|
||||
oauth2RefreshTokenMapper.insert(refreshToken);
|
||||
return refreshToken;
|
||||
}
|
||||
|
||||
private OAuth2AccessTokenDO getOAuth2AccessToken(String accessToken) {
|
||||
// 优先从 Redis 中获取
|
||||
OAuth2AccessTokenDO accessTokenDO = oauth2AccessTokenRedisDAO.get(accessToken);
|
||||
if (accessTokenDO != null) {
|
||||
return accessTokenDO;
|
||||
}
|
||||
|
||||
// 获取不到,从 MySQL 中获取
|
||||
accessTokenDO = oauth2AccessTokenMapper.selectById(accessToken);
|
||||
// 如果在 MySQL 存在,则往 Redis 中写入
|
||||
if (accessTokenDO != null) {
|
||||
oauth2AccessTokenRedisDAO.set(accessTokenDO);
|
||||
}
|
||||
return accessTokenDO;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除 accessToken 的 MySQL 与 Redis 的数据
|
||||
*
|
||||
* @param accessToken 访问令牌
|
||||
*/
|
||||
private void deleteOAuth2AccessToken(String accessToken) {
|
||||
// 删除 MySQL
|
||||
oauth2AccessTokenMapper.deleteById(accessToken);
|
||||
// 删除 Redis
|
||||
oauth2AccessTokenRedisDAO.delete(accessToken);
|
||||
}
|
||||
|
||||
private static String generateAccessToken() {
|
||||
return StringUtils.uuid(true);
|
||||
}
|
||||
|
||||
private static String generateRefreshToken() {
|
||||
return StringUtils.uuid(true);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package cn.iocoder.mall.systemservice.service.oauth.bo;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* OAuth2.0 访问令牌 BO
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class OAuth2AccessTokenBO {
|
||||
|
||||
/**
|
||||
* 访问令牌
|
||||
*/
|
||||
private String accessToken;
|
||||
/**
|
||||
* 刷新令牌
|
||||
*/
|
||||
private String refreshToken;
|
||||
/**
|
||||
* 用户编号
|
||||
*/
|
||||
private Integer userId;
|
||||
/**
|
||||
* 用户类型
|
||||
*/
|
||||
private Integer userType;
|
||||
/**
|
||||
* 过期时间
|
||||
*/
|
||||
private Date expiresTime;
|
||||
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
##################### 业务模块 #####################
|
||||
## OAuth2CodeService
|
||||
modules.oauth2-service.access-token-expire-time-millis = 2880000
|
||||
modules.oauth2-service.refresh-token-expire-time-millis = 43200000
|
||||
@@ -35,8 +35,6 @@ dubbo:
|
||||
provider:
|
||||
filter: -exception
|
||||
validation: true # 开启 Provider 参数校验
|
||||
OAuth2Rpc:
|
||||
version: 1.0.0
|
||||
AdminRpc:
|
||||
version: 1.0.0
|
||||
ResourceRpc:
|
||||
@@ -72,3 +70,8 @@ mall:
|
||||
error-code:
|
||||
group: ${spring.application.name}
|
||||
constants-class: cn.iocoder.mall.systemservice.enums.SystemErrorCodeConstants
|
||||
|
||||
# 业务配置
|
||||
biz:
|
||||
access-token-expire-time-millis: 2880000
|
||||
refresh-token-expire-time-millis: 43200000
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>system-service-project</artifactId>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>system-service-integration-test</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>system-service-app</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Test 相关 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,21 @@
|
||||
package cn.iocoder.mall.systemservice.service.oauth;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
public class OAuth2ServiceImplTest {
|
||||
|
||||
@Autowired
|
||||
private OAuth2ServiceImpl oauth2Service;
|
||||
|
||||
@Test
|
||||
public void testCheckAccessToken() {
|
||||
oauth2Service.checkAccessToken("yunai");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package cn.iocoder.mall.systemservice.service;
|
||||
Reference in New Issue
Block a user