Commit 4f52d1367be6ca504adff544cf069ef9f38456d8

Authored by YunaiV
1 parent ebee4ddb

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

Showing 30 changed files with 626 additions and 357 deletions
yudao-framework/yudao-spring-boot-starter-security/pom.xml
... ... @@ -44,18 +44,11 @@
44 44 <artifactId>spring-boot-starter-security</artifactId>
45 45 </dependency>
46 46  
47   - <!-- TODO 芋艿: -->
  47 + <!-- 业务组件 -->
48 48 <dependency>
49   - <groupId>org.activiti</groupId>
50   - <artifactId>activiti-engine</artifactId>
51   - <version>7.1.0.M6</version>
52   - <exclusions>
53   - <exclusion>
54   - <groupId>*</groupId>
55   - <artifactId>*</artifactId>
56   - </exclusion>
57   - </exclusions>
58   - <optional>true</optional>
  49 + <groupId>cn.iocoder.boot</groupId>
  50 + <artifactId>yudao-module-system-api</artifactId> <!-- 需要使用它,进行 Token 的校验 -->
  51 + <version>${revision}</version>
59 52 </dependency>
60 53 </dependencies>
61 54  
... ...
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
6 6 import cn.iocoder.yudao.framework.security.core.handler.AccessDeniedHandlerImpl;
7 7 import cn.iocoder.yudao.framework.security.core.handler.AuthenticationEntryPointImpl;
8 8 import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
  9 +import cn.iocoder.yudao.module.system.api.auth.OAuth2TokenApi;
9 10 import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
10 11 import org.springframework.boot.context.properties.EnableConfigurationProperties;
11 12 import org.springframework.context.annotation.Bean;
... ... @@ -72,8 +73,9 @@ public class YudaoSecurityAutoConfiguration {
72 73 * Token 认证过滤器 Bean
73 74 */
74 75 @Bean
75   - public TokenAuthenticationFilter authenticationTokenFilter(GlobalExceptionHandler globalExceptionHandler) {
76   - return new TokenAuthenticationFilter(securityProperties, globalExceptionHandler);
  76 + public TokenAuthenticationFilter authenticationTokenFilter(GlobalExceptionHandler globalExceptionHandler,
  77 + OAuth2TokenApi oauth2TokenApi) {
  78 + return new TokenAuthenticationFilter(securityProperties, globalExceptionHandler, oauth2TokenApi);
77 79 }
78 80  
79 81 /**
... ...
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/TokenAuthenticationFilter.java
1 1 package cn.iocoder.yudao.framework.security.core.filter;
2 2  
  3 +import cn.hutool.core.util.ObjectUtil;
3 4 import cn.hutool.core.util.StrUtil;
4 5 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 6 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
... ... @@ -8,7 +9,10 @@ import cn.iocoder.yudao.framework.security.core.LoginUser;
8 9 import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
9 10 import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
10 11 import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
  12 +import cn.iocoder.yudao.module.system.api.auth.OAuth2TokenApi;
  13 +import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenCheckRespDTO;
11 14 import lombok.RequiredArgsConstructor;
  15 +import org.springframework.security.access.AccessDeniedException;
12 16 import org.springframework.web.filter.OncePerRequestFilter;
13 17  
14 18 import javax.servlet.FilterChain;
... ... @@ -30,6 +34,8 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
30 34  
31 35 private final GlobalExceptionHandler globalExceptionHandler;
32 36  
  37 + private final OAuth2TokenApi oauth2TokenApi;
  38 +
33 39 @Override
34 40 @SuppressWarnings("NullableProblems")
35 41 protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
... ... @@ -39,11 +45,21 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
39 45 Integer userType = WebFrameworkUtils.getLoginUserType(request);
40 46 try {
41 47 // 验证 token 有效性
42   - LoginUser loginUser = null; // TODO 芋艿:待实现
  48 + OAuth2AccessTokenCheckRespDTO accessToken = oauth2TokenApi.checkAccessToken(token);
  49 + if (accessToken != null && ObjectUtil.notEqual(accessToken.getUserType(), userType)) { // 用户类型不匹配,无权限
  50 + throw new AccessDeniedException("错误的用户类型");
  51 + }
  52 + LoginUser loginUser = null;
  53 + if (accessToken != null) { // 如果不为空,说明认证通过,则转换成登录用户
  54 + loginUser = new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType())
  55 + .setTenantId(accessToken.getTenantId());
  56 + }
  57 +
43 58 // 模拟 Login 功能,方便日常开发调试
44 59 if (loginUser == null) {
45 60 loginUser = mockLoginUser(request, token, userType);
46 61 }
  62 +
47 63 // 设置当前用户
48 64 if (loginUser != null) {
49 65 SecurityFrameworkUtils.setLoginUser(loginUser, request);
... ...
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/service/SecurityAuthFrameworkService.java deleted
1   -package cn.iocoder.yudao.framework.security.core.service;
2   -
3   -import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
4   -import cn.iocoder.yudao.framework.security.core.LoginUser;
5   -import org.springframework.security.core.userdetails.UserDetailsService;
6   -
7   -/**
8   - * Security 框架 Auth Service 接口,定义不同用户类型的 {@link UserTypeEnum} 需要实现的方法
9   - *
10   - * @author 芋道源码
11   - */
12   -public interface SecurityAuthFrameworkService extends UserDetailsService {
13   -
14   - /**
15   - * 校验 token 的有效性,并获取用户信息
16   - * 通过后,刷新 token 的过期时间
17   - *
18   - * @param token token
19   - * @return 用户信息
20   - */
21   - LoginUser verifyTokenAndRefresh(String token);
22   -
23   - /**
24   - * 获得用户类型。每个用户类型,对应一个 SecurityAuthFrameworkService 实现类。
25   - *
26   - * @return 用户类型
27   - */
28   - UserTypeEnum getUserType();
29   -
30   -}
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 {
84 84 }
85 85 // 1. 优先,从 Attribute 中获取
86 86 Integer userType = (Integer) request.getAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_TYPE);
87   - if (userType == null) {
88   - return null;
  87 + if (userType != null) {
  88 + return userType;
89 89 }
90 90 // 2. 其次,基于 URL 前缀的约定
91 91 if (request.getRequestURI().startsWith(properties.getAdminApi().getPrefix())) {
... ...
yudao-module-system/yudao-module-system-api/pom.xml
... ... @@ -29,13 +29,6 @@
29 29 <optional>true</optional>
30 30 </dependency>
31 31  
32   - <!-- 用户信息 -->
33   - <dependency>
34   - <groupId>cn.iocoder.boot</groupId>
35   - <artifactId>yudao-spring-boot-starter-security</artifactId>
36   - <optional>true</optional>
37   - </dependency>
38   -
39 32 </dependencies>
40 33  
41 34 </project>
... ...
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2Api.java deleted
1   -package cn.iocoder.yudao.module.system.api.auth;
2   -
3   -/**
4   - * OAuth2.0 API 接口
5   - *
6   - * @author 芋道源码
7   - */
8   -public interface OAuth2Api {
9   -
10   -
11   -
12   -}
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2TokenApi.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.api.auth;
  2 +
  3 +import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenCheckRespDTO;
  4 +import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenCreateReqDTO;
  5 +import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenRespDTO;
  6 +
  7 +import javax.validation.Valid;
  8 +
  9 +/**
  10 + * OAuth2.0 Token API 接口
  11 + *
  12 + * @author 芋道源码
  13 + */
  14 +public interface OAuth2TokenApi {
  15 +
  16 + OAuth2AccessTokenRespDTO createAccessToken(@Valid OAuth2AccessTokenCreateReqDTO reqDTO);
  17 +
  18 + OAuth2AccessTokenCheckRespDTO checkAccessToken(String accessToken);
  19 +
  20 +// void removeToken(OAuth2RemoveTokenByUserReqDTO removeTokenDTO);
  21 +
  22 +}
... ...
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/UserSessionApi.java deleted
1   -package cn.iocoder.yudao.module.system.api.auth;
2   -
3   -import cn.iocoder.yudao.framework.security.core.LoginUser;
4   -
5   -import javax.validation.constraints.NotEmpty;
6   -import javax.validation.constraints.NotNull;
7   -
8   -/**
9   - * 在线用户 Session API 接口
10   - *
11   - * @author 芋道源码
12   - */
13   -public interface UserSessionApi {
14   -
15   - /**
16   - * 创建在线用户 Session
17   - *
18   - * @param loginUser 登录用户
19   - * @param userIp 用户 IP
20   - * @param userAgent 用户 UA
21   - * @return Token 令牌
22   - */
23   - String createUserSession(@NotNull(message = "登录用户不能为空") LoginUser loginUser, String userIp, String userAgent);
24   -
25   - /**
26   - * 刷新在线用户 Session 的更新时间
27   - *
28   - * @param token Token 令牌
29   - * @param loginUser 登录用户
30   - */
31   - void refreshUserSession(@NotEmpty(message = "Token 令牌不能为空") String token,
32   - @NotNull(message = "登录用户不能为空") LoginUser loginUser);
33   -
34   - /**
35   - * 删除在线用户 Session
36   - *
37   - * @param token Token 令牌
38   - */
39   - void deleteUserSession(String token);
40   -
41   - /**
42   - * 获得 Token 令牌对应的在线用户
43   - *
44   - * @param token Token 令牌
45   - * @return 在线用户
46   - */
47   - LoginUser getLoginUser(String token);
48   -
49   - /**
50   - * 获得 Session 超时时间,单位:毫秒
51   - *
52   - * @return 超时时间
53   - */
54   - Long getSessionTimeoutMillis();
55   -
56   -}
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenCheckRespDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.api.auth.dto;
  2 +
  3 +import lombok.Data;
  4 +
  5 +import java.io.Serializable;
  6 +
  7 +/**
  8 + * OAuth2.0 访问令牌的校验 Response DTO
  9 + *
  10 + * @author 芋道源码
  11 + */
  12 +@Data
  13 +public class OAuth2AccessTokenCheckRespDTO implements Serializable {
  14 +
  15 + /**
  16 + * 用户编号
  17 + */
  18 + private Long userId;
  19 + /**
  20 + * 用户类型
  21 + */
  22 + private Integer userType;
  23 + /**
  24 + * 租户编号
  25 + */
  26 + private Long tenantId;
  27 +
  28 +}
... ...
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenCreateReqDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.api.auth.dto;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
  4 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  5 +import lombok.Data;
  6 +import lombok.experimental.Accessors;
  7 +
  8 +import javax.validation.constraints.NotNull;
  9 +import java.io.Serializable;
  10 +
  11 +/**
  12 + * OAuth2.0 访问令牌创建 Request DTO
  13 + *
  14 + * @author 芋道源码
  15 + */
  16 +@Data
  17 +@Accessors(chain = true)
  18 +public class OAuth2AccessTokenCreateReqDTO implements Serializable {
  19 +
  20 + /**
  21 + * 用户编号
  22 + */
  23 + @NotNull(message = "用户编号不能为空")
  24 + private Integer userId;
  25 + /**
  26 + * 用户类型
  27 + */
  28 + @NotNull(message = "用户类型不能为空")
  29 + @InEnum(value = UserTypeEnum.class, message = "用户类型必须是 {value}")
  30 + private Integer userType;
  31 + /**
  32 + * 客户端编号
  33 + */
  34 + @NotNull(message = "客户端编号不能为空")
  35 + private Long clientId;
  36 +
  37 +}
... ...
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenRespDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.api.auth.dto;
  2 +
  3 +import lombok.Data;
  4 +import lombok.experimental.Accessors;
  5 +
  6 +import java.io.Serializable;
  7 +import java.util.Date;
  8 +
  9 +/**
  10 + * OAuth2.0 访问令牌的信息 Response DTO
  11 + *
  12 + * @author 芋道源码
  13 + */
  14 +@Data
  15 +@Accessors(chain = true)
  16 +public class OAuth2AccessTokenRespDTO implements Serializable {
  17 +
  18 + /**
  19 + * 访问令牌
  20 + */
  21 + private String accessToken;
  22 + /**
  23 + * 刷新令牌
  24 + */
  25 + private String refreshToken;
  26 + /**
  27 + * 用户编号
  28 + */
  29 + private Integer userId;
  30 + /**
  31 + * 用户类型
  32 + */
  33 + private Integer userType;
  34 + /**
  35 + * 过期时间
  36 + */
  37 + private Date expiresTime;
  38 +
  39 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2TokenApiImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.api.auth;
  2 +
  3 +import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenCheckRespDTO;
  4 +import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenCreateReqDTO;
  5 +import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenRespDTO;
  6 +import cn.iocoder.yudao.module.system.convert.auth.UserSessionConvert;
  7 +import cn.iocoder.yudao.module.system.service.auth.OAuth2TokenService;
  8 +import org.springframework.stereotype.Service;
  9 +
  10 +import javax.annotation.Resource;
  11 +
  12 +/**
  13 + * OAuth2.0 Token API 实现类
  14 + *
  15 + * @author 芋道源码
  16 + */
  17 +@Service
  18 +public class OAuth2TokenApiImpl implements OAuth2TokenApi {
  19 +
  20 + @Resource
  21 + private OAuth2TokenService oauth2TokenService;
  22 +
  23 + @Override
  24 + public OAuth2AccessTokenRespDTO createAccessToken(OAuth2AccessTokenCreateReqDTO reqDTO) {
  25 + return null;
  26 + }
  27 +
  28 + @Override
  29 + public OAuth2AccessTokenCheckRespDTO checkAccessToken(String accessToken) {
  30 + return UserSessionConvert.INSTANCE.convert(oauth2TokenService.checkAccessToken(accessToken));
  31 + }
  32 +
  33 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/auth/UserSessionApiImpl.java deleted
1   -package cn.iocoder.yudao.module.system.api.auth;
2   -
3   -import cn.iocoder.yudao.framework.security.core.LoginUser;
4   -import cn.iocoder.yudao.module.system.service.auth.UserSessionService;
5   -import org.springframework.stereotype.Service;
6   -import org.springframework.validation.annotation.Validated;
7   -
8   -import javax.annotation.Resource;
9   -
10   -/**
11   - * 在线用户 Session API 实现类
12   - *
13   - * @author 芋道源码
14   - */
15   -@Service
16   -@Validated
17   -public class UserSessionApiImpl implements UserSessionApi {
18   -
19   - @Resource
20   - private UserSessionService userSessionService;
21   -
22   - @Override
23   - public String createUserSession(LoginUser loginUser, String userIp, String userAgent) {
24   - return userSessionService.createUserSession(loginUser, userIp, userAgent);
25   - }
26   -
27   - @Override
28   - public void refreshUserSession(String token, LoginUser loginUser) {
29   - userSessionService.refreshUserSession(token, loginUser);
30   - }
31   -
32   - @Override
33   - public void deleteUserSession(String token) {
34   - userSessionService.deleteUserSession(token);
35   - }
36   -
37   - @Override
38   - public LoginUser getLoginUser(String token) {
39   - return userSessionService.getLoginUser(token);
40   - }
41   -
42   - @Override
43   - public Long getSessionTimeoutMillis() {
44   - return userSessionService.getSessionTimeoutMillis();
45   - }
46   -
47   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/UserSessionConvert.java
1 1 package cn.iocoder.yudao.module.system.convert.auth;
2 2  
  3 +import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenCheckRespDTO;
3 4 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.session.UserSessionPageItemRespVO;
  5 +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO;
4 6 import cn.iocoder.yudao.module.system.dal.dataobject.auth.UserSessionDO;
5 7 import org.mapstruct.Mapper;
6 8 import org.mapstruct.factory.Mappers;
... ... @@ -12,4 +14,6 @@ public interface UserSessionConvert {
12 14  
13 15 UserSessionPageItemRespVO convert(UserSessionDO session);
14 16  
  17 + OAuth2AccessTokenCheckRespDTO convert(OAuth2AccessTokenDO bean);
  18 +
15 19 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2AccessTokenDO.java
1 1 package cn.iocoder.yudao.module.system.dal.dataobject.auth;
2 2  
3 3 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
4   -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  4 +import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
  5 +import com.baomidou.mybatisplus.annotation.TableId;
5 6 import com.baomidou.mybatisplus.annotation.TableName;
6 7 import lombok.Data;
7 8 import lombok.EqualsAndHashCode;
... ... @@ -12,23 +13,31 @@ import java.util.Date;
12 13 /**
13 14 * OAuth2 访问令牌 DO
14 15 *
  16 + * 如下字段,暂时未使用,暂时不支持:
  17 + * user_name、authentication(用户信息)
  18 + *
15 19 * @author 芋道源码
16 20 */
17 21 @TableName("system_oauth2_access_token")
18 22 @Data
19 23 @EqualsAndHashCode(callSuper = true)
20 24 @Accessors(chain = true)
21   -public class OAuth2AccessTokenDO extends BaseDO {
  25 +public class OAuth2AccessTokenDO extends TenantBaseDO {
22 26  
23 27 /**
24 28 * 编号,数据库递增
25 29 */
  30 + @TableId
26 31 private Long id;
27 32 /**
28 33 * 访问令牌
29 34 */
30 35 private String accessToken;
31 36 /**
  37 + * 刷新令牌
  38 + */
  39 + private String refreshToken;
  40 + /**
32 41 * 用户编号
33 42 */
34 43 private Long userId;
... ... @@ -39,11 +48,11 @@ public class OAuth2AccessTokenDO extends BaseDO {
39 48 */
40 49 private Integer userType;
41 50 /**
42   - * 应用编号
  51 + * 客户端编号
43 52 *
44   - * 关联 {@link OAuth2ApplicationDO#getId()}
  53 + * 关联 {@link OAuth2ClientDO#getId()}
45 54 */
46   - private Long applicationId;
  55 + private Long clientId;
47 56 /**
48 57 * 过期时间
49 58 */
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2ApplicationDO.java renamed to 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;
2 2  
3 3 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
4 4 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  5 +import com.baomidou.mybatisplus.annotation.TableId;
5 6 import com.baomidou.mybatisplus.annotation.TableName;
6 7 import lombok.Data;
7 8 import lombok.EqualsAndHashCode;
... ... @@ -12,12 +13,8 @@ import java.util.List;
12 13 /**
13 14 * OAuth2 客户端 DO
14 15 *
15   - * 为什么不使用 Client 作为表名?
16   - * 1. clientId 字段被占用,导致表的 id 无法有合适的缩写
17   - * 2. 大多数 Github、Gitee 等平台,都会习惯称为第三方接入应用
18   - *
19 16 * 如下字段,考虑到使用相对不是很高频,主要是一些开关,暂时不支持:
20   - * authorized_grant_types、authorities、access_token_validity、refresh_token_validity、additional_information、autoapprove、resource_ids、scope
  17 + * authorized_grant_types、authorities、additional_information、autoapprove、resource_ids、scope
21 18 *
22 19 * @author 芋道源码
23 20 */
... ... @@ -25,24 +22,19 @@ import java.util.List;
25 22 @Data
26 23 @EqualsAndHashCode(callSuper = true)
27 24 @Accessors(chain = true)
28   -public class OAuth2ApplicationDO extends BaseDO {
  25 +public class OAuth2ClientDO extends BaseDO {
29 26  
30 27 /**
31   - * 编号,数据库递增
32   - */
33   - private Long id;
34   - /**
35 28 * 客户端编号
  29 + *
  30 + * 由于 SQL Server 在存储 String 主键有点问题,所以暂时使用 Long 类型
36 31 */
37   - private String clientId;
  32 + @TableId
  33 + private Long id;
38 34 /**
39 35 * 客户端密钥
40 36 */
41   - private String clientSecret;
42   - /**
43   - * 可重定向的 URI 地址
44   - */
45   - private List<String> redirectUris;
  37 + private String secret;
46 38 /**
47 39 * 应用名
48 40 */
... ... @@ -61,5 +53,17 @@ public class OAuth2ApplicationDO extends BaseDO {
61 53 * 枚举 {@link CommonStatusEnum}
62 54 */
63 55 private Integer status;
  56 + /**
  57 + * 访问令牌的有效期
  58 + */
  59 + private Integer accessTokenValiditySeconds;
  60 + /**
  61 + * 刷新令牌的有效期
  62 + */
  63 + private Integer refreshTokenValiditySeconds;
  64 + /**
  65 + * 可重定向的 URI 地址
  66 + */
  67 + private List<String> redirectUris;
64 68  
65 69 }
... ...
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 {
39 39 */
40 40 private Integer userType;
41 41 /**
42   - * 应用编号
  42 + * 客户端编号
43 43 *
44   - * 关联 {@link OAuth2ApplicationDO#getId()}
  44 + * 关联 {@link OAuth2ClientDO#getId()}
45 45 */
46   - private Long applicationId;
  46 + private Long clientId;
47 47 /**
48 48 * 刷新令牌
49 49 *
... ...
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;
12 12 /**
13 13 * OAuth2 刷新令牌
14 14 *
  15 + * @author 芋道源码
15 16 */
16 17 @TableName("system_oauth2_refresh_token")
17 18 @Data
... ... @@ -30,7 +31,7 @@ public class OAuth2RefreshTokenDO extends BaseDO {
30 31 /**
31 32 * 用户编号
32 33 */
33   - private Integer userId;
  34 + private Long userId;
34 35 /**
35 36 * 用户类型
36 37 *
... ... @@ -38,12 +39,14 @@ public class OAuth2RefreshTokenDO extends BaseDO {
38 39 */
39 40 private Integer userType;
40 41 /**
41   - * 过期时间
  42 + * 客户端编号
  43 + *
  44 + * 关联 {@link OAuth2ClientDO#getId()}
42 45 */
43   - private Date expiresTime;
  46 + private Long clientId;
44 47 /**
45   - * 创建 IP
  48 + * 过期时间
46 49 */
47   - private String createIp;
  50 + private Date expiresTime;
48 51  
49 52 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2AccessTokenMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.dal.mysql.auth;
  2 +
  3 +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  4 +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO;
  5 +import org.apache.ibatis.annotations.Mapper;
  6 +
  7 +@Mapper
  8 +public interface OAuth2AccessTokenMapper extends BaseMapperX<OAuth2AccessTokenDO> {
  9 +
  10 + default OAuth2AccessTokenDO selectByAccessToken(String accessToken) {
  11 + return selectOne(OAuth2AccessTokenDO::getAccessToken, accessToken);
  12 + }
  13 +
  14 +// default OAuth2AccessTokenDO selectByUserIdAndUserType(Integer userId, Integer userType) {
  15 +// return selectOne(new QueryWrapper<OAuth2AccessTokenDO>()
  16 +// .eq("user_id", userId).eq("user_type", userType));
  17 +// }
  18 +//
  19 +// default int deleteByUserIdAndUserType(Integer userId, Integer userType) {
  20 +// return delete(new QueryWrapper<OAuth2AccessTokenDO>()
  21 +// .eq("user_id", userId).eq("user_type", userType));
  22 +// }
  23 +//
  24 +// default int deleteByRefreshToken(String refreshToken) {
  25 +// return delete(new QueryWrapper<OAuth2AccessTokenDO>().eq("refresh_token", refreshToken));
  26 +// }
  27 +//
  28 +// default List<OAuth2AccessTokenDO> selectListByRefreshToken(String refreshToken) {
  29 +// return selectList(new QueryWrapper<OAuth2AccessTokenDO>().eq("refresh_token", refreshToken));
  30 +// }
  31 +
  32 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2RefreshTokenMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.dal.mysql.auth;
  2 +
  3 +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2RefreshTokenDO;
  4 +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  5 +import com.baomidou.mybatisplus.core.mapper.BaseMapper;
  6 +import org.apache.ibatis.annotations.Mapper;
  7 +
  8 +@Mapper
  9 +public interface OAuth2RefreshTokenMapper extends BaseMapper<OAuth2RefreshTokenDO> {
  10 +
  11 + default int deleteByUserIdAndUserType(Integer userId, Integer userType) {
  12 + return delete(new QueryWrapper<OAuth2RefreshTokenDO>()
  13 + .eq("user_id", userId).eq("user_type", userType));
  14 + }
  15 +
  16 +}
... ...
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;
2 2  
3 3 import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine;
4 4 import cn.iocoder.yudao.framework.security.core.LoginUser;
  5 +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO;
5 6  
6 7 import java.time.Duration;
7 8  
... ... @@ -22,6 +23,10 @@ public interface RedisKeyConstants {
22 23 "login_user:%s", // 参数为 token 令牌
23 24 STRING, LoginUser.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC);
24 25  
  26 + RedisKeyDefine OAUTH2_ACCESS_TOKEN = new RedisKeyDefine("访问令牌的缓存",
  27 + "oauth2_access_token:%s", // 参数为访问令牌 token
  28 + STRING, OAuth2AccessTokenDO.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC);
  29 +
25 30 RedisKeyDefine SOCIAL_AUTH_STATE = new RedisKeyDefine("社交登陆的 state", // 注意,它是被 JustAuth 的 justauth.type.prefix 使用到
26 31 "social_auth_state:%s", // 参数为 state
27 32 STRING, String.class, Duration.ofHours(24)); // 值为 state
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/auth/OAuth2AccessTokenRedisDAO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.dal.redis.auth;
  2 +
  3 +import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
  4 +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO;
  5 +import org.springframework.data.redis.core.StringRedisTemplate;
  6 +import org.springframework.stereotype.Repository;
  7 +
  8 +import javax.annotation.Resource;
  9 +import java.util.concurrent.TimeUnit;
  10 +
  11 +import static cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants.OAUTH2_ACCESS_TOKEN;
  12 +
  13 +/**
  14 + * {@link OAuth2AccessTokenDO} 的 RedisDAO
  15 + *
  16 + * @author 芋道源码
  17 + */
  18 +@Repository
  19 +public class OAuth2AccessTokenRedisDAO {
  20 +
  21 + @Resource
  22 + private StringRedisTemplate stringRedisTemplate;
  23 +
  24 + public OAuth2AccessTokenDO get(String accessToken) {
  25 + String redisKey = formatKey(accessToken);
  26 + return JsonUtils.parseObject(stringRedisTemplate.opsForValue().get(redisKey), OAuth2AccessTokenDO.class);
  27 + }
  28 +
  29 + public void set(OAuth2AccessTokenDO accessTokenDO) {
  30 + String redisKey = formatKey(accessTokenDO.getAccessToken());
  31 + // 清理多余字段,避免缓存
  32 + accessTokenDO.setUpdater(null).setUpdateTime(null).setCreateTime(null).setCreator(null).setDeleted(null);
  33 + stringRedisTemplate.opsForValue().set(redisKey, JsonUtils.toJsonString(accessTokenDO),
  34 + accessTokenDO.getExpiresTime().getTime() - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
  35 + }
  36 +
  37 + public void delete(String accessToken) {
  38 + String redisKey = formatKey(accessToken);
  39 + stringRedisTemplate.delete(redisKey);
  40 + }
  41 +
  42 + private static String formatKey(String accessToken) {
  43 + return String.format(OAUTH2_ACCESS_TOKEN.getKeyTemplate(), accessToken);
  44 + }
  45 +
  46 +}
... ...
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 {
49 49 @Resource
50 50 private UserSessionService userSessionService;
51 51 @Resource
  52 + private OAuth2TokenService oauth2TokenService;
  53 + @Resource
52 54 private SocialUserService socialUserService;
53 55  
54 56 @Resource
... ... @@ -207,8 +209,12 @@ public class AdminAuthServiceImpl implements AdminAuthService {
207 209 LoginLogTypeEnum logType, String userIp, String userAgent) {
208 210 // 插入登陆日志
209 211 createLoginLog(loginUser.getId(), username, logType, LoginResultEnum.SUCCESS);
210   - // 缓存登录用户到 Redis 中,返回 Token 令牌
211   - return userSessionService.createUserSession(loginUser, userIp, userAgent);
  212 + // 创建访问令牌
  213 + // TODO userIp、userAgent
  214 + // TODO clientId
  215 + return oauth2TokenService.createAccessToken(loginUser.getId(), getUserType().getValue(), 1L)
  216 + .getAccessToken();
  217 +// return userSessionService.createUserSession(loginUser, userIp, userAgent);
212 218 }
213 219  
214 220 @Override
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ClientService.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.service.auth;
  2 +
  3 +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2ClientDO;
  4 +
  5 +/**
  6 + * OAuth2.0 Client Service 接口
  7 + *
  8 + * 从功能上,和 JdbcClientDetailsService 的功能,提供客户端的操作
  9 + *
  10 + * @author 芋道源码
  11 + */
  12 +public interface OAuth2ClientService {
  13 +
  14 + /**
  15 + * 从缓存中,校验客户端是否合法
  16 + *
  17 + * @param id 客户端编号
  18 + * @return 客户端
  19 + */
  20 + OAuth2ClientDO validOAuthClientFromCache(Long id);
  21 +
  22 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ClientServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.service.auth;
  2 +
  3 +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2ClientDO;
  4 +import org.springframework.stereotype.Service;
  5 +
  6 +/**
  7 + * OAuth2.0 Client Service 实现类
  8 + *
  9 + * @author 芋道源码
  10 + */
  11 +@Service
  12 +public class OAuth2ClientServiceImpl implements OAuth2ClientService {
  13 +
  14 + @Override
  15 + public OAuth2ClientDO validOAuthClientFromCache(Long id) {
  16 + return new OAuth2ClientDO().setId(id)
  17 + .setAccessTokenValiditySeconds(60 * 30)
  18 + .setRefreshTokenValiditySeconds(60 * 60 * 24 * 30);
  19 + }
  20 +
  21 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ServiceImpl.java deleted
1   -package cn.iocoder.yudao.module.system.service.auth;
2   -
3   -import org.springframework.stereotype.Service;
4   -
5   -/**
6   - * OAuth2.0 Service 实现类
7   - *
8   - *
9   - *
10   - * @author 芋道源码
11   - */
12   -@Service
13   -public class OAuth2ServiceImpl implements OAuth2TokenService {
14   -
15   -// @Autowired
16   -// private SystemBizProperties systemBizProperties;
17   -//
18   -// @Autowired
19   -// private OAuth2AccessTokenMapper oauth2AccessTokenMapper;
20   -// @Autowired
21   -// private OAuth2RefreshTokenMapper oauth2RefreshTokenMapper;
22   -//
23   -// @Autowired
24   -// private OAuth2AccessTokenRedisDAO oauth2AccessTokenRedisDAO;
25   -//
26   -// @Override
27   -// @Transactional
28   -// public OAuth2AccessTokenRespDTO createAccessToken(OAuth2CreateAccessTokenReqDTO createAccessTokenDTO) {
29   -// // 创建刷新令牌 + 访问令牌
30   -// OAuth2RefreshTokenDO refreshTokenDO = createOAuth2RefreshToken(createAccessTokenDTO.getUserId(),
31   -// createAccessTokenDTO.getUserType(), createAccessTokenDTO.getCreateIp());
32   -// OAuth2AccessTokenDO accessTokenDO = createOAuth2AccessToken(refreshTokenDO, createAccessTokenDTO.getCreateIp());
33   -// // 返回访问令牌
34   -// return OAuth2Convert.INSTANCE.convert(accessTokenDO);
35   -// }
36   -//
37   -// @Override
38   -// @Transactional
39   -// public OAuth2AccessTokenRespDTO checkAccessToken(String accessToken) {
40   -// OAuth2AccessTokenDO accessTokenDO = this.getOAuth2AccessToken(accessToken);
41   -// if (accessTokenDO == null) { // 不存在
42   -// throw ServiceExceptionUtil.exception(OAUTH2_ACCESS_TOKEN_NOT_FOUND);
43   -// }
44   -// if (accessTokenDO.getExpiresTime().getTime() < System.currentTimeMillis()) { // 已过期
45   -// throw ServiceExceptionUtil.exception(OAUTH2_ACCESS_TOKEN_TOKEN_EXPIRED);
46   -// }
47   -// // 返回访问令牌
48   -// return OAuth2Convert.INSTANCE.convert(accessTokenDO);
49   -// }
50   -//
51   -// @Override
52   -// @Transactional
53   -// public OAuth2AccessTokenRespDTO refreshAccessToken(OAuth2RefreshAccessTokenReqDTO refreshAccessTokenDTO) {
54   -// OAuth2RefreshTokenDO refreshTokenDO = oauth2RefreshTokenMapper.selectById(refreshAccessTokenDTO.getRefreshToken());
55   -// // 校验刷新令牌是否合法
56   -// if (refreshTokenDO == null) { // 不存在
57   -// throw ServiceExceptionUtil.exception(OAUTH2_REFRESH_TOKEN_NOT_FOUND);
58   -// }
59   -// if (refreshTokenDO.getExpiresTime().getTime() < System.currentTimeMillis()) { // 已过期
60   -// throw ServiceExceptionUtil.exception(OAUTH_REFRESH_TOKEN_EXPIRED);
61   -// }
62   -//
63   -// // 标记 refreshToken 对应的 accessToken 都不合法
64   -// // 这块的实现,参考了 Spring Security OAuth2 的代码
65   -// List<OAuth2AccessTokenDO> accessTokenDOs = oauth2AccessTokenMapper.selectListByRefreshToken(refreshAccessTokenDTO.getRefreshToken());
66   -// accessTokenDOs.forEach(accessTokenDO -> deleteOAuth2AccessToken(accessTokenDO.getId()));
67   -//
68   -// // 创建访问令牌
69   -// OAuth2AccessTokenDO oauth2AccessTokenDO = createOAuth2AccessToken(refreshTokenDO, refreshAccessTokenDTO.getCreateIp());
70   -// // 返回访问令牌
71   -// return OAuth2Convert.INSTANCE.convert(oauth2AccessTokenDO);
72   -// }
73   -//
74   -// @Override
75   -// @Transactional
76   -// public void removeToken(OAuth2RemoveTokenByUserReqDTO removeTokenDTO) {
77   -// // 删除 Access Token
78   -// OAuth2AccessTokenDO accessTokenDO = oauth2AccessTokenMapper.selectByUserIdAndUserType(
79   -// removeTokenDTO.getUserId(), removeTokenDTO.getUserType());
80   -// if (accessTokenDO != null) {
81   -// this.deleteOAuth2AccessToken(accessTokenDO.getId());
82   -// }
83   -//
84   -// // 删除 Refresh Token
85   -// oauth2RefreshTokenMapper.deleteByUserIdAndUserType(removeTokenDTO.getUserId(), removeTokenDTO.getUserType());
86   -// }
87   -//
88   -// private OAuth2AccessTokenDO createOAuth2AccessToken(OAuth2RefreshTokenDO refreshTokenDO, String createIp) {
89   -// OAuth2AccessTokenDO accessToken = new OAuth2AccessTokenDO()
90   -// .setId(generateAccessToken())
91   -// .setUserId(refreshTokenDO.getUserId()).setUserType(refreshTokenDO.getUserType())
92   -// .setRefreshToken(refreshTokenDO.getId())
93   -// .setExpiresTime(new Date(System.currentTimeMillis() + systemBizProperties.getAccessTokenExpireTimeMillis()))
94   -// .setCreateIp(createIp);
95   -// oauth2AccessTokenMapper.insert(accessToken);
96   -// return accessToken;
97   -// }
98   -//
99   -// private OAuth2RefreshTokenDO createOAuth2RefreshToken(Integer userId, Integer userType, String createIp) {
100   -// OAuth2RefreshTokenDO refreshToken = new OAuth2RefreshTokenDO()
101   -// .setId(generateRefreshToken())
102   -// .setUserId(userId).setUserType(userType)
103   -// .setExpiresTime(new Date(System.currentTimeMillis() + systemBizProperties.getRefreshTokenExpireTimeMillis()))
104   -// .setCreateIp(createIp);
105   -// oauth2RefreshTokenMapper.insert(refreshToken);
106   -// return refreshToken;
107   -// }
108   -//
109   -// private OAuth2AccessTokenDO getOAuth2AccessToken(String accessToken) {
110   -// // 优先从 Redis 中获取
111   -// OAuth2AccessTokenDO accessTokenDO = oauth2AccessTokenRedisDAO.get(accessToken);
112   -// if (accessTokenDO != null) {
113   -// return accessTokenDO;
114   -// }
115   -//
116   -// // 获取不到,从 MySQL 中获取
117   -// accessTokenDO = oauth2AccessTokenMapper.selectById(accessToken);
118   -// // 如果在 MySQL 存在,则往 Redis 中写入
119   -// if (accessTokenDO != null) {
120   -// oauth2AccessTokenRedisDAO.set(accessTokenDO);
121   -// }
122   -// return accessTokenDO;
123   -// }
124   -//
125   -// /**
126   -// * 删除 accessToken 的 MySQL 与 Redis 的数据
127   -// *
128   -// * @param accessToken 访问令牌
129   -// */
130   -// private void deleteOAuth2AccessToken(String accessToken) {
131   -// // 删除 MySQL
132   -// oauth2AccessTokenMapper.deleteById(accessToken);
133   -// // 删除 Redis
134   -// oauth2AccessTokenRedisDAO.delete(accessToken);
135   -// }
136   -//
137   -// private static String generateAccessToken() {
138   -// return StringUtils.uuid(true);
139   -// }
140   -//
141   -// private static String generateRefreshToken() {
142   -// return StringUtils.uuid(true);
143   -// }
144   -
145   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenService.java
1 1 package cn.iocoder.yudao.module.system.service.auth;
2 2  
  3 +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO;
  4 +
3 5 /**
4 6 * OAuth2.0 Token Service 接口
5 7 *
6   - * 从功能上,和 Spring Security OAuth 的 JdbcTokenStore 的功能,提供访问令牌、刷新令牌的操作
  8 + * 从功能上,和 Spring Security OAuth 的 DefaultTokenServices + JdbcTokenStore 的功能,提供访问令牌、刷新令牌的操作
7 9 *
8 10 * @author 芋道源码
9 11 */
10 12 public interface OAuth2TokenService {
11 13  
12   -// OAuth2AccessTokenDO createAccessToken(Long userId, Integer userType, String createIp);
13   -//
14   -// OAuth2AccessTokenRespDTO checkAccessToken(String accessToken);
15   -//
16   -// OAuth2AccessTokenRespDTO refreshAccessToken(OAuth2RefreshAccessTokenReqDTO refreshAccessTokenDTO);
17   -//
18   -// void removeToken(OAuth2RemoveTokenByUserReqDTO removeTokenDTO);
  14 + /**
  15 + * 创建访问令牌
  16 + * 注意:该流程中,会包含创建刷新令牌的创建
  17 + *
  18 + * 参考 DefaultTokenServices 的 createAccessToken 方法
  19 + *
  20 + * @param userId 用户编号
  21 + * @param userType 用户类型
  22 + * @param clientId 客户端编号
  23 + * @return 访问令牌的信息
  24 + */
  25 + OAuth2AccessTokenDO createAccessToken(Long userId, Integer userType, Long clientId);
  26 +
  27 + /**
  28 + * 刷新访问令牌
  29 + *
  30 + * 参考 DefaultTokenServices 的 refreshAccessToken 方法
  31 + *
  32 + * @param refreshToken 刷新令牌
  33 + * @return 访问令牌的信息
  34 + */
  35 + OAuth2AccessTokenDO refreshAccessToken(String refreshToken);
  36 +
  37 + /**
  38 + * 获得访问令牌
  39 + *
  40 + * 参考 DefaultTokenServices 的 getAccessToken 方法
  41 + *
  42 + * @param accessToken 访问令牌
  43 + * @return 访问令牌的信息
  44 + */
  45 + OAuth2AccessTokenDO getAccessToken(String accessToken);
  46 +
  47 + /**
  48 + * 校验访问令牌
  49 + *
  50 + * @param accessToken 访问令牌
  51 + * @return 访问令牌的信息
  52 + */
  53 + OAuth2AccessTokenDO checkAccessToken(String accessToken);
  54 +
  55 + /**
  56 + * 移除访问令牌
  57 + * 注意:该流程中,会移除相关的刷新令牌
  58 + *
  59 + * 参考 DefaultTokenServices 的 revokeToken 方法
  60 + *
  61 + * @param accessToken 刷新令牌
  62 + * @return 是否移除到
  63 + */
  64 + boolean removeAccessToken(String accessToken);
19 65  
20 66 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.service.auth;
  2 +
  3 +import cn.hutool.core.util.IdUtil;
  4 +import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
  5 +import cn.iocoder.yudao.framework.common.util.date.DateUtils;
  6 +import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
  7 +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO;
  8 +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2ClientDO;
  9 +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2RefreshTokenDO;
  10 +import cn.iocoder.yudao.module.system.dal.mysql.auth.OAuth2AccessTokenMapper;
  11 +import cn.iocoder.yudao.module.system.dal.mysql.auth.OAuth2RefreshTokenMapper;
  12 +import cn.iocoder.yudao.module.system.dal.redis.auth.OAuth2AccessTokenRedisDAO;
  13 +import org.springframework.stereotype.Service;
  14 +import org.springframework.transaction.annotation.Transactional;
  15 +
  16 +import javax.annotation.Resource;
  17 +import java.util.Calendar;
  18 +
  19 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  20 +
  21 +/**
  22 + * OAuth2.0 Token Service 实现类
  23 + *
  24 + * @author 芋道源码
  25 + */
  26 +@Service
  27 +public class OAuth2TokenServiceImpl implements OAuth2TokenService {
  28 +
  29 + @Resource
  30 + private OAuth2AccessTokenMapper oauth2AccessTokenMapper;
  31 + @Resource
  32 + private OAuth2RefreshTokenMapper oauth2RefreshTokenMapper;
  33 +
  34 + @Resource
  35 + private OAuth2AccessTokenRedisDAO oauth2AccessTokenRedisDAO;
  36 +
  37 + @Resource
  38 + private OAuth2ClientService oauth2ClientService;
  39 +
  40 + @Override
  41 + @Transactional
  42 + public OAuth2AccessTokenDO createAccessToken(Long userId, Integer userType, Long clientId) {
  43 + OAuth2ClientDO clientDO = oauth2ClientService.validOAuthClientFromCache(clientId);
  44 + // 创建刷新令牌
  45 + OAuth2RefreshTokenDO refreshTokenDO = createOAuth2RefreshToken(userId, userType, clientDO);
  46 + // 创建访问令牌
  47 + OAuth2AccessTokenDO accessTokenDO = createOAuth2AccessToken(refreshTokenDO, clientDO);
  48 + // 记录到 Redis 中
  49 + oauth2AccessTokenRedisDAO.set(accessTokenDO);
  50 + return accessTokenDO;
  51 + }
  52 +
  53 + @Override
  54 + public OAuth2AccessTokenDO refreshAccessToken(String refreshToken) {
  55 + return null;
  56 + }
  57 +
  58 + @Override
  59 + public OAuth2AccessTokenDO getAccessToken(String accessToken) {
  60 + // 优先从 Redis 中获取
  61 + OAuth2AccessTokenDO accessTokenDO = oauth2AccessTokenRedisDAO.get(accessToken);
  62 + if (accessTokenDO != null) {
  63 + return accessTokenDO;
  64 + }
  65 +
  66 + // 获取不到,从 MySQL 中获取
  67 + accessTokenDO = oauth2AccessTokenMapper.selectById(accessToken);
  68 + // 如果在 MySQL 存在,则往 Redis 中写入
  69 + if (accessTokenDO != null && !DateUtils.isExpired(accessTokenDO.getExpiresTime())) {
  70 + oauth2AccessTokenRedisDAO.set(accessTokenDO);
  71 + }
  72 + return accessTokenDO;
  73 + }
  74 +
  75 + @Override
  76 + public OAuth2AccessTokenDO checkAccessToken(String accessToken) {
  77 + OAuth2AccessTokenDO accessTokenDO = getAccessToken(accessToken);
  78 + if (accessTokenDO == null) {
  79 + throw exception(GlobalErrorCodeConstants.UNAUTHORIZED, "Token 不存在");
  80 + }
  81 + if (DateUtils.isExpired(accessTokenDO.getExpiresTime())) {
  82 + throw exception(GlobalErrorCodeConstants.UNAUTHORIZED, "Token 已过期");
  83 + }
  84 + return accessTokenDO;
  85 + }
  86 +
  87 + @Override
  88 + public boolean removeAccessToken(String accessToken) {
  89 + return false;
  90 + }
  91 +
  92 +// @Override
  93 +// @Transactional
  94 +// public OAuth2AccessTokenRespDTO checkAccessToken(String accessToken) {
  95 +// OAuth2AccessTokenDO accessTokenDO = this.getOAuth2AccessToken(accessToken);
  96 +// if (accessTokenDO == null) { // 不存在
  97 +// throw ServiceExceptionUtil.exception(OAUTH2_ACCESS_TOKEN_NOT_FOUND);
  98 +// }
  99 +// if (accessTokenDO.getExpiresTime().getTime() < System.currentTimeMillis()) { // 已过期
  100 +// throw ServiceExceptionUtil.exception(OAUTH2_ACCESS_TOKEN_TOKEN_EXPIRED);
  101 +// }
  102 +// // 返回访问令牌
  103 +// return OAuth2Convert.INSTANCE.convert(accessTokenDO);
  104 +// }
  105 +
  106 +// @Override
  107 +// @Transactional
  108 +// public OAuth2AccessTokenRespDTO refreshAccessToken(OAuth2RefreshAccessTokenReqDTO refreshAccessTokenDTO) {
  109 +// OAuth2RefreshTokenDO refreshTokenDO = oauth2RefreshTokenMapper.selectById(refreshAccessTokenDTO.getRefreshToken());
  110 +// // 校验刷新令牌是否合法
  111 +// if (refreshTokenDO == null) { // 不存在
  112 +// throw ServiceExceptionUtil.exception(OAUTH2_REFRESH_TOKEN_NOT_FOUND);
  113 +// }
  114 +// if (refreshTokenDO.getExpiresTime().getTime() < System.currentTimeMillis()) { // 已过期
  115 +// throw ServiceExceptionUtil.exception(OAUTH_REFRESH_TOKEN_EXPIRED);
  116 +// }
  117 +//
  118 +// // 标记 refreshToken 对应的 accessToken 都不合法
  119 +// // 这块的实现,参考了 Spring Security OAuth2 的代码
  120 +// List<OAuth2AccessTokenDO> accessTokenDOs = oauth2AccessTokenMapper.selectListByRefreshToken(refreshAccessTokenDTO.getRefreshToken());
  121 +// accessTokenDOs.forEach(accessTokenDO -> deleteOAuth2AccessToken(accessTokenDO.getId()));
  122 +//
  123 +// // 创建访问令牌
  124 +// OAuth2AccessTokenDO oauth2AccessTokenDO = createOAuth2AccessToken(refreshTokenDO, refreshAccessTokenDTO.getCreateIp());
  125 +// // 返回访问令牌
  126 +// return OAuth2Convert.INSTANCE.convert(oauth2AccessTokenDO);
  127 +// }
  128 +//
  129 +// @Override
  130 +// @Transactional
  131 +// public void removeToken(OAuth2RemoveTokenByUserReqDTO removeTokenDTO) {
  132 +// // 删除 Access Token
  133 +// OAuth2AccessTokenDO accessTokenDO = oauth2AccessTokenMapper.selectByUserIdAndUserType(
  134 +// removeTokenDTO.getUserId(), removeTokenDTO.getUserType());
  135 +// if (accessTokenDO != null) {
  136 +// this.deleteOAuth2AccessToken(accessTokenDO.getId());
  137 +// }
  138 +//
  139 +// // 删除 Refresh Token
  140 +// oauth2RefreshTokenMapper.deleteByUserIdAndUserType(removeTokenDTO.getUserId(), removeTokenDTO.getUserType());
  141 +// }
  142 +
  143 + private OAuth2AccessTokenDO createOAuth2AccessToken(OAuth2RefreshTokenDO refreshTokenDO, OAuth2ClientDO clientDO) {
  144 + OAuth2AccessTokenDO accessToken = new OAuth2AccessTokenDO().setAccessToken(generateAccessToken())
  145 + .setUserId(refreshTokenDO.getUserId()).setUserType(refreshTokenDO.getUserType()).setClientId(clientDO.getId())
  146 + .setRefreshToken(refreshTokenDO.getRefreshToken())
  147 + .setExpiresTime(DateUtils.addDate(Calendar.SECOND, clientDO.getAccessTokenValiditySeconds()));
  148 + accessToken.setTenantId(TenantContextHolder.getTenantId()); // 手动设置租户编号,避免缓存到 Redis 的时候,无对应的租户编号
  149 + oauth2AccessTokenMapper.insert(accessToken);
  150 + return accessToken;
  151 + }
  152 +
  153 + private OAuth2RefreshTokenDO createOAuth2RefreshToken(Long userId, Integer userType, OAuth2ClientDO clientDO) {
  154 + OAuth2RefreshTokenDO refreshToken = new OAuth2RefreshTokenDO().setRefreshToken(generateRefreshToken())
  155 + .setUserId(userId).setUserType(userType).setClientId(clientDO.getId())
  156 + .setExpiresTime(DateUtils.addDate(Calendar.SECOND, clientDO.getRefreshTokenValiditySeconds()));
  157 + oauth2RefreshTokenMapper.insert(refreshToken);
  158 + return refreshToken;
  159 + }
  160 +
  161 +
  162 +// /**
  163 +// * 删除 accessToken 的 MySQL 与 Redis 的数据
  164 +// *
  165 +// * @param accessToken 访问令牌
  166 +// */
  167 +// private void deleteOAuth2AccessToken(String accessToken) {
  168 +// // 删除 MySQL
  169 +// oauth2AccessTokenMapper.deleteById(accessToken);
  170 +// // 删除 Redis
  171 +// oauth2AccessTokenRedisDAO.delete(accessToken);
  172 +// }
  173 +//
  174 + private static String generateAccessToken() {
  175 + return IdUtil.fastSimpleUUID();
  176 + }
  177 +
  178 + private static String generateRefreshToken() {
  179 + return IdUtil.fastSimpleUUID();
  180 + }
  181 +
  182 +}
... ...
yudao-server/pom.xml
... ... @@ -21,11 +21,11 @@
21 21 <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
22 22  
23 23 <dependencies>
24   - <dependency>
25   - <groupId>cn.iocoder.boot</groupId>
26   - <artifactId>yudao-module-member-biz</artifactId>
27   - <version>${revision}</version>
28   - </dependency>
  24 +<!-- <dependency>-->
  25 +<!-- <groupId>cn.iocoder.boot</groupId>-->
  26 +<!-- <artifactId>yudao-module-member-biz</artifactId>-->
  27 +<!-- <version>${revision}</version>-->
  28 +<!-- </dependency>-->
29 29 <dependency>
30 30 <groupId>cn.iocoder.boot</groupId>
31 31 <artifactId>yudao-module-system-biz</artifactId>
... ...