Commit aa8fb4acf0fda7b71acc2bdcacbdf3bef398a210

Authored by YunaiV
1 parent ce60ec08

搭建 oauth/authorize 的初步逻辑

yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/string/StrUtils.java
1 package cn.iocoder.yudao.framework.common.util.string; 1 package cn.iocoder.yudao.framework.common.util.string;
2 2
3 -import cn.hutool.core.util.ObjectUtil; 3 +import cn.hutool.core.util.ArrayUtil;
4 import cn.hutool.core.util.StrUtil; 4 import cn.hutool.core.util.StrUtil;
5 5
6 -import java.util.Map; 6 +import java.util.Collection;
7 7
8 /** 8 /**
9 * 字符串工具类 9 * 字符串工具类
@@ -17,21 +17,24 @@ public class StrUtils { @@ -17,21 +17,24 @@ public class StrUtils {
17 } 17 }
18 18
19 /** 19 /**
20 - * 指定字符串的  
21 - * @param str  
22 - * @param replaceMap  
23 - * @return 20 + * 给定字符串是否以任何一个字符串开始
  21 + * 给定字符串和数组为空都返回 false
  22 + *
  23 + * @param str 给定字符串
  24 + * @param prefixes 需要检测的开始字符串
  25 + * @since 3.0.6
24 */ 26 */
25 - public static String replace(String str, Map<String, String> replaceMap) {  
26 - assert StrUtil.isNotBlank(str);  
27 - if (ObjectUtil.isEmpty(replaceMap)) {  
28 - return str; 27 + public static boolean startWithAny(String str, Collection<String> prefixes) {
  28 + if (StrUtil.isEmpty(str) || ArrayUtil.isEmpty(prefixes)) {
  29 + return false;
29 } 30 }
30 - String result = null;  
31 - for (String key : replaceMap.keySet()) {  
32 - result = str.replace(key, replaceMap.get(key)); 31 +
  32 + for (CharSequence suffix : prefixes) {
  33 + if (StrUtil.startWith(str, suffix, false)) {
  34 + return true;
  35 + }
33 } 36 }
34 - return result; 37 + return false;
35 } 38 }
36 39
37 } 40 }
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java
@@ -126,5 +126,9 @@ public interface ErrorCodeConstants { @@ -126,5 +126,9 @@ public interface ErrorCodeConstants {
126 // ========== 系统敏感词 1002020000 ========= 126 // ========== 系统敏感词 1002020000 =========
127 ErrorCode OAUTH2_CLIENT_NOT_EXISTS = new ErrorCode(1002020000, "OAuth2 客户端不存在"); 127 ErrorCode OAUTH2_CLIENT_NOT_EXISTS = new ErrorCode(1002020000, "OAuth2 客户端不存在");
128 ErrorCode OAUTH2_CLIENT_EXISTS = new ErrorCode(1002020001, "OAuth2 客户端编号已存在"); 128 ErrorCode OAUTH2_CLIENT_EXISTS = new ErrorCode(1002020001, "OAuth2 客户端编号已存在");
  129 + ErrorCode OAUTH2_CLIENT_DISABLE = new ErrorCode(1002020002, "OAuth2 客户端已禁用");
  130 + ErrorCode OAUTH2_CLIENT_AUTHORIZED_GRANT_TYPE_NOT_EXISTS = new ErrorCode(1002020003, "不支持该授权类型");
  131 + ErrorCode OAUTH2_CLIENT_SCOPE_OVER = new ErrorCode(1002020004, "授权范围过大");
  132 + ErrorCode OAUTH2_CLIENT_REDIRECT_URI_NOT_MATCH = new ErrorCode(1002020004, "重定向地址不匹配");
129 133
130 } 134 }
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/OAuth2Controller.java
1 package cn.iocoder.yudao.module.system.controller.admin.auth; 1 package cn.iocoder.yudao.module.system.controller.admin.auth;
2 2
  3 +import cn.hutool.core.util.ObjectUtil;
3 import cn.hutool.core.util.StrUtil; 4 import cn.hutool.core.util.StrUtil;
4 -import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants; 5 +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
5 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 6 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  7 +import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
6 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; 8 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
  9 +import cn.iocoder.yudao.module.system.enums.auth.OAuth2GrantTypeEnum;
  10 +import cn.iocoder.yudao.module.system.service.auth.OAuth2ApproveService;
  11 +import cn.iocoder.yudao.module.system.service.auth.OAuth2ClientService;
7 import io.swagger.annotations.Api; 12 import io.swagger.annotations.Api;
8 import io.swagger.annotations.ApiImplicitParam; 13 import io.swagger.annotations.ApiImplicitParam;
9 import io.swagger.annotations.ApiImplicitParams; 14 import io.swagger.annotations.ApiImplicitParams;
10 import io.swagger.annotations.ApiOperation; 15 import io.swagger.annotations.ApiOperation;
11 import lombok.extern.slf4j.Slf4j; 16 import lombok.extern.slf4j.Slf4j;
12 import org.springframework.validation.annotation.Validated; 17 import org.springframework.validation.annotation.Validated;
13 -import org.springframework.web.bind.annotation.PostMapping;  
14 -import org.springframework.web.bind.annotation.RequestMapping;  
15 -import org.springframework.web.bind.annotation.RequestParam;  
16 -import org.springframework.web.bind.annotation.RestController; 18 +import org.springframework.web.bind.annotation.*;
17 19
  20 +import javax.annotation.Resource;
  21 +import java.util.Collections;
  22 +import java.util.List;
  23 +import java.util.Map;
  24 +
  25 +import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;
18 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception0; 26 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception0;
  27 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  28 +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
19 29
20 @Api(tags = "管理后台 - OAuth2.0 授权") 30 @Api(tags = "管理后台 - OAuth2.0 授权")
21 @RestController 31 @RestController
@@ -32,37 +42,105 @@ public class OAuth2Controller { @@ -32,37 +42,105 @@ public class OAuth2Controller {
32 42
33 // GET oauth/authorize AuthorizationEndpoint 43 // GET oauth/authorize AuthorizationEndpoint
34 44
35 - @PostMapping("/authorize")  
36 - @ApiOperation(value = "申请授权", notes = "适合 code 授权码模式,或者 implicit 简化模式;在 authorize.vue 单点登录界面被调用") 45 + @Resource
  46 + private OAuth2ClientService oauth2ClientService;
  47 + @Resource
  48 + private OAuth2ApproveService oauth2ApproveService;
  49 +
  50 + @GetMapping("/authorize")
  51 + @ApiOperation(value = "获得授权信息", notes = "适合 code 授权码模式,或者 implicit 简化模式;在 authorize.vue 单点登录界面被【获取】调用")
37 @ApiImplicitParams({ 52 @ApiImplicitParams({
38 @ApiImplicitParam(name = "response_type", required = true, value = "响应类型", example = "code", dataTypeClass = String.class), 53 @ApiImplicitParam(name = "response_type", required = true, value = "响应类型", example = "code", dataTypeClass = String.class),
39 @ApiImplicitParam(name = "client_id", required = true, value = "客户端编号", example = "tudou", dataTypeClass = String.class), 54 @ApiImplicitParam(name = "client_id", required = true, value = "客户端编号", example = "tudou", dataTypeClass = String.class),
40 - @ApiImplicitParam(name = "scope", value = "授权范围", example = "userinfo.read", dataTypeClass = String.class), // 多个使用逗号分隔 55 + @ApiImplicitParam(name = "scope", value = "授权范围", example = "userinfo.read", dataTypeClass = String.class), // 多个使用空格分隔
41 @ApiImplicitParam(name = "redirect_uri", required = true, value = "重定向 URI", example = "https://www.iocoder.cn", dataTypeClass = String.class), 56 @ApiImplicitParam(name = "redirect_uri", required = true, value = "重定向 URI", example = "https://www.iocoder.cn", dataTypeClass = String.class),
42 @ApiImplicitParam(name = "state", example = "123321", dataTypeClass = String.class) 57 @ApiImplicitParam(name = "state", example = "123321", dataTypeClass = String.class)
43 }) 58 })
44 - @OperateLog(enable = false) // 避免 Post 请求被记录操作日志  
45 - // 因为前后端分离,Axios 无法很好的处理 302 重定向,所以和 Spring Security OAuth 略有不同,返回结果是重定向的 URL,剩余交给前端处理  
46 - public CommonResult<String> authorize(@RequestParam("response_type") String responseType, 59 + // 情况一:满足自动授权,则走类似 approveOrDeny 的逻辑,最终返回是 CommonResult<String>
  60 + // 情况二:如果不满足自动授权,则返回授权相关的展示信息,最终返回是 CommonResult<OAuth2AuthorizeRespInfo>
  61 + public CommonResult<Object> authorize(@RequestParam("response_type") String responseType,
47 @RequestParam("client_id") String clientId, 62 @RequestParam("client_id") String clientId,
48 @RequestParam(value = "scope", required = false) String scope, 63 @RequestParam(value = "scope", required = false) String scope,
49 @RequestParam("redirect_uri") String redirectUri, 64 @RequestParam("redirect_uri") String redirectUri,
50 @RequestParam(value = "state", required = false) String state) { 65 @RequestParam(value = "state", required = false) String state) {
  66 + List<String> scopes = StrUtil.split(scope, ' ');
51 // 0. 校验用户已经登录。通过 Spring Security 实现 67 // 0. 校验用户已经登录。通过 Spring Security 实现
52 68
53 // 1.1 校验 responseType 是否满足 code 或者 token 值 69 // 1.1 校验 responseType 是否满足 code 或者 token 值
54 - if (!StrUtil.equalsAny(responseType, "code", "token")) {  
55 - throw exception0(GlobalErrorCodeConstants.BAD_REQUEST.getCode(), "response_type 参数值允许 code 和 token"); 70 + OAuth2GrantTypeEnum grantTypeEnum = getGrantTypeEnum(responseType);
  71 + // 1.2 校验 redirectUri 重定向域名是否合法 + 校验 scope 是否在 Client 授权范围内
  72 + oauth2ClientService.validOAuthClientFromCache(clientId, grantTypeEnum.getGrantType(), scopes, redirectUri);
  73 +
  74 + // 2. 判断是否满足自动授权(满足)
  75 + boolean approved = oauth2ApproveService.checkForPreApproval(getLoginUserId(), UserTypeEnum.ADMIN.getValue(), clientId, scopes);
  76 + if (approved) {
  77 + // 2.1 如果是 code 授权码模式,则发放 code 授权码,并重定向
  78 + if (grantTypeEnum == OAuth2GrantTypeEnum.AUTHORIZATION_CODE) {
  79 + return success(getAuthorizationCodeRedirect());
  80 + }
  81 + return success(getImplicitGrantRedirect());
56 } 82 }
57 - // 1.2 校验 redirectUri 重定向域名是否合法  
58 83
59 - // 1.3 校验 scope 是否在 Client 授权范围内 84 + // 3. 不满足自动授权,则返回授权相关的展示信息
  85 + return null;
  86 + }
60 87
61 - // 2.1 如果是 code 授权码模式,则发放 code 授权码,并重定向 88 + @PostMapping("/authorize")
  89 + @ApiOperation(value = "申请授权", notes = "适合 code 授权码模式,或者 implicit 简化模式;在 authorize.vue 单点登录界面被【提交】调用")
  90 + @ApiImplicitParams({
  91 + @ApiImplicitParam(name = "response_type", required = true, value = "响应类型", example = "code", dataTypeClass = String.class),
  92 + @ApiImplicitParam(name = "client_id", required = true, value = "客户端编号", example = "tudou", dataTypeClass = String.class),
  93 + @ApiImplicitParam(name = "scope", value = "授权范围", example = "userinfo.read", dataTypeClass = String.class), // 使用 Map<String, Boolean> 格式,Spring MVC 暂时不支持这么接收参数
  94 + @ApiImplicitParam(name = "redirect_uri", required = true, value = "重定向 URI", example = "https://www.iocoder.cn", dataTypeClass = String.class),
  95 + @ApiImplicitParam(name = "approved", value = "用户是否接受", example = "true", dataTypeClass = Boolean.class), // 该参数为 null 时,会基于用户是否已经授权过,进行自动判断
  96 + @ApiImplicitParam(name = "state", example = "123321", dataTypeClass = String.class)
  97 + })
  98 + @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
  99 + // 因为前后端分离,Axios 无法很好的处理 302 重定向,所以和 Spring Security OAuth 略有不同,返回结果是重定向的 URL,剩余交给前端处理
  100 + public CommonResult<String> approveOrDeny(@RequestParam("response_type") String responseType,
  101 + @RequestParam("client_id") String clientId,
  102 + @RequestParam(value = "scope", required = false) String scope,
  103 + @RequestParam("redirect_uri") String redirectUri,
  104 + @RequestParam("approved") Boolean approved,
  105 + @RequestParam(value = "state", required = false) String state) {
  106 + @SuppressWarnings("unchecked")
  107 + Map<String, Boolean> scopes = JsonUtils.parseObject(scope, Map.class);
  108 + scopes = ObjectUtil.defaultIfNull(scopes, Collections.emptyMap());
  109 + // TODO 芋艿:针对 approved + scopes 在看看 spring security 的实现
  110 + // 0. 校验用户已经登录。通过 Spring Security 实现
  111 +
  112 + // 1.1 校验 responseType 是否满足 code 或者 token 值
  113 + OAuth2GrantTypeEnum grantTypeEnum = getGrantTypeEnum(responseType);
  114 + // 1.2 校验 redirectUri 重定向域名是否合法 + 校验 scope 是否在 Client 授权范围内
  115 + oauth2ClientService.validOAuthClientFromCache(clientId, grantTypeEnum.getGrantType(), scopes.keySet(), redirectUri);
  116 +
  117 + // 2.1
  118 + // 2.2
  119 +
  120 + // 3.1 如果是 code 授权码模式,则发放 code 授权码,并重定向
  121 + if (grantTypeEnum == OAuth2GrantTypeEnum.AUTHORIZATION_CODE) {
  122 + return success(getAuthorizationCodeRedirect());
  123 + }
  124 + // 3.2 如果是 token 则是 implicit 简化模式,则发送 accessToken 访问令牌,并重定向
  125 + return success(getImplicitGrantRedirect());
  126 + }
  127 +
  128 + private static OAuth2GrantTypeEnum getGrantTypeEnum(String responseType) {
  129 + if (StrUtil.equals(responseType, "code")) {
  130 + return OAuth2GrantTypeEnum.AUTHORIZATION_CODE;
  131 + }
  132 + if (StrUtil.equalsAny(responseType, "token")) {
  133 + return OAuth2GrantTypeEnum.IMPLICIT;
  134 + }
  135 + throw exception0(BAD_REQUEST.getCode(), "response_type 参数值允许 code 和 token");
  136 + }
  137 +
  138 + private String getImplicitGrantRedirect() {
  139 + return "";
  140 + }
62 141
63 - // 2.2 如果是 token 则是 implicit 简化模式,则发送 accessToken 访问令牌,并重定向  
64 - // TODO 需要确认,是否要 refreshToken 生成  
65 - return CommonResult.success(""); 142 + private String getAuthorizationCodeRedirect() {
  143 + return "";
66 } 144 }
67 145
68 } 146 }
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2CodeDO.java
@@ -3,18 +3,21 @@ package cn.iocoder.yudao.module.system.dal.dataobject.auth; @@ -3,18 +3,21 @@ package cn.iocoder.yudao.module.system.dal.dataobject.auth;
3 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; 3 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
4 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; 4 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
5 import com.baomidou.mybatisplus.annotation.KeySequence; 5 import com.baomidou.mybatisplus.annotation.KeySequence;
  6 +import com.baomidou.mybatisplus.annotation.TableField;
6 import com.baomidou.mybatisplus.annotation.TableName; 7 import com.baomidou.mybatisplus.annotation.TableName;
  8 +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
7 import lombok.Data; 9 import lombok.Data;
8 import lombok.EqualsAndHashCode; 10 import lombok.EqualsAndHashCode;
9 11
10 import java.util.Date; 12 import java.util.Date;
  13 +import java.util.List;
11 14
12 /** 15 /**
13 * OAuth2 授权码 DO 16 * OAuth2 授权码 DO
14 * 17 *
15 * @author 芋道源码 18 * @author 芋道源码
16 */ 19 */
17 -@TableName("system_oauth2_code") 20 +@TableName(value = "system_oauth2_code", autoResultMap = true)
18 @KeySequence("system_oauth2_code_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 21 @KeySequence("system_oauth2_code_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
19 @Data 22 @Data
20 @EqualsAndHashCode(callSuper = true) 23 @EqualsAndHashCode(callSuper = true)
@@ -41,22 +44,25 @@ public class OAuth2CodeDO extends BaseDO { @@ -41,22 +44,25 @@ public class OAuth2CodeDO extends BaseDO {
41 /** 44 /**
42 * 客户端编号 45 * 客户端编号
43 * 46 *
44 - * 关联 {@link OAuth2ClientDO#getId()} 47 + * 关联 {@link OAuth2ClientDO#getClientId()}
45 */ 48 */
46 private String clientId; 49 private String clientId;
47 /** 50 /**
48 - * 刷新令牌  
49 - *  
50 - * 关联 {@link OAuth2RefreshTokenDO#getRefreshToken()} 51 + * 授权范围
51 */ 52 */
52 - private String refreshToken; 53 + @TableField(typeHandler = JacksonTypeHandler.class)
  54 + private List<String> scopes;
53 /** 55 /**
54 - * 过期时间 56 + * 重定向地址
55 */ 57 */
56 - private Date expiresTime; 58 + private String redirectUri;
57 /** 59 /**
58 - * 创建 IP 60 + * 状态
59 */ 61 */
60 - private String createIp; 62 + private String state;
  63 + /**
  64 + * 过期时间
  65 + */
  66 + private Date expiresTime;
61 67
62 } 68 }
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminOAuth2Service.java
1 package cn.iocoder.yudao.module.system.service.auth; 1 package cn.iocoder.yudao.module.system.service.auth;
2 2
  3 +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO;
  4 +
  5 +import java.util.Collection;
  6 +
3 /** 7 /**
4 * 管理后台的 OAuth2 Service 接口 8 * 管理后台的 OAuth2 Service 接口
5 * 9 *
@@ -11,4 +15,14 @@ package cn.iocoder.yudao.module.system.service.auth; @@ -11,4 +15,14 @@ package cn.iocoder.yudao.module.system.service.auth;
11 * @author 芋道源码 15 * @author 芋道源码
12 */ 16 */
13 public interface AdminOAuth2Service { 17 public interface AdminOAuth2Service {
  18 +
  19 + // ImplicitTokenGranter
  20 + OAuth2AccessTokenDO grantImplicit(Long userId, Integer userType,
  21 + String clientId, Collection<String> scopes);
  22 +
  23 + // AuthorizationCodeTokenGranter
  24 + String grantAuthorizationCode(Long userId, Integer userType,
  25 + String clientId, Collection<String> scopes,
  26 + String redirectUri, String state);
  27 +
14 } 28 }
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ApproveService.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.service.auth;
  2 +
  3 +import java.util.Collection;
  4 +
  5 +/**
  6 + * OAuth2 批准 Service 接口
  7 + *
  8 + * 从功能上,和 Spring Security OAuth 的 ApprovalStoreUserApprovalHandler 的功能,记录用户针对指定客户端的授权,减少手动确定。
  9 + *
  10 + * @author 芋道源码
  11 + */
  12 +public interface OAuth2ApproveService {
  13 +
  14 + /**
  15 + * 获得指定用户,针对指定客户端的指定授权,是否通过
  16 + *
  17 + * 参考 ApprovalStoreUserApprovalHandler 的 checkForPreApproval 方法
  18 + *
  19 + * @param userId 用户编号
  20 + * @param userType 用户类型
  21 + * @param clientId 客户端编号
  22 + * @param scopes 授权范围
  23 + * @return 是否授权通过
  24 + */
  25 + boolean checkForPreApproval(Long userId, Integer userType, String clientId, Collection<String> scopes);
  26 +
  27 +}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ApproveServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.service.auth;
  2 +
  3 +import org.springframework.stereotype.Service;
  4 +
  5 +import java.util.Collection;
  6 +
  7 +/**
  8 + * OAuth2 批准 Service 实现类
  9 + *
  10 + * @author 芋道源码
  11 + */
  12 +@Service
  13 +public class OAuth2ApproveServiceImpl implements OAuth2ApproveService {
  14 +
  15 + @Override
  16 + public boolean checkForPreApproval(Long userId, Integer userType, String clientId, Collection<String> scopes) {
  17 + return false;
  18 + }
  19 +
  20 +}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ClientService.java
@@ -7,6 +7,7 @@ import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2Clie @@ -7,6 +7,7 @@ import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2Clie
7 import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2ClientDO; 7 import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2ClientDO;
8 8
9 import javax.validation.Valid; 9 import javax.validation.Valid;
  10 +import java.util.Collection;
10 11
11 /** 12 /**
12 * OAuth2.0 Client Service 接口 13 * OAuth2.0 Client Service 接口
@@ -63,9 +64,24 @@ public interface OAuth2ClientService { @@ -63,9 +64,24 @@ public interface OAuth2ClientService {
63 /** 64 /**
64 * 从缓存中,校验客户端是否合法 65 * 从缓存中,校验客户端是否合法
65 * 66 *
  67 + * @return 客户端
  68 + */
  69 + default OAuth2ClientDO validOAuthClientFromCache(String clientId) {
  70 + return validOAuthClientFromCache(clientId, null, null, null);
  71 + }
  72 +
  73 + /**
  74 + * 从缓存中,校验客户端是否合法
  75 + *
  76 + * 非空时,进行校验
  77 + *
66 * @param clientId 客户端编号 78 * @param clientId 客户端编号
  79 + * @param authorizedGrantType 授权方式
  80 + * @param scopes 授权范围
  81 + * @param redirectUri 重定向地址
67 * @return 客户端 82 * @return 客户端
68 */ 83 */
69 - OAuth2ClientDO validOAuthClientFromCache(String clientId); 84 + OAuth2ClientDO validOAuthClientFromCache(String clientId, String authorizedGrantType,
  85 + Collection<String> scopes, String redirectUri);
70 86
71 } 87 }
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ClientServiceImpl.java
1 package cn.iocoder.yudao.module.system.service.auth; 1 package cn.iocoder.yudao.module.system.service.auth;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.StrUtil;
  5 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
4 import cn.iocoder.yudao.framework.common.pojo.PageResult; 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  7 +import cn.iocoder.yudao.framework.common.util.string.StrUtils;
5 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientCreateReqVO; 8 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientCreateReqVO;
6 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientPageReqVO; 9 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientPageReqVO;
7 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientUpdateReqVO; 10 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientUpdateReqVO;
@@ -18,15 +21,12 @@ import org.springframework.validation.annotation.Validated; @@ -18,15 +21,12 @@ import org.springframework.validation.annotation.Validated;
18 21
19 import javax.annotation.PostConstruct; 22 import javax.annotation.PostConstruct;
20 import javax.annotation.Resource; 23 import javax.annotation.Resource;
21 -import java.util.Date;  
22 -import java.util.List;  
23 -import java.util.Map; 24 +import java.util.*;
24 25
25 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 26 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
26 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; 27 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
27 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getMaxValue; 28 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getMaxValue;
28 -import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.OAUTH2_CLIENT_EXISTS;  
29 -import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.OAUTH2_CLIENT_NOT_EXISTS; 29 +import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
30 30
31 /** 31 /**
32 * OAuth2.0 Client Service 实现类 32 * OAuth2.0 Client Service 实现类
@@ -175,8 +175,29 @@ public class OAuth2ClientServiceImpl implements OAuth2ClientService { @@ -175,8 +175,29 @@ public class OAuth2ClientServiceImpl implements OAuth2ClientService {
175 } 175 }
176 176
177 @Override 177 @Override
178 - public OAuth2ClientDO validOAuthClientFromCache(String clientId) {  
179 - return clientCache.get(clientId); 178 + public OAuth2ClientDO validOAuthClientFromCache(String clientId, String authorizedGrantType, Collection<String> scopes, String redirectUri) {
  179 + // 校验客户端存在、且开启
  180 + OAuth2ClientDO client = clientCache.get(clientId);
  181 + if (client == null) {
  182 + throw exception(OAUTH2_CLIENT_EXISTS);
  183 + }
  184 + if (Objects.equals(client.getStatus(), CommonStatusEnum.ENABLE.getStatus())) {
  185 + throw exception(OAUTH2_CLIENT_DISABLE);
  186 + }
  187 +
  188 + // 校验授权方式
  189 + if (StrUtil.isNotEmpty(authorizedGrantType) && !CollUtil.contains(client.getAuthorizedGrantTypes(), authorizedGrantType)) {
  190 + throw exception(OAUTH2_CLIENT_AUTHORIZED_GRANT_TYPE_NOT_EXISTS);
  191 + }
  192 + // 校验授权范围
  193 + if (CollUtil.isNotEmpty(scopes) && !CollUtil.containsAll(client.getScopes(), scopes)) {
  194 + throw exception(OAUTH2_CLIENT_SCOPE_OVER);
  195 + }
  196 + // 校验回调地址
  197 + if (StrUtil.isNotEmpty(redirectUri) && !StrUtils.startWithAny(redirectUri, client.getRedirectUris())) {
  198 + throw exception(OAUTH2_CLIENT_REDIRECT_URI_NOT_MATCH);
  199 + }
  200 + return client;
180 } 201 }
181 202
182 } 203 }
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2CodeService.java
@@ -7,5 +7,8 @@ package cn.iocoder.yudao.module.system.service.auth; @@ -7,5 +7,8 @@ package cn.iocoder.yudao.module.system.service.auth;
7 * 7 *
8 * @author 芋道源码 8 * @author 芋道源码
9 */ 9 */
10 -public class OAuth2CodeService { 10 +public interface OAuth2CodeService {
  11 +
  12 +
  13 +
11 } 14 }
yudao-ui-admin/src/api/login.js
@@ -122,6 +122,14 @@ export function authorize() { @@ -122,6 +122,14 @@ export function authorize() {
122 response_type: 'code', 122 response_type: 'code',
123 client_id: 'test', 123 client_id: 'test',
124 redirect_uri: 'https://www.iocoder.cn', 124 redirect_uri: 'https://www.iocoder.cn',
  125 + // scopes: {
  126 + // read: true,
  127 + // write: false
  128 + // }
  129 + scope: {
  130 + read: true,
  131 + write: false
  132 + }
125 }, 133 },
126 method: 'post' 134 method: 'post'
127 }) 135 })