From 4f52d1367be6ca504adff544cf069ef9f38456d8 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 8 May 2022 23:52:31 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E7=AE=A1=E7=90=86=E5=90=8E?= =?UTF-8?q?=E5=8F=B0=E7=99=BB=E5=BD=95=E6=97=B6=EF=BC=8C=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=20OAuth2=20=E7=9A=84=20access=20token?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pom.xml | 15 +- .../YudaoSecurityAutoConfiguration.java | 6 +- .../filter/TokenAuthenticationFilter.java | 18 +- .../service/SecurityAuthFrameworkService.java | 30 --- .../web/core/util/WebFrameworkUtils.java | 4 +- .../yudao-module-system-api/pom.xml | 7 - .../module/system/api/auth/OAuth2Api.java | 12 -- .../system/api/auth/OAuth2TokenApi.java | 22 +++ .../system/api/auth/UserSessionApi.java | 56 ------ .../dto/OAuth2AccessTokenCheckRespDTO.java | 28 +++ .../dto/OAuth2AccessTokenCreateReqDTO.java | 37 ++++ .../auth/dto/OAuth2AccessTokenRespDTO.java | 39 ++++ .../system/api/auth/OAuth2TokenApiImpl.java | 33 ++++ .../system/api/auth/UserSessionApiImpl.java | 47 ----- .../convert/auth/UserSessionConvert.java | 4 + .../dataobject/auth/OAuth2AccessTokenDO.java | 19 +- ...ApplicationDO.java => OAuth2ClientDO.java} | 36 ++-- .../dal/dataobject/auth/OAuth2CodeDO.java | 6 +- .../dataobject/auth/OAuth2RefreshTokenDO.java | 13 +- .../mysql/auth/OAuth2AccessTokenMapper.java | 32 +++ .../mysql/auth/OAuth2RefreshTokenMapper.java | 16 ++ .../system/dal/redis/RedisKeyConstants.java | 5 + .../redis/auth/OAuth2AccessTokenRedisDAO.java | 46 +++++ .../service/auth/AdminAuthServiceImpl.java | 10 +- .../service/auth/OAuth2ClientService.java | 22 +++ .../service/auth/OAuth2ClientServiceImpl.java | 21 ++ .../service/auth/OAuth2ServiceImpl.java | 145 -------------- .../service/auth/OAuth2TokenService.java | 62 +++++- .../service/auth/OAuth2TokenServiceImpl.java | 182 ++++++++++++++++++ yudao-server/pom.xml | 10 +- 30 files changed, 626 insertions(+), 357 deletions(-) delete mode 100644 yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/service/SecurityAuthFrameworkService.java delete mode 100644 yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2Api.java create mode 100644 yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2TokenApi.java delete mode 100644 yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/UserSessionApi.java create mode 100644 yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenCheckRespDTO.java create mode 100644 yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenCreateReqDTO.java create mode 100644 yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenRespDTO.java create mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2TokenApiImpl.java delete mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/auth/UserSessionApiImpl.java rename yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/{OAuth2ApplicationDO.java => OAuth2ClientDO.java} (68%) create mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2AccessTokenMapper.java create mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2RefreshTokenMapper.java create mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/auth/OAuth2AccessTokenRedisDAO.java create mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ClientService.java create mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ClientServiceImpl.java delete mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ServiceImpl.java create mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenServiceImpl.java diff --git a/yudao-framework/yudao-spring-boot-starter-security/pom.xml b/yudao-framework/yudao-spring-boot-starter-security/pom.xml index ba33598c5..4e32a6c7c 100644 --- a/yudao-framework/yudao-spring-boot-starter-security/pom.xml +++ b/yudao-framework/yudao-spring-boot-starter-security/pom.xml @@ -44,18 +44,11 @@ spring-boot-starter-security - + - org.activiti - activiti-engine - 7.1.0.M6 - - - * - * - - - true + cn.iocoder.boot + yudao-module-system-api + ${revision} diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoSecurityAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoSecurityAutoConfiguration.java index 087edc32f..4933969bc 100644 --- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoSecurityAutoConfiguration.java +++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoSecurityAutoConfiguration.java @@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.security.core.filter.TokenAuthenticationFilter import cn.iocoder.yudao.framework.security.core.handler.AccessDeniedHandlerImpl; import cn.iocoder.yudao.framework.security.core.handler.AuthenticationEntryPointImpl; import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler; +import cn.iocoder.yudao.module.system.api.auth.OAuth2TokenApi; import org.springframework.beans.factory.config.MethodInvokingFactoryBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; @@ -72,8 +73,9 @@ public class YudaoSecurityAutoConfiguration { * Token 认证过滤器 Bean */ @Bean - public TokenAuthenticationFilter authenticationTokenFilter(GlobalExceptionHandler globalExceptionHandler) { - return new TokenAuthenticationFilter(securityProperties, globalExceptionHandler); + public TokenAuthenticationFilter authenticationTokenFilter(GlobalExceptionHandler globalExceptionHandler, + OAuth2TokenApi oauth2TokenApi) { + return new TokenAuthenticationFilter(securityProperties, globalExceptionHandler, oauth2TokenApi); } /** diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/TokenAuthenticationFilter.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/TokenAuthenticationFilter.java index b27863f60..75caa59ff 100644 --- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/TokenAuthenticationFilter.java +++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/TokenAuthenticationFilter.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.framework.security.core.filter; +import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; @@ -8,7 +9,10 @@ import cn.iocoder.yudao.framework.security.core.LoginUser; import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler; import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; +import cn.iocoder.yudao.module.system.api.auth.OAuth2TokenApi; +import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenCheckRespDTO; import lombok.RequiredArgsConstructor; +import org.springframework.security.access.AccessDeniedException; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; @@ -30,6 +34,8 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter { private final GlobalExceptionHandler globalExceptionHandler; + private final OAuth2TokenApi oauth2TokenApi; + @Override @SuppressWarnings("NullableProblems") protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) @@ -39,11 +45,21 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter { Integer userType = WebFrameworkUtils.getLoginUserType(request); try { // 验证 token 有效性 - LoginUser loginUser = null; // TODO 芋艿:待实现 + OAuth2AccessTokenCheckRespDTO accessToken = oauth2TokenApi.checkAccessToken(token); + if (accessToken != null && ObjectUtil.notEqual(accessToken.getUserType(), userType)) { // 用户类型不匹配,无权限 + throw new AccessDeniedException("错误的用户类型"); + } + LoginUser loginUser = null; + if (accessToken != null) { // 如果不为空,说明认证通过,则转换成登录用户 + loginUser = new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType()) + .setTenantId(accessToken.getTenantId()); + } + // 模拟 Login 功能,方便日常开发调试 if (loginUser == null) { loginUser = mockLoginUser(request, token, userType); } + // 设置当前用户 if (loginUser != null) { SecurityFrameworkUtils.setLoginUser(loginUser, request); diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/service/SecurityAuthFrameworkService.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/service/SecurityAuthFrameworkService.java deleted file mode 100644 index 3afef7acf..000000000 --- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/service/SecurityAuthFrameworkService.java +++ /dev/null @@ -1,30 +0,0 @@ -package cn.iocoder.yudao.framework.security.core.service; - -import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; -import cn.iocoder.yudao.framework.security.core.LoginUser; -import org.springframework.security.core.userdetails.UserDetailsService; - -/** - * Security 框架 Auth Service 接口,定义不同用户类型的 {@link UserTypeEnum} 需要实现的方法 - * - * @author 芋道源码 - */ -public interface SecurityAuthFrameworkService extends UserDetailsService { - - /** - * 校验 token 的有效性,并获取用户信息 - * 通过后,刷新 token 的过期时间 - * - * @param token token - * @return 用户信息 - */ - LoginUser verifyTokenAndRefresh(String token); - - /** - * 获得用户类型。每个用户类型,对应一个 SecurityAuthFrameworkService 实现类。 - * - * @return 用户类型 - */ - UserTypeEnum getUserType(); - -} diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/util/WebFrameworkUtils.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/util/WebFrameworkUtils.java index b3a7f7ecb..f5ac676fa 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/util/WebFrameworkUtils.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/util/WebFrameworkUtils.java @@ -84,8 +84,8 @@ public class WebFrameworkUtils { } // 1. 优先,从 Attribute 中获取 Integer userType = (Integer) request.getAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_TYPE); - if (userType == null) { - return null; + if (userType != null) { + return userType; } // 2. 其次,基于 URL 前缀的约定 if (request.getRequestURI().startsWith(properties.getAdminApi().getPrefix())) { diff --git a/yudao-module-system/yudao-module-system-api/pom.xml b/yudao-module-system/yudao-module-system-api/pom.xml index 81b129916..655db05a7 100644 --- a/yudao-module-system/yudao-module-system-api/pom.xml +++ b/yudao-module-system/yudao-module-system-api/pom.xml @@ -29,13 +29,6 @@ true - - - cn.iocoder.boot - yudao-spring-boot-starter-security - true - - diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2Api.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2Api.java deleted file mode 100644 index 973466e55..000000000 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2Api.java +++ /dev/null @@ -1,12 +0,0 @@ -package cn.iocoder.yudao.module.system.api.auth; - -/** - * OAuth2.0 API 接口 - * - * @author 芋道源码 - */ -public interface OAuth2Api { - - - -} diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2TokenApi.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2TokenApi.java new file mode 100644 index 000000000..44c9079f5 --- /dev/null +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2TokenApi.java @@ -0,0 +1,22 @@ +package cn.iocoder.yudao.module.system.api.auth; + +import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenCheckRespDTO; +import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenCreateReqDTO; +import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenRespDTO; + +import javax.validation.Valid; + +/** + * OAuth2.0 Token API 接口 + * + * @author 芋道源码 + */ +public interface OAuth2TokenApi { + + OAuth2AccessTokenRespDTO createAccessToken(@Valid OAuth2AccessTokenCreateReqDTO reqDTO); + + OAuth2AccessTokenCheckRespDTO checkAccessToken(String accessToken); + +// void removeToken(OAuth2RemoveTokenByUserReqDTO removeTokenDTO); + +} diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/UserSessionApi.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/UserSessionApi.java deleted file mode 100644 index e7fdcb982..000000000 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/UserSessionApi.java +++ /dev/null @@ -1,56 +0,0 @@ -package cn.iocoder.yudao.module.system.api.auth; - -import cn.iocoder.yudao.framework.security.core.LoginUser; - -import javax.validation.constraints.NotEmpty; -import javax.validation.constraints.NotNull; - -/** - * 在线用户 Session API 接口 - * - * @author 芋道源码 - */ -public interface UserSessionApi { - - /** - * 创建在线用户 Session - * - * @param loginUser 登录用户 - * @param userIp 用户 IP - * @param userAgent 用户 UA - * @return Token 令牌 - */ - String createUserSession(@NotNull(message = "登录用户不能为空") LoginUser loginUser, String userIp, String userAgent); - - /** - * 刷新在线用户 Session 的更新时间 - * - * @param token Token 令牌 - * @param loginUser 登录用户 - */ - void refreshUserSession(@NotEmpty(message = "Token 令牌不能为空") String token, - @NotNull(message = "登录用户不能为空") LoginUser loginUser); - - /** - * 删除在线用户 Session - * - * @param token Token 令牌 - */ - void deleteUserSession(String token); - - /** - * 获得 Token 令牌对应的在线用户 - * - * @param token Token 令牌 - * @return 在线用户 - */ - LoginUser getLoginUser(String token); - - /** - * 获得 Session 超时时间,单位:毫秒 - * - * @return 超时时间 - */ - Long getSessionTimeoutMillis(); - -} diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenCheckRespDTO.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenCheckRespDTO.java new file mode 100644 index 000000000..5acdc1ea8 --- /dev/null +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenCheckRespDTO.java @@ -0,0 +1,28 @@ +package cn.iocoder.yudao.module.system.api.auth.dto; + +import lombok.Data; + +import java.io.Serializable; + +/** + * OAuth2.0 访问令牌的校验 Response DTO + * + * @author 芋道源码 + */ +@Data +public class OAuth2AccessTokenCheckRespDTO implements Serializable { + + /** + * 用户编号 + */ + private Long userId; + /** + * 用户类型 + */ + private Integer userType; + /** + * 租户编号 + */ + private Long tenantId; + +} diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenCreateReqDTO.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenCreateReqDTO.java new file mode 100644 index 000000000..bf814f888 --- /dev/null +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenCreateReqDTO.java @@ -0,0 +1,37 @@ +package cn.iocoder.yudao.module.system.api.auth.dto; + +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; +import cn.iocoder.yudao.framework.common.validation.InEnum; +import lombok.Data; +import lombok.experimental.Accessors; + +import javax.validation.constraints.NotNull; +import java.io.Serializable; + +/** + * OAuth2.0 访问令牌创建 Request DTO + * + * @author 芋道源码 + */ +@Data +@Accessors(chain = true) +public class OAuth2AccessTokenCreateReqDTO implements Serializable { + + /** + * 用户编号 + */ + @NotNull(message = "用户编号不能为空") + private Integer userId; + /** + * 用户类型 + */ + @NotNull(message = "用户类型不能为空") + @InEnum(value = UserTypeEnum.class, message = "用户类型必须是 {value}") + private Integer userType; + /** + * 客户端编号 + */ + @NotNull(message = "客户端编号不能为空") + private Long clientId; + +} diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenRespDTO.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenRespDTO.java new file mode 100644 index 000000000..429fb071d --- /dev/null +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenRespDTO.java @@ -0,0 +1,39 @@ +package cn.iocoder.yudao.module.system.api.auth.dto; + +import lombok.Data; +import lombok.experimental.Accessors; + +import java.io.Serializable; +import java.util.Date; + +/** + * OAuth2.0 访问令牌的信息 Response DTO + * + * @author 芋道源码 + */ +@Data +@Accessors(chain = true) +public class OAuth2AccessTokenRespDTO implements Serializable { + + /** + * 访问令牌 + */ + private String accessToken; + /** + * 刷新令牌 + */ + private String refreshToken; + /** + * 用户编号 + */ + private Integer userId; + /** + * 用户类型 + */ + private Integer userType; + /** + * 过期时间 + */ + private Date expiresTime; + +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2TokenApiImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2TokenApiImpl.java new file mode 100644 index 000000000..e8537b0dc --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2TokenApiImpl.java @@ -0,0 +1,33 @@ +package cn.iocoder.yudao.module.system.api.auth; + +import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenCheckRespDTO; +import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenCreateReqDTO; +import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenRespDTO; +import cn.iocoder.yudao.module.system.convert.auth.UserSessionConvert; +import cn.iocoder.yudao.module.system.service.auth.OAuth2TokenService; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; + +/** + * OAuth2.0 Token API 实现类 + * + * @author 芋道源码 + */ +@Service +public class OAuth2TokenApiImpl implements OAuth2TokenApi { + + @Resource + private OAuth2TokenService oauth2TokenService; + + @Override + public OAuth2AccessTokenRespDTO createAccessToken(OAuth2AccessTokenCreateReqDTO reqDTO) { + return null; + } + + @Override + public OAuth2AccessTokenCheckRespDTO checkAccessToken(String accessToken) { + return UserSessionConvert.INSTANCE.convert(oauth2TokenService.checkAccessToken(accessToken)); + } + +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/auth/UserSessionApiImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/auth/UserSessionApiImpl.java deleted file mode 100644 index 63226c125..000000000 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/auth/UserSessionApiImpl.java +++ /dev/null @@ -1,47 +0,0 @@ -package cn.iocoder.yudao.module.system.api.auth; - -import cn.iocoder.yudao.framework.security.core.LoginUser; -import cn.iocoder.yudao.module.system.service.auth.UserSessionService; -import org.springframework.stereotype.Service; -import org.springframework.validation.annotation.Validated; - -import javax.annotation.Resource; - -/** - * 在线用户 Session API 实现类 - * - * @author 芋道源码 - */ -@Service -@Validated -public class UserSessionApiImpl implements UserSessionApi { - - @Resource - private UserSessionService userSessionService; - - @Override - public String createUserSession(LoginUser loginUser, String userIp, String userAgent) { - return userSessionService.createUserSession(loginUser, userIp, userAgent); - } - - @Override - public void refreshUserSession(String token, LoginUser loginUser) { - userSessionService.refreshUserSession(token, loginUser); - } - - @Override - public void deleteUserSession(String token) { - userSessionService.deleteUserSession(token); - } - - @Override - public LoginUser getLoginUser(String token) { - return userSessionService.getLoginUser(token); - } - - @Override - public Long getSessionTimeoutMillis() { - return userSessionService.getSessionTimeoutMillis(); - } - -} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/UserSessionConvert.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/UserSessionConvert.java index d30dfdcbb..9138795fd 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/UserSessionConvert.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/UserSessionConvert.java @@ -1,6 +1,8 @@ package cn.iocoder.yudao.module.system.convert.auth; +import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenCheckRespDTO; import cn.iocoder.yudao.module.system.controller.admin.auth.vo.session.UserSessionPageItemRespVO; +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO; import cn.iocoder.yudao.module.system.dal.dataobject.auth.UserSessionDO; import org.mapstruct.Mapper; import org.mapstruct.factory.Mappers; @@ -12,4 +14,6 @@ public interface UserSessionConvert { UserSessionPageItemRespVO convert(UserSessionDO session); + OAuth2AccessTokenCheckRespDTO convert(OAuth2AccessTokenDO bean); + } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2AccessTokenDO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2AccessTokenDO.java index b8a63c1e1..aaaa8152c 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2AccessTokenDO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2AccessTokenDO.java @@ -1,7 +1,8 @@ package cn.iocoder.yudao.module.system.dal.dataobject.auth; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO; +import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import lombok.EqualsAndHashCode; @@ -12,22 +13,30 @@ import java.util.Date; /** * OAuth2 访问令牌 DO * + * 如下字段,暂时未使用,暂时不支持: + * user_name、authentication(用户信息) + * * @author 芋道源码 */ @TableName("system_oauth2_access_token") @Data @EqualsAndHashCode(callSuper = true) @Accessors(chain = true) -public class OAuth2AccessTokenDO extends BaseDO { +public class OAuth2AccessTokenDO extends TenantBaseDO { /** * 编号,数据库递增 */ + @TableId private Long id; /** * 访问令牌 */ private String accessToken; + /** + * 刷新令牌 + */ + private String refreshToken; /** * 用户编号 */ @@ -39,11 +48,11 @@ public class OAuth2AccessTokenDO extends BaseDO { */ private Integer userType; /** - * 应用编号 + * 客户端编号 * - * 关联 {@link OAuth2ApplicationDO#getId()} + * 关联 {@link OAuth2ClientDO#getId()} */ - private Long applicationId; + private Long clientId; /** * 过期时间 */ diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2ApplicationDO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2ClientDO.java similarity index 68% rename from yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2ApplicationDO.java rename to yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2ClientDO.java index 20a2b6087..88db45149 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2ApplicationDO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2ClientDO.java @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.system.dal.dataobject.auth; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import lombok.EqualsAndHashCode; @@ -12,12 +13,8 @@ import java.util.List; /** * OAuth2 客户端 DO * - * 为什么不使用 Client 作为表名? - * 1. clientId 字段被占用,导致表的 id 无法有合适的缩写 - * 2. 大多数 Github、Gitee 等平台,都会习惯称为第三方接入应用 - * * 如下字段,考虑到使用相对不是很高频,主要是一些开关,暂时不支持: - * authorized_grant_types、authorities、access_token_validity、refresh_token_validity、additional_information、autoapprove、resource_ids、scope + * authorized_grant_types、authorities、additional_information、autoapprove、resource_ids、scope * * @author 芋道源码 */ @@ -25,24 +22,19 @@ import java.util.List; @Data @EqualsAndHashCode(callSuper = true) @Accessors(chain = true) -public class OAuth2ApplicationDO extends BaseDO { +public class OAuth2ClientDO extends BaseDO { - /** - * 编号,数据库递增 - */ - private Long id; /** * 客户端编号 + * + * 由于 SQL Server 在存储 String 主键有点问题,所以暂时使用 Long 类型 */ - private String clientId; + @TableId + private Long id; /** * 客户端密钥 */ - private String clientSecret; - /** - * 可重定向的 URI 地址 - */ - private List redirectUris; + private String secret; /** * 应用名 */ @@ -61,5 +53,17 @@ public class OAuth2ApplicationDO extends BaseDO { * 枚举 {@link CommonStatusEnum} */ private Integer status; + /** + * 访问令牌的有效期 + */ + private Integer accessTokenValiditySeconds; + /** + * 刷新令牌的有效期 + */ + private Integer refreshTokenValiditySeconds; + /** + * 可重定向的 URI 地址 + */ + private List redirectUris; } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2CodeDO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2CodeDO.java index 293bd0a41..7a851b01d 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2CodeDO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2CodeDO.java @@ -39,11 +39,11 @@ public class OAuth2CodeDO extends BaseDO { */ private Integer userType; /** - * 应用编号 + * 客户端编号 * - * 关联 {@link OAuth2ApplicationDO#getId()} + * 关联 {@link OAuth2ClientDO#getId()} */ - private Long applicationId; + private Long clientId; /** * 刷新令牌 * diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2RefreshTokenDO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2RefreshTokenDO.java index 42824f297..1f0aa3032 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2RefreshTokenDO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2RefreshTokenDO.java @@ -12,6 +12,7 @@ import java.util.Date; /** * OAuth2 刷新令牌 * + * @author 芋道源码 */ @TableName("system_oauth2_refresh_token") @Data @@ -30,20 +31,22 @@ public class OAuth2RefreshTokenDO extends BaseDO { /** * 用户编号 */ - private Integer userId; + private Long userId; /** * 用户类型 * * 枚举 {@link UserTypeEnum} */ private Integer userType; + /** + * 客户端编号 + * + * 关联 {@link OAuth2ClientDO#getId()} + */ + private Long clientId; /** * 过期时间 */ private Date expiresTime; - /** - * 创建 IP - */ - private String createIp; } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2AccessTokenMapper.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2AccessTokenMapper.java new file mode 100644 index 000000000..76d04d71a --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2AccessTokenMapper.java @@ -0,0 +1,32 @@ +package cn.iocoder.yudao.module.system.dal.mysql.auth; + +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; + +@Mapper +public interface OAuth2AccessTokenMapper extends BaseMapperX { + + default OAuth2AccessTokenDO selectByAccessToken(String accessToken) { + return selectOne(OAuth2AccessTokenDO::getAccessToken, accessToken); + } + +// default OAuth2AccessTokenDO selectByUserIdAndUserType(Integer userId, Integer userType) { +// return selectOne(new QueryWrapper() +// .eq("user_id", userId).eq("user_type", userType)); +// } +// +// default int deleteByUserIdAndUserType(Integer userId, Integer userType) { +// return delete(new QueryWrapper() +// .eq("user_id", userId).eq("user_type", userType)); +// } +// +// default int deleteByRefreshToken(String refreshToken) { +// return delete(new QueryWrapper().eq("refresh_token", refreshToken)); +// } +// +// default List selectListByRefreshToken(String refreshToken) { +// return selectList(new QueryWrapper().eq("refresh_token", refreshToken)); +// } + +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2RefreshTokenMapper.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2RefreshTokenMapper.java new file mode 100644 index 000000000..e5206d242 --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2RefreshTokenMapper.java @@ -0,0 +1,16 @@ +package cn.iocoder.yudao.module.system.dal.mysql.auth; + +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2RefreshTokenDO; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface OAuth2RefreshTokenMapper extends BaseMapper { + + default int deleteByUserIdAndUserType(Integer userId, Integer userType) { + return delete(new QueryWrapper() + .eq("user_id", userId).eq("user_type", userType)); + } + +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/RedisKeyConstants.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/RedisKeyConstants.java index b17c6fb4d..f70b546fc 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/RedisKeyConstants.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/RedisKeyConstants.java @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.system.dal.redis; import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine; import cn.iocoder.yudao.framework.security.core.LoginUser; +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO; import java.time.Duration; @@ -22,6 +23,10 @@ public interface RedisKeyConstants { "login_user:%s", // 参数为 token 令牌 STRING, LoginUser.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC); + RedisKeyDefine OAUTH2_ACCESS_TOKEN = new RedisKeyDefine("访问令牌的缓存", + "oauth2_access_token:%s", // 参数为访问令牌 token + STRING, OAuth2AccessTokenDO.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC); + RedisKeyDefine SOCIAL_AUTH_STATE = new RedisKeyDefine("社交登陆的 state", // 注意,它是被 JustAuth 的 justauth.type.prefix 使用到 "social_auth_state:%s", // 参数为 state STRING, String.class, Duration.ofHours(24)); // 值为 state diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/auth/OAuth2AccessTokenRedisDAO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/auth/OAuth2AccessTokenRedisDAO.java new file mode 100644 index 000000000..cb7fba538 --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/auth/OAuth2AccessTokenRedisDAO.java @@ -0,0 +1,46 @@ +package cn.iocoder.yudao.module.system.dal.redis.auth; + +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.concurrent.TimeUnit; + +import static cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants.OAUTH2_ACCESS_TOKEN; + +/** + * {@link OAuth2AccessTokenDO} 的 RedisDAO + * + * @author 芋道源码 + */ +@Repository +public class OAuth2AccessTokenRedisDAO { + + @Resource + private StringRedisTemplate stringRedisTemplate; + + public OAuth2AccessTokenDO get(String accessToken) { + String redisKey = formatKey(accessToken); + return JsonUtils.parseObject(stringRedisTemplate.opsForValue().get(redisKey), OAuth2AccessTokenDO.class); + } + + public void set(OAuth2AccessTokenDO accessTokenDO) { + String redisKey = formatKey(accessTokenDO.getAccessToken()); + // 清理多余字段,避免缓存 + accessTokenDO.setUpdater(null).setUpdateTime(null).setCreateTime(null).setCreator(null).setDeleted(null); + stringRedisTemplate.opsForValue().set(redisKey, JsonUtils.toJsonString(accessTokenDO), + accessTokenDO.getExpiresTime().getTime() - System.currentTimeMillis(), TimeUnit.MILLISECONDS); + } + + public void delete(String accessToken) { + String redisKey = formatKey(accessToken); + stringRedisTemplate.delete(redisKey); + } + + private static String formatKey(String accessToken) { + return String.format(OAUTH2_ACCESS_TOKEN.getKeyTemplate(), accessToken); + } + +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java index d049d9edb..69fd38e07 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java @@ -49,6 +49,8 @@ public class AdminAuthServiceImpl implements AdminAuthService { @Resource private UserSessionService userSessionService; @Resource + private OAuth2TokenService oauth2TokenService; + @Resource private SocialUserService socialUserService; @Resource @@ -207,8 +209,12 @@ public class AdminAuthServiceImpl implements AdminAuthService { LoginLogTypeEnum logType, String userIp, String userAgent) { // 插入登陆日志 createLoginLog(loginUser.getId(), username, logType, LoginResultEnum.SUCCESS); - // 缓存登录用户到 Redis 中,返回 Token 令牌 - return userSessionService.createUserSession(loginUser, userIp, userAgent); + // 创建访问令牌 + // TODO userIp、userAgent + // TODO clientId + return oauth2TokenService.createAccessToken(loginUser.getId(), getUserType().getValue(), 1L) + .getAccessToken(); +// return userSessionService.createUserSession(loginUser, userIp, userAgent); } @Override diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ClientService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ClientService.java new file mode 100644 index 000000000..71211f410 --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ClientService.java @@ -0,0 +1,22 @@ +package cn.iocoder.yudao.module.system.service.auth; + +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2ClientDO; + +/** + * OAuth2.0 Client Service 接口 + * + * 从功能上,和 JdbcClientDetailsService 的功能,提供客户端的操作 + * + * @author 芋道源码 + */ +public interface OAuth2ClientService { + + /** + * 从缓存中,校验客户端是否合法 + * + * @param id 客户端编号 + * @return 客户端 + */ + OAuth2ClientDO validOAuthClientFromCache(Long id); + +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ClientServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ClientServiceImpl.java new file mode 100644 index 000000000..9416eb353 --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ClientServiceImpl.java @@ -0,0 +1,21 @@ +package cn.iocoder.yudao.module.system.service.auth; + +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2ClientDO; +import org.springframework.stereotype.Service; + +/** + * OAuth2.0 Client Service 实现类 + * + * @author 芋道源码 + */ +@Service +public class OAuth2ClientServiceImpl implements OAuth2ClientService { + + @Override + public OAuth2ClientDO validOAuthClientFromCache(Long id) { + return new OAuth2ClientDO().setId(id) + .setAccessTokenValiditySeconds(60 * 30) + .setRefreshTokenValiditySeconds(60 * 60 * 24 * 30); + } + +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ServiceImpl.java deleted file mode 100644 index da08b0a57..000000000 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ServiceImpl.java +++ /dev/null @@ -1,145 +0,0 @@ -package cn.iocoder.yudao.module.system.service.auth; - -import org.springframework.stereotype.Service; - -/** - * OAuth2.0 Service 实现类 - * - * - * - * @author 芋道源码 - */ -@Service -public class OAuth2ServiceImpl implements OAuth2TokenService { - -// @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 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); -// } - -} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenService.java index 7e6bed380..65e2ddb6f 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenService.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenService.java @@ -1,20 +1,66 @@ package cn.iocoder.yudao.module.system.service.auth; +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO; + /** * OAuth2.0 Token Service 接口 * - * 从功能上,和 Spring Security OAuth 的 JdbcTokenStore 的功能,提供访问令牌、刷新令牌的操作 + * 从功能上,和 Spring Security OAuth 的 DefaultTokenServices + JdbcTokenStore 的功能,提供访问令牌、刷新令牌的操作 * * @author 芋道源码 */ public interface OAuth2TokenService { -// OAuth2AccessTokenDO createAccessToken(Long userId, Integer userType, String createIp); -// -// OAuth2AccessTokenRespDTO checkAccessToken(String accessToken); -// -// OAuth2AccessTokenRespDTO refreshAccessToken(OAuth2RefreshAccessTokenReqDTO refreshAccessTokenDTO); -// -// void removeToken(OAuth2RemoveTokenByUserReqDTO removeTokenDTO); + /** + * 创建访问令牌 + * 注意:该流程中,会包含创建刷新令牌的创建 + * + * 参考 DefaultTokenServices 的 createAccessToken 方法 + * + * @param userId 用户编号 + * @param userType 用户类型 + * @param clientId 客户端编号 + * @return 访问令牌的信息 + */ + OAuth2AccessTokenDO createAccessToken(Long userId, Integer userType, Long clientId); + + /** + * 刷新访问令牌 + * + * 参考 DefaultTokenServices 的 refreshAccessToken 方法 + * + * @param refreshToken 刷新令牌 + * @return 访问令牌的信息 + */ + OAuth2AccessTokenDO refreshAccessToken(String refreshToken); + + /** + * 获得访问令牌 + * + * 参考 DefaultTokenServices 的 getAccessToken 方法 + * + * @param accessToken 访问令牌 + * @return 访问令牌的信息 + */ + OAuth2AccessTokenDO getAccessToken(String accessToken); + + /** + * 校验访问令牌 + * + * @param accessToken 访问令牌 + * @return 访问令牌的信息 + */ + OAuth2AccessTokenDO checkAccessToken(String accessToken); + + /** + * 移除访问令牌 + * 注意:该流程中,会移除相关的刷新令牌 + * + * 参考 DefaultTokenServices 的 revokeToken 方法 + * + * @param accessToken 刷新令牌 + * @return 是否移除到 + */ + boolean removeAccessToken(String accessToken); } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenServiceImpl.java new file mode 100644 index 000000000..a9940c482 --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenServiceImpl.java @@ -0,0 +1,182 @@ +package cn.iocoder.yudao.module.system.service.auth; + +import cn.hutool.core.util.IdUtil; +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; +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO; +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2ClientDO; +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2RefreshTokenDO; +import cn.iocoder.yudao.module.system.dal.mysql.auth.OAuth2AccessTokenMapper; +import cn.iocoder.yudao.module.system.dal.mysql.auth.OAuth2RefreshTokenMapper; +import cn.iocoder.yudao.module.system.dal.redis.auth.OAuth2AccessTokenRedisDAO; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.Calendar; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; + +/** + * OAuth2.0 Token Service 实现类 + * + * @author 芋道源码 + */ +@Service +public class OAuth2TokenServiceImpl implements OAuth2TokenService { + + @Resource + private OAuth2AccessTokenMapper oauth2AccessTokenMapper; + @Resource + private OAuth2RefreshTokenMapper oauth2RefreshTokenMapper; + + @Resource + private OAuth2AccessTokenRedisDAO oauth2AccessTokenRedisDAO; + + @Resource + private OAuth2ClientService oauth2ClientService; + + @Override + @Transactional + public OAuth2AccessTokenDO createAccessToken(Long userId, Integer userType, Long clientId) { + OAuth2ClientDO clientDO = oauth2ClientService.validOAuthClientFromCache(clientId); + // 创建刷新令牌 + OAuth2RefreshTokenDO refreshTokenDO = createOAuth2RefreshToken(userId, userType, clientDO); + // 创建访问令牌 + OAuth2AccessTokenDO accessTokenDO = createOAuth2AccessToken(refreshTokenDO, clientDO); + // 记录到 Redis 中 + oauth2AccessTokenRedisDAO.set(accessTokenDO); + return accessTokenDO; + } + + @Override + public OAuth2AccessTokenDO refreshAccessToken(String refreshToken) { + return null; + } + + @Override + public OAuth2AccessTokenDO getAccessToken(String accessToken) { + // 优先从 Redis 中获取 + OAuth2AccessTokenDO accessTokenDO = oauth2AccessTokenRedisDAO.get(accessToken); + if (accessTokenDO != null) { + return accessTokenDO; + } + + // 获取不到,从 MySQL 中获取 + accessTokenDO = oauth2AccessTokenMapper.selectById(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 exception(GlobalErrorCodeConstants.UNAUTHORIZED, "Token 不存在"); + } + if (DateUtils.isExpired(accessTokenDO.getExpiresTime())) { + throw exception(GlobalErrorCodeConstants.UNAUTHORIZED, "Token 已过期"); + } + return accessTokenDO; + } + + @Override + public boolean removeAccessToken(String accessToken) { + return false; + } + +// @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 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, OAuth2ClientDO clientDO) { + OAuth2AccessTokenDO accessToken = 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; + } + + private OAuth2RefreshTokenDO createOAuth2RefreshToken(Long userId, Integer userType, OAuth2ClientDO clientDO) { + OAuth2RefreshTokenDO refreshToken = new OAuth2RefreshTokenDO().setRefreshToken(generateRefreshToken()) + .setUserId(userId).setUserType(userType).setClientId(clientDO.getId()) + .setExpiresTime(DateUtils.addDate(Calendar.SECOND, clientDO.getRefreshTokenValiditySeconds())); + oauth2RefreshTokenMapper.insert(refreshToken); + return refreshToken; + } + + +// /** +// * 删除 accessToken 的 MySQL 与 Redis 的数据 +// * +// * @param accessToken 访问令牌 +// */ +// private void deleteOAuth2AccessToken(String accessToken) { +// // 删除 MySQL +// oauth2AccessTokenMapper.deleteById(accessToken); +// // 删除 Redis +// oauth2AccessTokenRedisDAO.delete(accessToken); +// } +// + private static String generateAccessToken() { + return IdUtil.fastSimpleUUID(); + } + + private static String generateRefreshToken() { + return IdUtil.fastSimpleUUID(); + } + +} diff --git a/yudao-server/pom.xml b/yudao-server/pom.xml index 4480f88a5..e243290d5 100644 --- a/yudao-server/pom.xml +++ b/yudao-server/pom.xml @@ -21,11 +21,11 @@ https://github.com/YunaiV/ruoyi-vue-pro - - cn.iocoder.boot - yudao-module-member-biz - ${revision} - + + + + + cn.iocoder.boot yudao-module-system-biz