diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/JsonUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/JsonUtils.java index 21dc101f9..2a46edf6a 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/JsonUtils.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/JsonUtils.java @@ -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); + } + } diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java index 9fc86335e..a1d5b4d0b 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java @@ -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); } diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2TokenApi.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2TokenApi.java index ef82f5f53..5d0201565 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2TokenApi.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2TokenApi.java @@ -44,6 +44,6 @@ public interface OAuth2TokenApi { * @param clientId 客户端编号 * @return 访问令牌的信息 */ - OAuth2AccessTokenRespDTO refreshAccessToken(String refreshToken, Long clientId); + OAuth2AccessTokenRespDTO refreshAccessToken(String refreshToken, String clientId); } diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenCreateReqDTO.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenCreateReqDTO.java index e9c5b953b..09bd6d8c9 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenCreateReqDTO.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenCreateReqDTO.java @@ -30,6 +30,6 @@ public class OAuth2AccessTokenCreateReqDTO implements Serializable { * 客户端编号 */ @NotNull(message = "客户端编号不能为空") - private Long clientId; + private String clientId; } diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java index 494f6937a..b8368200d 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java @@ -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 客户端编号已存在"); } diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/auth/OAuth2ClientConstants.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/auth/OAuth2ClientConstants.java new file mode 100644 index 000000000..e48e132ff --- /dev/null +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/auth/OAuth2ClientConstants.java @@ -0,0 +1,12 @@ +package cn.iocoder.yudao.module.system.enums.auth; + +/** + * OAuth2.0 客户端的通用枚举 + * + * @author 芋道源码 + */ +public interface OAuth2ClientConstants { + + String CLIENT_ID_DEFAULT = "default"; + +} diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/auth/OAuth2ClientIdEnum.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/auth/OAuth2ClientIdEnum.java deleted file mode 100644 index d41c2d25e..000000000 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/auth/OAuth2ClientIdEnum.java +++ /dev/null @@ -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; - -} diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/auth/OAuth2GrantTypeEnum.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/auth/OAuth2GrantTypeEnum.java new file mode 100644 index 000000000..f2394b7af --- /dev/null +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/auth/OAuth2GrantTypeEnum.java @@ -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; + +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2TokenApiImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2TokenApiImpl.java index 80b7b1811..d9e873c6d 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2TokenApiImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2TokenApiImpl.java @@ -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); } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/OAuth2ClientController.http b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/OAuth2ClientController.http new file mode 100644 index 000000000..dcf60a6cf --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/OAuth2ClientController.http @@ -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": "{}" +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/client/OAuth2ClientBaseVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/client/OAuth2ClientBaseVO.java index a281eed84..2cd2b7466 100755 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/client/OAuth2ClientBaseVO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/client/OAuth2ClientBaseVO.java @@ -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 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 authorizedGrantTypes; + + @ApiModelProperty(value = "授权范围", example = "user_info") + private List scopes; + + @ApiModelProperty(value = "权限", example = "system:user:query") + private List authorities; + + @ApiModelProperty(value = "资源", example = "1024") + private List resourceIds; + + @ApiModelProperty(value = "附加信息", example = "{yunai: true}") + private String additionalInformation; + + @AssertTrue(message = "附加信息必须是 JSON 格式") + public boolean isAdditionalInformationJson() { + return StrUtil.isEmpty(additionalInformation) || JsonUtils.isJson(additionalInformation); + } } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/client/OAuth2ClientRespVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/client/OAuth2ClientRespVO.java index ba0197844..6416d7438 100755 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/client/OAuth2ClientRespVO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/client/OAuth2ClientRespVO.java @@ -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; diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/client/OAuth2ClientUpdateReqVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/client/OAuth2ClientUpdateReqVO.java index bbcd96fd2..27479050c 100755 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/client/OAuth2ClientUpdateReqVO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/client/OAuth2ClientUpdateReqVO.java @@ -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; + } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/token/OAuth2AccessTokenPageReqVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/token/OAuth2AccessTokenPageReqVO.java index 77fbb0c4a..41dff4af3 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/token/OAuth2AccessTokenPageReqVO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/token/OAuth2AccessTokenPageReqVO.java @@ -18,6 +18,6 @@ public class OAuth2AccessTokenPageReqVO extends PageParam { private Integer userType; @ApiModelProperty(value = "客户端编号", required = true, example = "2") - private Long clientId; + private String clientId; } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/token/OAuth2AccessTokenRespVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/token/OAuth2AccessTokenRespVO.java index 4de251f93..678a2156d 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/token/OAuth2AccessTokenRespVO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/token/OAuth2AccessTokenRespVO.java @@ -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; diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2AccessTokenDO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2AccessTokenDO.java index aaaa8152c..bd7105aba 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2AccessTokenDO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2AccessTokenDO.java @@ -52,7 +52,7 @@ public class OAuth2AccessTokenDO extends TenantBaseDO { * * 关联 {@link OAuth2ClientDO#getId()} */ - private Long clientId; + private String clientId; /** * 过期时间 */ diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2ClientDO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2ClientDO.java index cac80f5c2..58b498e05 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2ClientDO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2ClientDO.java @@ -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 redirectUris; + /** + * 是否自动授权 + */ + private Boolean autoApprove; + /** + * 授权类型(模式) + * + * 枚举 {@link OAuth2GrantTypeEnum} + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private List authorizedGrantTypes; + /** + * 授权范围 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private List scopes; + /** + * 权限 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private List authorities; + /** + * 资源 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private List resourceIds; + /** + * 附加信息,JSON 格式 + */ + private String additionalInformation; } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2CodeDO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2CodeDO.java index 7a851b01d..5a54be9eb 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2CodeDO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2CodeDO.java @@ -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; /** * 刷新令牌 * diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2RefreshTokenDO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2RefreshTokenDO.java index 1f0aa3032..08664d942 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2RefreshTokenDO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2RefreshTokenDO.java @@ -43,7 +43,7 @@ public class OAuth2RefreshTokenDO extends BaseDO { * * 关联 {@link OAuth2ClientDO#getId()} */ - private Long clientId; + private String clientId; /** * 过期时间 */ diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2ClientMapper.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2ClientMapper.java index 818128128..ce128e164 100755 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2ClientMapper.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2ClientMapper.java @@ -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 { .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); + } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/auth/OAuth2ClientRefreshConsumer.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/auth/OAuth2ClientRefreshConsumer.java new file mode 100644 index 000000000..f41dd3632 --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/auth/OAuth2ClientRefreshConsumer.java @@ -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 { + + @Resource + private OAuth2ClientService oauth2ClientService; + + @Override + public void onMessage(OAuth2ClientRefreshMessage message) { + log.info("[onMessage][收到 OAuth2Client 刷新消息]"); + oauth2ClientService.initLocalCache(); + } + +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/auth/OAuth2ClientRefreshMessage.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/auth/OAuth2ClientRefreshMessage.java new file mode 100644 index 000000000..3d18df150 --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/auth/OAuth2ClientRefreshMessage.java @@ -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"; + } + +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/auth/OAuth2ClientProducer.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/auth/OAuth2ClientProducer.java new file mode 100644 index 000000000..1a849efc6 --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/auth/OAuth2ClientProducer.java @@ -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); + } + +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java index 743c72aaa..66c00a233 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java @@ -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); } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ClientService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ClientService.java index 8d97ded3d..1dd57277e 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ClientService.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ClientService.java @@ -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); } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ClientServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ClientServiceImpl.java index 51cde3f0f..a9df22a9e 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ClientServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ClientServiceImpl.java @@ -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 clientCache; + /** + * 缓存角色的最大更新时间,用于后续的增量轮询,判断是否有更新 + */ + @Getter + private volatile Date maxUpdateTime; + @Resource private OAuth2ClientMapper oauth2ClientMapper; + @Resource + private OAuth2ClientProducer oauth2ClientProducer; + + /** + * 初始化 {@link #clientCache} 缓存 + */ + @Override + @PostConstruct + public void initLocalCache() { + // 获取客户端列表,如果有更新 + List 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 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); } } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenService.java index 541d2c7c7..ad373bcfb 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenService.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenService.java @@ -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); /** * 获得访问令牌 diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenServiceImpl.java index f8d824690..8a417ad6a 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenServiceImpl.java @@ -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; diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AuthServiceImplTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AuthServiceImplTest.java index 7355c4d77..a04a9719e 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AuthServiceImplTest.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AuthServiceImplTest.java @@ -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); // 调用, 并断言异常 diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ClientServiceImplTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ClientServiceImplTest.java index 816215f4d..1fb688d6a 100755 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ClientServiceImplTest.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ClientServiceImplTest.java @@ -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 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 pageResult = oAuth2ClientService.getOAuth2ClientPage(reqVO); + PageResult pageResult = oauth2ClientService.getOAuth2ClientPage(reqVO); // 断言 assertEquals(1, pageResult.getTotal()); assertEquals(1, pageResult.getList().size()); diff --git a/yudao-module-system/yudao-module-system-biz/src/test/resources/sql/create_tables.sql b/yudao-module-system/yudao-module-system-biz/src/test/resources/sql/create_tables.sql index c3a486ea0..3cc860101 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/resources/sql/create_tables.sql +++ b/yudao-module-system/yudao-module-system-biz/src/test/resources/sql/create_tables.sql @@ -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 '', diff --git a/yudao-ui-admin/src/components/generator/config.js b/yudao-ui-admin/src/components/generator/config.js index be7354b85..f242f11d1 100644 --- a/yudao-ui-admin/src/components/generator/config.js +++ b/yudao-ui-admin/src/components/generator/config.js @@ -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', diff --git a/yudao-ui-admin/src/utils/dict.js b/yudao-ui-admin/src/utils/dict.js index 82fd4a88e..1ddde889a 100644 --- a/yudao-ui-admin/src/utils/dict.js +++ b/yudao-ui-admin/src/utils/dict.js @@ -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', diff --git a/yudao-ui-admin/src/views/system/oauth2/client/index.vue b/yudao-ui-admin/src/views/system/oauth2/client/index.vue index f80c847ca..37f16635f 100755 --- a/yudao-ui-admin/src/views/system/oauth2/client/index.vue +++ b/yudao-ui-admin/src/views/system/oauth2/client/index.vue @@ -29,19 +29,32 @@ - + - - + + + - - - + + + + + + + + +