mirror of
				https://gitee.com/hhyykk/ipms-sjy.git
				synced 2025-11-04 04:08:43 +08:00 
			
		
		
		
	完成 OAuth2 的客户端模块
This commit is contained in:
		@@ -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;
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user