Commit aa8fb4acf0fda7b71acc2bdcacbdf3bef398a210
1 parent
ce60ec08
搭建 oauth/authorize 的初步逻辑
Showing
11 changed files
with
252 additions
and
52 deletions
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/string/StrUtils.java
| 1 | 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 | 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 | 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 | 126 | // ========== 系统敏感词 1002020000 ========= |
| 127 | 127 | ErrorCode OAUTH2_CLIENT_NOT_EXISTS = new ErrorCode(1002020000, "OAuth2 客户端不存在"); |
| 128 | 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 | 1 | package cn.iocoder.yudao.module.system.controller.admin.auth; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.util.ObjectUtil; | |
| 3 | 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 | 6 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 7 | +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | |
| 6 | 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 | 12 | import io.swagger.annotations.Api; |
| 8 | 13 | import io.swagger.annotations.ApiImplicitParam; |
| 9 | 14 | import io.swagger.annotations.ApiImplicitParams; |
| 10 | 15 | import io.swagger.annotations.ApiOperation; |
| 11 | 16 | import lombok.extern.slf4j.Slf4j; |
| 12 | 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 | 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 | 30 | @Api(tags = "管理后台 - OAuth2.0 授权") |
| 21 | 31 | @RestController |
| ... | ... | @@ -32,37 +42,105 @@ public class OAuth2Controller { |
| 32 | 42 | |
| 33 | 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 | 52 | @ApiImplicitParams({ |
| 38 | 53 | @ApiImplicitParam(name = "response_type", required = true, value = "响应类型", example = "code", dataTypeClass = String.class), |
| 39 | 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 | 56 | @ApiImplicitParam(name = "redirect_uri", required = true, value = "重定向 URI", example = "https://www.iocoder.cn", dataTypeClass = String.class), |
| 42 | 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 | 62 | @RequestParam("client_id") String clientId, |
| 48 | 63 | @RequestParam(value = "scope", required = false) String scope, |
| 49 | 64 | @RequestParam("redirect_uri") String redirectUri, |
| 50 | 65 | @RequestParam(value = "state", required = false) String state) { |
| 66 | + List<String> scopes = StrUtil.split(scope, ' '); | |
| 51 | 67 | // 0. 校验用户已经登录。通过 Spring Security 实现 |
| 52 | 68 | |
| 53 | 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 | 3 | import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; |
| 4 | 4 | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; |
| 5 | 5 | import com.baomidou.mybatisplus.annotation.KeySequence; |
| 6 | +import com.baomidou.mybatisplus.annotation.TableField; | |
| 6 | 7 | import com.baomidou.mybatisplus.annotation.TableName; |
| 8 | +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; | |
| 7 | 9 | import lombok.Data; |
| 8 | 10 | import lombok.EqualsAndHashCode; |
| 9 | 11 | |
| 10 | 12 | import java.util.Date; |
| 13 | +import java.util.List; | |
| 11 | 14 | |
| 12 | 15 | /** |
| 13 | 16 | * OAuth2 授权码 DO |
| 14 | 17 | * |
| 15 | 18 | * @author 芋道源码 |
| 16 | 19 | */ |
| 17 | -@TableName("system_oauth2_code") | |
| 20 | +@TableName(value = "system_oauth2_code", autoResultMap = true) | |
| 18 | 21 | @KeySequence("system_oauth2_code_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 |
| 19 | 22 | @Data |
| 20 | 23 | @EqualsAndHashCode(callSuper = true) |
| ... | ... | @@ -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 | 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 | 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 | 8 | * 管理后台的 OAuth2 Service 接口 |
| 5 | 9 | * |
| ... | ... | @@ -11,4 +15,14 @@ package cn.iocoder.yudao.module.system.service.auth; |
| 11 | 15 | * @author 芋道源码 |
| 12 | 16 | */ |
| 13 | 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 | 7 | import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2ClientDO; |
| 8 | 8 | |
| 9 | 9 | import javax.validation.Valid; |
| 10 | +import java.util.Collection; | |
| 10 | 11 | |
| 11 | 12 | /** |
| 12 | 13 | * OAuth2.0 Client Service 接口 |
| ... | ... | @@ -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 | 78 | * @param clientId 客户端编号 |
| 79 | + * @param authorizedGrantType 授权方式 | |
| 80 | + * @param scopes 授权范围 | |
| 81 | + * @param redirectUri 重定向地址 | |
| 67 | 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 | 1 | package cn.iocoder.yudao.module.system.service.auth; |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | +import cn.hutool.core.util.StrUtil; | |
| 5 | +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | |
| 4 | 6 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 7 | +import cn.iocoder.yudao.framework.common.util.string.StrUtils; | |
| 5 | 8 | import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientCreateReqVO; |
| 6 | 9 | import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientPageReqVO; |
| 7 | 10 | import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientUpdateReqVO; |
| ... | ... | @@ -18,15 +21,12 @@ import org.springframework.validation.annotation.Validated; |
| 18 | 21 | |
| 19 | 22 | import javax.annotation.PostConstruct; |
| 20 | 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 | 26 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 26 | 27 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; |
| 27 | 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 | 32 | * OAuth2.0 Client Service 实现类 |
| ... | ... | @@ -175,8 +175,29 @@ public class OAuth2ClientServiceImpl implements OAuth2ClientService { |
| 175 | 175 | } |
| 176 | 176 | |
| 177 | 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
yudao-ui-admin/src/api/login.js
| ... | ... | @@ -122,6 +122,14 @@ export function authorize() { |
| 122 | 122 | response_type: 'code', |
| 123 | 123 | client_id: 'test', |
| 124 | 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 | 134 | method: 'post' |
| 127 | 135 | }) | ... | ... |