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