Commit 4f52d1367be6ca504adff544cf069ef9f38456d8
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
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> | ... | ... |