Parcourir la source

实现管理后台登录时,使用 OAuth2 的 access token

YunaiV il y a 3 ans
Parent
commit
4f52d1367b
30 fichiers modifiés avec 626 ajouts et 357 suppressions
  1. 4 11
      yudao-framework/yudao-spring-boot-starter-security/pom.xml
  2. 4 2
      yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoSecurityAutoConfiguration.java
  3. 17 1
      yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/TokenAuthenticationFilter.java
  4. 0 30
      yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/service/SecurityAuthFrameworkService.java
  5. 2 2
      yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/util/WebFrameworkUtils.java
  6. 0 7
      yudao-module-system/yudao-module-system-api/pom.xml
  7. 0 12
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2Api.java
  8. 22 0
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2TokenApi.java
  9. 0 56
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/UserSessionApi.java
  10. 28 0
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenCheckRespDTO.java
  11. 37 0
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenCreateReqDTO.java
  12. 39 0
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenRespDTO.java
  13. 33 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2TokenApiImpl.java
  14. 0 47
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/auth/UserSessionApiImpl.java
  15. 4 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/UserSessionConvert.java
  16. 14 5
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2AccessTokenDO.java
  17. 20 16
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2ClientDO.java
  18. 3 3
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2CodeDO.java
  19. 8 5
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2RefreshTokenDO.java
  20. 32 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2AccessTokenMapper.java
  21. 16 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2RefreshTokenMapper.java
  22. 5 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/RedisKeyConstants.java
  23. 46 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/auth/OAuth2AccessTokenRedisDAO.java
  24. 8 2
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java
  25. 22 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ClientService.java
  26. 21 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ClientServiceImpl.java
  27. 0 145
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ServiceImpl.java
  28. 54 8
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenService.java
  29. 182 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenServiceImpl.java
  30. 5 5
      yudao-server/pom.xml

+ 4 - 11
yudao-framework/yudao-spring-boot-starter-security/pom.xml

@@ -44,18 +44,11 @@
             <artifactId>spring-boot-starter-security</artifactId>
         </dependency>
 
-        <!-- TODO 芋艿: -->
+        <!-- 业务组件 -->
         <dependency>
-            <groupId>org.activiti</groupId>
-            <artifactId>activiti-engine</artifactId>
-            <version>7.1.0.M6</version>
-            <exclusions>
-                <exclusion>
-                    <groupId>*</groupId>
-                    <artifactId>*</artifactId>
-                </exclusion>
-            </exclusions>
-            <optional>true</optional>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-module-system-api</artifactId> <!-- 需要使用它,进行 Token 的校验 -->
+            <version>${revision}</version>
         </dependency>
     </dependencies>
 

+ 4 - 2
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);
     }
 
     /**

+ 17 - 1
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);

+ 0 - 30
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/service/SecurityAuthFrameworkService.java

@@ -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();
-
-}

+ 2 - 2
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())) {

+ 0 - 7
yudao-module-system/yudao-module-system-api/pom.xml

@@ -29,13 +29,6 @@
             <optional>true</optional>
         </dependency>
 
-        <!-- 用户信息 -->
-        <dependency>
-            <groupId>cn.iocoder.boot</groupId>
-            <artifactId>yudao-spring-boot-starter-security</artifactId>
-            <optional>true</optional>
-        </dependency>
-
     </dependencies>
 
 </project>

+ 0 - 12
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2Api.java

@@ -1,12 +0,0 @@
-package cn.iocoder.yudao.module.system.api.auth;
-
-/**
- * OAuth2.0 API 接口
- *
- * @author 芋道源码
- */
-public interface OAuth2Api {
-
-
-
-}

+ 22 - 0
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);
+
+}

+ 0 - 56
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/UserSessionApi.java

@@ -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();
-
-}

+ 28 - 0
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;
+
+}

+ 37 - 0
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;
+
+}

+ 39 - 0
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;
+
+}

+ 33 - 0
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));
+    }
+
+}

+ 0 - 47
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/auth/UserSessionApiImpl.java

@@ -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();
-    }
-
-}

+ 4 - 0
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);
+
 }

+ 14 - 5
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;
     /**
      * 过期时间
      */

+ 20 - 16
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2ApplicationDO.java → 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<String> 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<String> redirectUris;
 
 }

+ 3 - 3
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;
     /**
      * 刷新令牌
      *

+ 8 - 5
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,7 +31,7 @@ public class OAuth2RefreshTokenDO extends BaseDO {
     /**
      * 用户编号
      */
-    private Integer userId;
+    private Long userId;
     /**
      * 用户类型
      *
@@ -38,12 +39,14 @@ public class OAuth2RefreshTokenDO extends BaseDO {
      */
     private Integer userType;
     /**
-     * 过期时间
+     * 客户端编号
+     *
+     * 关联 {@link OAuth2ClientDO#getId()}
      */
-    private Date expiresTime;
+    private Long clientId;
     /**
-     * 创建 IP
+     * 过期时间
      */
-    private String createIp;
+    private Date expiresTime;
 
 }

+ 32 - 0
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<OAuth2AccessTokenDO> {
+
+    default OAuth2AccessTokenDO selectByAccessToken(String accessToken) {
+        return selectOne(OAuth2AccessTokenDO::getAccessToken, accessToken);
+    }
+
+//    default OAuth2AccessTokenDO selectByUserIdAndUserType(Integer userId, Integer userType) {
+//        return selectOne(new QueryWrapper<OAuth2AccessTokenDO>()
+//                .eq("user_id", userId).eq("user_type", userType));
+//    }
+//
+//    default int deleteByUserIdAndUserType(Integer userId, Integer userType) {
+//        return delete(new QueryWrapper<OAuth2AccessTokenDO>()
+//                .eq("user_id", userId).eq("user_type", userType));
+//    }
+//
+//    default int deleteByRefreshToken(String refreshToken) {
+//        return delete(new QueryWrapper<OAuth2AccessTokenDO>().eq("refresh_token", refreshToken));
+//    }
+//
+//    default List<OAuth2AccessTokenDO> selectListByRefreshToken(String refreshToken) {
+//        return selectList(new QueryWrapper<OAuth2AccessTokenDO>().eq("refresh_token", refreshToken));
+//    }
+
+}

+ 16 - 0
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<OAuth2RefreshTokenDO> {
+
+    default int deleteByUserIdAndUserType(Integer userId, Integer userType) {
+        return delete(new QueryWrapper<OAuth2RefreshTokenDO>()
+                .eq("user_id", userId).eq("user_type", userType));
+    }
+
+}

+ 5 - 0
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

+ 46 - 0
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);
+    }
+
+}

+ 8 - 2
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

+ 22 - 0
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);
+
+}

+ 21 - 0
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);
+    }
+
+}

+ 0 - 145
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ServiceImpl.java

@@ -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<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);
-//    }
-
-}

+ 54 - 8
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);
 
 }

+ 182 - 0
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<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, 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();
+    }
+
+}

+ 5 - 5
yudao-server/pom.xml

@@ -21,11 +21,11 @@
     <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
 
     <dependencies>
-        <dependency>
-            <groupId>cn.iocoder.boot</groupId>
-            <artifactId>yudao-module-member-biz</artifactId>
-            <version>${revision}</version>
-        </dependency>
+<!--        <dependency>-->
+<!--            <groupId>cn.iocoder.boot</groupId>-->
+<!--            <artifactId>yudao-module-member-biz</artifactId>-->
+<!--            <version>${revision}</version>-->
+<!--        </dependency>-->
         <dependency>
             <groupId>cn.iocoder.boot</groupId>
             <artifactId>yudao-module-system-biz</artifactId>