mirror of
https://gitee.com/huangge1199_admin/vue-pro.git
synced 2024-11-26 09:11:52 +08:00
完成 OAuth2 的客户端模块
This commit is contained in:
parent
1f36af8e6a
commit
97db4586a8
@ -113,8 +113,7 @@ public class JsonUtils {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO @Li:和上面的风格保持一致哈。parseTree
|
||||
public static JsonNode readTree(String text) {
|
||||
public static JsonNode parseTree(String text) {
|
||||
try {
|
||||
return objectMapper.readTree(text);
|
||||
} catch (IOException e) {
|
||||
@ -123,7 +122,7 @@ public class JsonUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static JsonNode readTree(byte[] text) {
|
||||
public static JsonNode parseTree(byte[] text) {
|
||||
try {
|
||||
return objectMapper.readTree(text);
|
||||
} catch (IOException e) {
|
||||
@ -132,4 +131,8 @@ public class JsonUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isJson(String text) {
|
||||
return JSONUtil.isJson(text);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ import cn.iocoder.yudao.module.system.api.logger.LoginLogApi;
|
||||
import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
|
||||
import cn.iocoder.yudao.module.system.api.social.SocialUserApi;
|
||||
import cn.iocoder.yudao.module.system.enums.auth.OAuth2ClientIdEnum;
|
||||
import cn.iocoder.yudao.module.system.enums.auth.OAuth2ClientConstants;
|
||||
import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
|
||||
import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum;
|
||||
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
|
||||
@ -120,7 +120,7 @@ public class MemberAuthServiceImpl implements MemberAuthService {
|
||||
createLoginLog(user.getId(), mobile, logType, LoginResultEnum.SUCCESS);
|
||||
// 创建 Token 令牌
|
||||
OAuth2AccessTokenRespDTO accessTokenRespDTO = oauth2TokenApi.createAccessToken(new OAuth2AccessTokenCreateReqDTO()
|
||||
.setUserId(user.getId()).setUserType(getUserType().getValue()).setClientId(OAuth2ClientIdEnum.DEFAULT.getId()));
|
||||
.setUserId(user.getId()).setUserType(getUserType().getValue()).setClientId(OAuth2ClientConstants.CLIENT_ID_DEFAULT));
|
||||
// 构建返回结果
|
||||
return AuthConvert.INSTANCE.convert(accessTokenRespDTO);
|
||||
}
|
||||
@ -212,7 +212,7 @@ public class MemberAuthServiceImpl implements MemberAuthService {
|
||||
|
||||
@Override
|
||||
public AppAuthLoginRespVO refreshToken(String refreshToken) {
|
||||
OAuth2AccessTokenRespDTO accessTokenDO = oauth2TokenApi.refreshAccessToken(refreshToken, OAuth2ClientIdEnum.DEFAULT.getId());
|
||||
OAuth2AccessTokenRespDTO accessTokenDO = oauth2TokenApi.refreshAccessToken(refreshToken, OAuth2ClientConstants.CLIENT_ID_DEFAULT);
|
||||
return AuthConvert.INSTANCE.convert(accessTokenDO);
|
||||
}
|
||||
|
||||
|
@ -44,6 +44,6 @@ public interface OAuth2TokenApi {
|
||||
* @param clientId 客户端编号
|
||||
* @return 访问令牌的信息
|
||||
*/
|
||||
OAuth2AccessTokenRespDTO refreshAccessToken(String refreshToken, Long clientId);
|
||||
OAuth2AccessTokenRespDTO refreshAccessToken(String refreshToken, String clientId);
|
||||
|
||||
}
|
||||
|
@ -30,6 +30,6 @@ public class OAuth2AccessTokenCreateReqDTO implements Serializable {
|
||||
* 客户端编号
|
||||
*/
|
||||
@NotNull(message = "客户端编号不能为空")
|
||||
private Long clientId;
|
||||
private String clientId;
|
||||
|
||||
}
|
||||
|
@ -125,5 +125,6 @@ public interface ErrorCodeConstants {
|
||||
|
||||
// ========== 系统敏感词 1002020000 =========
|
||||
ErrorCode OAUTH2_CLIENT_NOT_EXISTS = new ErrorCode(1002020000, "OAuth2 客户端不存在");
|
||||
ErrorCode OAUTH2_CLIENT_EXISTS = new ErrorCode(1002020001, "OAuth2 客户端编号已存在");
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,12 @@
|
||||
package cn.iocoder.yudao.module.system.enums.auth;
|
||||
|
||||
/**
|
||||
* OAuth2.0 客户端的通用枚举
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface OAuth2ClientConstants {
|
||||
|
||||
String CLIENT_ID_DEFAULT = "default";
|
||||
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
package cn.iocoder.yudao.module.system.enums.auth;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* OAuth2.0 客户端的编号枚举
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum OAuth2ClientIdEnum {
|
||||
|
||||
DEFAULT(1L); // 系统默认
|
||||
|
||||
private final Long id;
|
||||
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package cn.iocoder.yudao.module.system.enums.auth;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* OAuth2 授权类型(模式)的枚举
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum OAuth2GrantTypeEnum {
|
||||
|
||||
PASSWORD("password"), // 密码模式
|
||||
AUTHORIZATION_CODE("authorization_code"), // 授权码模式
|
||||
IMPLICIT("implicit"), // 简化模式
|
||||
CLIENT_CREDENTIALS("client_credentials"), // 客户端模式
|
||||
REFRESH_TOKEN("refresh_token"), // 刷新模式
|
||||
;
|
||||
|
||||
private final String grantType;
|
||||
|
||||
}
|
@ -40,7 +40,7 @@ public class OAuth2TokenApiImpl implements OAuth2TokenApi {
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2AccessTokenRespDTO refreshAccessToken(String refreshToken, Long clientId) {
|
||||
public OAuth2AccessTokenRespDTO refreshAccessToken(String refreshToken, String clientId) {
|
||||
OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.refreshAccessToken(refreshToken, clientId);
|
||||
return OAuth2TokenConvert.INSTANCE.convert2(accessTokenDO);
|
||||
}
|
||||
|
@ -0,0 +1,23 @@
|
||||
### 请求 /login 接口 => 成功
|
||||
POST {{baseUrl}}/system/oauth2-client/create
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer {{token}}
|
||||
tenant-id: {{adminTenentId}}
|
||||
|
||||
{
|
||||
"id": "1",
|
||||
"secret": "admin123",
|
||||
"name": "芋道源码",
|
||||
"logo": "https://www.iocoder.cn/images/favicon.ico",
|
||||
"description": "我是描述",
|
||||
"status": 0,
|
||||
"accessTokenValiditySeconds": 180,
|
||||
"refreshTokenValiditySeconds": 8640,
|
||||
"redirectUris": ["https://www.iocoder.cn"],
|
||||
"autoApprove": true,
|
||||
"authorizedGrantTypes": ["password"],
|
||||
"scopes": ["user_info"],
|
||||
"authorities": ["system:user:query"],
|
||||
"resource_ids": ["1024"],
|
||||
"additionalInformation": "{}"
|
||||
}
|
@ -1,8 +1,13 @@
|
||||
package cn.iocoder.yudao.module.system.controller.admin.auth.vo.client;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
import org.hibernate.validator.constraints.URL;
|
||||
|
||||
import javax.validation.constraints.AssertTrue;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.List;
|
||||
|
||||
@ -15,7 +20,7 @@ public class OAuth2ClientBaseVO {
|
||||
|
||||
@ApiModelProperty(value = "客户端编号", required = true)
|
||||
@NotNull(message = "客户端编号不能为空")
|
||||
private Long id;
|
||||
private String clientId;
|
||||
|
||||
@ApiModelProperty(value = "客户端密钥", required = true)
|
||||
@NotNull(message = "客户端密钥不能为空")
|
||||
@ -27,6 +32,7 @@ public class OAuth2ClientBaseVO {
|
||||
|
||||
@ApiModelProperty(value = "应用图标", required = true)
|
||||
@NotNull(message = "应用图标不能为空")
|
||||
@URL(message = "应用图标的地址不正确")
|
||||
private String logo;
|
||||
|
||||
@ApiModelProperty(value = "应用描述")
|
||||
@ -46,6 +52,32 @@ public class OAuth2ClientBaseVO {
|
||||
|
||||
@ApiModelProperty(value = "可重定向的 URI 地址", required = true)
|
||||
@NotNull(message = "可重定向的 URI 地址不能为空")
|
||||
private List<String> redirectUris;
|
||||
private List<@NotEmpty(message = "重定向的 URI 不能为空")
|
||||
@URL(message = "重定向的 URI 格式不正确") String> redirectUris;
|
||||
|
||||
@ApiModelProperty(value = "是否自动授权", required = true, example = "true")
|
||||
@NotNull(message = "是否自动授权不能为空")
|
||||
private Boolean autoApprove;
|
||||
|
||||
@ApiModelProperty(value = "授权类型", required = true, example = "password", notes = "参见 OAuth2GrantTypeEnum 枚举")
|
||||
@NotNull(message = "授权类型不能为空")
|
||||
private List<String> authorizedGrantTypes;
|
||||
|
||||
@ApiModelProperty(value = "授权范围", example = "user_info")
|
||||
private List<String> scopes;
|
||||
|
||||
@ApiModelProperty(value = "权限", example = "system:user:query")
|
||||
private List<String> authorities;
|
||||
|
||||
@ApiModelProperty(value = "资源", example = "1024")
|
||||
private List<String> resourceIds;
|
||||
|
||||
@ApiModelProperty(value = "附加信息", example = "{yunai: true}")
|
||||
private String additionalInformation;
|
||||
|
||||
@AssertTrue(message = "附加信息必须是 JSON 格式")
|
||||
public boolean isAdditionalInformationJson() {
|
||||
return StrUtil.isEmpty(additionalInformation) || JsonUtils.isJson(additionalInformation);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -14,6 +14,9 @@ import java.util.Date;
|
||||
@ToString(callSuper = true)
|
||||
public class OAuth2ClientRespVO extends OAuth2ClientBaseVO {
|
||||
|
||||
@ApiModelProperty(value = "编号", required = true)
|
||||
private Long id;
|
||||
|
||||
@ApiModelProperty(value = "创建时间", required = true)
|
||||
private Date createTime;
|
||||
|
||||
|
@ -1,14 +1,21 @@
|
||||
package cn.iocoder.yudao.module.system.controller.admin.auth.vo.client;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@ApiModel("管理后台 - OAuth2 客户端更新 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class OAuth2ClientUpdateReqVO extends OAuth2ClientBaseVO {
|
||||
|
||||
@ApiModelProperty(value = "编号", required = true)
|
||||
@NotNull(message = "编号不能为空")
|
||||
private Long id;
|
||||
|
||||
}
|
||||
|
@ -18,6 +18,6 @@ public class OAuth2AccessTokenPageReqVO extends PageParam {
|
||||
private Integer userType;
|
||||
|
||||
@ApiModelProperty(value = "客户端编号", required = true, example = "2")
|
||||
private Long clientId;
|
||||
private String clientId;
|
||||
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ public class OAuth2AccessTokenRespVO {
|
||||
private Integer userType;
|
||||
|
||||
@ApiModelProperty(value = "客户端编号", required = true, example = "2")
|
||||
private Long clientId;
|
||||
private String clientId;
|
||||
|
||||
@ApiModelProperty(value = "创建时间", required = true)
|
||||
private Date createTime;
|
||||
|
@ -52,7 +52,7 @@ public class OAuth2AccessTokenDO extends TenantBaseDO {
|
||||
*
|
||||
* 关联 {@link OAuth2ClientDO#getId()}
|
||||
*/
|
||||
private Long clientId;
|
||||
private String clientId;
|
||||
/**
|
||||
* 过期时间
|
||||
*/
|
||||
|
@ -2,38 +2,37 @@ package cn.iocoder.yudao.module.system.dal.dataobject.auth;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import cn.iocoder.yudao.module.system.enums.auth.OAuth2GrantTypeEnum;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* OAuth2 客户端 DO
|
||||
*
|
||||
* 如下字段,考虑到使用相对不是很高频,主要是一些开关,暂时不支持:
|
||||
* authorized_grant_types、authorities、additional_information、autoapprove、resource_ids、scope
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@TableName(value = "system_oauth2_client", autoResultMap = true)
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Accessors(chain = true)
|
||||
public class OAuth2ClientDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 客户端编号
|
||||
* 编号,数据库自增
|
||||
*
|
||||
* 由于 SQL Server 在存储 String 主键有点问题,所以暂时使用 Long 类型
|
||||
*/
|
||||
@TableId(type = IdType.INPUT)
|
||||
@TableId
|
||||
private Long id;
|
||||
/**
|
||||
* 客户端编号
|
||||
*/
|
||||
private String clientId;
|
||||
/**
|
||||
* 客户端密钥
|
||||
*/
|
||||
@ -69,5 +68,35 @@ public class OAuth2ClientDO extends BaseDO {
|
||||
*/
|
||||
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||
private List<String> redirectUris;
|
||||
/**
|
||||
* 是否自动授权
|
||||
*/
|
||||
private Boolean autoApprove;
|
||||
/**
|
||||
* 授权类型(模式)
|
||||
*
|
||||
* 枚举 {@link OAuth2GrantTypeEnum}
|
||||
*/
|
||||
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||
private List<String> authorizedGrantTypes;
|
||||
/**
|
||||
* 授权范围
|
||||
*/
|
||||
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||
private List<String> scopes;
|
||||
/**
|
||||
* 权限
|
||||
*/
|
||||
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||
private List<String> authorities;
|
||||
/**
|
||||
* 资源
|
||||
*/
|
||||
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||
private List<String> resourceIds;
|
||||
/**
|
||||
* 附加信息,JSON 格式
|
||||
*/
|
||||
private String additionalInformation;
|
||||
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@ -17,7 +16,6 @@ import java.util.Date;
|
||||
@TableName("system_oauth2_code")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Accessors(chain = true)
|
||||
public class OAuth2CodeDO extends BaseDO {
|
||||
|
||||
/**
|
||||
@ -43,7 +41,7 @@ public class OAuth2CodeDO extends BaseDO {
|
||||
*
|
||||
* 关联 {@link OAuth2ClientDO#getId()}
|
||||
*/
|
||||
private Long clientId;
|
||||
private String clientId;
|
||||
/**
|
||||
* 刷新令牌
|
||||
*
|
||||
|
@ -43,7 +43,7 @@ public class OAuth2RefreshTokenDO extends BaseDO {
|
||||
*
|
||||
* 关联 {@link OAuth2ClientDO#getId()}
|
||||
*/
|
||||
private Long clientId;
|
||||
private String clientId;
|
||||
/**
|
||||
* 过期时间
|
||||
*/
|
||||
|
@ -6,6 +6,9 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientPageReqVO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2ClientDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* OAuth2 客户端 Mapper
|
||||
@ -22,4 +25,11 @@ public interface OAuth2ClientMapper extends BaseMapperX<OAuth2ClientDO> {
|
||||
.orderByDesc(OAuth2ClientDO::getId));
|
||||
}
|
||||
|
||||
default OAuth2ClientDO selectByClientId(String clientId) {
|
||||
return selectOne(OAuth2ClientDO::getClientId, clientId);
|
||||
}
|
||||
|
||||
@Select("SELECT COUNT(*) FROM system_oauth2_client WHERE update_time > #{maxUpdateTime}")
|
||||
int selectCountByUpdateTimeGt(Date maxUpdateTime);
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,29 @@
|
||||
package cn.iocoder.yudao.module.system.mq.consumer.auth;
|
||||
|
||||
import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener;
|
||||
import cn.iocoder.yudao.module.system.mq.message.auth.OAuth2ClientRefreshMessage;
|
||||
import cn.iocoder.yudao.module.system.service.auth.OAuth2ClientService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 针对 {@link OAuth2ClientRefreshMessage} 的消费者
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class OAuth2ClientRefreshConsumer extends AbstractChannelMessageListener<OAuth2ClientRefreshMessage> {
|
||||
|
||||
@Resource
|
||||
private OAuth2ClientService oauth2ClientService;
|
||||
|
||||
@Override
|
||||
public void onMessage(OAuth2ClientRefreshMessage message) {
|
||||
log.info("[onMessage][收到 OAuth2Client 刷新消息]");
|
||||
oauth2ClientService.initLocalCache();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package cn.iocoder.yudao.module.system.mq.message.auth;
|
||||
|
||||
import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* OAuth 2.0 客户端的数据刷新 Message
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class OAuth2ClientRefreshMessage extends AbstractChannelMessage {
|
||||
|
||||
@Override
|
||||
public String getChannel() {
|
||||
return "system.oauth2-client.refresh";
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package cn.iocoder.yudao.module.system.mq.producer.auth;
|
||||
|
||||
import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
|
||||
import cn.iocoder.yudao.module.system.mq.message.auth.OAuth2ClientRefreshMessage;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* OAuth 2.0 客户端相关消息的 Producer
|
||||
*/
|
||||
@Component
|
||||
public class OAuth2ClientProducer {
|
||||
|
||||
@Resource
|
||||
private RedisMQTemplate redisMQTemplate;
|
||||
|
||||
/**
|
||||
* 发送 {@link OAuth2ClientRefreshMessage} 消息
|
||||
*/
|
||||
public void sendOAuth2ClientRefreshMessage() {
|
||||
OAuth2ClientRefreshMessage message = new OAuth2ClientRefreshMessage();
|
||||
redisMQTemplate.send(message);
|
||||
}
|
||||
|
||||
}
|
@ -12,7 +12,7 @@ import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.*;
|
||||
import cn.iocoder.yudao.module.system.convert.auth.AuthConvert;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
|
||||
import cn.iocoder.yudao.module.system.enums.auth.OAuth2ClientIdEnum;
|
||||
import cn.iocoder.yudao.module.system.enums.auth.OAuth2ClientConstants;
|
||||
import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
|
||||
import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum;
|
||||
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
|
||||
@ -197,7 +197,7 @@ public class AdminAuthServiceImpl implements AdminAuthService {
|
||||
|
||||
@Override
|
||||
public AuthLoginRespVO refreshToken(String refreshToken) {
|
||||
OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.refreshAccessToken(refreshToken, OAuth2ClientIdEnum.DEFAULT.getId());
|
||||
OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.refreshAccessToken(refreshToken, OAuth2ClientConstants.CLIENT_ID_DEFAULT);
|
||||
return AuthConvert.INSTANCE.convert(accessTokenDO);
|
||||
}
|
||||
|
||||
@ -206,7 +206,7 @@ public class AdminAuthServiceImpl implements AdminAuthService {
|
||||
createLoginLog(userId, username, logType, LoginResultEnum.SUCCESS);
|
||||
// 创建访问令牌
|
||||
OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.createAccessToken(userId, getUserType().getValue(),
|
||||
OAuth2ClientIdEnum.DEFAULT.getId());
|
||||
OAuth2ClientConstants.CLIENT_ID_DEFAULT);
|
||||
// 构建返回结果
|
||||
return AuthConvert.INSTANCE.convert(accessTokenDO);
|
||||
}
|
||||
|
@ -18,7 +18,12 @@ import javax.validation.Valid;
|
||||
public interface OAuth2ClientService {
|
||||
|
||||
/**
|
||||
* 创建OAuth2 客户端
|
||||
* 初始化 OAuth2Client 的本地缓存
|
||||
*/
|
||||
void initLocalCache();
|
||||
|
||||
/**
|
||||
* 创建 OAuth2 客户端
|
||||
*
|
||||
* @param createReqVO 创建信息
|
||||
* @return 编号
|
||||
@ -26,21 +31,21 @@ public interface OAuth2ClientService {
|
||||
Long createOAuth2Client(@Valid OAuth2ClientCreateReqVO createReqVO);
|
||||
|
||||
/**
|
||||
* 更新OAuth2 客户端
|
||||
* 更新 OAuth2 客户端
|
||||
*
|
||||
* @param updateReqVO 更新信息
|
||||
*/
|
||||
void updateOAuth2Client(@Valid OAuth2ClientUpdateReqVO updateReqVO);
|
||||
|
||||
/**
|
||||
* 删除OAuth2 客户端
|
||||
* 删除 OAuth2 客户端
|
||||
*
|
||||
* @param id 编号
|
||||
*/
|
||||
void deleteOAuth2Client(Long id);
|
||||
|
||||
/**
|
||||
* 获得OAuth2 客户端
|
||||
* 获得 OAuth2 客户端
|
||||
*
|
||||
* @param id 编号
|
||||
* @return OAuth2 客户端
|
||||
@ -48,7 +53,7 @@ public interface OAuth2ClientService {
|
||||
OAuth2ClientDO getOAuth2Client(Long id);
|
||||
|
||||
/**
|
||||
* 获得OAuth2 客户端分页
|
||||
* 获得 OAuth2 客户端分页
|
||||
*
|
||||
* @param pageReqVO 分页查询
|
||||
* @return OAuth2 客户端分页
|
||||
@ -58,9 +63,9 @@ public interface OAuth2ClientService {
|
||||
/**
|
||||
* 从缓存中,校验客户端是否合法
|
||||
*
|
||||
* @param id 客户端编号
|
||||
* @param clientId 客户端编号
|
||||
* @return 客户端
|
||||
*/
|
||||
OAuth2ClientDO validOAuthClientFromCache(Long id);
|
||||
OAuth2ClientDO validOAuthClientFromCache(String clientId);
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package cn.iocoder.yudao.module.system.service.auth;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientCreateReqVO;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientPageReqVO;
|
||||
@ -7,11 +8,24 @@ import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2Clie
|
||||
import cn.iocoder.yudao.module.system.convert.auth.OAuth2ClientConvert;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2ClientDO;
|
||||
import cn.iocoder.yudao.module.system.dal.mysql.auth.OAuth2ClientMapper;
|
||||
import cn.iocoder.yudao.module.system.mq.producer.auth.OAuth2ClientProducer;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getMaxValue;
|
||||
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.OAUTH2_CLIENT_EXISTS;
|
||||
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.OAUTH2_CLIENT_NOT_EXISTS;
|
||||
|
||||
/**
|
||||
@ -20,35 +34,113 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.OAUTH2_CLI
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
@Slf4j
|
||||
public class OAuth2ClientServiceImpl implements OAuth2ClientService {
|
||||
|
||||
/**
|
||||
* 定时执行 {@link #schedulePeriodicRefresh()} 的周期
|
||||
* 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高
|
||||
*/
|
||||
private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;
|
||||
|
||||
/**
|
||||
* 客户端缓存
|
||||
* key:客户端编号 {@link OAuth2ClientDO#getClientId()} ()}
|
||||
*
|
||||
* 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
|
||||
*/
|
||||
@Getter
|
||||
private volatile Map<String, OAuth2ClientDO> clientCache;
|
||||
/**
|
||||
* 缓存角色的最大更新时间,用于后续的增量轮询,判断是否有更新
|
||||
*/
|
||||
@Getter
|
||||
private volatile Date maxUpdateTime;
|
||||
|
||||
@Resource
|
||||
private OAuth2ClientMapper oauth2ClientMapper;
|
||||
|
||||
@Resource
|
||||
private OAuth2ClientProducer oauth2ClientProducer;
|
||||
|
||||
/**
|
||||
* 初始化 {@link #clientCache} 缓存
|
||||
*/
|
||||
@Override
|
||||
@PostConstruct
|
||||
public void initLocalCache() {
|
||||
// 获取客户端列表,如果有更新
|
||||
List<OAuth2ClientDO> tenantList = loadOAuth2ClientIfUpdate(maxUpdateTime);
|
||||
if (CollUtil.isEmpty(tenantList)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 写入缓存
|
||||
clientCache = convertMap(tenantList, OAuth2ClientDO::getClientId);
|
||||
maxUpdateTime = getMaxValue(tenantList, OAuth2ClientDO::getUpdateTime);
|
||||
log.info("[initLocalCache][初始化 OAuth2Client 数量为 {}]", tenantList.size());
|
||||
}
|
||||
|
||||
@Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
|
||||
public void schedulePeriodicRefresh() {
|
||||
initLocalCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果客户端发生变化,从数据库中获取最新的全量客户端。
|
||||
* 如果未发生变化,则返回空
|
||||
*
|
||||
* @param maxUpdateTime 当前客户端的最大更新时间
|
||||
* @return 客户端列表
|
||||
*/
|
||||
private List<OAuth2ClientDO> loadOAuth2ClientIfUpdate(Date maxUpdateTime) {
|
||||
// 第一步,判断是否要更新。
|
||||
if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
|
||||
log.info("[loadOAuth2ClientIfUpdate][首次加载全量客户端]");
|
||||
} else { // 判断数据库中是否有更新的客户端
|
||||
if (oauth2ClientMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
|
||||
return null;
|
||||
}
|
||||
log.info("[loadOAuth2ClientIfUpdate][增量加载全量客户端]");
|
||||
}
|
||||
// 第二步,如果有更新,则从数据库加载所有客户端
|
||||
return oauth2ClientMapper.selectList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long createOAuth2Client(OAuth2ClientCreateReqVO createReqVO) {
|
||||
validateClientIdExists(null, createReqVO.getClientId());
|
||||
// 插入
|
||||
OAuth2ClientDO oAuth2Client = OAuth2ClientConvert.INSTANCE.convert(createReqVO);
|
||||
oauth2ClientMapper.insert(oAuth2Client);
|
||||
// 返回
|
||||
return oAuth2Client.getId();
|
||||
OAuth2ClientDO oauth2Client = OAuth2ClientConvert.INSTANCE.convert(createReqVO);
|
||||
oauth2ClientMapper.insert(oauth2Client);
|
||||
// 发送刷新消息
|
||||
oauth2ClientProducer.sendOAuth2ClientRefreshMessage();
|
||||
return oauth2Client.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateOAuth2Client(OAuth2ClientUpdateReqVO updateReqVO) {
|
||||
// 校验存在
|
||||
this.validateOAuth2ClientExists(updateReqVO.getId());
|
||||
validateOAuth2ClientExists(updateReqVO.getId());
|
||||
// 校验 Client 未被占用
|
||||
validateClientIdExists(updateReqVO.getId(), updateReqVO.getClientId());
|
||||
|
||||
// 更新
|
||||
OAuth2ClientDO updateObj = OAuth2ClientConvert.INSTANCE.convert(updateReqVO);
|
||||
oauth2ClientMapper.updateById(updateObj);
|
||||
// 发送刷新消息
|
||||
oauth2ClientProducer.sendOAuth2ClientRefreshMessage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteOAuth2Client(Long id) {
|
||||
// 校验存在
|
||||
this.validateOAuth2ClientExists(id);
|
||||
validateOAuth2ClientExists(id);
|
||||
// 删除
|
||||
oauth2ClientMapper.deleteById(id);
|
||||
// 发送刷新消息
|
||||
oauth2ClientProducer.sendOAuth2ClientRefreshMessage();
|
||||
}
|
||||
|
||||
private void validateOAuth2ClientExists(Long id) {
|
||||
@ -57,6 +149,21 @@ public class OAuth2ClientServiceImpl implements OAuth2ClientService {
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void validateClientIdExists(Long id, String clientId) {
|
||||
OAuth2ClientDO client = oauth2ClientMapper.selectByClientId(clientId);
|
||||
if (client == null) {
|
||||
return;
|
||||
}
|
||||
// 如果 id 为空,说明不用比较是否为相同 id 的客户端
|
||||
if (id == null) {
|
||||
throw exception(OAUTH2_CLIENT_EXISTS);
|
||||
}
|
||||
if (!client.getClientId().equals(clientId)) {
|
||||
throw exception(OAUTH2_CLIENT_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2ClientDO getOAuth2Client(Long id) {
|
||||
return oauth2ClientMapper.selectById(id);
|
||||
@ -68,10 +175,8 @@ public class OAuth2ClientServiceImpl implements OAuth2ClientService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2ClientDO validOAuthClientFromCache(Long id) {
|
||||
return new OAuth2ClientDO().setId(id)
|
||||
.setAccessTokenValiditySeconds(60 * 30)
|
||||
.setRefreshTokenValiditySeconds(60 * 60 * 24 * 30);
|
||||
public OAuth2ClientDO validOAuthClientFromCache(String clientId) {
|
||||
return clientCache.get(clientId);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ public interface OAuth2TokenService {
|
||||
* @param clientId 客户端编号
|
||||
* @return 访问令牌的信息
|
||||
*/
|
||||
OAuth2AccessTokenDO createAccessToken(Long userId, Integer userType, Long clientId);
|
||||
OAuth2AccessTokenDO createAccessToken(Long userId, Integer userType, String clientId);
|
||||
|
||||
/**
|
||||
* 刷新访问令牌
|
||||
@ -35,7 +35,7 @@ public interface OAuth2TokenService {
|
||||
* @param clientId 客户端编号
|
||||
* @return 访问令牌的信息
|
||||
*/
|
||||
OAuth2AccessTokenDO refreshAccessToken(String refreshToken, Long clientId);
|
||||
OAuth2AccessTokenDO refreshAccessToken(String refreshToken, String clientId);
|
||||
|
||||
/**
|
||||
* 获得访问令牌
|
||||
|
@ -45,7 +45,7 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService {
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public OAuth2AccessTokenDO createAccessToken(Long userId, Integer userType, Long clientId) {
|
||||
public OAuth2AccessTokenDO createAccessToken(Long userId, Integer userType, String clientId) {
|
||||
OAuth2ClientDO clientDO = oauth2ClientService.validOAuthClientFromCache(clientId);
|
||||
// 创建刷新令牌
|
||||
OAuth2RefreshTokenDO refreshTokenDO = createOAuth2RefreshToken(userId, userType, clientDO);
|
||||
@ -54,7 +54,7 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2AccessTokenDO refreshAccessToken(String refreshToken, Long clientId) {
|
||||
public OAuth2AccessTokenDO refreshAccessToken(String refreshToken, String clientId) {
|
||||
// 查询访问令牌
|
||||
OAuth2RefreshTokenDO refreshTokenDO = oauth2RefreshTokenMapper.selectByRefreshToken(refreshToken);
|
||||
if (refreshTokenDO == null) {
|
||||
@ -134,7 +134,7 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService {
|
||||
|
||||
private OAuth2AccessTokenDO createOAuth2AccessToken(OAuth2RefreshTokenDO refreshTokenDO, OAuth2ClientDO clientDO) {
|
||||
OAuth2AccessTokenDO accessTokenDO = new OAuth2AccessTokenDO().setAccessToken(generateAccessToken())
|
||||
.setUserId(refreshTokenDO.getUserId()).setUserType(refreshTokenDO.getUserType()).setClientId(clientDO.getId())
|
||||
.setUserId(refreshTokenDO.getUserId()).setUserType(refreshTokenDO.getUserType()).setClientId(clientDO.getClientId())
|
||||
.setRefreshToken(refreshTokenDO.getRefreshToken())
|
||||
.setExpiresTime(DateUtils.addDate(Calendar.SECOND, clientDO.getAccessTokenValiditySeconds()));
|
||||
accessTokenDO.setTenantId(TenantContextHolder.getTenantId()); // 手动设置租户编号,避免缓存到 Redis 的时候,无对应的租户编号
|
||||
@ -146,7 +146,7 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService {
|
||||
|
||||
private OAuth2RefreshTokenDO createOAuth2RefreshToken(Long userId, Integer userType, OAuth2ClientDO clientDO) {
|
||||
OAuth2RefreshTokenDO refreshToken = new OAuth2RefreshTokenDO().setRefreshToken(generateRefreshToken())
|
||||
.setUserId(userId).setUserType(userType).setClientId(clientDO.getId())
|
||||
.setUserId(userId).setUserType(userType).setClientId(clientDO.getClientId())
|
||||
.setExpiresTime(DateUtils.addDate(Calendar.SECOND, clientDO.getRefreshTokenValiditySeconds()));
|
||||
oauth2RefreshTokenMapper.insert(refreshToken);
|
||||
return refreshToken;
|
||||
|
@ -197,7 +197,7 @@ public class AuthServiceImplTest extends BaseDbUnitTest {
|
||||
// mock 缓存登录用户到 Redis
|
||||
OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class, o -> o.setUserId(1L)
|
||||
.setUserType(UserTypeEnum.ADMIN.getValue()));
|
||||
when(oauth2TokenService.createAccessToken(eq(1L), eq(UserTypeEnum.ADMIN.getValue()), eq(1L)))
|
||||
when(oauth2TokenService.createAccessToken(eq(1L), eq(UserTypeEnum.ADMIN.getValue()), eq("default")))
|
||||
.thenReturn(accessTokenDO);
|
||||
|
||||
// 调用, 并断言异常
|
||||
|
@ -8,19 +8,23 @@ import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2Clie
|
||||
import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientUpdateReqVO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2ClientDO;
|
||||
import cn.iocoder.yudao.module.system.dal.mysql.auth.OAuth2ClientMapper;
|
||||
import cn.iocoder.yudao.module.system.mq.producer.auth.OAuth2ClientProducer;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
|
||||
import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.max;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
|
||||
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.OAUTH2_CLIENT_NOT_EXISTS;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* {@link OAuth2ClientServiceImpl} 的单元测试类
|
||||
@ -31,40 +35,66 @@ import static org.junit.jupiter.api.Assertions.*;
|
||||
public class OAuth2ClientServiceImplTest extends BaseDbUnitTest {
|
||||
|
||||
@Resource
|
||||
private OAuth2ClientServiceImpl oAuth2ClientService;
|
||||
private OAuth2ClientServiceImpl oauth2ClientService;
|
||||
|
||||
@Resource
|
||||
private OAuth2ClientMapper oAuth2ClientMapper;
|
||||
private OAuth2ClientMapper oauth2ClientMapper;
|
||||
|
||||
@MockBean
|
||||
private OAuth2ClientProducer oauth2ClientProducer;
|
||||
|
||||
@Test
|
||||
public void testInitLocalCache() {
|
||||
// mock 数据
|
||||
OAuth2ClientDO clientDO1 = randomPojo(OAuth2ClientDO.class);
|
||||
oauth2ClientMapper.insert(clientDO1);
|
||||
OAuth2ClientDO clientDO2 = randomPojo(OAuth2ClientDO.class);
|
||||
oauth2ClientMapper.insert(clientDO2);
|
||||
|
||||
// 调用
|
||||
oauth2ClientService.initLocalCache();
|
||||
// 断言 clientCache 缓存
|
||||
Map<String, OAuth2ClientDO> clientCache = oauth2ClientService.getClientCache();
|
||||
assertEquals(2, clientCache.size());
|
||||
assertPojoEquals(clientDO1, clientCache.get(clientDO1.getClientId()));
|
||||
assertPojoEquals(clientDO2, clientCache.get(clientDO2.getClientId()));
|
||||
// 断言 maxUpdateTime 缓存
|
||||
assertEquals(max(clientDO1.getUpdateTime(), clientDO2.getUpdateTime()), oauth2ClientService.getMaxUpdateTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateOAuth2Client_success() {
|
||||
// 准备参数
|
||||
OAuth2ClientCreateReqVO reqVO = randomPojo(OAuth2ClientCreateReqVO.class);
|
||||
OAuth2ClientCreateReqVO reqVO = randomPojo(OAuth2ClientCreateReqVO.class,
|
||||
o -> o.setLogo(randomString()));
|
||||
|
||||
// 调用
|
||||
Long oauth2ClientId = oAuth2ClientService.createOAuth2Client(reqVO);
|
||||
Long oauth2ClientId = oauth2ClientService.createOAuth2Client(reqVO);
|
||||
// 断言
|
||||
assertNotNull(oauth2ClientId);
|
||||
// 校验记录的属性是否正确
|
||||
OAuth2ClientDO oAuth2Client = oAuth2ClientMapper.selectById(oauth2ClientId);
|
||||
OAuth2ClientDO oAuth2Client = oauth2ClientMapper.selectById(oauth2ClientId);
|
||||
assertPojoEquals(reqVO, oAuth2Client);
|
||||
verify(oauth2ClientProducer).sendOAuth2ClientRefreshMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateOAuth2Client_success() {
|
||||
// mock 数据
|
||||
OAuth2ClientDO dbOAuth2Client = randomPojo(OAuth2ClientDO.class);
|
||||
oAuth2ClientMapper.insert(dbOAuth2Client);// @Sql: 先插入出一条存在的数据
|
||||
oauth2ClientMapper.insert(dbOAuth2Client);// @Sql: 先插入出一条存在的数据
|
||||
// 准备参数
|
||||
OAuth2ClientUpdateReqVO reqVO = randomPojo(OAuth2ClientUpdateReqVO.class, o -> {
|
||||
o.setId(dbOAuth2Client.getId()); // 设置更新的 ID
|
||||
o.setLogo(randomString());
|
||||
});
|
||||
|
||||
// 调用
|
||||
oAuth2ClientService.updateOAuth2Client(reqVO);
|
||||
oauth2ClientService.updateOAuth2Client(reqVO);
|
||||
// 校验是否更新正确
|
||||
OAuth2ClientDO oAuth2Client = oAuth2ClientMapper.selectById(reqVO.getId()); // 获取最新的
|
||||
OAuth2ClientDO oAuth2Client = oauth2ClientMapper.selectById(reqVO.getId()); // 获取最新的
|
||||
assertPojoEquals(reqVO, oAuth2Client);
|
||||
verify(oauth2ClientProducer).sendOAuth2ClientRefreshMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -73,21 +103,22 @@ public class OAuth2ClientServiceImplTest extends BaseDbUnitTest {
|
||||
OAuth2ClientUpdateReqVO reqVO = randomPojo(OAuth2ClientUpdateReqVO.class);
|
||||
|
||||
// 调用, 并断言异常
|
||||
assertServiceException(() -> oAuth2ClientService.updateOAuth2Client(reqVO), OAUTH2_CLIENT_NOT_EXISTS);
|
||||
assertServiceException(() -> oauth2ClientService.updateOAuth2Client(reqVO), OAUTH2_CLIENT_NOT_EXISTS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteOAuth2Client_success() {
|
||||
// mock 数据
|
||||
OAuth2ClientDO dbOAuth2Client = randomPojo(OAuth2ClientDO.class);
|
||||
oAuth2ClientMapper.insert(dbOAuth2Client);// @Sql: 先插入出一条存在的数据
|
||||
oauth2ClientMapper.insert(dbOAuth2Client);// @Sql: 先插入出一条存在的数据
|
||||
// 准备参数
|
||||
Long id = dbOAuth2Client.getId();
|
||||
|
||||
// 调用
|
||||
oAuth2ClientService.deleteOAuth2Client(id);
|
||||
// 校验数据不存在了
|
||||
assertNull(oAuth2ClientMapper.selectById(id));
|
||||
oauth2ClientService.deleteOAuth2Client(id);
|
||||
// 校验数据不存在了
|
||||
assertNull(oauth2ClientMapper.selectById(id));
|
||||
verify(oauth2ClientProducer).sendOAuth2ClientRefreshMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -96,7 +127,7 @@ public class OAuth2ClientServiceImplTest extends BaseDbUnitTest {
|
||||
Long id = randomLongId();
|
||||
|
||||
// 调用, 并断言异常
|
||||
assertServiceException(() -> oAuth2ClientService.deleteOAuth2Client(id), OAUTH2_CLIENT_NOT_EXISTS);
|
||||
assertServiceException(() -> oauth2ClientService.deleteOAuth2Client(id), OAUTH2_CLIENT_NOT_EXISTS);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -107,18 +138,18 @@ public class OAuth2ClientServiceImplTest extends BaseDbUnitTest {
|
||||
o.setName("潜龙");
|
||||
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
});
|
||||
oAuth2ClientMapper.insert(dbOAuth2Client);
|
||||
oauth2ClientMapper.insert(dbOAuth2Client);
|
||||
// 测试 name 不匹配
|
||||
oAuth2ClientMapper.insert(cloneIgnoreId(dbOAuth2Client, o -> o.setName("凤凰")));
|
||||
oauth2ClientMapper.insert(cloneIgnoreId(dbOAuth2Client, o -> o.setName("凤凰")));
|
||||
// 测试 status 不匹配
|
||||
oAuth2ClientMapper.insert(cloneIgnoreId(dbOAuth2Client, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus())));
|
||||
oauth2ClientMapper.insert(cloneIgnoreId(dbOAuth2Client, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus())));
|
||||
// 准备参数
|
||||
OAuth2ClientPageReqVO reqVO = new OAuth2ClientPageReqVO();
|
||||
reqVO.setName("long");
|
||||
reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
|
||||
// 调用
|
||||
PageResult<OAuth2ClientDO> pageResult = oAuth2ClientService.getOAuth2ClientPage(reqVO);
|
||||
PageResult<OAuth2ClientDO> pageResult = oauth2ClientService.getOAuth2ClientPage(reqVO);
|
||||
// 断言
|
||||
assertEquals(1, pageResult.getTotal());
|
||||
assertEquals(1, pageResult.getList().size());
|
||||
|
@ -473,6 +473,7 @@ CREATE TABLE IF NOT EXISTS "system_sensitive_word" (
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "system_oauth2_client" (
|
||||
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
|
||||
"client_id" varchar NOT NULL,
|
||||
"secret" varchar NOT NULL,
|
||||
"name" varchar NOT NULL,
|
||||
"logo" varchar NOT NULL,
|
||||
@ -481,6 +482,12 @@ CREATE TABLE IF NOT EXISTS "system_oauth2_client" (
|
||||
"access_token_validity_seconds" int NOT NULL,
|
||||
"refresh_token_validity_seconds" int NOT NULL,
|
||||
"redirect_uris" varchar NOT NULL,
|
||||
"auto_approve" bit NOT NULL DEFAULT FALSE,
|
||||
"authorized_grant_types" varchar NOT NULL,
|
||||
"scopes" varchar NOT NULL DEFAULT '',
|
||||
"authorities" varchar NOT NULL DEFAULT '',
|
||||
"resource_ids" varchar NOT NULL DEFAULT '',
|
||||
"additional_information" varchar NOT NULL DEFAULT '',
|
||||
"creator" varchar DEFAULT '',
|
||||
"create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updater" varchar DEFAULT '',
|
||||
|
@ -499,7 +499,7 @@ export const selectComponents = [
|
||||
__slot__: {
|
||||
'list-type': true
|
||||
},
|
||||
action: 'https://jsonplaceholder.typicode.com/posts/',
|
||||
action: process.env.VUE_APP_BASE_API + "/admin-api/infra/file/upload", // 请求地址
|
||||
disabled: false,
|
||||
accept: '',
|
||||
name: 'file',
|
||||
|
@ -23,6 +23,7 @@ export const DICT_TYPE = {
|
||||
SYSTEM_SMS_SEND_STATUS: 'system_sms_send_status',
|
||||
SYSTEM_SMS_RECEIVE_STATUS: 'system_sms_receive_status',
|
||||
SYSTEM_ERROR_CODE_TYPE: 'system_error_code_type',
|
||||
SYSTEM_OAUTH2_GRANT_TYPE: 'system_oauth2_grant_type',
|
||||
|
||||
// ========== INFRA 模块 ==========
|
||||
INFRA_BOOLEAN_STRING: 'infra_boolean_string',
|
||||
|
@ -29,19 +29,32 @@
|
||||
|
||||
<!-- 列表 -->
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="客户端编号" align="center" prop="id" />
|
||||
<el-table-column label="客户端编号" align="center" prop="clientId" />
|
||||
<el-table-column label="客户端密钥" align="center" prop="secret" />
|
||||
<el-table-column label="应用名" align="center" prop="name" />
|
||||
<el-table-column label="应用图标" align="center" prop="logo" />
|
||||
<el-table-column label="应用描述" align="center" prop="description" />
|
||||
<el-table-column label="应用图标" align="center" prop="logo">
|
||||
<template slot-scope="scope">
|
||||
<img width="40px" height="40px" :src="scope.row.logo">
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" align="center" prop="status">
|
||||
<template slot-scope="scope">
|
||||
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="访问令牌的有效期" align="center" prop="accessTokenValiditySeconds" />
|
||||
<el-table-column label="刷新令牌的有效期" align="center" prop="refreshTokenValiditySeconds" />
|
||||
<el-table-column label="可重定向的 URI 地址" align="center" prop="redirectUris" />
|
||||
<el-table-column label="访问令牌的有效期" align="center" prop="accessTokenValiditySeconds">
|
||||
<template slot-scope="scope">{{ scope.row.accessTokenValiditySeconds }} 秒</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="刷新令牌的有效期" align="center" prop="refreshTokenValiditySeconds">
|
||||
<template slot-scope="scope">{{ scope.row.refreshTokenValiditySeconds }} 秒</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="授权类型" align="center" prop="authorizedGrantTypes">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :disable-transitions="true" v-for="(authorizedGrantType, index) in scope.row.authorizedGrantTypes" :index="index">
|
||||
{{ authorizedGrantType }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ parseTime(scope.row.createTime) }}</span>
|
||||
@ -63,6 +76,9 @@
|
||||
<!-- 对话框(添加 / 修改) -->
|
||||
<el-dialog :title="title" :visible.sync="open" width="700px" append-to-body>
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="160px">
|
||||
<el-form-item label="客户端编号" prop="secret">
|
||||
<el-input v-model="form.clientId" placeholder="请输入客户端编号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="客户端密钥" prop="secret">
|
||||
<el-input v-model="form.secret" placeholder="请输入客户端密钥" />
|
||||
</el-form-item>
|
||||
@ -70,8 +86,7 @@
|
||||
<el-input v-model="form.name" placeholder="请输入应用名" />
|
||||
</el-form-item>
|
||||
<el-form-item label="应用图标">
|
||||
<!-- <imageUpload v-model="form.logo" :limit="1"/>-->
|
||||
<file-upload v-model="form.logo" :limit="1"/>
|
||||
<imageUpload v-model="form.logo" :limit="1"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="应用描述">
|
||||
<el-input type="textarea" v-model="form.description" placeholder="请输入应用名" />
|
||||
@ -89,7 +104,39 @@
|
||||
<el-input-number v-model="form.refreshTokenValiditySeconds" placeholder="单位:秒" />
|
||||
</el-form-item>
|
||||
<el-form-item label="可重定向的 URI 地址" prop="redirectUris">
|
||||
<el-input v-model="form.redirectUris" placeholder="请输入可重定向的 URI 地址" />
|
||||
<el-select v-model="form.redirectUris" multiple filterable allow-create placeholder="请输入可重定向的 URI 地址" style="width: 500px" >
|
||||
<el-option v-for="redirectUri in form.redirectUris" :key="redirectUri" :label="redirectUri" :value="redirectUri"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否自动授权" prop="autoApprove">
|
||||
<el-radio-group v-model="form.autoApprove">
|
||||
<el-radio :key="true" :label="true">自动登录</el-radio>
|
||||
<el-radio :key="false" :label="false">手动登录</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="授权类型" prop="authorizedGrantTypes">
|
||||
<el-select v-model="form.authorizedGrantTypes" multiple filterable placeholder="请输入授权类型" style="width: 500px" >
|
||||
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.SYSTEM_OAUTH2_GRANT_TYPE)"
|
||||
:key="dict.value" :label="dict.label" :value="dict.value"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="授权范围" prop="scopes">
|
||||
<el-select v-model="form.scopes" multiple filterable allow-create placeholder="请输入授权范围" style="width: 500px" >
|
||||
<el-option v-for="scope in form.scopes" :key="scope" :label="scope" :value="scope"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="权限" prop="authorities">
|
||||
<el-select v-model="form.authorities" multiple filterable allow-create placeholder="请输入权限" style="width: 500px" >
|
||||
<el-option v-for="authority in form.authorities" :key="authority" :label="authority" :value="authority"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="资源" prop="resourceIds">
|
||||
<el-select v-model="form.resourceIds" multiple filterable allow-create placeholder="请输入资源" style="width: 500px" >
|
||||
<el-option v-for="resourceId in form.resourceIds" :key="resourceId" :label="resourceId" :value="resourceId"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="附加信息" prop="additionalInformation">
|
||||
<el-input type="textarea" v-model="form.additionalInformation" placeholder="请输入附加信息,JSON 格式数据" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
@ -141,6 +188,7 @@ export default {
|
||||
form: {},
|
||||
// 表单校验
|
||||
rules: {
|
||||
clientId: [{ required: true, message: "客户端编号不能为空", trigger: "blur" }],
|
||||
secret: [{ required: true, message: "客户端密钥不能为空", trigger: "blur" }],
|
||||
name: [{ required: true, message: "应用名不能为空", trigger: "blur" }],
|
||||
logo: [{ required: true, message: "应用图标不能为空", trigger: "blur" }],
|
||||
@ -148,6 +196,8 @@ export default {
|
||||
accessTokenValiditySeconds: [{ required: true, message: "访问令牌的有效期不能为空", trigger: "blur" }],
|
||||
refreshTokenValiditySeconds: [{ required: true, message: "刷新令牌的有效期不能为空", trigger: "blur" }],
|
||||
redirectUris: [{ required: true, message: "可重定向的 URI 地址不能为空", trigger: "blur" }],
|
||||
autoApprove: [{ required: true, message: "是否自动授权不能为空", trigger: "blur" }],
|
||||
authorizedGrantTypes: [{ required: true, message: "授权类型不能为空", trigger: "blur" }],
|
||||
}
|
||||
};
|
||||
},
|
||||
@ -176,14 +226,21 @@ export default {
|
||||
reset() {
|
||||
this.form = {
|
||||
id: undefined,
|
||||
clientId: undefined,
|
||||
secret: undefined,
|
||||
name: undefined,
|
||||
logo: undefined,
|
||||
description: undefined,
|
||||
status: CommonStatusEnum.ENABLE,
|
||||
accessTokenValiditySeconds: undefined,
|
||||
refreshTokenValiditySeconds: undefined,
|
||||
redirectUris: undefined,
|
||||
accessTokenValiditySeconds: 30 * 60,
|
||||
refreshTokenValiditySeconds: 30 * 24 * 60,
|
||||
redirectUris: [],
|
||||
autoApprove: true,
|
||||
authorizedGrantTypes: [],
|
||||
scopes: [],
|
||||
authorities: [],
|
||||
resourceIds: [],
|
||||
additionalInformation: undefined,
|
||||
};
|
||||
this.resetForm("form");
|
||||
},
|
||||
@ -239,7 +296,7 @@ export default {
|
||||
/** 删除按钮操作 */
|
||||
handleDelete(row) {
|
||||
const id = row.id;
|
||||
this.$modal.confirm('是否确认删除 OAuth2 客户端编号为"' + id + '"的数据项?').then(function() {
|
||||
this.$modal.confirm('是否确认删除客户端编号为"' + row.clientId + '"的数据项?').then(function() {
|
||||
return deleteOAuth2Client(id);
|
||||
}).then(() => {
|
||||
this.getList();
|
||||
|
Loading…
Reference in New Issue
Block a user