Commit 6ca88277d80a6f13ec69698bc3735bfe98538645
1 parent
7d1deab4
完成 oauth2 implicit 简化模式的实现
Showing
17 changed files
with
208 additions
and
35 deletions
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/http/HttpUtils.java
| @@ -3,8 +3,12 @@ package cn.iocoder.yudao.framework.common.util.http; | @@ -3,8 +3,12 @@ package cn.iocoder.yudao.framework.common.util.http; | ||
| 3 | import cn.hutool.core.map.TableMap; | 3 | import cn.hutool.core.map.TableMap; |
| 4 | import cn.hutool.core.net.url.UrlBuilder; | 4 | import cn.hutool.core.net.url.UrlBuilder; |
| 5 | import cn.hutool.core.util.ReflectUtil; | 5 | import cn.hutool.core.util.ReflectUtil; |
| 6 | +import org.springframework.web.util.UriComponents; | ||
| 7 | +import org.springframework.web.util.UriComponentsBuilder; | ||
| 6 | 8 | ||
| 9 | +import java.net.URI; | ||
| 7 | import java.nio.charset.Charset; | 10 | import java.nio.charset.Charset; |
| 11 | +import java.util.Map; | ||
| 8 | 12 | ||
| 9 | /** | 13 | /** |
| 10 | * HTTP 工具类 | 14 | * HTTP 工具类 |
| @@ -25,4 +29,70 @@ public class HttpUtils { | @@ -25,4 +29,70 @@ public class HttpUtils { | ||
| 25 | return builder.build(); | 29 | return builder.build(); |
| 26 | } | 30 | } |
| 27 | 31 | ||
| 32 | + private String append(String base, Map<String, ?> query, boolean fragment) { | ||
| 33 | + return append(base, query, null, fragment); | ||
| 34 | + } | ||
| 35 | + | ||
| 36 | + /** | ||
| 37 | + * 拼接 URL | ||
| 38 | + * | ||
| 39 | + * copy from Spring Security OAuth2 的 AuthorizationEndpoint 类的 append 方法 | ||
| 40 | + * | ||
| 41 | + * @param base 基础 URL | ||
| 42 | + * @param query 查询参数 | ||
| 43 | + * @param keys query 的 key,对应的原本的 key 的映射。例如说 query 里有个 key 是 xx,实际它的 key 是 extra_xx,则通过 keys 里添加这个映射 | ||
| 44 | + * @param fragment URL 的 fragment,即拼接到 # 中 | ||
| 45 | + * @return 拼接后的 URL | ||
| 46 | + */ | ||
| 47 | + public static String append(String base, Map<String, ?> query, Map<String, String> keys, boolean fragment) { | ||
| 48 | + UriComponentsBuilder template = UriComponentsBuilder.newInstance(); | ||
| 49 | + UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(base); | ||
| 50 | + URI redirectUri; | ||
| 51 | + try { | ||
| 52 | + // assume it's encoded to start with (if it came in over the wire) | ||
| 53 | + redirectUri = builder.build(true).toUri(); | ||
| 54 | + } catch (Exception e) { | ||
| 55 | + // ... but allow client registrations to contain hard-coded non-encoded values | ||
| 56 | + redirectUri = builder.build().toUri(); | ||
| 57 | + builder = UriComponentsBuilder.fromUri(redirectUri); | ||
| 58 | + } | ||
| 59 | + template.scheme(redirectUri.getScheme()).port(redirectUri.getPort()).host(redirectUri.getHost()) | ||
| 60 | + .userInfo(redirectUri.getUserInfo()).path(redirectUri.getPath()); | ||
| 61 | + | ||
| 62 | + if (fragment) { | ||
| 63 | + StringBuilder values = new StringBuilder(); | ||
| 64 | + if (redirectUri.getFragment() != null) { | ||
| 65 | + String append = redirectUri.getFragment(); | ||
| 66 | + values.append(append); | ||
| 67 | + } | ||
| 68 | + for (String key : query.keySet()) { | ||
| 69 | + if (values.length() > 0) { | ||
| 70 | + values.append("&"); | ||
| 71 | + } | ||
| 72 | + String name = key; | ||
| 73 | + if (keys != null && keys.containsKey(key)) { | ||
| 74 | + name = keys.get(key); | ||
| 75 | + } | ||
| 76 | + values.append(name).append("={").append(key).append("}"); | ||
| 77 | + } | ||
| 78 | + if (values.length() > 0) { | ||
| 79 | + template.fragment(values.toString()); | ||
| 80 | + } | ||
| 81 | + UriComponents encoded = template.build().expand(query).encode(); | ||
| 82 | + builder.fragment(encoded.getFragment()); | ||
| 83 | + } else { | ||
| 84 | + for (String key : query.keySet()) { | ||
| 85 | + String name = key; | ||
| 86 | + if (keys != null && keys.containsKey(key)) { | ||
| 87 | + name = keys.get(key); | ||
| 88 | + } | ||
| 89 | + template.queryParam(name, "{" + key + "}"); | ||
| 90 | + } | ||
| 91 | + template.fragment(redirectUri.getFragment()); | ||
| 92 | + UriComponents encoded = template.build().expand(query).encode(); | ||
| 93 | + builder.query(encoded.getQuery()); | ||
| 94 | + } | ||
| 95 | + return builder.build().toUriString(); | ||
| 96 | + } | ||
| 97 | + | ||
| 28 | } | 98 | } |
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/util/SecurityFrameworkUtils.java
| @@ -20,6 +20,8 @@ import java.util.Collections; | @@ -20,6 +20,8 @@ import java.util.Collections; | ||
| 20 | */ | 20 | */ |
| 21 | public class SecurityFrameworkUtils { | 21 | public class SecurityFrameworkUtils { |
| 22 | 22 | ||
| 23 | + public static final String TOKEN_TYPE = "Bearer"; | ||
| 24 | + | ||
| 23 | private SecurityFrameworkUtils() {} | 25 | private SecurityFrameworkUtils() {} |
| 24 | 26 | ||
| 25 | /** | 27 | /** |
| @@ -34,7 +36,7 @@ public class SecurityFrameworkUtils { | @@ -34,7 +36,7 @@ public class SecurityFrameworkUtils { | ||
| 34 | if (!StringUtils.hasText(authorization)) { | 36 | if (!StringUtils.hasText(authorization)) { |
| 35 | return null; | 37 | return null; |
| 36 | } | 38 | } |
| 37 | - int index = authorization.indexOf("Bearer "); | 39 | + int index = authorization.indexOf(TOKEN_TYPE + " "); |
| 38 | if (index == -1) { // 未找到 | 40 | if (index == -1) { // 未找到 |
| 39 | return null; | 41 | return null; |
| 40 | } | 42 | } |
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenCreateReqDTO.java
| @@ -6,6 +6,7 @@ import lombok.Data; | @@ -6,6 +6,7 @@ import lombok.Data; | ||
| 6 | 6 | ||
| 7 | import javax.validation.constraints.NotNull; | 7 | import javax.validation.constraints.NotNull; |
| 8 | import java.io.Serializable; | 8 | import java.io.Serializable; |
| 9 | +import java.util.List; | ||
| 9 | 10 | ||
| 10 | /** | 11 | /** |
| 11 | * OAuth2.0 访问令牌创建 Request DTO | 12 | * OAuth2.0 访问令牌创建 Request DTO |
| @@ -31,5 +32,9 @@ public class OAuth2AccessTokenCreateReqDTO implements Serializable { | @@ -31,5 +32,9 @@ public class OAuth2AccessTokenCreateReqDTO implements Serializable { | ||
| 31 | */ | 32 | */ |
| 32 | @NotNull(message = "客户端编号不能为空") | 33 | @NotNull(message = "客户端编号不能为空") |
| 33 | private String clientId; | 34 | private String clientId; |
| 35 | + /** | ||
| 36 | + * 授权范围 | ||
| 37 | + */ | ||
| 38 | + private List<String> scopes; | ||
| 34 | 39 | ||
| 35 | } | 40 | } |
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java
| @@ -129,6 +129,6 @@ public interface ErrorCodeConstants { | @@ -129,6 +129,6 @@ public interface ErrorCodeConstants { | ||
| 129 | ErrorCode OAUTH2_CLIENT_DISABLE = new ErrorCode(1002020002, "OAuth2 客户端已禁用"); | 129 | ErrorCode OAUTH2_CLIENT_DISABLE = new ErrorCode(1002020002, "OAuth2 客户端已禁用"); |
| 130 | ErrorCode OAUTH2_CLIENT_AUTHORIZED_GRANT_TYPE_NOT_EXISTS = new ErrorCode(1002020003, "不支持该授权类型"); | 130 | ErrorCode OAUTH2_CLIENT_AUTHORIZED_GRANT_TYPE_NOT_EXISTS = new ErrorCode(1002020003, "不支持该授权类型"); |
| 131 | ErrorCode OAUTH2_CLIENT_SCOPE_OVER = new ErrorCode(1002020004, "授权范围过大"); | 131 | ErrorCode OAUTH2_CLIENT_SCOPE_OVER = new ErrorCode(1002020004, "授权范围过大"); |
| 132 | - ErrorCode OAUTH2_CLIENT_REDIRECT_URI_NOT_MATCH = new ErrorCode(1002020004, "重定向地址不匹配"); | 132 | + ErrorCode OAUTH2_CLIENT_REDIRECT_URI_NOT_MATCH = new ErrorCode(1002020005, "重定向地址不匹配"); |
| 133 | 133 | ||
| 134 | } | 134 | } |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2TokenApiImpl.java
| @@ -24,7 +24,7 @@ public class OAuth2TokenApiImpl implements OAuth2TokenApi { | @@ -24,7 +24,7 @@ public class OAuth2TokenApiImpl implements OAuth2TokenApi { | ||
| 24 | @Override | 24 | @Override |
| 25 | public OAuth2AccessTokenRespDTO createAccessToken(OAuth2AccessTokenCreateReqDTO reqDTO) { | 25 | public OAuth2AccessTokenRespDTO createAccessToken(OAuth2AccessTokenCreateReqDTO reqDTO) { |
| 26 | OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.createAccessToken( | 26 | OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.createAccessToken( |
| 27 | - reqDTO.getUserId(), reqDTO.getUserType(), reqDTO.getClientId()); | 27 | + reqDTO.getUserId(), reqDTO.getUserType(), reqDTO.getClientId(), reqDTO.getScopes()); |
| 28 | return OAuth2TokenConvert.INSTANCE.convert2(accessTokenDO); | 28 | return OAuth2TokenConvert.INSTANCE.convert2(accessTokenDO); |
| 29 | } | 29 | } |
| 30 | 30 |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/OAuth2Controller.http
0 → 100644
| 1 | +### 请求 /system/oauth2/authorize 接口 => 成功 | ||
| 2 | +POST {{baseUrl}}/system/oauth2/authorize | ||
| 3 | +Content-Type: application/x-www-form-urlencoded | ||
| 4 | +Authorization: Bearer {{token}} | ||
| 5 | +tenant-id: {{adminTenentId}} | ||
| 6 | + | ||
| 7 | +response_type=token&client_id=default&scope={"user_info": true}&redirect_uri=https://www.iocoder.cn&auto_approve=true |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/OAuth2Controller.java
| 1 | package cn.iocoder.yudao.module.system.controller.admin.oauth2; | 1 | package cn.iocoder.yudao.module.system.controller.admin.oauth2; |
| 2 | 2 | ||
| 3 | +import cn.hutool.core.lang.Assert; | ||
| 3 | import cn.hutool.core.util.ObjectUtil; | 4 | import cn.hutool.core.util.ObjectUtil; |
| 4 | import cn.hutool.core.util.StrUtil; | 5 | import cn.hutool.core.util.StrUtil; |
| 5 | import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; | 6 | import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; |
| 6 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; | 7 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 7 | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | 8 | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; |
| 8 | import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; | 9 | import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; |
| 10 | +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO; | ||
| 11 | +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2ClientDO; | ||
| 9 | import cn.iocoder.yudao.module.system.enums.auth.OAuth2GrantTypeEnum; | 12 | import cn.iocoder.yudao.module.system.enums.auth.OAuth2GrantTypeEnum; |
| 10 | import cn.iocoder.yudao.module.system.service.oauth2.OAuth2ApproveService; | 13 | import cn.iocoder.yudao.module.system.service.oauth2.OAuth2ApproveService; |
| 11 | import cn.iocoder.yudao.module.system.service.oauth2.OAuth2ClientService; | 14 | import cn.iocoder.yudao.module.system.service.oauth2.OAuth2ClientService; |
| 12 | import cn.iocoder.yudao.module.system.service.oauth2.OAuth2GrantService; | 15 | import cn.iocoder.yudao.module.system.service.oauth2.OAuth2GrantService; |
| 16 | +import cn.iocoder.yudao.module.system.util.oauth2.OAuth2Utils; | ||
| 13 | import io.swagger.annotations.Api; | 17 | import io.swagger.annotations.Api; |
| 14 | import io.swagger.annotations.ApiImplicitParam; | 18 | import io.swagger.annotations.ApiImplicitParam; |
| 15 | import io.swagger.annotations.ApiImplicitParams; | 19 | import io.swagger.annotations.ApiImplicitParams; |
| @@ -26,6 +30,7 @@ import java.util.Map; | @@ -26,6 +30,7 @@ import java.util.Map; | ||
| 26 | import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST; | 30 | import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST; |
| 27 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception0; | 31 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception0; |
| 28 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; | 32 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| 33 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; | ||
| 29 | import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; | 34 | import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; |
| 30 | 35 | ||
| 31 | @Api(tags = "管理后台 - OAuth2.0 授权") | 36 | @Api(tags = "管理后台 - OAuth2.0 授权") |
| @@ -72,16 +77,6 @@ public class OAuth2Controller { | @@ -72,16 +77,6 @@ public class OAuth2Controller { | ||
| 72 | // 1.2 校验 redirectUri 重定向域名是否合法 + 校验 scope 是否在 Client 授权范围内 | 77 | // 1.2 校验 redirectUri 重定向域名是否合法 + 校验 scope 是否在 Client 授权范围内 |
| 73 | oauth2ClientService.validOAuthClientFromCache(clientId, grantTypeEnum.getGrantType(), scopes, redirectUri); | 78 | oauth2ClientService.validOAuthClientFromCache(clientId, grantTypeEnum.getGrantType(), scopes, redirectUri); |
| 74 | 79 | ||
| 75 | - // 2. 判断是否满足自动授权(满足) | ||
| 76 | - boolean approved = oauth2ApproveService.checkForPreApproval(getLoginUserId(), UserTypeEnum.ADMIN.getValue(), clientId, scopes); | ||
| 77 | - if (approved) { | ||
| 78 | - // 2.1 如果是 code 授权码模式,则发放 code 授权码,并重定向 | ||
| 79 | - if (grantTypeEnum == OAuth2GrantTypeEnum.AUTHORIZATION_CODE) { | ||
| 80 | - return success(getAuthorizationCodeRedirect()); | ||
| 81 | - } | ||
| 82 | - return success(getImplicitGrantRedirect()); | ||
| 83 | - } | ||
| 84 | - | ||
| 85 | // 3. 不满足自动授权,则返回授权相关的展示信息 | 80 | // 3. 不满足自动授权,则返回授权相关的展示信息 |
| 86 | return null; | 81 | return null; |
| 87 | } | 82 | } |
| @@ -104,7 +99,7 @@ public class OAuth2Controller { | @@ -104,7 +99,7 @@ public class OAuth2Controller { | ||
| 104 | @RequestParam("client_id") String clientId, | 99 | @RequestParam("client_id") String clientId, |
| 105 | @RequestParam(value = "scope", required = false) String scope, | 100 | @RequestParam(value = "scope", required = false) String scope, |
| 106 | @RequestParam("redirect_uri") String redirectUri, | 101 | @RequestParam("redirect_uri") String redirectUri, |
| 107 | - @RequestParam(value = "autoApprove") Boolean autoApprove, | 102 | + @RequestParam(value = "auto_approve") Boolean autoApprove, |
| 108 | @RequestParam(value = "state", required = false) String state) { | 103 | @RequestParam(value = "state", required = false) String state) { |
| 109 | @SuppressWarnings("unchecked") | 104 | @SuppressWarnings("unchecked") |
| 110 | Map<String, Boolean> scopes = JsonUtils.parseObject(scope, Map.class); | 105 | Map<String, Boolean> scopes = JsonUtils.parseObject(scope, Map.class); |
| @@ -115,27 +110,28 @@ public class OAuth2Controller { | @@ -115,27 +110,28 @@ public class OAuth2Controller { | ||
| 115 | // 1.1 校验 responseType 是否满足 code 或者 token 值 | 110 | // 1.1 校验 responseType 是否满足 code 或者 token 值 |
| 116 | OAuth2GrantTypeEnum grantTypeEnum = getGrantTypeEnum(responseType); | 111 | OAuth2GrantTypeEnum grantTypeEnum = getGrantTypeEnum(responseType); |
| 117 | // 1.2 校验 redirectUri 重定向域名是否合法 + 校验 scope 是否在 Client 授权范围内 | 112 | // 1.2 校验 redirectUri 重定向域名是否合法 + 校验 scope 是否在 Client 授权范围内 |
| 118 | - oauth2ClientService.validOAuthClientFromCache(clientId, grantTypeEnum.getGrantType(), scopes.keySet(), redirectUri); | 113 | + OAuth2ClientDO client = oauth2ClientService.validOAuthClientFromCache(clientId, grantTypeEnum.getGrantType(), scopes.keySet(), redirectUri); |
| 119 | 114 | ||
| 120 | // 2.1 假设 approved 为 null,说明是场景一 | 115 | // 2.1 假设 approved 为 null,说明是场景一 |
| 121 | if (Boolean.TRUE.equals(autoApprove)) { | 116 | if (Boolean.TRUE.equals(autoApprove)) { |
| 122 | // 如果无法自动授权通过,则返回空 url,前端不进行跳转 | 117 | // 如果无法自动授权通过,则返回空 url,前端不进行跳转 |
| 123 | - if (!oauth2ApproveService.checkForPreApproval(getLoginUserId(), UserTypeEnum.ADMIN.getValue(), clientId, scopes.keySet())) { | 118 | + if (!oauth2ApproveService.checkForPreApproval(getLoginUserId(), getUserType(), clientId, scopes.keySet())) { |
| 124 | return success(null); | 119 | return success(null); |
| 125 | } | 120 | } |
| 126 | } else { // 2.2 假设 approved 非 null,说明是场景二 | 121 | } else { // 2.2 假设 approved 非 null,说明是场景二 |
| 127 | // 如果计算后不通过,则跳转一个错误链接 | 122 | // 如果计算后不通过,则跳转一个错误链接 |
| 128 | - if (!oauth2ApproveService.updateAfterApproval(getLoginUserId(), UserTypeEnum.ADMIN.getValue(), clientId, scopes)) { | 123 | + if (!oauth2ApproveService.updateAfterApproval(getLoginUserId(), getUserType(), clientId, scopes)) { |
| 129 | return success("TODO"); | 124 | return success("TODO"); |
| 130 | } | 125 | } |
| 131 | } | 126 | } |
| 132 | 127 | ||
| 133 | // 3.1 如果是 code 授权码模式,则发放 code 授权码,并重定向 | 128 | // 3.1 如果是 code 授权码模式,则发放 code 授权码,并重定向 |
| 129 | + List<String> approveScopes = convertList(scopes.entrySet(), Map.Entry::getKey, Map.Entry::getValue); | ||
| 134 | if (grantTypeEnum == OAuth2GrantTypeEnum.AUTHORIZATION_CODE) { | 130 | if (grantTypeEnum == OAuth2GrantTypeEnum.AUTHORIZATION_CODE) { |
| 135 | return success(getAuthorizationCodeRedirect()); | 131 | return success(getAuthorizationCodeRedirect()); |
| 136 | } | 132 | } |
| 137 | // 3.2 如果是 token 则是 implicit 简化模式,则发送 accessToken 访问令牌,并重定向 | 133 | // 3.2 如果是 token 则是 implicit 简化模式,则发送 accessToken 访问令牌,并重定向 |
| 138 | - return success(getImplicitGrantRedirect()); | 134 | + return success(getImplicitGrantRedirect(getLoginUserId(), client, redirectUri, state, approveScopes)); |
| 139 | } | 135 | } |
| 140 | 136 | ||
| 141 | private static OAuth2GrantTypeEnum getGrantTypeEnum(String responseType) { | 137 | private static OAuth2GrantTypeEnum getGrantTypeEnum(String responseType) { |
| @@ -148,12 +144,22 @@ public class OAuth2Controller { | @@ -148,12 +144,22 @@ public class OAuth2Controller { | ||
| 148 | throw exception0(BAD_REQUEST.getCode(), "response_type 参数值允许 code 和 token"); | 144 | throw exception0(BAD_REQUEST.getCode(), "response_type 参数值允许 code 和 token"); |
| 149 | } | 145 | } |
| 150 | 146 | ||
| 151 | - private String getImplicitGrantRedirect() { | ||
| 152 | - return ""; | 147 | + private String getImplicitGrantRedirect(Long userId, OAuth2ClientDO client, |
| 148 | + String redirectUri, String state, List<String> scopes) { | ||
| 149 | + OAuth2AccessTokenDO accessTokenDO = oAuth2GrantService.grantImplicit(userId, getUserType(), client.getClientId(), scopes); | ||
| 150 | + Assert.notNull(accessTokenDO, "访问令牌不能为空"); // 防御性检查 | ||
| 151 | + // 拼接 URL | ||
| 152 | + // noinspection unchecked | ||
| 153 | + return OAuth2Utils.buildImplicitRedirectUri(redirectUri, accessTokenDO.getAccessToken(), state, accessTokenDO.getExpiresTime(), | ||
| 154 | + scopes, JsonUtils.parseObject(client.getAdditionalInformation(), Map.class)); | ||
| 153 | } | 155 | } |
| 154 | 156 | ||
| 155 | private String getAuthorizationCodeRedirect() { | 157 | private String getAuthorizationCodeRedirect() { |
| 156 | return ""; | 158 | return ""; |
| 157 | } | 159 | } |
| 158 | 160 | ||
| 161 | + private Integer getUserType() { | ||
| 162 | + return UserTypeEnum.ADMIN.getValue(); | ||
| 163 | + } | ||
| 164 | + | ||
| 159 | } | 165 | } |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2AccessTokenDO.java
| @@ -3,13 +3,16 @@ package cn.iocoder.yudao.module.system.dal.dataobject.auth; | @@ -3,13 +3,16 @@ 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.tenant.core.db.TenantBaseDO; | 4 | import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO; |
| 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.TableId; | 7 | import com.baomidou.mybatisplus.annotation.TableId; |
| 7 | import com.baomidou.mybatisplus.annotation.TableName; | 8 | import com.baomidou.mybatisplus.annotation.TableName; |
| 9 | +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; | ||
| 8 | import lombok.Data; | 10 | import lombok.Data; |
| 9 | import lombok.EqualsAndHashCode; | 11 | import lombok.EqualsAndHashCode; |
| 10 | import lombok.experimental.Accessors; | 12 | import lombok.experimental.Accessors; |
| 11 | 13 | ||
| 12 | import java.util.Date; | 14 | import java.util.Date; |
| 15 | +import java.util.List; | ||
| 13 | 16 | ||
| 14 | /** | 17 | /** |
| 15 | * OAuth2 访问令牌 DO | 18 | * OAuth2 访问令牌 DO |
| @@ -56,6 +59,11 @@ public class OAuth2AccessTokenDO extends TenantBaseDO { | @@ -56,6 +59,11 @@ public class OAuth2AccessTokenDO extends TenantBaseDO { | ||
| 56 | */ | 59 | */ |
| 57 | private String clientId; | 60 | private String clientId; |
| 58 | /** | 61 | /** |
| 62 | + * 授权范围 | ||
| 63 | + */ | ||
| 64 | + @TableField(typeHandler = JacksonTypeHandler.class) | ||
| 65 | + private List<String> scopes; | ||
| 66 | + /** | ||
| 59 | * 过期时间 | 67 | * 过期时间 |
| 60 | */ | 68 | */ |
| 61 | private Date expiresTime; | 69 | private Date expiresTime; |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2RefreshTokenDO.java
| @@ -3,12 +3,15 @@ package cn.iocoder.yudao.module.system.dal.dataobject.auth; | @@ -3,12 +3,15 @@ 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 | import lombok.experimental.Accessors; | 11 | import lombok.experimental.Accessors; |
| 10 | 12 | ||
| 11 | import java.util.Date; | 13 | import java.util.Date; |
| 14 | +import java.util.List; | ||
| 12 | 15 | ||
| 13 | /** | 16 | /** |
| 14 | * OAuth2 刷新令牌 | 17 | * OAuth2 刷新令牌 |
| @@ -48,6 +51,11 @@ public class OAuth2RefreshTokenDO extends BaseDO { | @@ -48,6 +51,11 @@ public class OAuth2RefreshTokenDO extends BaseDO { | ||
| 48 | */ | 51 | */ |
| 49 | private String clientId; | 52 | private String clientId; |
| 50 | /** | 53 | /** |
| 54 | + * 授权范围 | ||
| 55 | + */ | ||
| 56 | + @TableField(typeHandler = JacksonTypeHandler.class) | ||
| 57 | + private List<String> scopes; | ||
| 58 | + /** | ||
| 51 | * 过期时间 | 59 | * 过期时间 |
| 52 | */ | 60 | */ |
| 53 | private Date expiresTime; | 61 | private Date expiresTime; |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java
| @@ -207,7 +207,7 @@ public class AdminAuthServiceImpl implements AdminAuthService { | @@ -207,7 +207,7 @@ public class AdminAuthServiceImpl implements AdminAuthService { | ||
| 207 | createLoginLog(userId, username, logType, LoginResultEnum.SUCCESS); | 207 | createLoginLog(userId, username, logType, LoginResultEnum.SUCCESS); |
| 208 | // 创建访问令牌 | 208 | // 创建访问令牌 |
| 209 | OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.createAccessToken(userId, getUserType().getValue(), | 209 | OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.createAccessToken(userId, getUserType().getValue(), |
| 210 | - OAuth2ClientConstants.CLIENT_ID_DEFAULT); | 210 | + OAuth2ClientConstants.CLIENT_ID_DEFAULT, null); |
| 211 | // 构建返回结果 | 211 | // 构建返回结果 |
| 212 | return AuthConvert.INSTANCE.convert(accessTokenDO); | 212 | return AuthConvert.INSTANCE.convert(accessTokenDO); |
| 213 | } | 213 | } |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2ClientServiceImpl.java
| 1 | package cn.iocoder.yudao.module.system.service.oauth2; | 1 | package cn.iocoder.yudao.module.system.service.oauth2; |
| 2 | 2 | ||
| 3 | import cn.hutool.core.collection.CollUtil; | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | +import cn.hutool.core.util.ObjectUtil; | ||
| 4 | import cn.hutool.core.util.StrUtil; | 5 | import cn.hutool.core.util.StrUtil; |
| 5 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | 6 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| 6 | import cn.iocoder.yudao.framework.common.pojo.PageResult; | 7 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| @@ -181,7 +182,7 @@ public class OAuth2ClientServiceImpl implements OAuth2ClientService { | @@ -181,7 +182,7 @@ public class OAuth2ClientServiceImpl implements OAuth2ClientService { | ||
| 181 | if (client == null) { | 182 | if (client == null) { |
| 182 | throw exception(OAUTH2_CLIENT_EXISTS); | 183 | throw exception(OAUTH2_CLIENT_EXISTS); |
| 183 | } | 184 | } |
| 184 | - if (Objects.equals(client.getStatus(), CommonStatusEnum.ENABLE.getStatus())) { | 185 | + if (ObjectUtil.notEqual(client.getStatus(), CommonStatusEnum.ENABLE.getStatus())) { |
| 185 | throw exception(OAUTH2_CLIENT_DISABLE); | 186 | throw exception(OAUTH2_CLIENT_DISABLE); |
| 186 | } | 187 | } |
| 187 | 188 |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2GrantService.java
| @@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.system.service.oauth2; | @@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.system.service.oauth2; | ||
| 2 | 2 | ||
| 3 | import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO; | 3 | import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO; |
| 4 | 4 | ||
| 5 | -import java.util.Collection; | 5 | +import java.util.List; |
| 6 | 6 | ||
| 7 | /** | 7 | /** |
| 8 | * OAuth2 授予 Service 接口 | 8 | * OAuth2 授予 Service 接口 |
| @@ -20,11 +20,11 @@ public interface OAuth2GrantService { | @@ -20,11 +20,11 @@ public interface OAuth2GrantService { | ||
| 20 | 20 | ||
| 21 | // ImplicitTokenGranter | 21 | // ImplicitTokenGranter |
| 22 | OAuth2AccessTokenDO grantImplicit(Long userId, Integer userType, | 22 | OAuth2AccessTokenDO grantImplicit(Long userId, Integer userType, |
| 23 | - String clientId, Collection<String> scopes); | 23 | + String clientId, List<String> scopes); |
| 24 | 24 | ||
| 25 | // AuthorizationCodeTokenGranter | 25 | // AuthorizationCodeTokenGranter |
| 26 | String grantAuthorizationCode(Long userId, Integer userType, | 26 | String grantAuthorizationCode(Long userId, Integer userType, |
| 27 | - String clientId, Collection<String> scopes, | 27 | + String clientId, List<String> scopes, |
| 28 | String redirectUri, String state); | 28 | String redirectUri, String state); |
| 29 | 29 | ||
| 30 | } | 30 | } |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2GrantServiceImpl.java
| @@ -3,7 +3,9 @@ package cn.iocoder.yudao.module.system.service.oauth2; | @@ -3,7 +3,9 @@ package cn.iocoder.yudao.module.system.service.oauth2; | ||
| 3 | import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO; | 3 | import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO; |
| 4 | import org.springframework.stereotype.Service; | 4 | import org.springframework.stereotype.Service; |
| 5 | 5 | ||
| 6 | +import javax.annotation.Resource; | ||
| 6 | import java.util.Collection; | 7 | import java.util.Collection; |
| 8 | +import java.util.List; | ||
| 7 | 9 | ||
| 8 | /** | 10 | /** |
| 9 | * OAuth2 授予 Service 实现类 | 11 | * OAuth2 授予 Service 实现类 |
| @@ -13,15 +15,18 @@ import java.util.Collection; | @@ -13,15 +15,18 @@ import java.util.Collection; | ||
| 13 | @Service | 15 | @Service |
| 14 | public class OAuth2GrantServiceImpl implements OAuth2GrantService { | 16 | public class OAuth2GrantServiceImpl implements OAuth2GrantService { |
| 15 | 17 | ||
| 18 | + @Resource | ||
| 19 | + private OAuth2TokenService oauth2TokenService; | ||
| 20 | + | ||
| 16 | @Override | 21 | @Override |
| 17 | public OAuth2AccessTokenDO grantImplicit(Long userId, Integer userType, | 22 | public OAuth2AccessTokenDO grantImplicit(Long userId, Integer userType, |
| 18 | - String clientId, Collection<String> scopes) { | ||
| 19 | - return null; | 23 | + String clientId, List<String> scopes) { |
| 24 | + return oauth2TokenService.createAccessToken(userId, userType, clientId, scopes); | ||
| 20 | } | 25 | } |
| 21 | 26 | ||
| 22 | @Override | 27 | @Override |
| 23 | public String grantAuthorizationCode(Long userId, Integer userType, | 28 | public String grantAuthorizationCode(Long userId, Integer userType, |
| 24 | - String clientId, Collection<String> scopes, | 29 | + String clientId, List<String> scopes, |
| 25 | String redirectUri, String state) { | 30 | String redirectUri, String state) { |
| 26 | return null; | 31 | return null; |
| 27 | } | 32 | } |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2TokenService.java
| @@ -4,6 +4,8 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; | @@ -4,6 +4,8 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||
| 4 | import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.token.OAuth2AccessTokenPageReqVO; | 4 | import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.token.OAuth2AccessTokenPageReqVO; |
| 5 | import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO; | 5 | import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO; |
| 6 | 6 | ||
| 7 | +import java.util.List; | ||
| 8 | + | ||
| 7 | /** | 9 | /** |
| 8 | * OAuth2.0 Token Service 接口 | 10 | * OAuth2.0 Token Service 接口 |
| 9 | * | 11 | * |
| @@ -22,9 +24,10 @@ public interface OAuth2TokenService { | @@ -22,9 +24,10 @@ public interface OAuth2TokenService { | ||
| 22 | * @param userId 用户编号 | 24 | * @param userId 用户编号 |
| 23 | * @param userType 用户类型 | 25 | * @param userType 用户类型 |
| 24 | * @param clientId 客户端编号 | 26 | * @param clientId 客户端编号 |
| 27 | + * @param scopes 授权范围 | ||
| 25 | * @return 访问令牌的信息 | 28 | * @return 访问令牌的信息 |
| 26 | */ | 29 | */ |
| 27 | - OAuth2AccessTokenDO createAccessToken(Long userId, Integer userType, String clientId); | 30 | + OAuth2AccessTokenDO createAccessToken(Long userId, Integer userType, String clientId, List<String> scopes); |
| 28 | 31 | ||
| 29 | /** | 32 | /** |
| 30 | * 刷新访问令牌 | 33 | * 刷新访问令牌 |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2TokenServiceImpl.java
| @@ -45,10 +45,10 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService { | @@ -45,10 +45,10 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService { | ||
| 45 | 45 | ||
| 46 | @Override | 46 | @Override |
| 47 | @Transactional | 47 | @Transactional |
| 48 | - public OAuth2AccessTokenDO createAccessToken(Long userId, Integer userType, String clientId) { | 48 | + public OAuth2AccessTokenDO createAccessToken(Long userId, Integer userType, String clientId, List<String> scopes) { |
| 49 | OAuth2ClientDO clientDO = oauth2ClientService.validOAuthClientFromCache(clientId); | 49 | OAuth2ClientDO clientDO = oauth2ClientService.validOAuthClientFromCache(clientId); |
| 50 | // 创建刷新令牌 | 50 | // 创建刷新令牌 |
| 51 | - OAuth2RefreshTokenDO refreshTokenDO = createOAuth2RefreshToken(userId, userType, clientDO); | 51 | + OAuth2RefreshTokenDO refreshTokenDO = createOAuth2RefreshToken(userId, userType, clientDO, scopes); |
| 52 | // 创建访问令牌 | 52 | // 创建访问令牌 |
| 53 | return createOAuth2AccessToken(refreshTokenDO, clientDO); | 53 | return createOAuth2AccessToken(refreshTokenDO, clientDO); |
| 54 | } | 54 | } |
| @@ -134,7 +134,8 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService { | @@ -134,7 +134,8 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService { | ||
| 134 | 134 | ||
| 135 | private OAuth2AccessTokenDO createOAuth2AccessToken(OAuth2RefreshTokenDO refreshTokenDO, OAuth2ClientDO clientDO) { | 135 | private OAuth2AccessTokenDO createOAuth2AccessToken(OAuth2RefreshTokenDO refreshTokenDO, OAuth2ClientDO clientDO) { |
| 136 | OAuth2AccessTokenDO accessTokenDO = new OAuth2AccessTokenDO().setAccessToken(generateAccessToken()) | 136 | OAuth2AccessTokenDO accessTokenDO = new OAuth2AccessTokenDO().setAccessToken(generateAccessToken()) |
| 137 | - .setUserId(refreshTokenDO.getUserId()).setUserType(refreshTokenDO.getUserType()).setClientId(clientDO.getClientId()) | 137 | + .setUserId(refreshTokenDO.getUserId()).setUserType(refreshTokenDO.getUserType()) |
| 138 | + .setClientId(clientDO.getClientId()).setScopes(refreshTokenDO.getScopes()) | ||
| 138 | .setRefreshToken(refreshTokenDO.getRefreshToken()) | 139 | .setRefreshToken(refreshTokenDO.getRefreshToken()) |
| 139 | .setExpiresTime(DateUtils.addDate(Calendar.SECOND, clientDO.getAccessTokenValiditySeconds())); | 140 | .setExpiresTime(DateUtils.addDate(Calendar.SECOND, clientDO.getAccessTokenValiditySeconds())); |
| 140 | accessTokenDO.setTenantId(TenantContextHolder.getTenantId()); // 手动设置租户编号,避免缓存到 Redis 的时候,无对应的租户编号 | 141 | accessTokenDO.setTenantId(TenantContextHolder.getTenantId()); // 手动设置租户编号,避免缓存到 Redis 的时候,无对应的租户编号 |
| @@ -144,9 +145,10 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService { | @@ -144,9 +145,10 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService { | ||
| 144 | return accessTokenDO; | 145 | return accessTokenDO; |
| 145 | } | 146 | } |
| 146 | 147 | ||
| 147 | - private OAuth2RefreshTokenDO createOAuth2RefreshToken(Long userId, Integer userType, OAuth2ClientDO clientDO) { | 148 | + private OAuth2RefreshTokenDO createOAuth2RefreshToken(Long userId, Integer userType, OAuth2ClientDO clientDO, List<String> scopes) { |
| 148 | OAuth2RefreshTokenDO refreshToken = new OAuth2RefreshTokenDO().setRefreshToken(generateRefreshToken()) | 149 | OAuth2RefreshTokenDO refreshToken = new OAuth2RefreshTokenDO().setRefreshToken(generateRefreshToken()) |
| 149 | - .setUserId(userId).setUserType(userType).setClientId(clientDO.getClientId()) | 150 | + .setUserId(userId).setUserType(userType) |
| 151 | + .setClientId(clientDO.getClientId()).setScopes(scopes) | ||
| 150 | .setExpiresTime(DateUtils.addDate(Calendar.SECOND, clientDO.getRefreshTokenValiditySeconds())); | 152 | .setExpiresTime(DateUtils.addDate(Calendar.SECOND, clientDO.getRefreshTokenValiditySeconds())); |
| 151 | oauth2RefreshTokenMapper.insert(refreshToken); | 153 | oauth2RefreshTokenMapper.insert(refreshToken); |
| 152 | return refreshToken; | 154 | return refreshToken; |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/util/oauth2/OAuth2Utils.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.system.util.oauth2; | ||
| 2 | + | ||
| 3 | +import cn.hutool.core.collection.CollUtil; | ||
| 4 | +import cn.iocoder.yudao.framework.common.util.http.HttpUtils; | ||
| 5 | +import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; | ||
| 6 | + | ||
| 7 | +import java.util.*; | ||
| 8 | + | ||
| 9 | +/** | ||
| 10 | + * OAuth2 相关的工具类 | ||
| 11 | + * | ||
| 12 | + * @author 芋道源码 | ||
| 13 | + */ | ||
| 14 | +public class OAuth2Utils { | ||
| 15 | + | ||
| 16 | + /** | ||
| 17 | + * 构建简化模式下,重定向的 URI | ||
| 18 | + * | ||
| 19 | + * copy from Spring Security OAuth2 的 AuthorizationEndpoint 类的 appendAccessToken 方法 | ||
| 20 | + * | ||
| 21 | + * @param redirectUri 重定向 URI | ||
| 22 | + * @param accessToken 访问令牌 | ||
| 23 | + * @param state 状态 | ||
| 24 | + * @param expireTime 过期时间 | ||
| 25 | + * @param scopes 授权范围 | ||
| 26 | + * @param additionalInformation 附加信息 | ||
| 27 | + * @return 简化授权模式下的重定向 URI | ||
| 28 | + */ | ||
| 29 | + public static String buildImplicitRedirectUri(String redirectUri, String accessToken, String state, Date expireTime, | ||
| 30 | + Collection<String> scopes, Map<String, Object> additionalInformation) { | ||
| 31 | + Map<String, Object> vars = new LinkedHashMap<String, Object>(); | ||
| 32 | + Map<String, String> keys = new HashMap<String, String>(); | ||
| 33 | + vars.put("access_token", accessToken); | ||
| 34 | + vars.put("token_type", SecurityFrameworkUtils.TOKEN_TYPE.toLowerCase()); | ||
| 35 | + if (state != null) { | ||
| 36 | + vars.put("state", state); | ||
| 37 | + } | ||
| 38 | + if (expireTime != null) { | ||
| 39 | + long expires_in = (expireTime.getTime() - System.currentTimeMillis()) / 1000; | ||
| 40 | + vars.put("expires_in", expires_in); | ||
| 41 | + } | ||
| 42 | + if (CollUtil.isNotEmpty(scopes)) { | ||
| 43 | + vars.put("scope", CollUtil.join(scopes, " ")); | ||
| 44 | + } | ||
| 45 | + for (String key : additionalInformation.keySet()) { | ||
| 46 | + Object value = additionalInformation.get(key); | ||
| 47 | + if (value != null) { | ||
| 48 | + keys.put("extra_" + key, key); | ||
| 49 | + vars.put("extra_" + key, value); | ||
| 50 | + } | ||
| 51 | + } | ||
| 52 | + // Do not include the refresh token (even if there is one) | ||
| 53 | + return HttpUtils.append(redirectUri, vars, keys, true); | ||
| 54 | + } | ||
| 55 | + | ||
| 56 | +} |
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AuthServiceImplTest.java
| @@ -201,7 +201,7 @@ public class AuthServiceImplTest extends BaseDbUnitTest { | @@ -201,7 +201,7 @@ public class AuthServiceImplTest extends BaseDbUnitTest { | ||
| 201 | // mock 缓存登录用户到 Redis | 201 | // mock 缓存登录用户到 Redis |
| 202 | OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class, o -> o.setUserId(1L) | 202 | OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class, o -> o.setUserId(1L) |
| 203 | .setUserType(UserTypeEnum.ADMIN.getValue())); | 203 | .setUserType(UserTypeEnum.ADMIN.getValue())); |
| 204 | - when(oauth2TokenService.createAccessToken(eq(1L), eq(UserTypeEnum.ADMIN.getValue()), eq("default"))) | 204 | + when(oauth2TokenService.createAccessToken(eq(1L), eq(UserTypeEnum.ADMIN.getValue()), eq("default"), isNull())) |
| 205 | .thenReturn(accessTokenDO); | 205 | .thenReturn(accessTokenDO); |
| 206 | 206 | ||
| 207 | // 调用, 并断言异常 | 207 | // 调用, 并断言异常 |