Commit 82cf4e1775beeddbd0a08066e4414289a593f364
Committed by
Gitee
!176 基于 OAuth2.0 实现 SSO 单点登录
Merge pull request !176 from 芋道源码/feature/1.6.2
Showing
81 changed files
with
2117 additions
and
249 deletions
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java
| @@ -68,6 +68,13 @@ public class CollectionUtils { | @@ -68,6 +68,13 @@ public class CollectionUtils { | ||
| 68 | return from.stream().map(func).filter(Objects::nonNull).collect(Collectors.toSet()); | 68 | return from.stream().map(func).filter(Objects::nonNull).collect(Collectors.toSet()); |
| 69 | } | 69 | } |
| 70 | 70 | ||
| 71 | + public static <T, U> Set<U> convertSet(Collection<T> from, Function<T, U> func, Predicate<T> filter) { | ||
| 72 | + if (CollUtil.isEmpty(from)) { | ||
| 73 | + return new HashSet<>(); | ||
| 74 | + } | ||
| 75 | + return from.stream().filter(filter).map(func).filter(Objects::nonNull).collect(Collectors.toSet()); | ||
| 76 | + } | ||
| 77 | + | ||
| 71 | public static <T, K> Map<K, T> convertMap(Collection<T> from, Function<T, K> keyFunc) { | 78 | public static <T, K> Map<K, T> convertMap(Collection<T> from, Function<T, K> keyFunc) { |
| 72 | if (CollUtil.isEmpty(from)) { | 79 | if (CollUtil.isEmpty(from)) { |
| 73 | return new HashMap<>(); | 80 | return new HashMap<>(); |
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/http/HttpUtils.java
| 1 | package cn.iocoder.yudao.framework.common.util.http; | 1 | package cn.iocoder.yudao.framework.common.util.http; |
| 2 | 2 | ||
| 3 | +import cn.hutool.core.codec.Base64; | ||
| 3 | import cn.hutool.core.map.TableMap; | 4 | import cn.hutool.core.map.TableMap; |
| 4 | import cn.hutool.core.net.url.UrlBuilder; | 5 | import cn.hutool.core.net.url.UrlBuilder; |
| 5 | import cn.hutool.core.util.ReflectUtil; | 6 | import cn.hutool.core.util.ReflectUtil; |
| 7 | +import cn.hutool.core.util.StrUtil; | ||
| 8 | +import org.springframework.util.StringUtils; | ||
| 9 | +import org.springframework.web.util.UriComponents; | ||
| 10 | +import org.springframework.web.util.UriComponentsBuilder; | ||
| 6 | 11 | ||
| 12 | +import javax.servlet.http.HttpServletRequest; | ||
| 13 | +import java.net.URI; | ||
| 7 | import java.nio.charset.Charset; | 14 | import java.nio.charset.Charset; |
| 15 | +import java.util.Map; | ||
| 8 | 16 | ||
| 9 | /** | 17 | /** |
| 10 | * HTTP 工具类 | 18 | * HTTP 工具类 |
| @@ -25,4 +33,94 @@ public class HttpUtils { | @@ -25,4 +33,94 @@ public class HttpUtils { | ||
| 25 | return builder.build(); | 33 | return builder.build(); |
| 26 | } | 34 | } |
| 27 | 35 | ||
| 36 | + private String append(String base, Map<String, ?> query, boolean fragment) { | ||
| 37 | + return append(base, query, null, fragment); | ||
| 38 | + } | ||
| 39 | + | ||
| 40 | + /** | ||
| 41 | + * 拼接 URL | ||
| 42 | + * | ||
| 43 | + * copy from Spring Security OAuth2 的 AuthorizationEndpoint 类的 append 方法 | ||
| 44 | + * | ||
| 45 | + * @param base 基础 URL | ||
| 46 | + * @param query 查询参数 | ||
| 47 | + * @param keys query 的 key,对应的原本的 key 的映射。例如说 query 里有个 key 是 xx,实际它的 key 是 extra_xx,则通过 keys 里添加这个映射 | ||
| 48 | + * @param fragment URL 的 fragment,即拼接到 # 中 | ||
| 49 | + * @return 拼接后的 URL | ||
| 50 | + */ | ||
| 51 | + public static String append(String base, Map<String, ?> query, Map<String, String> keys, boolean fragment) { | ||
| 52 | + UriComponentsBuilder template = UriComponentsBuilder.newInstance(); | ||
| 53 | + UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(base); | ||
| 54 | + URI redirectUri; | ||
| 55 | + try { | ||
| 56 | + // assume it's encoded to start with (if it came in over the wire) | ||
| 57 | + redirectUri = builder.build(true).toUri(); | ||
| 58 | + } catch (Exception e) { | ||
| 59 | + // ... but allow client registrations to contain hard-coded non-encoded values | ||
| 60 | + redirectUri = builder.build().toUri(); | ||
| 61 | + builder = UriComponentsBuilder.fromUri(redirectUri); | ||
| 62 | + } | ||
| 63 | + template.scheme(redirectUri.getScheme()).port(redirectUri.getPort()).host(redirectUri.getHost()) | ||
| 64 | + .userInfo(redirectUri.getUserInfo()).path(redirectUri.getPath()); | ||
| 65 | + | ||
| 66 | + if (fragment) { | ||
| 67 | + StringBuilder values = new StringBuilder(); | ||
| 68 | + if (redirectUri.getFragment() != null) { | ||
| 69 | + String append = redirectUri.getFragment(); | ||
| 70 | + values.append(append); | ||
| 71 | + } | ||
| 72 | + for (String key : query.keySet()) { | ||
| 73 | + if (values.length() > 0) { | ||
| 74 | + values.append("&"); | ||
| 75 | + } | ||
| 76 | + String name = key; | ||
| 77 | + if (keys != null && keys.containsKey(key)) { | ||
| 78 | + name = keys.get(key); | ||
| 79 | + } | ||
| 80 | + values.append(name).append("={").append(key).append("}"); | ||
| 81 | + } | ||
| 82 | + if (values.length() > 0) { | ||
| 83 | + template.fragment(values.toString()); | ||
| 84 | + } | ||
| 85 | + UriComponents encoded = template.build().expand(query).encode(); | ||
| 86 | + builder.fragment(encoded.getFragment()); | ||
| 87 | + } else { | ||
| 88 | + for (String key : query.keySet()) { | ||
| 89 | + String name = key; | ||
| 90 | + if (keys != null && keys.containsKey(key)) { | ||
| 91 | + name = keys.get(key); | ||
| 92 | + } | ||
| 93 | + template.queryParam(name, "{" + key + "}"); | ||
| 94 | + } | ||
| 95 | + template.fragment(redirectUri.getFragment()); | ||
| 96 | + UriComponents encoded = template.build().expand(query).encode(); | ||
| 97 | + builder.query(encoded.getQuery()); | ||
| 98 | + } | ||
| 99 | + return builder.build().toUriString(); | ||
| 100 | + } | ||
| 101 | + | ||
| 102 | + public static String[] obtainBasicAuthorization(HttpServletRequest request) { | ||
| 103 | + String clientId; | ||
| 104 | + String clientSecret; | ||
| 105 | + // 先从 Header 中获取 | ||
| 106 | + String authorization = request.getHeader("Authorization"); | ||
| 107 | + authorization = StrUtil.subAfter(authorization, "Basic ", true); | ||
| 108 | + if (StringUtils.hasText(authorization)) { | ||
| 109 | + authorization = Base64.decodeStr(authorization); | ||
| 110 | + clientId = StrUtil.subBefore(authorization, ":", false); | ||
| 111 | + clientSecret = StrUtil.subAfter(authorization, ":", false); | ||
| 112 | + // 再从 Param 中获取 | ||
| 113 | + } else { | ||
| 114 | + clientId = request.getParameter("client_id"); | ||
| 115 | + clientSecret = request.getParameter("client_secret"); | ||
| 116 | + } | ||
| 117 | + | ||
| 118 | + // 如果两者非空,则返回 | ||
| 119 | + if (StrUtil.isNotEmpty(clientId) && StrUtil.isNotEmpty(clientSecret)) { | ||
| 120 | + return new String[]{clientId, clientSecret}; | ||
| 121 | + } | ||
| 122 | + return null; | ||
| 123 | + } | ||
| 124 | + | ||
| 125 | + | ||
| 28 | } | 126 | } |
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-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/LoginUser.java
| @@ -6,6 +6,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; | @@ -6,6 +6,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; | ||
| 6 | import lombok.Data; | 6 | import lombok.Data; |
| 7 | 7 | ||
| 8 | import java.util.HashMap; | 8 | import java.util.HashMap; |
| 9 | +import java.util.List; | ||
| 9 | import java.util.Map; | 10 | import java.util.Map; |
| 10 | 11 | ||
| 11 | /** | 12 | /** |
| @@ -30,6 +31,10 @@ public class LoginUser { | @@ -30,6 +31,10 @@ public class LoginUser { | ||
| 30 | * 租户编号 | 31 | * 租户编号 |
| 31 | */ | 32 | */ |
| 32 | private Long tenantId; | 33 | private Long tenantId; |
| 34 | + /** | ||
| 35 | + * 授权范围 | ||
| 36 | + */ | ||
| 37 | + private List<String> scopes; | ||
| 33 | 38 | ||
| 34 | // ========== 上下文 ========== | 39 | // ========== 上下文 ========== |
| 35 | /** | 40 | /** |
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/TokenAuthenticationFilter.java
| @@ -79,7 +79,7 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter { | @@ -79,7 +79,7 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter { | ||
| 79 | } | 79 | } |
| 80 | // 构建登录用户 | 80 | // 构建登录用户 |
| 81 | return new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType()) | 81 | return new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType()) |
| 82 | - .setTenantId(accessToken.getTenantId()); | 82 | + .setTenantId(accessToken.getTenantId()).setScopes(accessToken.getScopes()); |
| 83 | } catch (ServiceException serviceException) { | 83 | } catch (ServiceException serviceException) { |
| 84 | // 校验 Token 不通过时,考虑到一些接口是无需登录的,所以直接返回 null 即可 | 84 | // 校验 Token 不通过时,考虑到一些接口是无需登录的,所以直接返回 null 即可 |
| 85 | return null; | 85 | return null; |
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/service/SecurityFrameworkService.java
| @@ -41,4 +41,19 @@ public interface SecurityFrameworkService { | @@ -41,4 +41,19 @@ public interface SecurityFrameworkService { | ||
| 41 | */ | 41 | */ |
| 42 | boolean hasAnyRoles(String... roles); | 42 | boolean hasAnyRoles(String... roles); |
| 43 | 43 | ||
| 44 | + /** | ||
| 45 | + * 判断是否有授权 | ||
| 46 | + * | ||
| 47 | + * @param scope 授权 | ||
| 48 | + * @return 是否 | ||
| 49 | + */ | ||
| 50 | + boolean hasScope(String scope); | ||
| 51 | + | ||
| 52 | + /** | ||
| 53 | + * 判断是否有授权范围,任一一个即可 | ||
| 54 | + * | ||
| 55 | + * @param scope 授权范围数组 | ||
| 56 | + * @return 是否 | ||
| 57 | + */ | ||
| 58 | + boolean hasAnyScopes(String... scope); | ||
| 44 | } | 59 | } |
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/service/SecurityFrameworkServiceImpl.java
| 1 | package cn.iocoder.yudao.framework.security.core.service; | 1 | package cn.iocoder.yudao.framework.security.core.service; |
| 2 | 2 | ||
| 3 | +import cn.hutool.core.collection.CollUtil; | ||
| 4 | +import cn.iocoder.yudao.framework.security.core.LoginUser; | ||
| 5 | +import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; | ||
| 3 | import cn.iocoder.yudao.module.system.api.permission.PermissionApi; | 6 | import cn.iocoder.yudao.module.system.api.permission.PermissionApi; |
| 4 | import lombok.AllArgsConstructor; | 7 | import lombok.AllArgsConstructor; |
| 5 | 8 | ||
| 9 | +import java.util.Arrays; | ||
| 10 | + | ||
| 6 | import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; | 11 | import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; |
| 7 | 12 | ||
| 8 | /** | 13 | /** |
| @@ -35,4 +40,18 @@ public class SecurityFrameworkServiceImpl implements SecurityFrameworkService { | @@ -35,4 +40,18 @@ public class SecurityFrameworkServiceImpl implements SecurityFrameworkService { | ||
| 35 | return permissionApi.hasAnyRoles(getLoginUserId(), roles); | 40 | return permissionApi.hasAnyRoles(getLoginUserId(), roles); |
| 36 | } | 41 | } |
| 37 | 42 | ||
| 43 | + @Override | ||
| 44 | + public boolean hasScope(String scope) { | ||
| 45 | + return hasAnyScopes(scope); | ||
| 46 | + } | ||
| 47 | + | ||
| 48 | + @Override | ||
| 49 | + public boolean hasAnyScopes(String... scope) { | ||
| 50 | + LoginUser user = SecurityFrameworkUtils.getLoginUser(); | ||
| 51 | + if (user == null) { | ||
| 52 | + return false; | ||
| 53 | + } | ||
| 54 | + return CollUtil.containsAny(user.getScopes(), Arrays.asList(scope)); | ||
| 55 | + } | ||
| 56 | + | ||
| 38 | } | 57 | } |
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 AUTHORIZATION_BEARER = "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(AUTHORIZATION_BEARER + " "); |
| 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/OAuth2AccessTokenCheckRespDTO.java
| @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.system.api.auth.dto; | @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.system.api.auth.dto; | ||
| 3 | import lombok.Data; | 3 | import lombok.Data; |
| 4 | 4 | ||
| 5 | import java.io.Serializable; | 5 | import java.io.Serializable; |
| 6 | +import java.util.List; | ||
| 6 | 7 | ||
| 7 | /** | 8 | /** |
| 8 | * OAuth2.0 访问令牌的校验 Response DTO | 9 | * OAuth2.0 访问令牌的校验 Response DTO |
| @@ -24,5 +25,9 @@ public class OAuth2AccessTokenCheckRespDTO implements Serializable { | @@ -24,5 +25,9 @@ public class OAuth2AccessTokenCheckRespDTO implements Serializable { | ||
| 24 | * 租户编号 | 25 | * 租户编号 |
| 25 | */ | 26 | */ |
| 26 | private Long tenantId; | 27 | private Long tenantId; |
| 28 | + /** | ||
| 29 | + * 授权范围的数组 | ||
| 30 | + */ | ||
| 31 | + private List<String> scopes; | ||
| 27 | 32 | ||
| 28 | } | 33 | } |
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
| @@ -123,8 +123,23 @@ public interface ErrorCodeConstants { | @@ -123,8 +123,23 @@ public interface ErrorCodeConstants { | ||
| 123 | ErrorCode SENSITIVE_WORD_NOT_EXISTS = new ErrorCode(1002019000, "系统敏感词在所有标签中都不存在"); | 123 | ErrorCode SENSITIVE_WORD_NOT_EXISTS = new ErrorCode(1002019000, "系统敏感词在所有标签中都不存在"); |
| 124 | ErrorCode SENSITIVE_WORD_EXISTS = new ErrorCode(1002019001, "系统敏感词已在标签中存在"); | 124 | ErrorCode SENSITIVE_WORD_EXISTS = new ErrorCode(1002019001, "系统敏感词已在标签中存在"); |
| 125 | 125 | ||
| 126 | - // ========== 系统敏感词 1002020000 ========= | 126 | + // ========== OAuth2 客户端 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(1002020005, "无效 redirect_uri: {}"); | ||
| 133 | + ErrorCode OAUTH2_CLIENT_CLIENT_SECRET_ERROR = new ErrorCode(1002020006, "无效 client_secret: {}"); | ||
| 134 | + | ||
| 135 | + // ========== OAuth2 授权 1002021000 ========= | ||
| 136 | + ErrorCode OAUTH2_GRANT_CLIENT_ID_MISMATCH = new ErrorCode(1002021000, "client_id 不匹配"); | ||
| 137 | + ErrorCode OAUTH2_GRANT_REDIRECT_URI_MISMATCH = new ErrorCode(1002021001, "redirect_uri 不匹配"); | ||
| 138 | + ErrorCode OAUTH2_GRANT_STATE_MISMATCH = new ErrorCode(1002021002, "state 不匹配"); | ||
| 139 | + ErrorCode OAUTH2_GRANT_CODE_NOT_EXISTS = new ErrorCode(1002021003, "code 不存在"); | ||
| 140 | + | ||
| 141 | + // ========== OAuth2 授权 1002022000 ========= | ||
| 142 | + ErrorCode OAUTH2_CODE_NOT_EXISTS = new ErrorCode(1002022000, "code 不存在"); | ||
| 143 | + ErrorCode OAUTH2_CODE_EXPIRE = new ErrorCode(1002022000, "code 已过期"); | ||
| 129 | 144 | ||
| 130 | } | 145 | } |
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/auth/OAuth2GrantTypeEnum.java
| 1 | package cn.iocoder.yudao.module.system.enums.auth; | 1 | package cn.iocoder.yudao.module.system.enums.auth; |
| 2 | 2 | ||
| 3 | +import cn.hutool.core.util.ArrayUtil; | ||
| 3 | import lombok.AllArgsConstructor; | 4 | import lombok.AllArgsConstructor; |
| 4 | import lombok.Getter; | 5 | import lombok.Getter; |
| 5 | 6 | ||
| @@ -21,4 +22,8 @@ public enum OAuth2GrantTypeEnum { | @@ -21,4 +22,8 @@ public enum OAuth2GrantTypeEnum { | ||
| 21 | 22 | ||
| 22 | private final String grantType; | 23 | private final String grantType; |
| 23 | 24 | ||
| 25 | + public static OAuth2GrantTypeEnum getByGranType(String grantType) { | ||
| 26 | + return ArrayUtil.firstMatch(o -> o.getGrantType().equals(grantType), values()); | ||
| 27 | + } | ||
| 28 | + | ||
| 24 | } | 29 | } |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/auth/OAuth2TokenApiImpl.java
| @@ -4,8 +4,8 @@ import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenCheckRespDTO | @@ -4,8 +4,8 @@ import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenCheckRespDTO | ||
| 4 | import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenCreateReqDTO; | 4 | import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenCreateReqDTO; |
| 5 | import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenRespDTO; | 5 | import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenRespDTO; |
| 6 | import cn.iocoder.yudao.module.system.convert.auth.OAuth2TokenConvert; | 6 | import cn.iocoder.yudao.module.system.convert.auth.OAuth2TokenConvert; |
| 7 | -import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO; | ||
| 8 | -import cn.iocoder.yudao.module.system.service.auth.OAuth2TokenService; | 7 | +import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; |
| 8 | +import cn.iocoder.yudao.module.system.service.oauth2.OAuth2TokenService; | ||
| 9 | import org.springframework.stereotype.Service; | 9 | import org.springframework.stereotype.Service; |
| 10 | 10 | ||
| 11 | import javax.annotation.Resource; | 11 | import javax.annotation.Resource; |
| @@ -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/auth/AuthController.java
| @@ -6,7 +6,7 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult; | @@ -6,7 +6,7 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult; | ||
| 6 | import cn.iocoder.yudao.framework.common.util.collection.SetUtils; | 6 | import cn.iocoder.yudao.framework.common.util.collection.SetUtils; |
| 7 | import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; | 7 | import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; |
| 8 | import cn.iocoder.yudao.framework.security.config.SecurityProperties; | 8 | import cn.iocoder.yudao.framework.security.config.SecurityProperties; |
| 9 | -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.*; | 9 | +import cn.iocoder.yudao.module.system.controller.admin.auth.vo.*; |
| 10 | import cn.iocoder.yudao.module.system.convert.auth.AuthConvert; | 10 | import cn.iocoder.yudao.module.system.convert.auth.AuthConvert; |
| 11 | import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO; | 11 | import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO; |
| 12 | import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO; | 12 | import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO; |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/OAuth2Controller.java deleted
| 1 | -package cn.iocoder.yudao.module.system.controller.admin.auth; | ||
| 2 | - | ||
| 3 | -import io.swagger.annotations.Api; | ||
| 4 | -import lombok.extern.slf4j.Slf4j; | ||
| 5 | -import org.springframework.validation.annotation.Validated; | ||
| 6 | -import org.springframework.web.bind.annotation.RequestMapping; | ||
| 7 | -import org.springframework.web.bind.annotation.RestController; | ||
| 8 | - | ||
| 9 | -@Api(tags = "管理后台 - OAuth2.0 授权") | ||
| 10 | -@RestController | ||
| 11 | -@RequestMapping("/system/oauth2") | ||
| 12 | -@Validated | ||
| 13 | -@Slf4j | ||
| 14 | -public class OAuth2Controller { | ||
| 15 | - | ||
| 16 | -// POST oauth/token TokenEndpoint:Password、Implicit、Code、Refresh Token | ||
| 17 | - | ||
| 18 | -// POST oauth/check_token CheckTokenEndpoint | ||
| 19 | - | ||
| 20 | -// DELETE oauth/token ConsumerTokenServices#revokeToken | ||
| 21 | - | ||
| 22 | -// GET oauth/authorize AuthorizationEndpoint | ||
| 23 | - | ||
| 24 | -} |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthLoginReqVO.java renamed to yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthLoginReqVO.java
| 1 | -package cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth; | 1 | +package cn.iocoder.yudao.module.system.controller.admin.auth.vo; |
| 2 | 2 | ||
| 3 | import io.swagger.annotations.ApiModel; | 3 | import io.swagger.annotations.ApiModel; |
| 4 | import io.swagger.annotations.ApiModelProperty; | 4 | import io.swagger.annotations.ApiModelProperty; |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthLoginRespVO.java renamed to yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthLoginRespVO.java
| 1 | -package cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth; | 1 | +package cn.iocoder.yudao.module.system.controller.admin.auth.vo; |
| 2 | 2 | ||
| 3 | import io.swagger.annotations.ApiModel; | 3 | import io.swagger.annotations.ApiModel; |
| 4 | import io.swagger.annotations.ApiModelProperty; | 4 | import io.swagger.annotations.ApiModelProperty; |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthMenuRespVO.java renamed to yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthMenuRespVO.java
| 1 | -package cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth; | 1 | +package cn.iocoder.yudao.module.system.controller.admin.auth.vo; |
| 2 | 2 | ||
| 3 | import io.swagger.annotations.ApiModel; | 3 | import io.swagger.annotations.ApiModel; |
| 4 | import io.swagger.annotations.ApiModelProperty; | 4 | import io.swagger.annotations.ApiModelProperty; |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthPermissionInfoRespVO.java renamed to yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthPermissionInfoRespVO.java
| 1 | -package cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth; | 1 | +package cn.iocoder.yudao.module.system.controller.admin.auth.vo; |
| 2 | 2 | ||
| 3 | import io.swagger.annotations.ApiModel; | 3 | import io.swagger.annotations.ApiModel; |
| 4 | import io.swagger.annotations.ApiModelProperty; | 4 | import io.swagger.annotations.ApiModelProperty; |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthSmsLoginReqVO.java renamed to yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthSmsLoginReqVO.java
| 1 | -package cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth; | 1 | +package cn.iocoder.yudao.module.system.controller.admin.auth.vo; |
| 2 | 2 | ||
| 3 | import io.swagger.annotations.ApiModel; | 3 | import io.swagger.annotations.ApiModel; |
| 4 | import io.swagger.annotations.ApiModelProperty; | 4 | import io.swagger.annotations.ApiModelProperty; |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthSmsSendReqVO.java renamed to yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthSmsSendReqVO.java
| 1 | -package cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth; | 1 | +package cn.iocoder.yudao.module.system.controller.admin.auth.vo; |
| 2 | 2 | ||
| 3 | import cn.iocoder.yudao.framework.common.validation.InEnum; | 3 | import cn.iocoder.yudao.framework.common.validation.InEnum; |
| 4 | import cn.iocoder.yudao.framework.common.validation.Mobile; | 4 | import cn.iocoder.yudao.framework.common.validation.Mobile; |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthSocialBindLoginReqVO.java renamed to yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthSocialBindLoginReqVO.java
| 1 | -package cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth; | 1 | +package cn.iocoder.yudao.module.system.controller.admin.auth.vo; |
| 2 | 2 | ||
| 3 | import cn.iocoder.yudao.framework.common.validation.InEnum; | 3 | import cn.iocoder.yudao.framework.common.validation.InEnum; |
| 4 | import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum; | 4 | import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum; |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthSocialQuickLoginReqVO.java renamed to yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthSocialQuickLoginReqVO.java
| 1 | -package cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth; | 1 | +package cn.iocoder.yudao.module.system.controller.admin.auth.vo; |
| 2 | 2 | ||
| 3 | import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum; | 3 | import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum; |
| 4 | import cn.iocoder.yudao.framework.common.validation.InEnum; | 4 | import cn.iocoder.yudao.framework.common.validation.InEnum; |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/OAuth2ClientController.http renamed to yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/OAuth2ClientController.http
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/OAuth2ClientController.java renamed to yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/OAuth2ClientController.java
| 1 | -package cn.iocoder.yudao.module.system.controller.admin.auth; | 1 | +package cn.iocoder.yudao.module.system.controller.admin.oauth2; |
| 2 | 2 | ||
| 3 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; | 3 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 4 | import cn.iocoder.yudao.framework.common.pojo.PageResult; | 4 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 5 | -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; | ||
| 7 | -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientRespVO; | ||
| 8 | -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientUpdateReqVO; | 5 | +import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.client.OAuth2ClientCreateReqVO; |
| 6 | +import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.client.OAuth2ClientPageReqVO; | ||
| 7 | +import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.client.OAuth2ClientRespVO; | ||
| 8 | +import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.client.OAuth2ClientUpdateReqVO; | ||
| 9 | import cn.iocoder.yudao.module.system.convert.auth.OAuth2ClientConvert; | 9 | import cn.iocoder.yudao.module.system.convert.auth.OAuth2ClientConvert; |
| 10 | -import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2ClientDO; | ||
| 11 | -import cn.iocoder.yudao.module.system.service.auth.OAuth2ClientService; | 10 | +import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2ClientDO; |
| 11 | +import cn.iocoder.yudao.module.system.service.oauth2.OAuth2ClientService; | ||
| 12 | import io.swagger.annotations.Api; | 12 | import io.swagger.annotations.Api; |
| 13 | import io.swagger.annotations.ApiImplicitParam; | 13 | import io.swagger.annotations.ApiImplicitParam; |
| 14 | import io.swagger.annotations.ApiOperation; | 14 | import io.swagger.annotations.ApiOperation; |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/OAuth2OpenController.http
0 → 100644
| 1 | +### 请求 /system/oauth2/authorize 接口 => 成功 | ||
| 2 | +GET {{baseUrl}}/system/oauth2/authorize?clientId=default | ||
| 3 | +Authorization: Bearer {{token}} | ||
| 4 | +tenant-id: {{adminTenentId}} | ||
| 5 | + | ||
| 6 | +### 请求 /system/oauth2/authorize + token 接口 => 成功 | ||
| 7 | +POST {{baseUrl}}/system/oauth2/authorize | ||
| 8 | +Content-Type: application/x-www-form-urlencoded | ||
| 9 | +Authorization: Bearer {{token}} | ||
| 10 | +tenant-id: {{adminTenentId}} | ||
| 11 | + | ||
| 12 | +response_type=token&client_id=default&scope={"user.read": true}&redirect_uri=https://www.iocoder.cn&auto_approve=true | ||
| 13 | + | ||
| 14 | +### 请求 /system/oauth2/authorize + code 接口 => 成功 | ||
| 15 | +POST {{baseUrl}}/system/oauth2/authorize | ||
| 16 | +Content-Type: application/x-www-form-urlencoded | ||
| 17 | +Authorization: Bearer {{token}} | ||
| 18 | +tenant-id: {{adminTenentId}} | ||
| 19 | + | ||
| 20 | +response_type=code&client_id=default&scope={"user.read": true}&redirect_uri=https://www.iocoder.cn&auto_approve=false | ||
| 21 | + | ||
| 22 | +### 请求 /system/oauth2/token + code 接口 => 成功 | ||
| 23 | +POST {{baseUrl}}/system/oauth2/token | ||
| 24 | +Content-Type: application/x-www-form-urlencoded | ||
| 25 | +Authorization: Basic ZGVmYXVsdDphZG1pbjEyMw== | ||
| 26 | +tenant-id: {{adminTenentId}} | ||
| 27 | + | ||
| 28 | +grant_type=authorization_code&redirect_uri=https://www.iocoder.cn&code=189956c07a174588a97157eabef2f93a | ||
| 29 | + | ||
| 30 | +### 请求 /system/oauth2/token + password 接口 => 成功 | ||
| 31 | +POST {{baseUrl}}/system/oauth2/token | ||
| 32 | +Content-Type: application/x-www-form-urlencoded | ||
| 33 | +Authorization: Basic ZGVmYXVsdDphZG1pbjEyMw== | ||
| 34 | +tenant-id: {{adminTenentId}} | ||
| 35 | + | ||
| 36 | +grant_type=password&username=admin&password=admin123&scope=user.read | ||
| 37 | + | ||
| 38 | +### 请求 /system/oauth2/token + refresh_token 接口 => 成功 | ||
| 39 | +POST {{baseUrl}}/system/oauth2/token | ||
| 40 | +Content-Type: application/x-www-form-urlencoded | ||
| 41 | +Authorization: Basic ZGVmYXVsdDphZG1pbjEyMw== | ||
| 42 | +tenant-id: {{adminTenentId}} | ||
| 43 | + | ||
| 44 | +grant_type=refresh_token&refresh_token=00895465d6994f72a9d926ceeed0f588 | ||
| 45 | + | ||
| 46 | +### 请求 /system/oauth2/token + DELETE 接口 => 成功 | ||
| 47 | +DELETE {{baseUrl}}/system/oauth2/token?token=ca8a188f464441d6949c51493a2b7596 | ||
| 48 | +Authorization: Basic ZGVmYXVsdDphZG1pbjEyMw== | ||
| 49 | +tenant-id: {{adminTenentId}} | ||
| 50 | + | ||
| 51 | +### 请求 /system/oauth2/check-token 接口 => 成功 | ||
| 52 | +POST {{baseUrl}}/system/oauth2/check-token?token=620d307c5b4148df8a98dd6c6c547106 | ||
| 53 | +Authorization: Basic ZGVmYXVsdDphZG1pbjEyMw== | ||
| 54 | +tenant-id: {{adminTenentId}} | ||
| 55 | + | ||
| 56 | +### 请求 /system/oauth2/user/get 接口 => 成功 | ||
| 57 | +GET {{baseUrl}}/system/oauth2/user/get | ||
| 58 | +Authorization: Bearer 9502bd7a768a4ade920b90f41e2efd5c | ||
| 59 | +tenant-id: {{adminTenentId}} | ||
| 60 | + | ||
| 61 | +### 请求 /system/oauth2/user/update 接口 => 成功 | ||
| 62 | +PUT {{baseUrl}}/system/oauth2/user/update | ||
| 63 | +Content-Type: application/json | ||
| 64 | +Authorization: Bearer 9502bd7a768a4ade920b90f41e2efd5c | ||
| 65 | +tenant-id: {{adminTenentId}} | ||
| 66 | + | ||
| 67 | +{ | ||
| 68 | + "nickname": "芋道源码" | ||
| 69 | +} |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/OAuth2OpenController.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.system.controller.admin.oauth2; | ||
| 2 | + | ||
| 3 | +import cn.hutool.core.collection.CollUtil; | ||
| 4 | +import cn.hutool.core.lang.Assert; | ||
| 5 | +import cn.hutool.core.util.ArrayUtil; | ||
| 6 | +import cn.hutool.core.util.ObjectUtil; | ||
| 7 | +import cn.hutool.core.util.StrUtil; | ||
| 8 | +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; | ||
| 9 | +import cn.iocoder.yudao.framework.common.pojo.CommonResult; | ||
| 10 | +import cn.iocoder.yudao.framework.common.util.http.HttpUtils; | ||
| 11 | +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | ||
| 12 | +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; | ||
| 13 | +import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.open.OAuth2OpenAccessTokenRespVO; | ||
| 14 | +import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.open.OAuth2OpenAuthorizeInfoRespVO; | ||
| 15 | +import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.open.OAuth2OpenCheckTokenRespVO; | ||
| 16 | +import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.open.user.OAuth2OpenUserInfoRespVO; | ||
| 17 | +import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.open.user.OAuth2OpenUserUpdateReqVO; | ||
| 18 | +import cn.iocoder.yudao.module.system.convert.oauth2.OAuth2OpenConvert; | ||
| 19 | +import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO; | ||
| 20 | +import cn.iocoder.yudao.module.system.dal.dataobject.dept.PostDO; | ||
| 21 | +import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; | ||
| 22 | +import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2ApproveDO; | ||
| 23 | +import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2ClientDO; | ||
| 24 | +import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; | ||
| 25 | +import cn.iocoder.yudao.module.system.enums.auth.OAuth2GrantTypeEnum; | ||
| 26 | +import cn.iocoder.yudao.module.system.service.dept.DeptService; | ||
| 27 | +import cn.iocoder.yudao.module.system.service.dept.PostService; | ||
| 28 | +import cn.iocoder.yudao.module.system.service.oauth2.OAuth2ApproveService; | ||
| 29 | +import cn.iocoder.yudao.module.system.service.oauth2.OAuth2ClientService; | ||
| 30 | +import cn.iocoder.yudao.module.system.service.oauth2.OAuth2GrantService; | ||
| 31 | +import cn.iocoder.yudao.module.system.service.oauth2.OAuth2TokenService; | ||
| 32 | +import cn.iocoder.yudao.module.system.service.user.AdminUserService; | ||
| 33 | +import cn.iocoder.yudao.module.system.util.oauth2.OAuth2Utils; | ||
| 34 | +import io.swagger.annotations.Api; | ||
| 35 | +import io.swagger.annotations.ApiImplicitParam; | ||
| 36 | +import io.swagger.annotations.ApiImplicitParams; | ||
| 37 | +import io.swagger.annotations.ApiOperation; | ||
| 38 | +import lombok.extern.slf4j.Slf4j; | ||
| 39 | +import org.springframework.security.access.prepost.PreAuthorize; | ||
| 40 | +import org.springframework.validation.annotation.Validated; | ||
| 41 | +import org.springframework.web.bind.annotation.*; | ||
| 42 | + | ||
| 43 | +import javax.annotation.Resource; | ||
| 44 | +import javax.servlet.http.HttpServletRequest; | ||
| 45 | +import javax.validation.Valid; | ||
| 46 | +import java.util.Collections; | ||
| 47 | +import java.util.List; | ||
| 48 | +import java.util.Map; | ||
| 49 | + | ||
| 50 | +import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST; | ||
| 51 | +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception0; | ||
| 52 | +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; | ||
| 53 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; | ||
| 54 | +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; | ||
| 55 | + | ||
| 56 | +/** | ||
| 57 | + * 提供给外部应用调用为主 | ||
| 58 | + * | ||
| 59 | + * 一般来说,管理后台的 /system-api/* 是不直接提供给外部应用使用,主要是外部应用能够访问的数据与接口是有限的,而管理后台的 RBAC 无法很好的控制。 | ||
| 60 | + * 参考大量的开放平台,都是独立的一套 OpenAPI,对应到【本系统】就是在 Controller 下新建 open 包,实现 /open-api/* 接口,然后通过 scope 进行控制。 | ||
| 61 | + * 另外,一个公司如果有多个管理后台,它们 client_id 产生的 access token 相互之间是无法互通的,即无法访问它们系统的 API 接口,直到两个 client_id 产生信任授权。 | ||
| 62 | + * | ||
| 63 | + * 考虑到【本系统】暂时不想做的过于复杂,默认只有获取到 access token 之后,可以访问【本系统】管理后台的 /system-api/* 所有接口,除非手动添加 scope 控制。 | ||
| 64 | + * scope 的使用示例,可见当前类的 getUserInfo 和 updateUserInfo 方法,上面有 @PreAuthorize("@ss.hasScope('user.read')") 和 @PreAuthorize("@ss.hasScope('user.write')") 注解 | ||
| 65 | + * | ||
| 66 | + * @author 芋道源码 | ||
| 67 | + */ | ||
| 68 | +@Api(tags = "管理后台 - OAuth2.0 授权") | ||
| 69 | +@RestController | ||
| 70 | +@RequestMapping("/system/oauth2") | ||
| 71 | +@Validated | ||
| 72 | +@Slf4j | ||
| 73 | +public class OAuth2OpenController { | ||
| 74 | + | ||
| 75 | + @Resource | ||
| 76 | + private OAuth2GrantService oauth2GrantService; | ||
| 77 | + @Resource | ||
| 78 | + private OAuth2ClientService oauth2ClientService; | ||
| 79 | + @Resource | ||
| 80 | + private OAuth2ApproveService oauth2ApproveService; | ||
| 81 | + @Resource | ||
| 82 | + private OAuth2TokenService oauth2TokenService; | ||
| 83 | + | ||
| 84 | + /** | ||
| 85 | + * 对应 Spring Security OAuth 的 TokenEndpoint 类的 postAccessToken 方法 | ||
| 86 | + * | ||
| 87 | + * 授权码 authorization_code 模式时:code + redirectUri + state 参数 | ||
| 88 | + * 密码 password 模式时:username + password + scope 参数 | ||
| 89 | + * 刷新 refresh_token 模式时:refreshToken 参数 | ||
| 90 | + * 客户端 client_credentials 模式:scope 参数 | ||
| 91 | + * 简化 implicit 模式时:不支持 | ||
| 92 | + * | ||
| 93 | + * 注意,默认需要传递 client_id + client_secret 参数 | ||
| 94 | + */ | ||
| 95 | + @PostMapping("/token") | ||
| 96 | + @ApiOperation(value = "获得访问令牌", notes = "适合 code 授权码模式,或者 implicit 简化模式;在 sso.vue 单点登录界面被【获取】调用") | ||
| 97 | + @ApiImplicitParams({ | ||
| 98 | + @ApiImplicitParam(name = "grant_type", required = true, value = "授权类型", example = "code", dataTypeClass = String.class), | ||
| 99 | + @ApiImplicitParam(name = "code", value = "授权范围", example = "userinfo.read", dataTypeClass = String.class), | ||
| 100 | + @ApiImplicitParam(name = "redirect_uri", value = "重定向 URI", example = "https://www.iocoder.cn", dataTypeClass = String.class), | ||
| 101 | + @ApiImplicitParam(name = "username", example = "tudou", dataTypeClass = String.class), | ||
| 102 | + @ApiImplicitParam(name = "password", example = "cai", dataTypeClass = String.class), // 多个使用空格分隔 | ||
| 103 | + @ApiImplicitParam(name = "scope", example = "user_info", dataTypeClass = String.class) | ||
| 104 | + }) | ||
| 105 | + @OperateLog(enable = false) // 避免 Post 请求被记录操作日志 | ||
| 106 | + public CommonResult<OAuth2OpenAccessTokenRespVO> postAccessToken(HttpServletRequest request, | ||
| 107 | + @RequestParam("grant_type") String grantType, | ||
| 108 | + @RequestParam(value = "code", required = false) String code, // 授权码模式 | ||
| 109 | + @RequestParam(value = "redirect_uri", required = false) String redirectUri, // 授权码模式 | ||
| 110 | + @RequestParam(value = "state", required = false) String state, // 授权码模式 | ||
| 111 | + @RequestParam(value = "username", required = false) String username, // 密码模式 | ||
| 112 | + @RequestParam(value = "password", required = false) String password, // 密码模式 | ||
| 113 | + @RequestParam(value = "scope", required = false) String scope, // 密码模式 | ||
| 114 | + @RequestParam(value = "refresh_token", required = false) String refreshToken) { // 刷新模式 | ||
| 115 | + List<String> scopes = OAuth2Utils.buildScopes(scope); | ||
| 116 | + // 授权类型 | ||
| 117 | + OAuth2GrantTypeEnum grantTypeEnum = OAuth2GrantTypeEnum.getByGranType(grantType); | ||
| 118 | + if (grantTypeEnum == null) { | ||
| 119 | + throw exception0(BAD_REQUEST.getCode(), StrUtil.format("未知授权类型({})", grantType)); | ||
| 120 | + } | ||
| 121 | + if (grantTypeEnum == OAuth2GrantTypeEnum.IMPLICIT) { | ||
| 122 | + throw exception0(BAD_REQUEST.getCode(), "Token 接口不支持 implicit 授权模式"); | ||
| 123 | + } | ||
| 124 | + | ||
| 125 | + // 校验客户端 | ||
| 126 | + String[] clientIdAndSecret = obtainBasicAuthorization(request); | ||
| 127 | + OAuth2ClientDO client = oauth2ClientService.validOAuthClientFromCache(clientIdAndSecret[0], clientIdAndSecret[1], | ||
| 128 | + grantType, scopes, redirectUri); | ||
| 129 | + | ||
| 130 | + // 根据授权模式,获取访问令牌 | ||
| 131 | + OAuth2AccessTokenDO accessTokenDO; | ||
| 132 | + switch (grantTypeEnum) { | ||
| 133 | + case AUTHORIZATION_CODE: | ||
| 134 | + accessTokenDO = oauth2GrantService.grantAuthorizationCodeForAccessToken(client.getClientId(), code, redirectUri, state); | ||
| 135 | + break; | ||
| 136 | + case PASSWORD: | ||
| 137 | + accessTokenDO = oauth2GrantService.grantPassword(username, password, client.getClientId(), scopes); | ||
| 138 | + break; | ||
| 139 | + case CLIENT_CREDENTIALS: | ||
| 140 | + accessTokenDO = oauth2GrantService.grantClientCredentials(client.getClientId(), scopes); | ||
| 141 | + break; | ||
| 142 | + case REFRESH_TOKEN: | ||
| 143 | + accessTokenDO = oauth2GrantService.grantRefreshToken(refreshToken, client.getClientId()); | ||
| 144 | + break; | ||
| 145 | + default: | ||
| 146 | + throw new IllegalArgumentException("未知授权类型:" + grantType); | ||
| 147 | + } | ||
| 148 | + Assert.notNull(accessTokenDO, "访问令牌不能为空"); // 防御性检查 | ||
| 149 | + return success(OAuth2OpenConvert.INSTANCE.convert(accessTokenDO)); | ||
| 150 | + } | ||
| 151 | + | ||
| 152 | + @DeleteMapping("/token") | ||
| 153 | + @ApiOperation(value = "删除访问令牌") | ||
| 154 | + @ApiImplicitParam(name = "token", required = true, value = "访问令牌", example = "biu", dataTypeClass = String.class) | ||
| 155 | + @OperateLog(enable = false) // 避免 Post 请求被记录操作日志 | ||
| 156 | + public CommonResult<Boolean> revokeToken(HttpServletRequest request, | ||
| 157 | + @RequestParam("token") String token) { | ||
| 158 | + // 校验客户端 | ||
| 159 | + String[] clientIdAndSecret = obtainBasicAuthorization(request); | ||
| 160 | + OAuth2ClientDO client = oauth2ClientService.validOAuthClientFromCache(clientIdAndSecret[0], clientIdAndSecret[1], | ||
| 161 | + null, null, null); | ||
| 162 | + | ||
| 163 | + // 删除访问令牌 | ||
| 164 | + return success(oauth2GrantService.revokeToken(client.getClientId(), token)); | ||
| 165 | + } | ||
| 166 | + | ||
| 167 | + /** | ||
| 168 | + * 对应 Spring Security OAuth 的 CheckTokenEndpoint 类的 checkToken 方法 | ||
| 169 | + */ | ||
| 170 | + @PostMapping("/check-token") | ||
| 171 | + @ApiOperation(value = "校验访问令牌") | ||
| 172 | + @ApiImplicitParam(name = "token", required = true, value = "访问令牌", example = "biu", dataTypeClass = String.class) | ||
| 173 | + @OperateLog(enable = false) // 避免 Post 请求被记录操作日志 | ||
| 174 | + public CommonResult<OAuth2OpenCheckTokenRespVO> checkToken(HttpServletRequest request, | ||
| 175 | + @RequestParam("token") String token) { | ||
| 176 | + // 校验客户端 | ||
| 177 | + String[] clientIdAndSecret = obtainBasicAuthorization(request); | ||
| 178 | + oauth2ClientService.validOAuthClientFromCache(clientIdAndSecret[0], clientIdAndSecret[1], | ||
| 179 | + null, null, null); | ||
| 180 | + | ||
| 181 | + // 校验令牌 | ||
| 182 | + OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.checkAccessToken(token); | ||
| 183 | + Assert.notNull(accessTokenDO, "访问令牌不能为空"); // 防御性检查 | ||
| 184 | + return success(OAuth2OpenConvert.INSTANCE.convert2(accessTokenDO)); | ||
| 185 | + } | ||
| 186 | + | ||
| 187 | + /** | ||
| 188 | + * 对应 Spring Security OAuth 的 AuthorizationEndpoint 类的 authorize 方法 | ||
| 189 | + */ | ||
| 190 | + @GetMapping("/authorize") | ||
| 191 | + @ApiOperation(value = "获得授权信息", notes = "适合 code 授权码模式,或者 implicit 简化模式;在 sso.vue 单点登录界面被【获取】调用") | ||
| 192 | + @ApiImplicitParam(name = "clientId", required = true, value = "客户端编号", example = "tudou", dataTypeClass = String.class) | ||
| 193 | + public CommonResult<OAuth2OpenAuthorizeInfoRespVO> authorize(@RequestParam("clientId") String clientId) { | ||
| 194 | + // 0. 校验用户已经登录。通过 Spring Security 实现 | ||
| 195 | + | ||
| 196 | + // 1. 获得 Client 客户端的信息 | ||
| 197 | + OAuth2ClientDO client = oauth2ClientService.validOAuthClientFromCache(clientId, null, | ||
| 198 | + null, null, null); | ||
| 199 | + // 2. 获得用户已经授权的信息 | ||
| 200 | + List<OAuth2ApproveDO> approves = oauth2ApproveService.getApproveList(getLoginUserId(), getUserType(), clientId); | ||
| 201 | + // 拼接返回 | ||
| 202 | + return success(OAuth2OpenConvert.INSTANCE.convert(client, approves)); | ||
| 203 | + } | ||
| 204 | + | ||
| 205 | + /** | ||
| 206 | + * 对应 Spring Security OAuth 的 AuthorizationEndpoint 类的 approveOrDeny 方法 | ||
| 207 | + * | ||
| 208 | + * 场景一:【自动授权 autoApprove = true】 | ||
| 209 | + * 刚进入 sso.vue 界面,调用该接口,用户历史已经给该应用做过对应的授权,或者 OAuth2Client 支持该 scope 的自动授权 | ||
| 210 | + * 场景二:【手动授权 autoApprove = false】 | ||
| 211 | + * 在 sso.vue 界面,用户选择好 scope 授权范围,调用该接口,进行授权。此时,approved 为 true 或者 false | ||
| 212 | + * | ||
| 213 | + * 因为前后端分离,Axios 无法很好的处理 302 重定向,所以和 Spring Security OAuth 略有不同,返回结果是重定向的 URL,剩余交给前端处理 | ||
| 214 | + */ | ||
| 215 | + @PostMapping("/authorize") | ||
| 216 | + @ApiOperation(value = "申请授权", notes = "适合 code 授权码模式,或者 implicit 简化模式;在 sso.vue 单点登录界面被【提交】调用") | ||
| 217 | + @ApiImplicitParams({ | ||
| 218 | + @ApiImplicitParam(name = "response_type", required = true, value = "响应类型", example = "code", dataTypeClass = String.class), | ||
| 219 | + @ApiImplicitParam(name = "client_id", required = true, value = "客户端编号", example = "tudou", dataTypeClass = String.class), | ||
| 220 | + @ApiImplicitParam(name = "scope", value = "授权范围", example = "userinfo.read", dataTypeClass = String.class), // 使用 Map<String, Boolean> 格式,Spring MVC 暂时不支持这么接收参数 | ||
| 221 | + @ApiImplicitParam(name = "redirect_uri", required = true, value = "重定向 URI", example = "https://www.iocoder.cn", dataTypeClass = String.class), | ||
| 222 | + @ApiImplicitParam(name = "autoApprove", required = true, value = "用户是否接受", example = "true", dataTypeClass = Boolean.class), | ||
| 223 | + @ApiImplicitParam(name = "state", example = "123321", dataTypeClass = String.class) | ||
| 224 | + }) | ||
| 225 | + @OperateLog(enable = false) // 避免 Post 请求被记录操作日志 | ||
| 226 | + public CommonResult<String> approveOrDeny(@RequestParam("response_type") String responseType, | ||
| 227 | + @RequestParam("client_id") String clientId, | ||
| 228 | + @RequestParam(value = "scope", required = false) String scope, | ||
| 229 | + @RequestParam("redirect_uri") String redirectUri, | ||
| 230 | + @RequestParam(value = "auto_approve") Boolean autoApprove, | ||
| 231 | + @RequestParam(value = "state", required = false) String state) { | ||
| 232 | + @SuppressWarnings("unchecked") | ||
| 233 | + Map<String, Boolean> scopes = JsonUtils.parseObject(scope, Map.class); | ||
| 234 | + scopes = ObjectUtil.defaultIfNull(scopes, Collections.emptyMap()); | ||
| 235 | + // TODO 芋艿:针对 approved + scopes 在看看 spring security 的实现 | ||
| 236 | + // 0. 校验用户已经登录。通过 Spring Security 实现 | ||
| 237 | + | ||
| 238 | + // 1.1 校验 responseType 是否满足 code 或者 token 值 | ||
| 239 | + OAuth2GrantTypeEnum grantTypeEnum = getGrantTypeEnum(responseType); | ||
| 240 | + // 1.2 校验 redirectUri 重定向域名是否合法 + 校验 scope 是否在 Client 授权范围内 | ||
| 241 | + OAuth2ClientDO client = oauth2ClientService.validOAuthClientFromCache(clientId, null, | ||
| 242 | + grantTypeEnum.getGrantType(), scopes.keySet(), redirectUri); | ||
| 243 | + | ||
| 244 | + // 2.1 假设 approved 为 null,说明是场景一 | ||
| 245 | + if (Boolean.TRUE.equals(autoApprove)) { | ||
| 246 | + // 如果无法自动授权通过,则返回空 url,前端不进行跳转 | ||
| 247 | + if (!oauth2ApproveService.checkForPreApproval(getLoginUserId(), getUserType(), clientId, scopes.keySet())) { | ||
| 248 | + return success(null); | ||
| 249 | + } | ||
| 250 | + } else { // 2.2 假设 approved 非 null,说明是场景二 | ||
| 251 | + // 如果计算后不通过,则跳转一个错误链接 | ||
| 252 | + if (!oauth2ApproveService.updateAfterApproval(getLoginUserId(), getUserType(), clientId, scopes)) { | ||
| 253 | + return success(OAuth2Utils.buildUnsuccessfulRedirect(redirectUri, responseType, state, | ||
| 254 | + "access_denied", "User denied access")); | ||
| 255 | + } | ||
| 256 | + } | ||
| 257 | + | ||
| 258 | + // 3.1 如果是 code 授权码模式,则发放 code 授权码,并重定向 | ||
| 259 | + List<String> approveScopes = convertList(scopes.entrySet(), Map.Entry::getKey, Map.Entry::getValue); | ||
| 260 | + if (grantTypeEnum == OAuth2GrantTypeEnum.AUTHORIZATION_CODE) { | ||
| 261 | + return success(getAuthorizationCodeRedirect(getLoginUserId(), client, approveScopes, redirectUri, state)); | ||
| 262 | + } | ||
| 263 | + // 3.2 如果是 token 则是 implicit 简化模式,则发送 accessToken 访问令牌,并重定向 | ||
| 264 | + return success(getImplicitGrantRedirect(getLoginUserId(), client, approveScopes, redirectUri, state)); | ||
| 265 | + } | ||
| 266 | + | ||
| 267 | + private static OAuth2GrantTypeEnum getGrantTypeEnum(String responseType) { | ||
| 268 | + if (StrUtil.equals(responseType, "code")) { | ||
| 269 | + return OAuth2GrantTypeEnum.AUTHORIZATION_CODE; | ||
| 270 | + } | ||
| 271 | + if (StrUtil.equalsAny(responseType, "token")) { | ||
| 272 | + return OAuth2GrantTypeEnum.IMPLICIT; | ||
| 273 | + } | ||
| 274 | + throw exception0(BAD_REQUEST.getCode(), "response_type 参数值允许 code 和 token"); | ||
| 275 | + } | ||
| 276 | + | ||
| 277 | + private String getImplicitGrantRedirect(Long userId, OAuth2ClientDO client, | ||
| 278 | + List<String> scopes, String redirectUri, String state) { | ||
| 279 | + // 1. 创建 access token 访问令牌 | ||
| 280 | + OAuth2AccessTokenDO accessTokenDO = oauth2GrantService.grantImplicit(userId, getUserType(), client.getClientId(), scopes); | ||
| 281 | + Assert.notNull(accessTokenDO, "访问令牌不能为空"); // 防御性检查 | ||
| 282 | + // 2. 拼接重定向的 URL | ||
| 283 | + // noinspection unchecked | ||
| 284 | + return OAuth2Utils.buildImplicitRedirectUri(redirectUri, accessTokenDO.getAccessToken(), state, accessTokenDO.getExpiresTime(), | ||
| 285 | + scopes, JsonUtils.parseObject(client.getAdditionalInformation(), Map.class)); | ||
| 286 | + } | ||
| 287 | + | ||
| 288 | + private String getAuthorizationCodeRedirect(Long userId, OAuth2ClientDO client, | ||
| 289 | + List<String> scopes, String redirectUri, String state) { | ||
| 290 | + // 1. 创建 code 授权码 | ||
| 291 | + String authorizationCode = oauth2GrantService.grantAuthorizationCodeForCode(userId,getUserType(), client.getClientId(), scopes, | ||
| 292 | + redirectUri, state); | ||
| 293 | + // 2. 拼接重定向的 URL | ||
| 294 | + return OAuth2Utils.buildAuthorizationCodeRedirectUri(redirectUri, authorizationCode, state); | ||
| 295 | + } | ||
| 296 | + | ||
| 297 | + private Integer getUserType() { | ||
| 298 | + return UserTypeEnum.ADMIN.getValue(); | ||
| 299 | + } | ||
| 300 | + | ||
| 301 | + private String[] obtainBasicAuthorization(HttpServletRequest request) { | ||
| 302 | + String[] clientIdAndSecret = HttpUtils.obtainBasicAuthorization(request); | ||
| 303 | + if (ArrayUtil.isEmpty(clientIdAndSecret) || clientIdAndSecret.length != 2) { | ||
| 304 | + throw exception0(BAD_REQUEST.getCode(), "client_id 或 client_secret 未正确传递"); | ||
| 305 | + } | ||
| 306 | + return clientIdAndSecret; | ||
| 307 | + } | ||
| 308 | + | ||
| 309 | + // ============ 用户操作的示例,展示 scope 的使用 ============ | ||
| 310 | + | ||
| 311 | + @Resource | ||
| 312 | + private AdminUserService userService; | ||
| 313 | + @Resource | ||
| 314 | + private DeptService deptService; | ||
| 315 | + @Resource | ||
| 316 | + private PostService postService; | ||
| 317 | + | ||
| 318 | + @GetMapping("/user/get") | ||
| 319 | + @ApiOperation("获得用户基本信息") | ||
| 320 | + @PreAuthorize("@ss.hasScope('user.read')") | ||
| 321 | + public CommonResult<OAuth2OpenUserInfoRespVO> getUserInfo() { | ||
| 322 | + // 获得用户基本信息 | ||
| 323 | + AdminUserDO user = userService.getUser(getLoginUserId()); | ||
| 324 | + OAuth2OpenUserInfoRespVO resp = OAuth2OpenConvert.INSTANCE.convert(user); | ||
| 325 | + // 获得部门信息 | ||
| 326 | + if (user.getDeptId() != null) { | ||
| 327 | + DeptDO dept = deptService.getDept(user.getDeptId()); | ||
| 328 | + resp.setDept(OAuth2OpenConvert.INSTANCE.convert(dept)); | ||
| 329 | + } | ||
| 330 | + // 获得岗位信息 | ||
| 331 | + if (CollUtil.isNotEmpty(user.getPostIds())) { | ||
| 332 | + List<PostDO> posts = postService.getPosts(user.getPostIds()); | ||
| 333 | + resp.setPosts(OAuth2OpenConvert.INSTANCE.convertList(posts)); | ||
| 334 | + } | ||
| 335 | + return success(resp); | ||
| 336 | + } | ||
| 337 | + | ||
| 338 | + @PutMapping("/user/update") | ||
| 339 | + @ApiOperation("更新用户基本信息") | ||
| 340 | + @PreAuthorize("@ss.hasScope('user.write')") | ||
| 341 | + public CommonResult<Boolean> updateUserInfo(@Valid @RequestBody OAuth2OpenUserUpdateReqVO reqVO) { | ||
| 342 | + // 这里将 UserProfileUpdateReqVO =》UserProfileUpdateReqVO 对象,实现接口的复用。 | ||
| 343 | + // 主要是,AdminUserService 没有自己的 BO 对象,所以复用只能这么做 | ||
| 344 | + userService.updateUserProfile(getLoginUserId(), OAuth2OpenConvert.INSTANCE.convert(reqVO)); | ||
| 345 | + return success(true); | ||
| 346 | + } | ||
| 347 | + | ||
| 348 | +} |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/OAuth2TokenController.java renamed to yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/OAuth2TokenController.java
| 1 | -package cn.iocoder.yudao.module.system.controller.admin.auth; | 1 | +package cn.iocoder.yudao.module.system.controller.admin.oauth2; |
| 2 | 2 | ||
| 3 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; | 3 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 4 | import cn.iocoder.yudao.framework.common.pojo.PageResult; | 4 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 5 | -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.token.OAuth2AccessTokenPageReqVO; | ||
| 6 | -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.token.OAuth2AccessTokenRespVO; | 5 | +import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.token.OAuth2AccessTokenPageReqVO; |
| 6 | +import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.token.OAuth2AccessTokenRespVO; | ||
| 7 | import cn.iocoder.yudao.module.system.convert.auth.OAuth2TokenConvert; | 7 | import cn.iocoder.yudao.module.system.convert.auth.OAuth2TokenConvert; |
| 8 | -import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO; | 8 | +import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; |
| 9 | import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum; | 9 | import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum; |
| 10 | import cn.iocoder.yudao.module.system.service.auth.AdminAuthService; | 10 | import cn.iocoder.yudao.module.system.service.auth.AdminAuthService; |
| 11 | -import cn.iocoder.yudao.module.system.service.auth.OAuth2TokenService; | 11 | +import cn.iocoder.yudao.module.system.service.oauth2.OAuth2TokenService; |
| 12 | import io.swagger.annotations.Api; | 12 | import io.swagger.annotations.Api; |
| 13 | import io.swagger.annotations.ApiImplicitParam; | 13 | import io.swagger.annotations.ApiImplicitParam; |
| 14 | import io.swagger.annotations.ApiOperation; | 14 | import io.swagger.annotations.ApiOperation; |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/client/OAuth2ClientBaseVO.java renamed to yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/vo/client/OAuth2ClientBaseVO.java
| 1 | -package cn.iocoder.yudao.module.system.controller.admin.auth.vo.client; | 1 | +package cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.client; |
| 2 | 2 | ||
| 3 | import cn.hutool.core.util.StrUtil; | 3 | import cn.hutool.core.util.StrUtil; |
| 4 | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | 4 | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; |
| @@ -18,47 +18,43 @@ import java.util.List; | @@ -18,47 +18,43 @@ import java.util.List; | ||
| 18 | @Data | 18 | @Data |
| 19 | public class OAuth2ClientBaseVO { | 19 | public class OAuth2ClientBaseVO { |
| 20 | 20 | ||
| 21 | - @ApiModelProperty(value = "客户端编号", required = true) | 21 | + @ApiModelProperty(value = "客户端编号", required = true, example = "tudou") |
| 22 | @NotNull(message = "客户端编号不能为空") | 22 | @NotNull(message = "客户端编号不能为空") |
| 23 | private String clientId; | 23 | private String clientId; |
| 24 | 24 | ||
| 25 | - @ApiModelProperty(value = "客户端密钥", required = true) | 25 | + @ApiModelProperty(value = "客户端密钥", required = true, example = "fan") |
| 26 | @NotNull(message = "客户端密钥不能为空") | 26 | @NotNull(message = "客户端密钥不能为空") |
| 27 | private String secret; | 27 | private String secret; |
| 28 | 28 | ||
| 29 | - @ApiModelProperty(value = "应用名", required = true) | 29 | + @ApiModelProperty(value = "应用名", required = true, example = "土豆") |
| 30 | @NotNull(message = "应用名不能为空") | 30 | @NotNull(message = "应用名不能为空") |
| 31 | private String name; | 31 | private String name; |
| 32 | 32 | ||
| 33 | - @ApiModelProperty(value = "应用图标", required = true) | 33 | + @ApiModelProperty(value = "应用图标", required = true, example = "https://www.iocoder.cn/xx.png") |
| 34 | @NotNull(message = "应用图标不能为空") | 34 | @NotNull(message = "应用图标不能为空") |
| 35 | @URL(message = "应用图标的地址不正确") | 35 | @URL(message = "应用图标的地址不正确") |
| 36 | private String logo; | 36 | private String logo; |
| 37 | 37 | ||
| 38 | - @ApiModelProperty(value = "应用描述") | 38 | + @ApiModelProperty(value = "应用描述", example = "我是一个应用") |
| 39 | private String description; | 39 | private String description; |
| 40 | 40 | ||
| 41 | - @ApiModelProperty(value = "状态", required = true) | 41 | + @ApiModelProperty(value = "状态", required = true, example = "1", notes = "参见 CommonStatusEnum 枚举") |
| 42 | @NotNull(message = "状态不能为空") | 42 | @NotNull(message = "状态不能为空") |
| 43 | private Integer status; | 43 | private Integer status; |
| 44 | 44 | ||
| 45 | - @ApiModelProperty(value = "访问令牌的有效期", required = true) | 45 | + @ApiModelProperty(value = "访问令牌的有效期", required = true, example = "8640") |
| 46 | @NotNull(message = "访问令牌的有效期不能为空") | 46 | @NotNull(message = "访问令牌的有效期不能为空") |
| 47 | private Integer accessTokenValiditySeconds; | 47 | private Integer accessTokenValiditySeconds; |
| 48 | 48 | ||
| 49 | - @ApiModelProperty(value = "刷新令牌的有效期", required = true) | 49 | + @ApiModelProperty(value = "刷新令牌的有效期", required = true, example = "8640000") |
| 50 | @NotNull(message = "刷新令牌的有效期不能为空") | 50 | @NotNull(message = "刷新令牌的有效期不能为空") |
| 51 | private Integer refreshTokenValiditySeconds; | 51 | private Integer refreshTokenValiditySeconds; |
| 52 | 52 | ||
| 53 | - @ApiModelProperty(value = "可重定向的 URI 地址", required = true) | 53 | + @ApiModelProperty(value = "可重定向的 URI 地址", required = true, example = "https://www.iocoder.cn") |
| 54 | @NotNull(message = "可重定向的 URI 地址不能为空") | 54 | @NotNull(message = "可重定向的 URI 地址不能为空") |
| 55 | private List<@NotEmpty(message = "重定向的 URI 不能为空") | 55 | private List<@NotEmpty(message = "重定向的 URI 不能为空") |
| 56 | @URL(message = "重定向的 URI 格式不正确") String> redirectUris; | 56 | @URL(message = "重定向的 URI 格式不正确") String> redirectUris; |
| 57 | 57 | ||
| 58 | - @ApiModelProperty(value = "是否自动授权", required = true, example = "true") | ||
| 59 | - @NotNull(message = "是否自动授权不能为空") | ||
| 60 | - private Boolean autoApprove; | ||
| 61 | - | ||
| 62 | @ApiModelProperty(value = "授权类型", required = true, example = "password", notes = "参见 OAuth2GrantTypeEnum 枚举") | 58 | @ApiModelProperty(value = "授权类型", required = true, example = "password", notes = "参见 OAuth2GrantTypeEnum 枚举") |
| 63 | @NotNull(message = "授权类型不能为空") | 59 | @NotNull(message = "授权类型不能为空") |
| 64 | private List<String> authorizedGrantTypes; | 60 | private List<String> authorizedGrantTypes; |
| @@ -66,6 +62,9 @@ public class OAuth2ClientBaseVO { | @@ -66,6 +62,9 @@ public class OAuth2ClientBaseVO { | ||
| 66 | @ApiModelProperty(value = "授权范围", example = "user_info") | 62 | @ApiModelProperty(value = "授权范围", example = "user_info") |
| 67 | private List<String> scopes; | 63 | private List<String> scopes; |
| 68 | 64 | ||
| 65 | + @ApiModelProperty(value = "自动通过的授权范围", example = "user_info") | ||
| 66 | + private List<String> autoApproveScopes; | ||
| 67 | + | ||
| 69 | @ApiModelProperty(value = "权限", example = "system:user:query") | 68 | @ApiModelProperty(value = "权限", example = "system:user:query") |
| 70 | private List<String> authorities; | 69 | private List<String> authorities; |
| 71 | 70 |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/client/OAuth2ClientCreateReqVO.java renamed to yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/vo/client/OAuth2ClientCreateReqVO.java
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/client/OAuth2ClientPageReqVO.java renamed to yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/vo/client/OAuth2ClientPageReqVO.java
| 1 | -package cn.iocoder.yudao.module.system.controller.admin.auth.vo.client; | 1 | +package cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.client; |
| 2 | 2 | ||
| 3 | import lombok.*; | 3 | import lombok.*; |
| 4 | import io.swagger.annotations.*; | 4 | import io.swagger.annotations.*; |
| @@ -10,10 +10,10 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam; | @@ -10,10 +10,10 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam; | ||
| 10 | @ToString(callSuper = true) | 10 | @ToString(callSuper = true) |
| 11 | public class OAuth2ClientPageReqVO extends PageParam { | 11 | public class OAuth2ClientPageReqVO extends PageParam { |
| 12 | 12 | ||
| 13 | - @ApiModelProperty(value = "应用名") | 13 | + @ApiModelProperty(value = "应用名", example = "土豆", notes = "模糊匹配") |
| 14 | private String name; | 14 | private String name; |
| 15 | 15 | ||
| 16 | - @ApiModelProperty(value = "状态") | 16 | + @ApiModelProperty(value = "状态", example = "1", notes = "参见 CommonStatusEnum 枚举") |
| 17 | private Integer status; | 17 | private Integer status; |
| 18 | 18 | ||
| 19 | } | 19 | } |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/client/OAuth2ClientRespVO.java renamed to yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/vo/client/OAuth2ClientRespVO.java
| 1 | -package cn.iocoder.yudao.module.system.controller.admin.auth.vo.client; | 1 | +package cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.client; |
| 2 | 2 | ||
| 3 | import io.swagger.annotations.ApiModel; | 3 | import io.swagger.annotations.ApiModel; |
| 4 | import io.swagger.annotations.ApiModelProperty; | 4 | import io.swagger.annotations.ApiModelProperty; |
| @@ -14,7 +14,7 @@ import java.util.Date; | @@ -14,7 +14,7 @@ import java.util.Date; | ||
| 14 | @ToString(callSuper = true) | 14 | @ToString(callSuper = true) |
| 15 | public class OAuth2ClientRespVO extends OAuth2ClientBaseVO { | 15 | public class OAuth2ClientRespVO extends OAuth2ClientBaseVO { |
| 16 | 16 | ||
| 17 | - @ApiModelProperty(value = "编号", required = true) | 17 | + @ApiModelProperty(value = "编号", required = true, example = "1024") |
| 18 | private Long id; | 18 | private Long id; |
| 19 | 19 | ||
| 20 | @ApiModelProperty(value = "创建时间", required = true) | 20 | @ApiModelProperty(value = "创建时间", required = true) |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/client/OAuth2ClientUpdateReqVO.java renamed to yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/vo/client/OAuth2ClientUpdateReqVO.java
| 1 | -package cn.iocoder.yudao.module.system.controller.admin.auth.vo.client; | 1 | +package cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.client; |
| 2 | 2 | ||
| 3 | import io.swagger.annotations.ApiModel; | 3 | import io.swagger.annotations.ApiModel; |
| 4 | import io.swagger.annotations.ApiModelProperty; | 4 | import io.swagger.annotations.ApiModelProperty; |
| @@ -14,7 +14,7 @@ import javax.validation.constraints.NotNull; | @@ -14,7 +14,7 @@ import javax.validation.constraints.NotNull; | ||
| 14 | @ToString(callSuper = true) | 14 | @ToString(callSuper = true) |
| 15 | public class OAuth2ClientUpdateReqVO extends OAuth2ClientBaseVO { | 15 | public class OAuth2ClientUpdateReqVO extends OAuth2ClientBaseVO { |
| 16 | 16 | ||
| 17 | - @ApiModelProperty(value = "编号", required = true) | 17 | + @ApiModelProperty(value = "编号", required = true, example = "1024") |
| 18 | @NotNull(message = "编号不能为空") | 18 | @NotNull(message = "编号不能为空") |
| 19 | private Long id; | 19 | private Long id; |
| 20 | 20 |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/vo/open/OAuth2OpenAccessTokenRespVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.open; | ||
| 2 | + | ||
| 3 | +import com.fasterxml.jackson.annotation.JsonProperty; | ||
| 4 | +import io.swagger.annotations.ApiModel; | ||
| 5 | +import io.swagger.annotations.ApiModelProperty; | ||
| 6 | +import lombok.AllArgsConstructor; | ||
| 7 | +import lombok.Data; | ||
| 8 | +import lombok.NoArgsConstructor; | ||
| 9 | + | ||
| 10 | +@ApiModel("管理后台 - 【开放接口】访问令牌 Response VO") | ||
| 11 | +@Data | ||
| 12 | +@NoArgsConstructor | ||
| 13 | +@AllArgsConstructor | ||
| 14 | +public class OAuth2OpenAccessTokenRespVO { | ||
| 15 | + | ||
| 16 | + @ApiModelProperty(value = "访问令牌", required = true, example = "tudou") | ||
| 17 | + @JsonProperty("access_token") | ||
| 18 | + private String accessToken; | ||
| 19 | + | ||
| 20 | + @ApiModelProperty(value = "刷新令牌", required = true, example = "nice") | ||
| 21 | + @JsonProperty("refresh_token") | ||
| 22 | + private String refreshToken; | ||
| 23 | + | ||
| 24 | + @ApiModelProperty(value = "令牌类型", required = true, example = "bearer") | ||
| 25 | + @JsonProperty("token_type") | ||
| 26 | + private String tokenType; | ||
| 27 | + | ||
| 28 | + @ApiModelProperty(value = "过期时间", required = true, example = "42430", notes = "单位:秒") | ||
| 29 | + @JsonProperty("expires_in") | ||
| 30 | + private Long expiresIn; | ||
| 31 | + | ||
| 32 | + @ApiModelProperty(value = "授权范围", example = "user_info", notes = "如果多个授权范围,使用空格分隔") | ||
| 33 | + private String scope; | ||
| 34 | + | ||
| 35 | +} |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/vo/open/OAuth2OpenAuthorizeInfoRespVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.open; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.framework.common.core.KeyValue; | ||
| 4 | +import io.swagger.annotations.ApiModel; | ||
| 5 | +import io.swagger.annotations.ApiModelProperty; | ||
| 6 | +import lombok.AllArgsConstructor; | ||
| 7 | +import lombok.Data; | ||
| 8 | +import lombok.NoArgsConstructor; | ||
| 9 | + | ||
| 10 | +import java.util.List; | ||
| 11 | + | ||
| 12 | +@ApiModel("管理后台 - 授权页的信息 Response VO") | ||
| 13 | +@Data | ||
| 14 | +@NoArgsConstructor | ||
| 15 | +@AllArgsConstructor | ||
| 16 | +public class OAuth2OpenAuthorizeInfoRespVO { | ||
| 17 | + | ||
| 18 | + /** | ||
| 19 | + * 客户端 | ||
| 20 | + */ | ||
| 21 | + private Client client; | ||
| 22 | + | ||
| 23 | + @ApiModelProperty(value = "scope 的选中信息", required = true, notes = "使用 List 保证有序性,Key 是 scope,Value 为是否选中") | ||
| 24 | + private List<KeyValue<String, Boolean>> scopes; | ||
| 25 | + | ||
| 26 | + @Data | ||
| 27 | + @NoArgsConstructor | ||
| 28 | + @AllArgsConstructor | ||
| 29 | + public static class Client { | ||
| 30 | + | ||
| 31 | + @ApiModelProperty(value = "应用名", required = true, example = "土豆") | ||
| 32 | + private String name; | ||
| 33 | + | ||
| 34 | + @ApiModelProperty(value = "应用图标", required = true, example = "https://www.iocoder.cn/xx.png") | ||
| 35 | + private String logo; | ||
| 36 | + | ||
| 37 | + } | ||
| 38 | + | ||
| 39 | +} |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/vo/open/OAuth2OpenCheckTokenRespVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.open; | ||
| 2 | + | ||
| 3 | +import com.fasterxml.jackson.annotation.JsonProperty; | ||
| 4 | +import io.swagger.annotations.ApiModel; | ||
| 5 | +import io.swagger.annotations.ApiModelProperty; | ||
| 6 | +import lombok.AllArgsConstructor; | ||
| 7 | +import lombok.Data; | ||
| 8 | +import lombok.NoArgsConstructor; | ||
| 9 | + | ||
| 10 | +import java.util.Set; | ||
| 11 | + | ||
| 12 | +@ApiModel("管理后台 - 【开放接口】校验令牌 Response VO") | ||
| 13 | +@Data | ||
| 14 | +@NoArgsConstructor | ||
| 15 | +@AllArgsConstructor | ||
| 16 | +public class OAuth2OpenCheckTokenRespVO { | ||
| 17 | + | ||
| 18 | + @ApiModelProperty(value = "用户编号", required = true, example = "666") | ||
| 19 | + @JsonProperty("user_id") | ||
| 20 | + private Long userId; | ||
| 21 | + @ApiModelProperty(value = "用户类型", required = true, example = "2", notes = "参见 UserTypeEnum 枚举") | ||
| 22 | + @JsonProperty("user_type") | ||
| 23 | + private Integer userType; | ||
| 24 | + @ApiModelProperty(value = "租户编号", required = true, example = "1024") | ||
| 25 | + @JsonProperty("tenant_id") | ||
| 26 | + private Long tenantId; | ||
| 27 | + | ||
| 28 | + @ApiModelProperty(value = "客户端编号", required = true, example = "car") | ||
| 29 | + private String clientId; | ||
| 30 | + @ApiModelProperty(value = "授权范围", required = true, example = "user_info") | ||
| 31 | + private Set<String> scopes; | ||
| 32 | + | ||
| 33 | + @ApiModelProperty(value = "访问令牌", required = true, example = "tudou") | ||
| 34 | + @JsonProperty("access_token") | ||
| 35 | + private String accessToken; | ||
| 36 | + | ||
| 37 | + @ApiModelProperty(value = "过期时间", required = true, example = "1593092157", notes = "时间戳 / 1000,即单位:秒") | ||
| 38 | + @JsonProperty("exp") | ||
| 39 | + private Long exp; | ||
| 40 | +} |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/vo/open/user/OAuth2OpenUserInfoRespVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.open.user; | ||
| 2 | + | ||
| 3 | +import io.swagger.annotations.ApiModel; | ||
| 4 | +import io.swagger.annotations.ApiModelProperty; | ||
| 5 | +import lombok.AllArgsConstructor; | ||
| 6 | +import lombok.Data; | ||
| 7 | +import lombok.NoArgsConstructor; | ||
| 8 | + | ||
| 9 | +import java.util.List; | ||
| 10 | + | ||
| 11 | +@ApiModel("管理后台 - 【开放接口】获得用户基本信息 Response VO") | ||
| 12 | +@Data | ||
| 13 | +@NoArgsConstructor | ||
| 14 | +@AllArgsConstructor | ||
| 15 | +public class OAuth2OpenUserInfoRespVO { | ||
| 16 | + | ||
| 17 | + @ApiModelProperty(value = "用户编号", required = true, example = "1") | ||
| 18 | + private Long id; | ||
| 19 | + | ||
| 20 | + @ApiModelProperty(value = "用户昵称", required = true, example = "芋艿") | ||
| 21 | + private String username; | ||
| 22 | + | ||
| 23 | + @ApiModelProperty(value = "用户昵称", required = true, example = "芋道") | ||
| 24 | + private String nickname; | ||
| 25 | + | ||
| 26 | + @ApiModelProperty(value = "用户邮箱", example = "yudao@iocoder.cn") | ||
| 27 | + private String email; | ||
| 28 | + @ApiModelProperty(value = "手机号码", example = "15601691300") | ||
| 29 | + private String mobile; | ||
| 30 | + | ||
| 31 | + @ApiModelProperty(value = "用户性别", example = "1", notes = "参见 SexEnum 枚举类") | ||
| 32 | + private Integer sex; | ||
| 33 | + | ||
| 34 | + @ApiModelProperty(value = "用户头像", example = "https://www.iocoder.cn/xxx.png") | ||
| 35 | + private String avatar; | ||
| 36 | + | ||
| 37 | + /** | ||
| 38 | + * 所在部门 | ||
| 39 | + */ | ||
| 40 | + private Dept dept; | ||
| 41 | + | ||
| 42 | + /** | ||
| 43 | + * 所属岗位数组 | ||
| 44 | + */ | ||
| 45 | + private List<Post> posts; | ||
| 46 | + | ||
| 47 | + @ApiModel("部门") | ||
| 48 | + @Data | ||
| 49 | + public static class Dept { | ||
| 50 | + | ||
| 51 | + @ApiModelProperty(value = "部门编号", required = true, example = "1") | ||
| 52 | + private Long id; | ||
| 53 | + | ||
| 54 | + @ApiModelProperty(value = "部门名称", required = true, example = "研发部") | ||
| 55 | + private String name; | ||
| 56 | + | ||
| 57 | + } | ||
| 58 | + | ||
| 59 | + @ApiModel("岗位") | ||
| 60 | + @Data | ||
| 61 | + public static class Post { | ||
| 62 | + | ||
| 63 | + @ApiModelProperty(value = "岗位编号", required = true, example = "1") | ||
| 64 | + private Long id; | ||
| 65 | + | ||
| 66 | + @ApiModelProperty(value = "岗位名称", required = true, example = "开发") | ||
| 67 | + private String name; | ||
| 68 | + | ||
| 69 | + } | ||
| 70 | + | ||
| 71 | +} |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/vo/open/user/OAuth2OpenUserUpdateReqVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.open.user; | ||
| 2 | + | ||
| 3 | +import io.swagger.annotations.ApiModel; | ||
| 4 | +import io.swagger.annotations.ApiModelProperty; | ||
| 5 | +import lombok.AllArgsConstructor; | ||
| 6 | +import lombok.Data; | ||
| 7 | +import lombok.NoArgsConstructor; | ||
| 8 | +import org.hibernate.validator.constraints.Length; | ||
| 9 | + | ||
| 10 | +import javax.validation.constraints.Email; | ||
| 11 | +import javax.validation.constraints.Size; | ||
| 12 | + | ||
| 13 | +@ApiModel("管理后台 - 【开放接口】更新用户基本信息 Request VO") | ||
| 14 | +@Data | ||
| 15 | +@NoArgsConstructor | ||
| 16 | +@AllArgsConstructor | ||
| 17 | +public class OAuth2OpenUserUpdateReqVO { | ||
| 18 | + | ||
| 19 | + @ApiModelProperty(value = "用户昵称", required = true, example = "芋艿") | ||
| 20 | + @Size(max = 30, message = "用户昵称长度不能超过 30 个字符") | ||
| 21 | + private String nickname; | ||
| 22 | + | ||
| 23 | + @ApiModelProperty(value = "用户邮箱", example = "yudao@iocoder.cn") | ||
| 24 | + @Email(message = "邮箱格式不正确") | ||
| 25 | + @Size(max = 50, message = "邮箱长度不能超过 50 个字符") | ||
| 26 | + private String email; | ||
| 27 | + | ||
| 28 | + @ApiModelProperty(value = "手机号码", example = "15601691300") | ||
| 29 | + @Length(min = 11, max = 11, message = "手机号长度必须 11 位") | ||
| 30 | + private String mobile; | ||
| 31 | + | ||
| 32 | + @ApiModelProperty(value = "用户性别", example = "1", notes = "参见 SexEnum 枚举类") | ||
| 33 | + private Integer sex; | ||
| 34 | + | ||
| 35 | +} |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/token/OAuth2AccessTokenPageReqVO.java renamed to yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/vo/token/OAuth2AccessTokenPageReqVO.java
| 1 | -package cn.iocoder.yudao.module.system.controller.admin.auth.vo.token; | 1 | +package cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.token; |
| 2 | 2 | ||
| 3 | import cn.iocoder.yudao.framework.common.pojo.PageParam; | 3 | import cn.iocoder.yudao.framework.common.pojo.PageParam; |
| 4 | import io.swagger.annotations.ApiModel; | 4 | import io.swagger.annotations.ApiModel; |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/token/OAuth2AccessTokenRespVO.java renamed to yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/vo/token/OAuth2AccessTokenRespVO.java
| 1 | -package cn.iocoder.yudao.module.system.controller.admin.auth.vo.token; | 1 | +package cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.token; |
| 2 | 2 | ||
| 3 | import io.swagger.annotations.ApiModel; | 3 | import io.swagger.annotations.ApiModel; |
| 4 | import io.swagger.annotations.ApiModelProperty; | 4 | import io.swagger.annotations.ApiModelProperty; |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/UserProfileController.java
| @@ -46,7 +46,6 @@ public class UserProfileController { | @@ -46,7 +46,6 @@ public class UserProfileController { | ||
| 46 | private AdminUserService userService; | 46 | private AdminUserService userService; |
| 47 | @Resource | 47 | @Resource |
| 48 | private DeptService deptService; | 48 | private DeptService deptService; |
| 49 | - | ||
| 50 | @Resource | 49 | @Resource |
| 51 | private PostService postService; | 50 | private PostService postService; |
| 52 | @Resource | 51 | @Resource |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/profile/UserProfileUpdateReqVO.java
| @@ -13,7 +13,7 @@ import javax.validation.constraints.Size; | @@ -13,7 +13,7 @@ import javax.validation.constraints.Size; | ||
| 13 | public class UserProfileUpdateReqVO { | 13 | public class UserProfileUpdateReqVO { |
| 14 | 14 | ||
| 15 | @ApiModelProperty(value = "用户昵称", required = true, example = "芋艿") | 15 | @ApiModelProperty(value = "用户昵称", required = true, example = "芋艿") |
| 16 | - @Size(max = 30, message = "用户昵称长度不能超过30个字符") | 16 | + @Size(max = 30, message = "用户昵称长度不能超过 30 个字符") |
| 17 | private String nickname; | 17 | private String nickname; |
| 18 | 18 | ||
| 19 | @ApiModelProperty(value = "用户邮箱", example = "yudao@iocoder.cn") | 19 | @ApiModelProperty(value = "用户邮箱", example = "yudao@iocoder.cn") |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/AuthConvert.java
| @@ -4,8 +4,8 @@ import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | @@ -4,8 +4,8 @@ import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | ||
| 4 | import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeSendReqDTO; | 4 | import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeSendReqDTO; |
| 5 | import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO; | 5 | import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO; |
| 6 | import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO; | 6 | import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO; |
| 7 | -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.*; | ||
| 8 | -import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO; | 7 | +import cn.iocoder.yudao.module.system.controller.admin.auth.vo.*; |
| 8 | +import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; | ||
| 9 | import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO; | 9 | import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO; |
| 10 | import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO; | 10 | import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO; |
| 11 | import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; | 11 | import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/OAuth2ClientConvert.java
| 1 | package cn.iocoder.yudao.module.system.convert.auth; | 1 | package cn.iocoder.yudao.module.system.convert.auth; |
| 2 | 2 | ||
| 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; | 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 4 | -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientCreateReqVO; | ||
| 5 | -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientRespVO; | ||
| 6 | -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientUpdateReqVO; | ||
| 7 | -import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2ClientDO; | 4 | +import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.client.OAuth2ClientCreateReqVO; |
| 5 | +import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.client.OAuth2ClientRespVO; | ||
| 6 | +import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.client.OAuth2ClientUpdateReqVO; | ||
| 7 | +import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2ClientDO; | ||
| 8 | import org.mapstruct.Mapper; | 8 | import org.mapstruct.Mapper; |
| 9 | import org.mapstruct.factory.Mappers; | 9 | import org.mapstruct.factory.Mappers; |
| 10 | 10 |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/OAuth2TokenConvert.java
| @@ -3,8 +3,8 @@ package cn.iocoder.yudao.module.system.convert.auth; | @@ -3,8 +3,8 @@ package cn.iocoder.yudao.module.system.convert.auth; | ||
| 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; | 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 4 | import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenCheckRespDTO; | 4 | import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenCheckRespDTO; |
| 5 | import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenRespDTO; | 5 | import cn.iocoder.yudao.module.system.api.auth.dto.OAuth2AccessTokenRespDTO; |
| 6 | -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.token.OAuth2AccessTokenRespVO; | ||
| 7 | -import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO; | 6 | +import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.token.OAuth2AccessTokenRespVO; |
| 7 | +import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; | ||
| 8 | import org.mapstruct.Mapper; | 8 | import org.mapstruct.Mapper; |
| 9 | import org.mapstruct.factory.Mappers; | 9 | import org.mapstruct.factory.Mappers; |
| 10 | 10 |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/oauth2/OAuth2OpenConvert.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.system.convert.oauth2; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.framework.common.core.KeyValue; | ||
| 4 | +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; | ||
| 5 | +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | ||
| 6 | +import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; | ||
| 7 | +import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.open.OAuth2OpenAccessTokenRespVO; | ||
| 8 | +import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.open.OAuth2OpenAuthorizeInfoRespVO; | ||
| 9 | +import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.open.OAuth2OpenCheckTokenRespVO; | ||
| 10 | +import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.open.user.OAuth2OpenUserInfoRespVO; | ||
| 11 | +import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.open.user.OAuth2OpenUserUpdateReqVO; | ||
| 12 | +import cn.iocoder.yudao.module.system.controller.admin.user.vo.profile.UserProfileUpdateReqVO; | ||
| 13 | +import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO; | ||
| 14 | +import cn.iocoder.yudao.module.system.dal.dataobject.dept.PostDO; | ||
| 15 | +import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; | ||
| 16 | +import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2ApproveDO; | ||
| 17 | +import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2ClientDO; | ||
| 18 | +import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; | ||
| 19 | +import cn.iocoder.yudao.module.system.util.oauth2.OAuth2Utils; | ||
| 20 | +import org.mapstruct.Mapper; | ||
| 21 | +import org.mapstruct.factory.Mappers; | ||
| 22 | + | ||
| 23 | +import java.util.ArrayList; | ||
| 24 | +import java.util.List; | ||
| 25 | +import java.util.Map; | ||
| 26 | + | ||
| 27 | +@Mapper | ||
| 28 | +public interface OAuth2OpenConvert { | ||
| 29 | + | ||
| 30 | + OAuth2OpenConvert INSTANCE = Mappers.getMapper(OAuth2OpenConvert.class); | ||
| 31 | + | ||
| 32 | + default OAuth2OpenAccessTokenRespVO convert(OAuth2AccessTokenDO bean) { | ||
| 33 | + OAuth2OpenAccessTokenRespVO respVO = convert0(bean); | ||
| 34 | + respVO.setTokenType(SecurityFrameworkUtils.AUTHORIZATION_BEARER.toLowerCase()); | ||
| 35 | + respVO.setExpiresIn(OAuth2Utils.getExpiresIn(bean.getExpiresTime())); | ||
| 36 | + respVO.setScope(OAuth2Utils.buildScopeStr(bean.getScopes())); | ||
| 37 | + return respVO; | ||
| 38 | + } | ||
| 39 | + OAuth2OpenAccessTokenRespVO convert0(OAuth2AccessTokenDO bean); | ||
| 40 | + | ||
| 41 | + default OAuth2OpenCheckTokenRespVO convert2(OAuth2AccessTokenDO bean) { | ||
| 42 | + OAuth2OpenCheckTokenRespVO respVO = convert3(bean); | ||
| 43 | + respVO.setExp(bean.getExpiresTime().getTime() / 1000L); | ||
| 44 | + respVO.setUserType(UserTypeEnum.ADMIN.getValue()); | ||
| 45 | + return respVO; | ||
| 46 | + } | ||
| 47 | + OAuth2OpenCheckTokenRespVO convert3(OAuth2AccessTokenDO bean); | ||
| 48 | + | ||
| 49 | + // ============ 用户操作的示例 ============ | ||
| 50 | + | ||
| 51 | + OAuth2OpenUserInfoRespVO convert(AdminUserDO bean); | ||
| 52 | + OAuth2OpenUserInfoRespVO.Dept convert(DeptDO dept); | ||
| 53 | + List<OAuth2OpenUserInfoRespVO.Post> convertList(List<PostDO> list); | ||
| 54 | + | ||
| 55 | + UserProfileUpdateReqVO convert(OAuth2OpenUserUpdateReqVO bean); | ||
| 56 | + | ||
| 57 | + default OAuth2OpenAuthorizeInfoRespVO convert(OAuth2ClientDO client, List<OAuth2ApproveDO> approves) { | ||
| 58 | + // 构建 scopes | ||
| 59 | + List<KeyValue<String, Boolean>> scopes = new ArrayList<>(client.getScopes().size()); | ||
| 60 | + Map<String, OAuth2ApproveDO> approveMap = CollectionUtils.convertMap(approves, OAuth2ApproveDO::getScope); | ||
| 61 | + client.getScopes().forEach(scope -> { | ||
| 62 | + OAuth2ApproveDO approve = approveMap.get(scope); | ||
| 63 | + scopes.add(new KeyValue<>(scope, approve != null ? approve.getApproved() : false)); | ||
| 64 | + }); | ||
| 65 | + // 拼接返回 | ||
| 66 | + return new OAuth2OpenAuthorizeInfoRespVO( | ||
| 67 | + new OAuth2OpenAuthorizeInfoRespVO.Client(client.getName(), client.getLogo()), scopes); | ||
| 68 | + } | ||
| 69 | + | ||
| 70 | +} |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2AccessTokenDO.java renamed to yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/oauth2/OAuth2AccessTokenDO.java
| 1 | -package cn.iocoder.yudao.module.system.dal.dataobject.auth; | 1 | +package cn.iocoder.yudao.module.system.dal.dataobject.oauth2; |
| 2 | 2 | ||
| 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/oauth2/OAuth2ApproveDO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.system.dal.dataobject.oauth2; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; | ||
| 4 | +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; | ||
| 5 | +import com.baomidou.mybatisplus.annotation.KeySequence; | ||
| 6 | +import com.baomidou.mybatisplus.annotation.TableId; | ||
| 7 | +import com.baomidou.mybatisplus.annotation.TableName; | ||
| 8 | +import lombok.Data; | ||
| 9 | +import lombok.EqualsAndHashCode; | ||
| 10 | + | ||
| 11 | +import java.util.Date; | ||
| 12 | + | ||
| 13 | +/** | ||
| 14 | + * OAuth2 批准 DO | ||
| 15 | + * | ||
| 16 | + * 用户在 sso.vue 界面时,记录接受的 scope 列表 | ||
| 17 | + * | ||
| 18 | + * @author 芋道源码 | ||
| 19 | + */ | ||
| 20 | +@TableName(value = "system_oauth2_approve", autoResultMap = true) | ||
| 21 | +@KeySequence("system_oauth2_approve_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 | ||
| 22 | +@Data | ||
| 23 | +@EqualsAndHashCode(callSuper = true) | ||
| 24 | +public class OAuth2ApproveDO extends BaseDO { | ||
| 25 | + | ||
| 26 | + /** | ||
| 27 | + * 编号,数据库自增 | ||
| 28 | + */ | ||
| 29 | + @TableId | ||
| 30 | + private Long id; | ||
| 31 | + /** | ||
| 32 | + * 用户编号 | ||
| 33 | + */ | ||
| 34 | + private Long userId; | ||
| 35 | + /** | ||
| 36 | + * 用户类型 | ||
| 37 | + * | ||
| 38 | + * 枚举 {@link UserTypeEnum} | ||
| 39 | + */ | ||
| 40 | + private Integer userType; | ||
| 41 | + /** | ||
| 42 | + * 客户端编号 | ||
| 43 | + * | ||
| 44 | + * 关联 {@link OAuth2ClientDO#getId()} | ||
| 45 | + */ | ||
| 46 | + private String clientId; | ||
| 47 | + /** | ||
| 48 | + * 授权范围 | ||
| 49 | + */ | ||
| 50 | + private String scope; | ||
| 51 | + /** | ||
| 52 | + * 是否接受 | ||
| 53 | + * | ||
| 54 | + * true - 接受 | ||
| 55 | + * false - 拒绝 | ||
| 56 | + */ | ||
| 57 | + private Boolean approved; | ||
| 58 | + /** | ||
| 59 | + * 过期时间 | ||
| 60 | + */ | ||
| 61 | + private Date expiresTime; | ||
| 62 | + | ||
| 63 | +} |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2ClientDO.java renamed to yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/oauth2/OAuth2ClientDO.java
| 1 | -package cn.iocoder.yudao.module.system.dal.dataobject.auth; | 1 | +package cn.iocoder.yudao.module.system.dal.dataobject.oauth2; |
| 2 | 2 | ||
| 3 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | 3 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| 4 | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; | 4 | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; |
| @@ -71,10 +71,6 @@ public class OAuth2ClientDO extends BaseDO { | @@ -71,10 +71,6 @@ public class OAuth2ClientDO extends BaseDO { | ||
| 71 | @TableField(typeHandler = JacksonTypeHandler.class) | 71 | @TableField(typeHandler = JacksonTypeHandler.class) |
| 72 | private List<String> redirectUris; | 72 | private List<String> redirectUris; |
| 73 | /** | 73 | /** |
| 74 | - * 是否自动授权 | ||
| 75 | - */ | ||
| 76 | - private Boolean autoApprove; | ||
| 77 | - /** | ||
| 78 | * 授权类型(模式) | 74 | * 授权类型(模式) |
| 79 | * | 75 | * |
| 80 | * 枚举 {@link OAuth2GrantTypeEnum} | 76 | * 枚举 {@link OAuth2GrantTypeEnum} |
| @@ -87,6 +83,13 @@ public class OAuth2ClientDO extends BaseDO { | @@ -87,6 +83,13 @@ public class OAuth2ClientDO extends BaseDO { | ||
| 87 | @TableField(typeHandler = JacksonTypeHandler.class) | 83 | @TableField(typeHandler = JacksonTypeHandler.class) |
| 88 | private List<String> scopes; | 84 | private List<String> scopes; |
| 89 | /** | 85 | /** |
| 86 | + * 自动授权的 Scope | ||
| 87 | + * | ||
| 88 | + * code 授权时,如果 scope 在这个范围内,则自动通过 | ||
| 89 | + */ | ||
| 90 | + @TableField(typeHandler = JacksonTypeHandler.class) | ||
| 91 | + private List<String> autoApproveScopes; | ||
| 92 | + /** | ||
| 90 | * 权限 | 93 | * 权限 |
| 91 | */ | 94 | */ |
| 92 | @TableField(typeHandler = JacksonTypeHandler.class) | 95 | @TableField(typeHandler = JacksonTypeHandler.class) |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/auth/OAuth2CodeDO.java renamed to yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/oauth2/OAuth2CodeDO.java
| 1 | -package cn.iocoder.yudao.module.system.dal.dataobject.auth; | 1 | +package cn.iocoder.yudao.module.system.dal.dataobject.oauth2; |
| 2 | 2 | ||
| 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/dal/dataobject/auth/OAuth2RefreshTokenDO.java renamed to yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/oauth2/OAuth2RefreshTokenDO.java
| 1 | -package cn.iocoder.yudao.module.system.dal.dataobject.auth; | 1 | +package cn.iocoder.yudao.module.system.dal.dataobject.oauth2; |
| 2 | 2 | ||
| 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/dal/mysql/auth/OAuth2AccessTokenMapper.java renamed to yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/oauth2/OAuth2AccessTokenMapper.java
| 1 | -package cn.iocoder.yudao.module.system.dal.mysql.auth; | 1 | +package cn.iocoder.yudao.module.system.dal.mysql.oauth2; |
| 2 | 2 | ||
| 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; | 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 4 | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; | 4 | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| 5 | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; | 5 | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| 6 | -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.token.OAuth2AccessTokenPageReqVO; | ||
| 7 | -import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO; | 6 | +import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.token.OAuth2AccessTokenPageReqVO; |
| 7 | +import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; | ||
| 8 | import org.apache.ibatis.annotations.Mapper; | 8 | import org.apache.ibatis.annotations.Mapper; |
| 9 | 9 | ||
| 10 | import java.util.Date; | 10 | import java.util.Date; |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/oauth2/OAuth2ApproveMapper.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.system.dal.mysql.oauth2; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; | ||
| 4 | +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; | ||
| 5 | +import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2ApproveDO; | ||
| 6 | +import org.apache.ibatis.annotations.Mapper; | ||
| 7 | + | ||
| 8 | +import java.util.List; | ||
| 9 | + | ||
| 10 | +@Mapper | ||
| 11 | +public interface OAuth2ApproveMapper extends BaseMapperX<OAuth2ApproveDO> { | ||
| 12 | + | ||
| 13 | + default int update(OAuth2ApproveDO updateObj) { | ||
| 14 | + return update(updateObj, new LambdaQueryWrapperX<OAuth2ApproveDO>() | ||
| 15 | + .eq(OAuth2ApproveDO::getUserId, updateObj.getUserId()) | ||
| 16 | + .eq(OAuth2ApproveDO::getUserType, updateObj.getUserType()) | ||
| 17 | + .eq(OAuth2ApproveDO::getClientId, updateObj.getClientId()) | ||
| 18 | + .eq(OAuth2ApproveDO::getScope, updateObj.getScope())); | ||
| 19 | + } | ||
| 20 | + | ||
| 21 | + default List<OAuth2ApproveDO> selectListByUserIdAndUserTypeAndClientId(Long userId, Integer userType, String clientId) { | ||
| 22 | + return selectList(new LambdaQueryWrapperX<OAuth2ApproveDO>() | ||
| 23 | + .eq(OAuth2ApproveDO::getUserId, userId) | ||
| 24 | + .eq(OAuth2ApproveDO::getUserType, userType) | ||
| 25 | + .eq(OAuth2ApproveDO::getClientId, clientId)); | ||
| 26 | + } | ||
| 27 | + | ||
| 28 | +} |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2ClientMapper.java renamed to yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/oauth2/OAuth2ClientMapper.java
| 1 | -package cn.iocoder.yudao.module.system.dal.mysql.auth; | 1 | +package cn.iocoder.yudao.module.system.dal.mysql.oauth2; |
| 2 | 2 | ||
| 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; | 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 4 | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; | 4 | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| 5 | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; | 5 | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| 6 | -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientPageReqVO; | ||
| 7 | -import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2ClientDO; | 6 | +import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.client.OAuth2ClientPageReqVO; |
| 7 | +import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2ClientDO; | ||
| 8 | import org.apache.ibatis.annotations.Mapper; | 8 | import org.apache.ibatis.annotations.Mapper; |
| 9 | import org.apache.ibatis.annotations.Select; | 9 | import org.apache.ibatis.annotations.Select; |
| 10 | 10 |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/oauth2/OAuth2CodeMapper.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.system.dal.mysql.oauth2; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; | ||
| 4 | +import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2CodeDO; | ||
| 5 | +import org.apache.ibatis.annotations.Mapper; | ||
| 6 | + | ||
| 7 | +@Mapper | ||
| 8 | +public interface OAuth2CodeMapper extends BaseMapperX<OAuth2CodeDO> { | ||
| 9 | + | ||
| 10 | + default OAuth2CodeDO selectByCode(String code) { | ||
| 11 | + return selectOne(OAuth2CodeDO::getCode, code); | ||
| 12 | + } | ||
| 13 | + | ||
| 14 | +} |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2RefreshTokenMapper.java renamed to yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/oauth2/OAuth2RefreshTokenMapper.java
| 1 | -package cn.iocoder.yudao.module.system.dal.mysql.auth; | 1 | +package cn.iocoder.yudao.module.system.dal.mysql.oauth2; |
| 2 | 2 | ||
| 3 | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; | 3 | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| 4 | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; | 4 | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| 5 | -import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2RefreshTokenDO; | 5 | +import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2RefreshTokenDO; |
| 6 | import org.apache.ibatis.annotations.Mapper; | 6 | import org.apache.ibatis.annotations.Mapper; |
| 7 | 7 | ||
| 8 | @Mapper | 8 | @Mapper |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/RedisKeyConstants.java
| 1 | package cn.iocoder.yudao.module.system.dal.redis; | 1 | package cn.iocoder.yudao.module.system.dal.redis; |
| 2 | 2 | ||
| 3 | import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine; | 3 | import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine; |
| 4 | -import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO; | 4 | +import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; |
| 5 | 5 | ||
| 6 | import java.time.Duration; | 6 | import java.time.Duration; |
| 7 | 7 |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/auth/OAuth2AccessTokenRedisDAO.java renamed to yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/oauth2/OAuth2AccessTokenRedisDAO.java
| 1 | -package cn.iocoder.yudao.module.system.dal.redis.auth; | 1 | +package cn.iocoder.yudao.module.system.dal.redis.oauth2; |
| 2 | 2 | ||
| 3 | import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | 3 | import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; |
| 4 | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | 4 | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; |
| 5 | -import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO; | 5 | +import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; |
| 6 | import org.springframework.data.redis.core.StringRedisTemplate; | 6 | import org.springframework.data.redis.core.StringRedisTemplate; |
| 7 | import org.springframework.stereotype.Repository; | 7 | import org.springframework.stereotype.Repository; |
| 8 | 8 |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/security/config/SecurityConfiguration.java
| @@ -35,6 +35,9 @@ public class SecurityConfiguration { | @@ -35,6 +35,9 @@ public class SecurityConfiguration { | ||
| 35 | registry.antMatchers(buildAdminApi("/system/tenant/get-id-by-name")).permitAll(); | 35 | registry.antMatchers(buildAdminApi("/system/tenant/get-id-by-name")).permitAll(); |
| 36 | // 短信回调 API | 36 | // 短信回调 API |
| 37 | registry.antMatchers(buildAdminApi("/system/sms/callback/**")).permitAll(); | 37 | registry.antMatchers(buildAdminApi("/system/sms/callback/**")).permitAll(); |
| 38 | + // OAuth2 API | ||
| 39 | + registry.antMatchers(buildAdminApi("/system/oauth2/token")).permitAll(); | ||
| 40 | + registry.antMatchers(buildAdminApi("/system/oauth2/check-token")).permitAll(); | ||
| 38 | } | 41 | } |
| 39 | 42 | ||
| 40 | }; | 43 | }; |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/auth/OAuth2ClientRefreshConsumer.java
| @@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.system.mq.consumer.auth; | @@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.system.mq.consumer.auth; | ||
| 2 | 2 | ||
| 3 | import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener; | 3 | import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener; |
| 4 | import cn.iocoder.yudao.module.system.mq.message.auth.OAuth2ClientRefreshMessage; | 4 | import cn.iocoder.yudao.module.system.mq.message.auth.OAuth2ClientRefreshMessage; |
| 5 | -import cn.iocoder.yudao.module.system.service.auth.OAuth2ClientService; | 5 | +import cn.iocoder.yudao.module.system.service.oauth2.OAuth2ClientService; |
| 6 | import lombok.extern.slf4j.Slf4j; | 6 | import lombok.extern.slf4j.Slf4j; |
| 7 | import org.springframework.stereotype.Component; | 7 | import org.springframework.stereotype.Component; |
| 8 | 8 |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthService.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.controller.admin.auth.vo.auth.*; | 3 | +import cn.iocoder.yudao.module.system.controller.admin.auth.vo.*; |
| 4 | +import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; | ||
| 4 | 5 | ||
| 5 | import javax.validation.Valid; | 6 | import javax.validation.Valid; |
| 6 | 7 | ||
| @@ -14,6 +15,15 @@ import javax.validation.Valid; | @@ -14,6 +15,15 @@ import javax.validation.Valid; | ||
| 14 | public interface AdminAuthService { | 15 | public interface AdminAuthService { |
| 15 | 16 | ||
| 16 | /** | 17 | /** |
| 18 | + * 验证账号 + 密码。如果通过,则返回用户 | ||
| 19 | + * | ||
| 20 | + * @param username 账号 | ||
| 21 | + * @param password 密码 | ||
| 22 | + * @return 用户 | ||
| 23 | + */ | ||
| 24 | + AdminUserDO authenticate(String username, String password); | ||
| 25 | + | ||
| 26 | + /** | ||
| 17 | * 账号登录 | 27 | * 账号登录 |
| 18 | * | 28 | * |
| 19 | * @param reqVO 登录信息 | 29 | * @param reqVO 登录信息 |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java
| @@ -8,9 +8,9 @@ import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; | @@ -8,9 +8,9 @@ import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; | ||
| 8 | import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; | 8 | import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; |
| 9 | import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO; | 9 | import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO; |
| 10 | import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi; | 10 | import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi; |
| 11 | -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.*; | 11 | +import cn.iocoder.yudao.module.system.controller.admin.auth.vo.*; |
| 12 | import cn.iocoder.yudao.module.system.convert.auth.AuthConvert; | 12 | import cn.iocoder.yudao.module.system.convert.auth.AuthConvert; |
| 13 | -import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO; | 13 | +import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; |
| 14 | import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; | 14 | import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; |
| 15 | import cn.iocoder.yudao.module.system.enums.auth.OAuth2ClientConstants; | 15 | import cn.iocoder.yudao.module.system.enums.auth.OAuth2ClientConstants; |
| 16 | import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum; | 16 | import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum; |
| @@ -19,6 +19,7 @@ import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum; | @@ -19,6 +19,7 @@ import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum; | ||
| 19 | import cn.iocoder.yudao.module.system.service.common.CaptchaService; | 19 | import cn.iocoder.yudao.module.system.service.common.CaptchaService; |
| 20 | import cn.iocoder.yudao.module.system.service.logger.LoginLogService; | 20 | import cn.iocoder.yudao.module.system.service.logger.LoginLogService; |
| 21 | import cn.iocoder.yudao.module.system.service.member.MemberService; | 21 | import cn.iocoder.yudao.module.system.service.member.MemberService; |
| 22 | +import cn.iocoder.yudao.module.system.service.oauth2.OAuth2TokenService; | ||
| 22 | import cn.iocoder.yudao.module.system.service.social.SocialUserService; | 23 | import cn.iocoder.yudao.module.system.service.social.SocialUserService; |
| 23 | import cn.iocoder.yudao.module.system.service.user.AdminUserService; | 24 | import cn.iocoder.yudao.module.system.service.user.AdminUserService; |
| 24 | import com.google.common.annotations.VisibleForTesting; | 25 | import com.google.common.annotations.VisibleForTesting; |
| @@ -62,12 +63,33 @@ public class AdminAuthServiceImpl implements AdminAuthService { | @@ -62,12 +63,33 @@ public class AdminAuthServiceImpl implements AdminAuthService { | ||
| 62 | private SmsCodeApi smsCodeApi; | 63 | private SmsCodeApi smsCodeApi; |
| 63 | 64 | ||
| 64 | @Override | 65 | @Override |
| 66 | + public AdminUserDO authenticate(String username, String password) { | ||
| 67 | + final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_USERNAME; | ||
| 68 | + // 校验账号是否存在 | ||
| 69 | + AdminUserDO user = userService.getUserByUsername(username); | ||
| 70 | + if (user == null) { | ||
| 71 | + createLoginLog(null, username, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS); | ||
| 72 | + throw exception(AUTH_LOGIN_BAD_CREDENTIALS); | ||
| 73 | + } | ||
| 74 | + if (!userService.isPasswordMatch(password, user.getPassword())) { | ||
| 75 | + createLoginLog(user.getId(), username, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS); | ||
| 76 | + throw exception(AUTH_LOGIN_BAD_CREDENTIALS); | ||
| 77 | + } | ||
| 78 | + // 校验是否禁用 | ||
| 79 | + if (ObjectUtil.notEqual(user.getStatus(), CommonStatusEnum.ENABLE.getStatus())) { | ||
| 80 | + createLoginLog(user.getId(), username, logTypeEnum, LoginResultEnum.USER_DISABLED); | ||
| 81 | + throw exception(AUTH_LOGIN_USER_DISABLED); | ||
| 82 | + } | ||
| 83 | + return user; | ||
| 84 | + } | ||
| 85 | + | ||
| 86 | + @Override | ||
| 65 | public AuthLoginRespVO login(AuthLoginReqVO reqVO) { | 87 | public AuthLoginRespVO login(AuthLoginReqVO reqVO) { |
| 66 | // 判断验证码是否正确 | 88 | // 判断验证码是否正确 |
| 67 | verifyCaptcha(reqVO); | 89 | verifyCaptcha(reqVO); |
| 68 | 90 | ||
| 69 | // 使用账号密码,进行登录 | 91 | // 使用账号密码,进行登录 |
| 70 | - AdminUserDO user = login0(reqVO.getUsername(), reqVO.getPassword()); | 92 | + AdminUserDO user = authenticate(reqVO.getUsername(), reqVO.getPassword()); |
| 71 | 93 | ||
| 72 | // 创建 Token 令牌,记录登录日志 | 94 | // 创建 Token 令牌,记录登录日志 |
| 73 | return createTokenAfterLoginSuccess(user.getId(), reqVO.getUsername(), LoginLogTypeEnum.LOGIN_USERNAME); | 95 | return createTokenAfterLoginSuccess(user.getId(), reqVO.getUsername(), LoginLogTypeEnum.LOGIN_USERNAME); |
| @@ -124,27 +146,6 @@ public class AdminAuthServiceImpl implements AdminAuthService { | @@ -124,27 +146,6 @@ public class AdminAuthServiceImpl implements AdminAuthService { | ||
| 124 | captchaService.deleteCaptchaCode(reqVO.getUuid()); | 146 | captchaService.deleteCaptchaCode(reqVO.getUuid()); |
| 125 | } | 147 | } |
| 126 | 148 | ||
| 127 | - @VisibleForTesting | ||
| 128 | - AdminUserDO login0(String username, String password) { | ||
| 129 | - final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_USERNAME; | ||
| 130 | - // 校验账号是否存在 | ||
| 131 | - AdminUserDO user = userService.getUserByUsername(username); | ||
| 132 | - if (user == null) { | ||
| 133 | - createLoginLog(null, username, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS); | ||
| 134 | - throw exception(AUTH_LOGIN_BAD_CREDENTIALS); | ||
| 135 | - } | ||
| 136 | - if (!userService.isPasswordMatch(password, user.getPassword())) { | ||
| 137 | - createLoginLog(user.getId(), username, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS); | ||
| 138 | - throw exception(AUTH_LOGIN_BAD_CREDENTIALS); | ||
| 139 | - } | ||
| 140 | - // 校验是否禁用 | ||
| 141 | - if (ObjectUtil.notEqual(user.getStatus(), CommonStatusEnum.ENABLE.getStatus())) { | ||
| 142 | - createLoginLog(user.getId(), username, logTypeEnum, LoginResultEnum.USER_DISABLED); | ||
| 143 | - throw exception(AUTH_LOGIN_USER_DISABLED); | ||
| 144 | - } | ||
| 145 | - return user; | ||
| 146 | - } | ||
| 147 | - | ||
| 148 | private void createLoginLog(Long userId, String username, | 149 | private void createLoginLog(Long userId, String username, |
| 149 | LoginLogTypeEnum logTypeEnum, LoginResultEnum loginResult) { | 150 | LoginLogTypeEnum logTypeEnum, LoginResultEnum loginResult) { |
| 150 | // 插入登录日志 | 151 | // 插入登录日志 |
| @@ -186,7 +187,7 @@ public class AdminAuthServiceImpl implements AdminAuthService { | @@ -186,7 +187,7 @@ public class AdminAuthServiceImpl implements AdminAuthService { | ||
| 186 | @Override | 187 | @Override |
| 187 | public AuthLoginRespVO socialBindLogin(AuthSocialBindLoginReqVO reqVO) { | 188 | public AuthLoginRespVO socialBindLogin(AuthSocialBindLoginReqVO reqVO) { |
| 188 | // 使用账号密码,进行登录。 | 189 | // 使用账号密码,进行登录。 |
| 189 | - AdminUserDO user = login0(reqVO.getUsername(), reqVO.getPassword()); | 190 | + AdminUserDO user = authenticate(reqVO.getUsername(), reqVO.getPassword()); |
| 190 | 191 | ||
| 191 | // 绑定社交用户 | 192 | // 绑定社交用户 |
| 192 | socialUserService.bindSocialUser(AuthConvert.INSTANCE.convert(user.getId(), getUserType().getValue(), reqVO)); | 193 | socialUserService.bindSocialUser(AuthConvert.INSTANCE.convert(user.getId(), getUserType().getValue(), reqVO)); |
| @@ -206,7 +207,7 @@ public class AdminAuthServiceImpl implements AdminAuthService { | @@ -206,7 +207,7 @@ public class AdminAuthServiceImpl implements AdminAuthService { | ||
| 206 | createLoginLog(userId, username, logType, LoginResultEnum.SUCCESS); | 207 | createLoginLog(userId, username, logType, LoginResultEnum.SUCCESS); |
| 207 | // 创建访问令牌 | 208 | // 创建访问令牌 |
| 208 | OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.createAccessToken(userId, getUserType().getValue(), | 209 | OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.createAccessToken(userId, getUserType().getValue(), |
| 209 | - OAuth2ClientConstants.CLIENT_ID_DEFAULT); | 210 | + OAuth2ClientConstants.CLIENT_ID_DEFAULT, null); |
| 210 | // 构建返回结果 | 211 | // 构建返回结果 |
| 211 | return AuthConvert.INSTANCE.convert(accessTokenDO); | 212 | return AuthConvert.INSTANCE.convert(accessTokenDO); |
| 212 | } | 213 | } |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminOAuth2Service.java deleted
| 1 | -package cn.iocoder.yudao.module.system.service.auth; | ||
| 2 | - | ||
| 3 | -/** | ||
| 4 | - * 管理后台的 OAuth2 Service 接口 | ||
| 5 | - * | ||
| 6 | - * 将自身的 AdminUser 用户,授权给第三方应用,采用 OAuth2.0 的协议。 | ||
| 7 | - * | ||
| 8 | - * 问题:为什么自身也作为一个第三方应用,也走这套流程呢? | ||
| 9 | - * 回复:当然可以这么做,采用 Implicit 模式。考虑到大多数开发者使用不到这个特性,OAuth2.0 毕竟有一定学习成本,所以暂时没有采取这种方式。 | ||
| 10 | - * | ||
| 11 | - * @author 芋道源码 | ||
| 12 | - */ | ||
| 13 | -public interface AdminOAuth2Service { | ||
| 14 | -} |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2CodeService.java deleted
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2ApproveService.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.system.service.oauth2; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2ApproveDO; | ||
| 4 | + | ||
| 5 | +import java.util.Collection; | ||
| 6 | +import java.util.List; | ||
| 7 | +import java.util.Map; | ||
| 8 | + | ||
| 9 | +/** | ||
| 10 | + * OAuth2 批准 Service 接口 | ||
| 11 | + * | ||
| 12 | + * 从功能上,和 Spring Security OAuth 的 ApprovalStoreUserApprovalHandler 的功能,记录用户针对指定客户端的授权,减少手动确定。 | ||
| 13 | + * | ||
| 14 | + * @author 芋道源码 | ||
| 15 | + */ | ||
| 16 | +public interface OAuth2ApproveService { | ||
| 17 | + | ||
| 18 | + /** | ||
| 19 | + * 获得指定用户,针对指定客户端的指定授权,是否通过 | ||
| 20 | + * | ||
| 21 | + * 参考 ApprovalStoreUserApprovalHandler 的 checkForPreApproval 方法 | ||
| 22 | + * | ||
| 23 | + * @param userId 用户编号 | ||
| 24 | + * @param userType 用户类型 | ||
| 25 | + * @param clientId 客户端编号 | ||
| 26 | + * @param requestedScopes 授权范围 | ||
| 27 | + * @return 是否授权通过 | ||
| 28 | + */ | ||
| 29 | + boolean checkForPreApproval(Long userId, Integer userType, String clientId, Collection<String> requestedScopes); | ||
| 30 | + | ||
| 31 | + /** | ||
| 32 | + * 在用户发起批准时,基于 scopes 的选项,计算最终是否通过 | ||
| 33 | + * | ||
| 34 | + * @param userId 用户编号 | ||
| 35 | + * @param userType 用户类型 | ||
| 36 | + * @param clientId 客户端编号 | ||
| 37 | + * @param requestedScopes 授权范围 | ||
| 38 | + * @return 是否授权通过 | ||
| 39 | + */ | ||
| 40 | + boolean updateAfterApproval(Long userId, Integer userType, String clientId, Map<String, Boolean> requestedScopes); | ||
| 41 | + | ||
| 42 | + /** | ||
| 43 | + * 获得用户的批准列表,排除已过期的 | ||
| 44 | + * | ||
| 45 | + * @param userId 用户编号 | ||
| 46 | + * @param userType 用户类型 | ||
| 47 | + * @param clientId 客户端编号 | ||
| 48 | + * @return 是否授权通过 | ||
| 49 | + */ | ||
| 50 | + List<OAuth2ApproveDO> getApproveList(Long userId, Integer userType, String clientId); | ||
| 51 | + | ||
| 52 | +} |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2ApproveServiceImpl.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.system.service.oauth2; | ||
| 2 | + | ||
| 3 | +import cn.hutool.core.collection.CollUtil; | ||
| 4 | +import cn.hutool.core.lang.Assert; | ||
| 5 | +import cn.iocoder.yudao.framework.common.util.date.DateUtils; | ||
| 6 | +import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2ApproveDO; | ||
| 7 | +import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2ClientDO; | ||
| 8 | +import cn.iocoder.yudao.module.system.dal.mysql.oauth2.OAuth2ApproveMapper; | ||
| 9 | +import org.springframework.stereotype.Service; | ||
| 10 | +import org.springframework.validation.annotation.Validated; | ||
| 11 | + | ||
| 12 | +import javax.annotation.Resource; | ||
| 13 | +import java.util.*; | ||
| 14 | + | ||
| 15 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; | ||
| 16 | + | ||
| 17 | +/** | ||
| 18 | + * OAuth2 批准 Service 实现类 | ||
| 19 | + * | ||
| 20 | + * @author 芋道源码 | ||
| 21 | + */ | ||
| 22 | +@Service | ||
| 23 | +@Validated | ||
| 24 | +public class OAuth2ApproveServiceImpl implements OAuth2ApproveService { | ||
| 25 | + | ||
| 26 | + /** | ||
| 27 | + * 批准的过期时间,默认 30 天 | ||
| 28 | + */ | ||
| 29 | + private static final Integer TIMEOUT = 30 * 24 * 60 * 60; // 单位:秒 | ||
| 30 | + | ||
| 31 | + @Resource | ||
| 32 | + private OAuth2ClientService oauth2ClientService; | ||
| 33 | + | ||
| 34 | + @Resource | ||
| 35 | + private OAuth2ApproveMapper oauth2ApproveMapper; | ||
| 36 | + | ||
| 37 | + @Override | ||
| 38 | + public boolean checkForPreApproval(Long userId, Integer userType, String clientId, Collection<String> requestedScopes) { | ||
| 39 | + // 第一步,基于 Client 的自动授权计算,如果 scopes 都在自动授权中,则返回 true 通过 | ||
| 40 | + OAuth2ClientDO clientDO = oauth2ClientService.validOAuthClientFromCache(clientId); | ||
| 41 | + Assert.notNull(clientDO, "客户端不能为空"); // 防御性编程 | ||
| 42 | + if (CollUtil.containsAll(clientDO.getAutoApproveScopes(), requestedScopes)) { | ||
| 43 | + // gh-877 - if all scopes are auto approved, approvals still need to be added to the approval store. | ||
| 44 | + Date expireTime = DateUtils.addDate(Calendar.SECOND, TIMEOUT); | ||
| 45 | + for (String scope : requestedScopes) { | ||
| 46 | + saveApprove(userId, userType, clientId, scope, true, expireTime); | ||
| 47 | + } | ||
| 48 | + return true; | ||
| 49 | + } | ||
| 50 | + | ||
| 51 | + // 第二步,算上用户已经批准的授权。如果 scopes 都包含,则返回 true | ||
| 52 | + List<OAuth2ApproveDO> approveDOs = oauth2ApproveMapper.selectListByUserIdAndUserTypeAndClientId( | ||
| 53 | + userId, userType, clientId); | ||
| 54 | + Set<String> scopes = convertSet(approveDOs, OAuth2ApproveDO::getScope, | ||
| 55 | + o -> o.getApproved() && !DateUtils.isExpired(o.getExpiresTime())); // 只保留未过期的 | ||
| 56 | + return CollUtil.containsAll(scopes, requestedScopes); | ||
| 57 | + } | ||
| 58 | + | ||
| 59 | + @Override | ||
| 60 | + public boolean updateAfterApproval(Long userId, Integer userType, String clientId, Map<String, Boolean> requestedScopes) { | ||
| 61 | + // 如果 requestedScopes 为空,说明没有要求,则返回 true 通过 | ||
| 62 | + if (CollUtil.isEmpty(requestedScopes)) { | ||
| 63 | + return true; | ||
| 64 | + } | ||
| 65 | + | ||
| 66 | + // 更新批准的信息 | ||
| 67 | + boolean success = false; // 需要至少有一个同意 | ||
| 68 | + Date expireTime = DateUtils.addDate(Calendar.SECOND, TIMEOUT); | ||
| 69 | + for (Map.Entry<String, Boolean> entry :requestedScopes.entrySet()) { | ||
| 70 | + if (entry.getValue()) { | ||
| 71 | + success = true; | ||
| 72 | + } | ||
| 73 | + saveApprove(userId, userType, clientId, entry.getKey(), entry.getValue(), expireTime); | ||
| 74 | + } | ||
| 75 | + return success; | ||
| 76 | + } | ||
| 77 | + | ||
| 78 | + @Override | ||
| 79 | + public List<OAuth2ApproveDO> getApproveList(Long userId, Integer userType, String clientId) { | ||
| 80 | + List<OAuth2ApproveDO> approveDOs = oauth2ApproveMapper.selectListByUserIdAndUserTypeAndClientId( | ||
| 81 | + userId, userType, clientId); | ||
| 82 | + approveDOs.removeIf(o -> DateUtils.isExpired(o.getExpiresTime())); | ||
| 83 | + return approveDOs; | ||
| 84 | + } | ||
| 85 | + | ||
| 86 | + private void saveApprove(Long userId, Integer userType, String clientId, | ||
| 87 | + String scope, Boolean approved, Date expireTime) { | ||
| 88 | + // 先更新 | ||
| 89 | + OAuth2ApproveDO approveDO = new OAuth2ApproveDO().setUserId(userId).setUserType(userType) | ||
| 90 | + .setClientId(clientId).setScope(scope).setApproved(approved).setExpiresTime(expireTime); | ||
| 91 | + if (oauth2ApproveMapper.update(approveDO) == 1) { | ||
| 92 | + return; | ||
| 93 | + } | ||
| 94 | + // 失败,则说明不存在,进行更新 | ||
| 95 | + oauth2ApproveMapper.insert(approveDO); | ||
| 96 | + } | ||
| 97 | + | ||
| 98 | +} |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ClientService.java renamed to yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2ClientService.java
| 1 | -package cn.iocoder.yudao.module.system.service.auth; | 1 | +package cn.iocoder.yudao.module.system.service.oauth2; |
| 2 | 2 | ||
| 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; | 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 4 | -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientCreateReqVO; | ||
| 5 | -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientPageReqVO; | ||
| 6 | -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientUpdateReqVO; | ||
| 7 | -import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2ClientDO; | 4 | +import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.client.OAuth2ClientCreateReqVO; |
| 5 | +import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.client.OAuth2ClientPageReqVO; | ||
| 6 | +import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.client.OAuth2ClientUpdateReqVO; | ||
| 7 | +import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.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,25 @@ public interface OAuth2ClientService { | @@ -63,9 +64,25 @@ 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, null); | ||
| 71 | + } | ||
| 72 | + | ||
| 73 | + /** | ||
| 74 | + * 从缓存中,校验客户端是否合法 | ||
| 75 | + * | ||
| 76 | + * 非空时,进行校验 | ||
| 77 | + * | ||
| 66 | * @param clientId 客户端编号 | 78 | * @param clientId 客户端编号 |
| 79 | + * @param clientSecret 客户端密钥 | ||
| 80 | + * @param authorizedGrantType 授权方式 | ||
| 81 | + * @param scopes 授权范围 | ||
| 82 | + * @param redirectUri 重定向地址 | ||
| 67 | * @return 客户端 | 83 | * @return 客户端 |
| 68 | */ | 84 | */ |
| 69 | - OAuth2ClientDO validOAuthClientFromCache(String clientId); | 85 | + OAuth2ClientDO validOAuthClientFromCache(String clientId, String clientSecret, |
| 86 | + String authorizedGrantType, Collection<String> scopes, String redirectUri); | ||
| 70 | 87 | ||
| 71 | } | 88 | } |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ClientServiceImpl.java renamed to 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.auth; | 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; | ||
| 5 | +import cn.hutool.core.util.StrUtil; | ||
| 6 | +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | ||
| 4 | import cn.iocoder.yudao.framework.common.pojo.PageResult; | 7 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 5 | -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; | ||
| 7 | -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientUpdateReqVO; | 8 | +import cn.iocoder.yudao.framework.common.util.string.StrUtils; |
| 9 | +import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.client.OAuth2ClientCreateReqVO; | ||
| 10 | +import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.client.OAuth2ClientPageReqVO; | ||
| 11 | +import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.client.OAuth2ClientUpdateReqVO; | ||
| 8 | import cn.iocoder.yudao.module.system.convert.auth.OAuth2ClientConvert; | 12 | import cn.iocoder.yudao.module.system.convert.auth.OAuth2ClientConvert; |
| 9 | -import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2ClientDO; | ||
| 10 | -import cn.iocoder.yudao.module.system.dal.mysql.auth.OAuth2ClientMapper; | 13 | +import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2ClientDO; |
| 14 | +import cn.iocoder.yudao.module.system.dal.mysql.oauth2.OAuth2ClientMapper; | ||
| 11 | import cn.iocoder.yudao.module.system.mq.producer.auth.OAuth2ClientProducer; | 15 | import cn.iocoder.yudao.module.system.mq.producer.auth.OAuth2ClientProducer; |
| 12 | import com.google.common.annotations.VisibleForTesting; | 16 | import com.google.common.annotations.VisibleForTesting; |
| 13 | import lombok.Getter; | 17 | import lombok.Getter; |
| @@ -18,15 +22,12 @@ import org.springframework.validation.annotation.Validated; | @@ -18,15 +22,12 @@ import org.springframework.validation.annotation.Validated; | ||
| 18 | 22 | ||
| 19 | import javax.annotation.PostConstruct; | 23 | import javax.annotation.PostConstruct; |
| 20 | import javax.annotation.Resource; | 24 | import javax.annotation.Resource; |
| 21 | -import java.util.Date; | ||
| 22 | -import java.util.List; | ||
| 23 | -import java.util.Map; | 25 | +import java.util.*; |
| 24 | 26 | ||
| 25 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | 27 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 26 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; | 28 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; |
| 27 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getMaxValue; | 29 | 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; | 30 | +import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*; |
| 30 | 31 | ||
| 31 | /** | 32 | /** |
| 32 | * OAuth2.0 Client Service 实现类 | 33 | * OAuth2.0 Client Service 实现类 |
| @@ -175,8 +176,34 @@ public class OAuth2ClientServiceImpl implements OAuth2ClientService { | @@ -175,8 +176,34 @@ public class OAuth2ClientServiceImpl implements OAuth2ClientService { | ||
| 175 | } | 176 | } |
| 176 | 177 | ||
| 177 | @Override | 178 | @Override |
| 178 | - public OAuth2ClientDO validOAuthClientFromCache(String clientId) { | ||
| 179 | - return clientCache.get(clientId); | 179 | + public OAuth2ClientDO validOAuthClientFromCache(String clientId, String clientSecret, |
| 180 | + String authorizedGrantType, Collection<String> scopes, String redirectUri) { | ||
| 181 | + // 校验客户端存在、且开启 | ||
| 182 | + OAuth2ClientDO client = clientCache.get(clientId); | ||
| 183 | + if (client == null) { | ||
| 184 | + throw exception(OAUTH2_CLIENT_NOT_EXISTS); | ||
| 185 | + } | ||
| 186 | + if (ObjectUtil.notEqual(client.getStatus(), CommonStatusEnum.ENABLE.getStatus())) { | ||
| 187 | + throw exception(OAUTH2_CLIENT_DISABLE); | ||
| 188 | + } | ||
| 189 | + | ||
| 190 | + // 校验客户端密钥 | ||
| 191 | + if (StrUtil.isNotEmpty(clientSecret) && ObjectUtil.notEqual(client.getSecret(), clientSecret)) { | ||
| 192 | + throw exception(OAUTH2_CLIENT_CLIENT_SECRET_ERROR, clientSecret); | ||
| 193 | + } | ||
| 194 | + // 校验授权方式 | ||
| 195 | + if (StrUtil.isNotEmpty(authorizedGrantType) && !CollUtil.contains(client.getAuthorizedGrantTypes(), authorizedGrantType)) { | ||
| 196 | + throw exception(OAUTH2_CLIENT_AUTHORIZED_GRANT_TYPE_NOT_EXISTS); | ||
| 197 | + } | ||
| 198 | + // 校验授权范围 | ||
| 199 | + if (CollUtil.isNotEmpty(scopes) && !CollUtil.containsAll(client.getScopes(), scopes)) { | ||
| 200 | + throw exception(OAUTH2_CLIENT_SCOPE_OVER); | ||
| 201 | + } | ||
| 202 | + // 校验回调地址 | ||
| 203 | + if (StrUtil.isNotEmpty(redirectUri) && !StrUtils.startWithAny(redirectUri, client.getRedirectUris())) { | ||
| 204 | + throw exception(OAUTH2_CLIENT_REDIRECT_URI_NOT_MATCH, redirectUri); | ||
| 205 | + } | ||
| 206 | + return client; | ||
| 180 | } | 207 | } |
| 181 | 208 | ||
| 182 | } | 209 | } |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2CodeService.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.system.service.oauth2; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2CodeDO; | ||
| 4 | + | ||
| 5 | +import java.util.List; | ||
| 6 | + | ||
| 7 | +/** | ||
| 8 | + * OAuth2.0 授权码 Service 接口 | ||
| 9 | + * | ||
| 10 | + * 从功能上,和 Spring Security OAuth 的 JdbcAuthorizationCodeServices 的功能,提供授权码的操作 | ||
| 11 | + * | ||
| 12 | + * @author 芋道源码 | ||
| 13 | + */ | ||
| 14 | +public interface OAuth2CodeService { | ||
| 15 | + | ||
| 16 | + /** | ||
| 17 | + * 创建授权码 | ||
| 18 | + * | ||
| 19 | + * 参考 JdbcAuthorizationCodeServices 的 createAuthorizationCode 方法 | ||
| 20 | + * | ||
| 21 | + * @param userId 用户编号 | ||
| 22 | + * @param userType 用户类型 | ||
| 23 | + * @param clientId 客户端编号 | ||
| 24 | + * @param scopes 授权范围 | ||
| 25 | + * @param redirectUri 重定向 URI | ||
| 26 | + * @param state 状态 | ||
| 27 | + * @return 授权码的信息 | ||
| 28 | + */ | ||
| 29 | + OAuth2CodeDO createAuthorizationCode(Long userId, Integer userType, String clientId, List<String> scopes, | ||
| 30 | + String redirectUri, String state); | ||
| 31 | + | ||
| 32 | + /** | ||
| 33 | + * 使用授权码 | ||
| 34 | + * | ||
| 35 | + * @param code 授权码 | ||
| 36 | + */ | ||
| 37 | + OAuth2CodeDO consumeAuthorizationCode(String code); | ||
| 38 | + | ||
| 39 | +} |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2CodeServiceImpl.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.system.service.oauth2; | ||
| 2 | + | ||
| 3 | +import cn.hutool.core.util.IdUtil; | ||
| 4 | +import cn.iocoder.yudao.framework.common.util.date.DateUtils; | ||
| 5 | +import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2CodeDO; | ||
| 6 | +import cn.iocoder.yudao.module.system.dal.mysql.oauth2.OAuth2CodeMapper; | ||
| 7 | +import org.springframework.stereotype.Service; | ||
| 8 | +import org.springframework.validation.annotation.Validated; | ||
| 9 | + | ||
| 10 | +import javax.annotation.Resource; | ||
| 11 | +import java.util.Calendar; | ||
| 12 | +import java.util.List; | ||
| 13 | + | ||
| 14 | +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | ||
| 15 | +import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.OAUTH2_CODE_EXPIRE; | ||
| 16 | +import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.OAUTH2_CODE_NOT_EXISTS; | ||
| 17 | + | ||
| 18 | +/** | ||
| 19 | + * OAuth2.0 授权码 Service 实现类 | ||
| 20 | + * | ||
| 21 | + * @author 芋道源码 | ||
| 22 | + */ | ||
| 23 | +@Service | ||
| 24 | +@Validated | ||
| 25 | +public class OAuth2CodeServiceImpl implements OAuth2CodeService { | ||
| 26 | + | ||
| 27 | + /** | ||
| 28 | + * 授权码的过期时间,默认 5 分钟 | ||
| 29 | + */ | ||
| 30 | + private static final Integer TIMEOUT = 5 * 60; | ||
| 31 | + | ||
| 32 | + @Resource | ||
| 33 | + private OAuth2CodeMapper oauth2CodeMapper; | ||
| 34 | + | ||
| 35 | + @Override | ||
| 36 | + public OAuth2CodeDO createAuthorizationCode(Long userId, Integer userType, String clientId, List<String> scopes, | ||
| 37 | + String redirectUri, String state) { | ||
| 38 | + OAuth2CodeDO codeDO = new OAuth2CodeDO().setCode(generateCode()) | ||
| 39 | + .setUserId(userId).setUserType(userType) | ||
| 40 | + .setClientId(clientId).setScopes(scopes) | ||
| 41 | + .setExpiresTime(DateUtils.addDate(Calendar.SECOND, TIMEOUT)) | ||
| 42 | + .setRedirectUri(redirectUri).setState(state); | ||
| 43 | + oauth2CodeMapper.insert(codeDO); | ||
| 44 | + return codeDO; | ||
| 45 | + } | ||
| 46 | + | ||
| 47 | + @Override | ||
| 48 | + public OAuth2CodeDO consumeAuthorizationCode(String code) { | ||
| 49 | + OAuth2CodeDO codeDO = oauth2CodeMapper.selectByCode(code); | ||
| 50 | + if (codeDO == null) { | ||
| 51 | + throw exception(OAUTH2_CODE_NOT_EXISTS); | ||
| 52 | + } | ||
| 53 | + if (DateUtils.isExpired(codeDO.getExpiresTime())) { | ||
| 54 | + throw exception(OAUTH2_CODE_EXPIRE); | ||
| 55 | + } | ||
| 56 | + oauth2CodeMapper.deleteById(codeDO.getId()); | ||
| 57 | + return codeDO; | ||
| 58 | + } | ||
| 59 | + | ||
| 60 | + private static String generateCode() { | ||
| 61 | + return IdUtil.fastSimpleUUID(); | ||
| 62 | + } | ||
| 63 | + | ||
| 64 | +} |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2GrantService.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.system.service.oauth2; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; | ||
| 4 | + | ||
| 5 | +import java.util.List; | ||
| 6 | + | ||
| 7 | +/** | ||
| 8 | + * OAuth2 授予 Service 接口 | ||
| 9 | + * | ||
| 10 | + * 从功能上,和 Spring Security OAuth 的 TokenGranter 的功能,提供访问令牌、刷新令牌的操作 | ||
| 11 | + * | ||
| 12 | + * 将自身的 AdminUser 用户,授权给第三方应用,采用 OAuth2.0 的协议。 | ||
| 13 | + * | ||
| 14 | + * 问题:为什么自身也作为一个第三方应用,也走这套流程呢? | ||
| 15 | + * 回复:当然可以这么做,采用 Implicit 模式。考虑到大多数开发者使用不到这个特性,OAuth2.0 毕竟有一定学习成本,所以暂时没有采取这种方式。 | ||
| 16 | + * | ||
| 17 | + * @author 芋道源码 | ||
| 18 | + */ | ||
| 19 | +public interface OAuth2GrantService { | ||
| 20 | + | ||
| 21 | + /** | ||
| 22 | + * 简化模式 | ||
| 23 | + * | ||
| 24 | + * 对应 Spring Security OAuth2 的 ImplicitTokenGranter 功能 | ||
| 25 | + * | ||
| 26 | + * @param userId 用户编号 | ||
| 27 | + * @param userType 用户类型 | ||
| 28 | + * @param clientId 客户端编号 | ||
| 29 | + * @param scopes 授权范围 | ||
| 30 | + * @return 访问令牌 | ||
| 31 | + */ | ||
| 32 | + OAuth2AccessTokenDO grantImplicit(Long userId, Integer userType, | ||
| 33 | + String clientId, List<String> scopes); | ||
| 34 | + | ||
| 35 | + /** | ||
| 36 | + * 授权码模式,第一阶段,获得 code 授权码 | ||
| 37 | + * | ||
| 38 | + * 对应 Spring Security OAuth2 的 AuthorizationEndpoint 的 generateCode 方法 | ||
| 39 | + * | ||
| 40 | + * @param userId 用户编号 | ||
| 41 | + * @param userType 用户类型 | ||
| 42 | + * @param clientId 客户端编号 | ||
| 43 | + * @param scopes 授权范围 | ||
| 44 | + * @param redirectUri 重定向 URI | ||
| 45 | + * @param state 状态 | ||
| 46 | + * @return 授权码 | ||
| 47 | + */ | ||
| 48 | + String grantAuthorizationCodeForCode(Long userId, Integer userType, | ||
| 49 | + String clientId, List<String> scopes, | ||
| 50 | + String redirectUri, String state); | ||
| 51 | + | ||
| 52 | + /** | ||
| 53 | + * 授权码模式,第二阶段,获得 accessToken 访问令牌 | ||
| 54 | + * | ||
| 55 | + * 对应 Spring Security OAuth2 的 AuthorizationCodeTokenGranter 功能 | ||
| 56 | + * | ||
| 57 | + * @param clientId 客户端编号 | ||
| 58 | + * @param code 授权码 | ||
| 59 | + * @param redirectUri 重定向 URI | ||
| 60 | + * @param state 状态 | ||
| 61 | + * @return 访问令牌 | ||
| 62 | + */ | ||
| 63 | + OAuth2AccessTokenDO grantAuthorizationCodeForAccessToken(String clientId, String code, | ||
| 64 | + String redirectUri, String state); | ||
| 65 | + | ||
| 66 | + /** | ||
| 67 | + * 密码模式 | ||
| 68 | + * | ||
| 69 | + * 对应 Spring Security OAuth2 的 ResourceOwnerPasswordTokenGranter 功能 | ||
| 70 | + * | ||
| 71 | + * @param username 账号 | ||
| 72 | + * @param password 密码 | ||
| 73 | + * @param clientId 客户端编号 | ||
| 74 | + * @param scopes 授权范围 | ||
| 75 | + * @return 访问令牌 | ||
| 76 | + */ | ||
| 77 | + OAuth2AccessTokenDO grantPassword(String username, String password, | ||
| 78 | + String clientId, List<String> scopes); | ||
| 79 | + | ||
| 80 | + /** | ||
| 81 | + * 刷新模式 | ||
| 82 | + * | ||
| 83 | + * 对应 Spring Security OAuth2 的 ResourceOwnerPasswordTokenGranter 功能 | ||
| 84 | + * | ||
| 85 | + * @param refreshToken 刷新令牌 | ||
| 86 | + * @param clientId 客户端编号 | ||
| 87 | + * @return 访问令牌 | ||
| 88 | + */ | ||
| 89 | + OAuth2AccessTokenDO grantRefreshToken(String refreshToken, String clientId); | ||
| 90 | + | ||
| 91 | + /** | ||
| 92 | + * 客户端模式 | ||
| 93 | + * | ||
| 94 | + * 对应 Spring Security OAuth2 的 ClientCredentialsTokenGranter 功能 | ||
| 95 | + * | ||
| 96 | + * @param clientId 客户端编号 | ||
| 97 | + * @param scopes 授权范围 | ||
| 98 | + * @return 访问令牌 | ||
| 99 | + */ | ||
| 100 | + OAuth2AccessTokenDO grantClientCredentials(String clientId, List<String> scopes); | ||
| 101 | + | ||
| 102 | + /** | ||
| 103 | + * 移除访问令牌 | ||
| 104 | + * | ||
| 105 | + * 对应 Spring Security OAuth2 的 ConsumerTokenServices 的 revokeToken 方法 | ||
| 106 | + * | ||
| 107 | + * @param accessToken 访问令牌 | ||
| 108 | + * @param clientId 客户端编号 | ||
| 109 | + * @return 是否移除到 | ||
| 110 | + */ | ||
| 111 | + boolean revokeToken(String clientId, String accessToken); | ||
| 112 | + | ||
| 113 | +} |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2GrantServiceImpl.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.system.service.oauth2; | ||
| 2 | + | ||
| 3 | +import cn.hutool.core.lang.Assert; | ||
| 4 | +import cn.hutool.core.util.ObjectUtil; | ||
| 5 | +import cn.hutool.core.util.StrUtil; | ||
| 6 | +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; | ||
| 7 | +import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; | ||
| 8 | +import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2CodeDO; | ||
| 9 | +import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; | ||
| 10 | +import cn.iocoder.yudao.module.system.enums.ErrorCodeConstants; | ||
| 11 | +import cn.iocoder.yudao.module.system.service.auth.AdminAuthService; | ||
| 12 | +import org.springframework.stereotype.Service; | ||
| 13 | + | ||
| 14 | +import javax.annotation.Resource; | ||
| 15 | +import java.util.List; | ||
| 16 | + | ||
| 17 | +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | ||
| 18 | + | ||
| 19 | +/** | ||
| 20 | + * OAuth2 授予 Service 实现类 | ||
| 21 | + * | ||
| 22 | + * @author 芋道源码 | ||
| 23 | + */ | ||
| 24 | +@Service | ||
| 25 | +public class OAuth2GrantServiceImpl implements OAuth2GrantService { | ||
| 26 | + | ||
| 27 | + @Resource | ||
| 28 | + private OAuth2TokenService oauth2TokenService; | ||
| 29 | + @Resource | ||
| 30 | + private OAuth2CodeService oauth2CodeService; | ||
| 31 | + @Resource | ||
| 32 | + private AdminAuthService adminAuthService; | ||
| 33 | + | ||
| 34 | + @Override | ||
| 35 | + public OAuth2AccessTokenDO grantImplicit(Long userId, Integer userType, | ||
| 36 | + String clientId, List<String> scopes) { | ||
| 37 | + return oauth2TokenService.createAccessToken(userId, userType, clientId, scopes); | ||
| 38 | + } | ||
| 39 | + | ||
| 40 | + @Override | ||
| 41 | + public String grantAuthorizationCodeForCode(Long userId, Integer userType, | ||
| 42 | + String clientId, List<String> scopes, | ||
| 43 | + String redirectUri, String state) { | ||
| 44 | + return oauth2CodeService.createAuthorizationCode(userId, userType, clientId, scopes, | ||
| 45 | + redirectUri, state).getCode(); | ||
| 46 | + } | ||
| 47 | + | ||
| 48 | + @Override | ||
| 49 | + public OAuth2AccessTokenDO grantAuthorizationCodeForAccessToken(String clientId, String code, | ||
| 50 | + String redirectUri, String state) { | ||
| 51 | + OAuth2CodeDO codeDO = oauth2CodeService.consumeAuthorizationCode(code); | ||
| 52 | + Assert.notNull(codeDO, "授权码不能为空"); // 防御性编程 | ||
| 53 | + // 校验 clientId 是否匹配 | ||
| 54 | + if (!StrUtil.equals(clientId, codeDO.getClientId())) { | ||
| 55 | + throw exception(ErrorCodeConstants.OAUTH2_GRANT_CLIENT_ID_MISMATCH); | ||
| 56 | + } | ||
| 57 | + // 校验 redirectUri 是否匹配 | ||
| 58 | + if (!StrUtil.equals(redirectUri, codeDO.getRedirectUri())) { | ||
| 59 | + throw exception(ErrorCodeConstants.OAUTH2_GRANT_REDIRECT_URI_MISMATCH); | ||
| 60 | + } | ||
| 61 | + // 校验 state 是否匹配 | ||
| 62 | + state = StrUtil.nullToDefault(state, ""); // 数据库 state 为 null 时,会设置为 "" 空串 | ||
| 63 | + if (!StrUtil.equals(state, codeDO.getState())) { | ||
| 64 | + throw exception(ErrorCodeConstants.OAUTH2_GRANT_STATE_MISMATCH); | ||
| 65 | + } | ||
| 66 | + | ||
| 67 | + // 创建访问令牌 | ||
| 68 | + return oauth2TokenService.createAccessToken(codeDO.getUserId(), codeDO.getUserType(), | ||
| 69 | + codeDO.getClientId(), codeDO.getScopes()); | ||
| 70 | + } | ||
| 71 | + | ||
| 72 | + @Override | ||
| 73 | + public OAuth2AccessTokenDO grantPassword(String username, String password, String clientId, List<String> scopes) { | ||
| 74 | + // 使用账号 + 密码进行登录 | ||
| 75 | + AdminUserDO user = adminAuthService.authenticate(username, password); | ||
| 76 | + Assert.notNull(user, "用户不能为空!"); // 防御性编程 | ||
| 77 | + | ||
| 78 | + // 创建访问令牌 | ||
| 79 | + return oauth2TokenService.createAccessToken(user.getId(), UserTypeEnum.ADMIN.getValue(), clientId, scopes); | ||
| 80 | + } | ||
| 81 | + | ||
| 82 | + @Override | ||
| 83 | + public OAuth2AccessTokenDO grantRefreshToken(String refreshToken, String clientId) { | ||
| 84 | + return oauth2TokenService.refreshAccessToken(refreshToken, clientId); | ||
| 85 | + } | ||
| 86 | + | ||
| 87 | + @Override | ||
| 88 | + public OAuth2AccessTokenDO grantClientCredentials(String clientId, List<String> scopes) { | ||
| 89 | + // TODO 芋艿:项目中使用 OAuth2 解决的是三方应用的授权,内部的 SSO 等问题,所以暂时不考虑 client_credentials 这个场景 | ||
| 90 | + throw new UnsupportedOperationException("暂时不支持 client_credentials 授权模式"); | ||
| 91 | + } | ||
| 92 | + | ||
| 93 | + @Override | ||
| 94 | + public boolean revokeToken(String clientId, String accessToken) { | ||
| 95 | + // 先查询,保证 clientId 时匹配的 | ||
| 96 | + OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.getAccessToken(accessToken); | ||
| 97 | + if (accessTokenDO == null || ObjectUtil.notEqual(clientId, accessTokenDO.getClientId())) { | ||
| 98 | + return false; | ||
| 99 | + } | ||
| 100 | + // 再删除 | ||
| 101 | + return oauth2TokenService.removeAccessToken(accessToken) != null; | ||
| 102 | + } | ||
| 103 | + | ||
| 104 | +} |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenService.java renamed to yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2TokenService.java
| 1 | -package cn.iocoder.yudao.module.system.service.auth; | 1 | +package cn.iocoder.yudao.module.system.service.oauth2; |
| 2 | 2 | ||
| 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; | 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 4 | -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.token.OAuth2AccessTokenPageReqVO; | ||
| 5 | -import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO; | 4 | +import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.token.OAuth2AccessTokenPageReqVO; |
| 5 | +import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; | ||
| 6 | + | ||
| 7 | +import java.util.List; | ||
| 6 | 8 | ||
| 7 | /** | 9 | /** |
| 8 | * OAuth2.0 Token Service 接口 | 10 | * OAuth2.0 Token Service 接口 |
| @@ -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/auth/OAuth2TokenServiceImpl.java renamed to yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2TokenServiceImpl.java
| 1 | -package cn.iocoder.yudao.module.system.service.auth; | 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.IdUtil; | 4 | import cn.hutool.core.util.IdUtil; |
| @@ -7,13 +7,13 @@ import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstant | @@ -7,13 +7,13 @@ import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstant | ||
| 7 | import cn.iocoder.yudao.framework.common.pojo.PageResult; | 7 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 8 | import cn.iocoder.yudao.framework.common.util.date.DateUtils; | 8 | import cn.iocoder.yudao.framework.common.util.date.DateUtils; |
| 9 | import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; | 9 | import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; |
| 10 | -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.token.OAuth2AccessTokenPageReqVO; | ||
| 11 | -import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO; | ||
| 12 | -import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2ClientDO; | ||
| 13 | -import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2RefreshTokenDO; | ||
| 14 | -import cn.iocoder.yudao.module.system.dal.mysql.auth.OAuth2AccessTokenMapper; | ||
| 15 | -import cn.iocoder.yudao.module.system.dal.mysql.auth.OAuth2RefreshTokenMapper; | ||
| 16 | -import cn.iocoder.yudao.module.system.dal.redis.auth.OAuth2AccessTokenRedisDAO; | 10 | +import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.token.OAuth2AccessTokenPageReqVO; |
| 11 | +import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; | ||
| 12 | +import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2ClientDO; | ||
| 13 | +import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2RefreshTokenDO; | ||
| 14 | +import cn.iocoder.yudao.module.system.dal.mysql.oauth2.OAuth2AccessTokenMapper; | ||
| 15 | +import cn.iocoder.yudao.module.system.dal.mysql.oauth2.OAuth2RefreshTokenMapper; | ||
| 16 | +import cn.iocoder.yudao.module.system.dal.redis.oauth2.OAuth2AccessTokenRedisDAO; | ||
| 17 | import org.springframework.stereotype.Service; | 17 | import org.springframework.stereotype.Service; |
| 18 | import org.springframework.transaction.annotation.Transactional; | 18 | import org.springframework.transaction.annotation.Transactional; |
| 19 | 19 | ||
| @@ -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.hutool.core.util.StrUtil; | ||
| 5 | +import cn.iocoder.yudao.framework.common.util.http.HttpUtils; | ||
| 6 | +import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; | ||
| 7 | + | ||
| 8 | +import java.util.*; | ||
| 9 | + | ||
| 10 | +/** | ||
| 11 | + * OAuth2 相关的工具类 | ||
| 12 | + * | ||
| 13 | + * @author 芋道源码 | ||
| 14 | + */ | ||
| 15 | +public class OAuth2Utils { | ||
| 16 | + | ||
| 17 | + /** | ||
| 18 | + * 构建授权码模式下,重定向的 URI | ||
| 19 | + * | ||
| 20 | + * copy from Spring Security OAuth2 的 AuthorizationEndpoint 类的 getSuccessfulRedirect 方法 | ||
| 21 | + * | ||
| 22 | + * @param redirectUri 重定向 URI | ||
| 23 | + * @param authorizationCode 授权码 | ||
| 24 | + * @param state 状态 | ||
| 25 | + * @return 授权码模式下的重定向 URI | ||
| 26 | + */ | ||
| 27 | + public static String buildAuthorizationCodeRedirectUri(String redirectUri, String authorizationCode, String state) { | ||
| 28 | + Map<String, String> query = new LinkedHashMap<>(); | ||
| 29 | + query.put("code", authorizationCode); | ||
| 30 | + if (state != null) { | ||
| 31 | + query.put("state", state); | ||
| 32 | + } | ||
| 33 | + return HttpUtils.append(redirectUri, query, null, false); | ||
| 34 | + } | ||
| 35 | + | ||
| 36 | + /** | ||
| 37 | + * 构建简化模式下,重定向的 URI | ||
| 38 | + * | ||
| 39 | + * copy from Spring Security OAuth2 的 AuthorizationEndpoint 类的 appendAccessToken 方法 | ||
| 40 | + * | ||
| 41 | + * @param redirectUri 重定向 URI | ||
| 42 | + * @param accessToken 访问令牌 | ||
| 43 | + * @param state 状态 | ||
| 44 | + * @param expireTime 过期时间 | ||
| 45 | + * @param scopes 授权范围 | ||
| 46 | + * @param additionalInformation 附加信息 | ||
| 47 | + * @return 简化授权模式下的重定向 URI | ||
| 48 | + */ | ||
| 49 | + public static String buildImplicitRedirectUri(String redirectUri, String accessToken, String state, Date expireTime, | ||
| 50 | + Collection<String> scopes, Map<String, Object> additionalInformation) { | ||
| 51 | + Map<String, Object> vars = new LinkedHashMap<String, Object>(); | ||
| 52 | + Map<String, String> keys = new HashMap<String, String>(); | ||
| 53 | + vars.put("access_token", accessToken); | ||
| 54 | + vars.put("token_type", SecurityFrameworkUtils.AUTHORIZATION_BEARER.toLowerCase()); | ||
| 55 | + if (state != null) { | ||
| 56 | + vars.put("state", state); | ||
| 57 | + } | ||
| 58 | + if (expireTime != null) { | ||
| 59 | + vars.put("expires_in", getExpiresIn(expireTime)); | ||
| 60 | + } | ||
| 61 | + if (CollUtil.isNotEmpty(scopes)) { | ||
| 62 | + vars.put("scope", buildScopeStr(scopes)); | ||
| 63 | + } | ||
| 64 | + for (String key : additionalInformation.keySet()) { | ||
| 65 | + Object value = additionalInformation.get(key); | ||
| 66 | + if (value != null) { | ||
| 67 | + keys.put("extra_" + key, key); | ||
| 68 | + vars.put("extra_" + key, value); | ||
| 69 | + } | ||
| 70 | + } | ||
| 71 | + // Do not include the refresh token (even if there is one) | ||
| 72 | + return HttpUtils.append(redirectUri, vars, keys, true); | ||
| 73 | + } | ||
| 74 | + | ||
| 75 | + public static String buildUnsuccessfulRedirect(String redirectUri, String responseType, String state, | ||
| 76 | + String error, String description) { | ||
| 77 | + Map<String, String> query = new LinkedHashMap<String, String>(); | ||
| 78 | + query.put("error", error); | ||
| 79 | + query.put("error_description", description); | ||
| 80 | + if (state != null) { | ||
| 81 | + query.put("state", state); | ||
| 82 | + } | ||
| 83 | + return HttpUtils.append(redirectUri, query, null, !responseType.contains("code")); | ||
| 84 | + } | ||
| 85 | + | ||
| 86 | + public static long getExpiresIn(Date expireTime) { | ||
| 87 | + return (expireTime.getTime() - System.currentTimeMillis()) / 1000; | ||
| 88 | + } | ||
| 89 | + | ||
| 90 | + public static String buildScopeStr(Collection<String> scopes) { | ||
| 91 | + return CollUtil.join(scopes, " "); | ||
| 92 | + } | ||
| 93 | + | ||
| 94 | + public static List<String> buildScopes(String scope) { | ||
| 95 | + return StrUtil.split(scope, ' '); | ||
| 96 | + } | ||
| 97 | + | ||
| 98 | +} |
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AuthServiceImplTest.java renamed to yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImplTest.java
| @@ -5,15 +5,16 @@ import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; | @@ -5,15 +5,16 @@ import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; | ||
| 5 | import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; | 5 | import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; |
| 6 | import cn.iocoder.yudao.framework.test.core.util.AssertUtils; | 6 | import cn.iocoder.yudao.framework.test.core.util.AssertUtils; |
| 7 | import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi; | 7 | import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi; |
| 8 | -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthLoginReqVO; | ||
| 9 | -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthLoginRespVO; | ||
| 10 | -import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO; | 8 | +import cn.iocoder.yudao.module.system.controller.admin.auth.vo.AuthLoginReqVO; |
| 9 | +import cn.iocoder.yudao.module.system.controller.admin.auth.vo.AuthLoginRespVO; | ||
| 10 | +import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; | ||
| 11 | import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; | 11 | import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; |
| 12 | import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum; | 12 | import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum; |
| 13 | import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum; | 13 | import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum; |
| 14 | import cn.iocoder.yudao.module.system.service.common.CaptchaService; | 14 | import cn.iocoder.yudao.module.system.service.common.CaptchaService; |
| 15 | import cn.iocoder.yudao.module.system.service.logger.LoginLogService; | 15 | import cn.iocoder.yudao.module.system.service.logger.LoginLogService; |
| 16 | import cn.iocoder.yudao.module.system.service.member.MemberService; | 16 | import cn.iocoder.yudao.module.system.service.member.MemberService; |
| 17 | +import cn.iocoder.yudao.module.system.service.oauth2.OAuth2TokenService; | ||
| 17 | import cn.iocoder.yudao.module.system.service.social.SocialUserService; | 18 | import cn.iocoder.yudao.module.system.service.social.SocialUserService; |
| 18 | import cn.iocoder.yudao.module.system.service.user.AdminUserService; | 19 | import cn.iocoder.yudao.module.system.service.user.AdminUserService; |
| 19 | import org.junit.jupiter.api.BeforeEach; | 20 | import org.junit.jupiter.api.BeforeEach; |
| @@ -33,7 +34,7 @@ import static org.mockito.ArgumentMatchers.eq; | @@ -33,7 +34,7 @@ import static org.mockito.ArgumentMatchers.eq; | ||
| 33 | import static org.mockito.Mockito.*; | 34 | import static org.mockito.Mockito.*; |
| 34 | 35 | ||
| 35 | @Import(AdminAuthServiceImpl.class) | 36 | @Import(AdminAuthServiceImpl.class) |
| 36 | -public class AuthServiceImplTest extends BaseDbUnitTest { | 37 | +public class AdminAuthServiceImplTest extends BaseDbUnitTest { |
| 37 | 38 | ||
| 38 | @Resource | 39 | @Resource |
| 39 | private AdminAuthServiceImpl authService; | 40 | private AdminAuthServiceImpl authService; |
| @@ -62,7 +63,7 @@ public class AuthServiceImplTest extends BaseDbUnitTest { | @@ -62,7 +63,7 @@ public class AuthServiceImplTest extends BaseDbUnitTest { | ||
| 62 | } | 63 | } |
| 63 | 64 | ||
| 64 | @Test | 65 | @Test |
| 65 | - public void testLogin0_success() { | 66 | + public void testAuthenticate_success() { |
| 66 | // 准备参数 | 67 | // 准备参数 |
| 67 | String username = randomString(); | 68 | String username = randomString(); |
| 68 | String password = randomString(); | 69 | String password = randomString(); |
| @@ -74,19 +75,19 @@ public class AuthServiceImplTest extends BaseDbUnitTest { | @@ -74,19 +75,19 @@ public class AuthServiceImplTest extends BaseDbUnitTest { | ||
| 74 | when(userService.isPasswordMatch(eq(password), eq(user.getPassword()))).thenReturn(true); | 75 | when(userService.isPasswordMatch(eq(password), eq(user.getPassword()))).thenReturn(true); |
| 75 | 76 | ||
| 76 | // 调用 | 77 | // 调用 |
| 77 | - AdminUserDO loginUser = authService.login0(username, password); | 78 | + AdminUserDO loginUser = authService.authenticate(username, password); |
| 78 | // 校验 | 79 | // 校验 |
| 79 | assertPojoEquals(user, loginUser); | 80 | assertPojoEquals(user, loginUser); |
| 80 | } | 81 | } |
| 81 | 82 | ||
| 82 | @Test | 83 | @Test |
| 83 | - public void testLogin0_userNotFound() { | 84 | + public void testAuthenticate_userNotFound() { |
| 84 | // 准备参数 | 85 | // 准备参数 |
| 85 | String username = randomString(); | 86 | String username = randomString(); |
| 86 | String password = randomString(); | 87 | String password = randomString(); |
| 87 | 88 | ||
| 88 | // 调用, 并断言异常 | 89 | // 调用, 并断言异常 |
| 89 | - AssertUtils.assertServiceException(() -> authService.login0(username, password), | 90 | + AssertUtils.assertServiceException(() -> authService.authenticate(username, password), |
| 90 | AUTH_LOGIN_BAD_CREDENTIALS); | 91 | AUTH_LOGIN_BAD_CREDENTIALS); |
| 91 | verify(loginLogService).createLoginLog( | 92 | verify(loginLogService).createLoginLog( |
| 92 | argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType()) | 93 | argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType()) |
| @@ -96,7 +97,7 @@ public class AuthServiceImplTest extends BaseDbUnitTest { | @@ -96,7 +97,7 @@ public class AuthServiceImplTest extends BaseDbUnitTest { | ||
| 96 | } | 97 | } |
| 97 | 98 | ||
| 98 | @Test | 99 | @Test |
| 99 | - public void testLogin0_badCredentials() { | 100 | + public void testAuthenticate_badCredentials() { |
| 100 | // 准备参数 | 101 | // 准备参数 |
| 101 | String username = randomString(); | 102 | String username = randomString(); |
| 102 | String password = randomString(); | 103 | String password = randomString(); |
| @@ -106,7 +107,7 @@ public class AuthServiceImplTest extends BaseDbUnitTest { | @@ -106,7 +107,7 @@ public class AuthServiceImplTest extends BaseDbUnitTest { | ||
| 106 | when(userService.getUserByUsername(eq(username))).thenReturn(user); | 107 | when(userService.getUserByUsername(eq(username))).thenReturn(user); |
| 107 | 108 | ||
| 108 | // 调用, 并断言异常 | 109 | // 调用, 并断言异常 |
| 109 | - AssertUtils.assertServiceException(() -> authService.login0(username, password), | 110 | + AssertUtils.assertServiceException(() -> authService.authenticate(username, password), |
| 110 | AUTH_LOGIN_BAD_CREDENTIALS); | 111 | AUTH_LOGIN_BAD_CREDENTIALS); |
| 111 | verify(loginLogService).createLoginLog( | 112 | verify(loginLogService).createLoginLog( |
| 112 | argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType()) | 113 | argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType()) |
| @@ -116,7 +117,7 @@ public class AuthServiceImplTest extends BaseDbUnitTest { | @@ -116,7 +117,7 @@ public class AuthServiceImplTest extends BaseDbUnitTest { | ||
| 116 | } | 117 | } |
| 117 | 118 | ||
| 118 | @Test | 119 | @Test |
| 119 | - public void testLogin0_userDisabled() { | 120 | + public void testAuthenticate_userDisabled() { |
| 120 | // 准备参数 | 121 | // 准备参数 |
| 121 | String username = randomString(); | 122 | String username = randomString(); |
| 122 | String password = randomString(); | 123 | String password = randomString(); |
| @@ -128,7 +129,7 @@ public class AuthServiceImplTest extends BaseDbUnitTest { | @@ -128,7 +129,7 @@ public class AuthServiceImplTest extends BaseDbUnitTest { | ||
| 128 | when(userService.isPasswordMatch(eq(password), eq(user.getPassword()))).thenReturn(true); | 129 | when(userService.isPasswordMatch(eq(password), eq(user.getPassword()))).thenReturn(true); |
| 129 | 130 | ||
| 130 | // 调用, 并断言异常 | 131 | // 调用, 并断言异常 |
| 131 | - AssertUtils.assertServiceException(() -> authService.login0(username, password), | 132 | + AssertUtils.assertServiceException(() -> authService.authenticate(username, password), |
| 132 | AUTH_LOGIN_USER_DISABLED); | 133 | AUTH_LOGIN_USER_DISABLED); |
| 133 | verify(loginLogService).createLoginLog( | 134 | verify(loginLogService).createLoginLog( |
| 134 | argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType()) | 135 | argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType()) |
| @@ -200,7 +201,7 @@ public class AuthServiceImplTest extends BaseDbUnitTest { | @@ -200,7 +201,7 @@ public class AuthServiceImplTest extends BaseDbUnitTest { | ||
| 200 | // mock 缓存登录用户到 Redis | 201 | // mock 缓存登录用户到 Redis |
| 201 | OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class, o -> o.setUserId(1L) | 202 | OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class, o -> o.setUserId(1L) |
| 202 | .setUserType(UserTypeEnum.ADMIN.getValue())); | 203 | .setUserType(UserTypeEnum.ADMIN.getValue())); |
| 203 | - when(oauth2TokenService.createAccessToken(eq(1L), eq(UserTypeEnum.ADMIN.getValue()), eq("default"))) | 204 | + when(oauth2TokenService.createAccessToken(eq(1L), eq(UserTypeEnum.ADMIN.getValue()), eq("default"), isNull())) |
| 204 | .thenReturn(accessTokenDO); | 205 | .thenReturn(accessTokenDO); |
| 205 | 206 | ||
| 206 | // 调用, 并断言异常 | 207 | // 调用, 并断言异常 |
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/OAuth2ClientServiceImplTest.java
| @@ -3,12 +3,13 @@ package cn.iocoder.yudao.module.system.service.auth; | @@ -3,12 +3,13 @@ package cn.iocoder.yudao.module.system.service.auth; | ||
| 3 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | 3 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| 4 | import cn.iocoder.yudao.framework.common.pojo.PageResult; | 4 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 5 | import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; | 5 | import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; |
| 6 | -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientCreateReqVO; | ||
| 7 | -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientPageReqVO; | ||
| 8 | -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.client.OAuth2ClientUpdateReqVO; | ||
| 9 | -import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2ClientDO; | ||
| 10 | -import cn.iocoder.yudao.module.system.dal.mysql.auth.OAuth2ClientMapper; | 6 | +import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.client.OAuth2ClientCreateReqVO; |
| 7 | +import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.client.OAuth2ClientPageReqVO; | ||
| 8 | +import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.client.OAuth2ClientUpdateReqVO; | ||
| 9 | +import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2ClientDO; | ||
| 10 | +import cn.iocoder.yudao.module.system.dal.mysql.oauth2.OAuth2ClientMapper; | ||
| 11 | import cn.iocoder.yudao.module.system.mq.producer.auth.OAuth2ClientProducer; | 11 | import cn.iocoder.yudao.module.system.mq.producer.auth.OAuth2ClientProducer; |
| 12 | +import cn.iocoder.yudao.module.system.service.oauth2.OAuth2ClientServiceImpl; | ||
| 12 | import org.junit.jupiter.api.Disabled; | 13 | import org.junit.jupiter.api.Disabled; |
| 13 | import org.junit.jupiter.api.Test; | 14 | import org.junit.jupiter.api.Test; |
| 14 | import org.springframework.boot.test.mock.mockito.MockBean; | 15 | import org.springframework.boot.test.mock.mockito.MockBean; |
yudao-ui-admin/src/api/login.js
| @@ -109,3 +109,40 @@ export function refreshToken() { | @@ -109,3 +109,40 @@ export function refreshToken() { | ||
| 109 | method: 'post' | 109 | method: 'post' |
| 110 | }) | 110 | }) |
| 111 | } | 111 | } |
| 112 | + | ||
| 113 | +// ========== OAUTH 2.0 相关 ========== | ||
| 114 | + | ||
| 115 | +export function getAuthorize(clientId) { | ||
| 116 | + return request({ | ||
| 117 | + url: '/system/oauth2/authorize?clientId=' + clientId, | ||
| 118 | + method: 'get' | ||
| 119 | + }) | ||
| 120 | +} | ||
| 121 | + | ||
| 122 | +export function authorize(responseType, clientId, redirectUri, state, | ||
| 123 | + autoApprove, checkedScopes, uncheckedScopes) { | ||
| 124 | + // 构建 scopes | ||
| 125 | + const scopes = {}; | ||
| 126 | + for (const scope of checkedScopes) { | ||
| 127 | + scopes[scope] = true; | ||
| 128 | + } | ||
| 129 | + for (const scope of uncheckedScopes) { | ||
| 130 | + scopes[scope] = false; | ||
| 131 | + } | ||
| 132 | + // 发起请求 | ||
| 133 | + return service({ | ||
| 134 | + url: '/system/oauth2/authorize', | ||
| 135 | + headers:{ | ||
| 136 | + 'Content-type': 'application/x-www-form-urlencoded', | ||
| 137 | + }, | ||
| 138 | + params: { | ||
| 139 | + response_type: responseType, | ||
| 140 | + client_id: clientId, | ||
| 141 | + redirect_uri: redirectUri, | ||
| 142 | + state: state, | ||
| 143 | + auto_approve: autoApprove, | ||
| 144 | + scope: JSON.stringify(scopes) | ||
| 145 | + }, | ||
| 146 | + method: 'post' | ||
| 147 | + }) | ||
| 148 | +} |
yudao-ui-admin/src/router/index.js
| @@ -43,6 +43,11 @@ export const constantRoutes = [ | @@ -43,6 +43,11 @@ export const constantRoutes = [ | ||
| 43 | hidden: true | 43 | hidden: true |
| 44 | }, | 44 | }, |
| 45 | { | 45 | { |
| 46 | + path: '/sso', | ||
| 47 | + component: (resolve) => require(['@/views/sso'], resolve), | ||
| 48 | + hidden: true | ||
| 49 | + }, | ||
| 50 | + { | ||
| 46 | path: '/social-login', | 51 | path: '/social-login', |
| 47 | component: (resolve) => require(['@/views/socialLogin'], resolve), | 52 | component: (resolve) => require(['@/views/socialLogin'], resolve), |
| 48 | hidden: true | 53 | hidden: true |
yudao-ui-admin/src/views/sso.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <div class="container"> | ||
| 3 | + <div class="logo"></div> | ||
| 4 | + <!-- 登录区域 --> | ||
| 5 | + <div class="content"> | ||
| 6 | + <!-- 配图 --> | ||
| 7 | + <div class="pic"></div> | ||
| 8 | + <!-- 表单 --> | ||
| 9 | + <div class="field"> | ||
| 10 | + <!-- [移动端]标题 --> | ||
| 11 | + <h2 class="mobile-title"> | ||
| 12 | + <h3 class="title">芋道后台管理系统</h3> | ||
| 13 | + </h2> | ||
| 14 | + | ||
| 15 | + <!-- 表单 --> | ||
| 16 | + <div class="form-cont"> | ||
| 17 | + <el-tabs class="form" style=" float:none;" value="uname"> | ||
| 18 | + <el-tab-pane :label="'三方授权(' + client.name + ')'" name="uname"> | ||
| 19 | + </el-tab-pane> | ||
| 20 | + </el-tabs> | ||
| 21 | + <div> | ||
| 22 | + <el-form ref="loginForm" :model="loginForm" :rules="LoginRules" class="login-form"> | ||
| 23 | + <el-form-item prop="tenantName" v-if="tenantEnable"> | ||
| 24 | + <el-input v-model="loginForm.tenantName" type="text" auto-complete="off" placeholder='租户'> | ||
| 25 | + <svg-icon slot="prefix" icon-class="tree" class="el-input__icon input-icon"/> | ||
| 26 | + </el-input> | ||
| 27 | + </el-form-item> | ||
| 28 | + <!-- 授权范围的选择 --> | ||
| 29 | + 此第三方应用请求获得以下权限: | ||
| 30 | + <el-form-item prop="scopes"> | ||
| 31 | + <el-checkbox-group v-model="loginForm.scopes"> | ||
| 32 | + <el-checkbox v-for="scope in params.scopes" :label="scope" :key="scope" | ||
| 33 | + style="display: block; margin-bottom: -10px;">{{formatScope(scope)}}</el-checkbox> | ||
| 34 | + </el-checkbox-group> | ||
| 35 | + </el-form-item> | ||
| 36 | + <!-- 下方的登录按钮 --> | ||
| 37 | + <el-form-item style="width:100%;"> | ||
| 38 | + <el-button :loading="loading" size="medium" type="primary" style="width:60%;" | ||
| 39 | + @click.native.prevent="handleAuthorize(true)"> | ||
| 40 | + <span v-if="!loading">统一授权</span> | ||
| 41 | + <span v-else>授 权 中...</span> | ||
| 42 | + </el-button> | ||
| 43 | + <el-button size="medium" style="width:36%" | ||
| 44 | + @click.native.prevent="handleAuthorize(false)">拒绝</el-button> | ||
| 45 | + </el-form-item> | ||
| 46 | + </el-form> | ||
| 47 | + </div> | ||
| 48 | + </div> | ||
| 49 | + </div> | ||
| 50 | + </div> | ||
| 51 | + <!-- footer --> | ||
| 52 | + <div class="footer"> | ||
| 53 | + Copyright © 2020-2022 iocoder.cn All Rights Reserved. | ||
| 54 | + </div> | ||
| 55 | + </div> | ||
| 56 | +</template> | ||
| 57 | + | ||
| 58 | +<script> | ||
| 59 | +import {getTenantIdByName} from "@/api/system/tenant"; | ||
| 60 | +import {getTenantEnable} from "@/utils/ruoyi"; | ||
| 61 | +import {authorize, getAuthorize} from "@/api/login"; | ||
| 62 | +import {getTenantName, setTenantId} from "@/utils/auth"; | ||
| 63 | + | ||
| 64 | +export default { | ||
| 65 | + name: "Login", | ||
| 66 | + data() { | ||
| 67 | + return { | ||
| 68 | + tenantEnable: true, | ||
| 69 | + loginForm: { | ||
| 70 | + tenantName: "芋道源码", | ||
| 71 | + scopes: [], // 已选中的 scope 数组 | ||
| 72 | + }, | ||
| 73 | + params: { // URL 上的 client_id、scope 等参数 | ||
| 74 | + responseType: undefined, | ||
| 75 | + clientId: undefined, | ||
| 76 | + redirectUri: undefined, | ||
| 77 | + state: undefined, | ||
| 78 | + scopes: [], // 优先从 query 参数获取;如果未传递,从后端获取 | ||
| 79 | + }, | ||
| 80 | + client: { // 客户端信息 | ||
| 81 | + name: '', | ||
| 82 | + logo: '', | ||
| 83 | + }, | ||
| 84 | + LoginRules: { | ||
| 85 | + tenantName: [ | ||
| 86 | + {required: true, trigger: "blur", message: "租户不能为空"}, | ||
| 87 | + { | ||
| 88 | + validator: (rule, value, callback) => { | ||
| 89 | + // debugger | ||
| 90 | + getTenantIdByName(value).then(res => { | ||
| 91 | + const tenantId = res.data; | ||
| 92 | + if (tenantId && tenantId >= 0) { | ||
| 93 | + // 设置租户 | ||
| 94 | + setTenantId(tenantId) | ||
| 95 | + callback(); | ||
| 96 | + } else { | ||
| 97 | + callback('租户不存在'); | ||
| 98 | + } | ||
| 99 | + }); | ||
| 100 | + }, | ||
| 101 | + trigger: 'blur' | ||
| 102 | + } | ||
| 103 | + ] | ||
| 104 | + }, | ||
| 105 | + loading: false | ||
| 106 | + }; | ||
| 107 | + }, | ||
| 108 | + created() { | ||
| 109 | + // 租户开关 | ||
| 110 | + this.tenantEnable = getTenantEnable(); | ||
| 111 | + this.getCookie(); | ||
| 112 | + | ||
| 113 | + // 解析参数 | ||
| 114 | + // 例如说【自动授权不通过】:client_id=default&redirect_uri=https%3A%2F%2Fwww.iocoder.cn&response_type=code&scope=user.read%20user.write | ||
| 115 | + // 例如说【自动授权通过】:client_id=default&redirect_uri=https%3A%2F%2Fwww.iocoder.cn&response_type=code&scope=user.read | ||
| 116 | + this.params.responseType = this.$route.query.response_type | ||
| 117 | + this.params.clientId = this.$route.query.client_id | ||
| 118 | + this.params.redirectUri = this.$route.query.redirect_uri | ||
| 119 | + this.params.state = this.$route.query.state | ||
| 120 | + if (this.$route.query.scope) { | ||
| 121 | + this.params.scopes = this.$route.query.scope.split(' ') | ||
| 122 | + } | ||
| 123 | + | ||
| 124 | + // 如果有 scope 参数,先执行一次自动授权,看看是否之前都授权过了。 | ||
| 125 | + if (this.params.scopes.length > 0) { | ||
| 126 | + this.doAuthorize(true, this.params.scopes, []).then(res => { | ||
| 127 | + const href = res.data | ||
| 128 | + if (!href) { | ||
| 129 | + console.log('自动授权未通过!') | ||
| 130 | + return; | ||
| 131 | + } | ||
| 132 | + location.href = href | ||
| 133 | + }) | ||
| 134 | + } | ||
| 135 | + | ||
| 136 | + // 获取授权页的基本信息 | ||
| 137 | + getAuthorize(this.params.clientId).then(res => { | ||
| 138 | + this.client = res.data.client | ||
| 139 | + // 解析 scope | ||
| 140 | + let scopes | ||
| 141 | + // 1.1 如果 params.scope 非空,则过滤下返回的 scopes | ||
| 142 | + if (this.params.scopes.length > 0) { | ||
| 143 | + scopes = [] | ||
| 144 | + for (const scope of res.data.scopes) { | ||
| 145 | + if (this.params.scopes.indexOf(scope.key) >= 0) { | ||
| 146 | + scopes.push(scope) | ||
| 147 | + } | ||
| 148 | + } | ||
| 149 | + // 1.2 如果 params.scope 为空,则使用返回的 scopes 设置它 | ||
| 150 | + } else { | ||
| 151 | + scopes = res.data.scopes | ||
| 152 | + for (const scope of scopes) { | ||
| 153 | + this.params.scopes.push(scope.key) | ||
| 154 | + } | ||
| 155 | + } | ||
| 156 | + // 生成已选中的 checkedScopes | ||
| 157 | + for (const scope of scopes) { | ||
| 158 | + if (scope.value) { | ||
| 159 | + this.loginForm.scopes.push(scope.key) | ||
| 160 | + } | ||
| 161 | + } | ||
| 162 | + }) | ||
| 163 | + }, | ||
| 164 | + methods: { | ||
| 165 | + getCookie() { | ||
| 166 | + const tenantName = getTenantName(); | ||
| 167 | + this.loginForm = { | ||
| 168 | + ...this.loginForm, | ||
| 169 | + tenantName: tenantName ? tenantName : this.loginForm.tenantName, | ||
| 170 | + }; | ||
| 171 | + }, | ||
| 172 | + handleAuthorize(approved) { | ||
| 173 | + this.$refs.loginForm.validate(valid => { | ||
| 174 | + if (!valid) { | ||
| 175 | + return | ||
| 176 | + } | ||
| 177 | + this.loading = true | ||
| 178 | + // 计算 checkedScopes + uncheckedScopes | ||
| 179 | + let checkedScopes; | ||
| 180 | + let uncheckedScopes; | ||
| 181 | + if (approved) { // 同意授权,按照用户的选择 | ||
| 182 | + checkedScopes = this.loginForm.scopes | ||
| 183 | + uncheckedScopes = this.params.scopes.filter(item => checkedScopes.indexOf(item) === -1) | ||
| 184 | + } else { // 拒绝,则都是取消 | ||
| 185 | + checkedScopes = [] | ||
| 186 | + uncheckedScopes = this.params.scopes | ||
| 187 | + } | ||
| 188 | + // 提交授权的请求 | ||
| 189 | + this.doAuthorize(false, checkedScopes, uncheckedScopes).then(res => { | ||
| 190 | + const href = res.data | ||
| 191 | + if (!href) { | ||
| 192 | + return; | ||
| 193 | + } | ||
| 194 | + location.href = href | ||
| 195 | + }).finally(() => { | ||
| 196 | + this.loading = false | ||
| 197 | + }) | ||
| 198 | + }) | ||
| 199 | + }, | ||
| 200 | + doAuthorize(autoApprove, checkedScopes, uncheckedScopes) { | ||
| 201 | + return authorize(this.params.responseType, this.params.clientId, this.params.redirectUri, this.params.state, | ||
| 202 | + autoApprove, checkedScopes, uncheckedScopes) | ||
| 203 | + }, | ||
| 204 | + formatScope(scope) { | ||
| 205 | + // 格式化 scope 授权范围,方便用户理解。 | ||
| 206 | + // 这里仅仅是一个 demo,可以考虑录入到字典数据中,例如说字典类型 "system_oauth2_scope",它的每个 scope 都是一条字典数据。 | ||
| 207 | + switch (scope) { | ||
| 208 | + case 'user.read': return '访问你的个人信息' | ||
| 209 | + case 'user.write': return '修改你的个人信息' | ||
| 210 | + default: return scope | ||
| 211 | + } | ||
| 212 | + } | ||
| 213 | + } | ||
| 214 | +}; | ||
| 215 | +</script> | ||
| 216 | +<style lang="scss" scoped> | ||
| 217 | +@import "~@/assets/styles/login.scss"; | ||
| 218 | +.oauth-login { | ||
| 219 | + display: flex; | ||
| 220 | + align-items: cen; | ||
| 221 | + cursor:pointer; | ||
| 222 | +} | ||
| 223 | +.oauth-login-item { | ||
| 224 | + display: flex; | ||
| 225 | + align-items: center; | ||
| 226 | + margin-right: 10px; | ||
| 227 | +} | ||
| 228 | +.oauth-login-item img { | ||
| 229 | + height: 25px; | ||
| 230 | + width: 25px; | ||
| 231 | +} | ||
| 232 | +.oauth-login-item span:hover { | ||
| 233 | + text-decoration: underline red; | ||
| 234 | + color: red; | ||
| 235 | +} | ||
| 236 | +</style> |
yudao-ui-admin/src/views/system/oauth2/client/index.vue
| @@ -108,23 +108,16 @@ | @@ -108,23 +108,16 @@ | ||
| 108 | <el-option v-for="redirectUri in form.redirectUris" :key="redirectUri" :label="redirectUri" :value="redirectUri"/> | 108 | <el-option v-for="redirectUri in form.redirectUris" :key="redirectUri" :label="redirectUri" :value="redirectUri"/> |
| 109 | </el-select> | 109 | </el-select> |
| 110 | </el-form-item> | 110 | </el-form-item> |
| 111 | - <el-form-item label="是否自动授权" prop="autoApprove"> | ||
| 112 | - <el-radio-group v-model="form.autoApprove"> | ||
| 113 | - <el-radio :key="true" :label="true">自动登录</el-radio> | ||
| 114 | - <el-radio :key="false" :label="false">手动登录</el-radio> | ||
| 115 | - </el-radio-group> | ||
| 116 | - </el-form-item> | ||
| 117 | - <el-form-item label="授权类型" prop="authorizedGrantTypes"> | ||
| 118 | - <el-select v-model="form.authorizedGrantTypes" multiple filterable placeholder="请输入授权类型" style="width: 500px" > | ||
| 119 | - <el-option v-for="dict in this.getDictDatas(DICT_TYPE.SYSTEM_OAUTH2_GRANT_TYPE)" | ||
| 120 | - :key="dict.value" :label="dict.label" :value="dict.value"/> | ||
| 121 | - </el-select> | ||
| 122 | - </el-form-item> | ||
| 123 | <el-form-item label="授权范围" prop="scopes"> | 111 | <el-form-item label="授权范围" prop="scopes"> |
| 124 | <el-select v-model="form.scopes" multiple filterable allow-create placeholder="请输入授权范围" style="width: 500px" > | 112 | <el-select v-model="form.scopes" multiple filterable allow-create placeholder="请输入授权范围" style="width: 500px" > |
| 125 | <el-option v-for="scope in form.scopes" :key="scope" :label="scope" :value="scope"/> | 113 | <el-option v-for="scope in form.scopes" :key="scope" :label="scope" :value="scope"/> |
| 126 | </el-select> | 114 | </el-select> |
| 127 | </el-form-item> | 115 | </el-form-item> |
| 116 | + <el-form-item label="自动授权" prop="autoApproveScopes"> | ||
| 117 | + <el-select v-model="form.autoApproveScopes" multiple filterable placeholder="请输入授权范围" style="width: 500px" > | ||
| 118 | + <el-option v-for="scope in form.scopes" :key="scope" :label="scope" :value="scope"/> | ||
| 119 | + </el-select> | ||
| 120 | + </el-form-item> | ||
| 128 | <el-form-item label="权限" prop="authorities"> | 121 | <el-form-item label="权限" prop="authorities"> |
| 129 | <el-select v-model="form.authorities" multiple filterable allow-create placeholder="请输入权限" style="width: 500px" > | 122 | <el-select v-model="form.authorities" multiple filterable allow-create placeholder="请输入权限" style="width: 500px" > |
| 130 | <el-option v-for="authority in form.authorities" :key="authority" :label="authority" :value="authority"/> | 123 | <el-option v-for="authority in form.authorities" :key="authority" :label="authority" :value="authority"/> |
| @@ -196,7 +189,6 @@ export default { | @@ -196,7 +189,6 @@ export default { | ||
| 196 | accessTokenValiditySeconds: [{ required: true, message: "访问令牌的有效期不能为空", trigger: "blur" }], | 189 | accessTokenValiditySeconds: [{ required: true, message: "访问令牌的有效期不能为空", trigger: "blur" }], |
| 197 | refreshTokenValiditySeconds: [{ required: true, message: "刷新令牌的有效期不能为空", trigger: "blur" }], | 190 | refreshTokenValiditySeconds: [{ required: true, message: "刷新令牌的有效期不能为空", trigger: "blur" }], |
| 198 | redirectUris: [{ required: true, message: "可重定向的 URI 地址不能为空", trigger: "blur" }], | 191 | redirectUris: [{ required: true, message: "可重定向的 URI 地址不能为空", trigger: "blur" }], |
| 199 | - autoApprove: [{ required: true, message: "是否自动授权不能为空", trigger: "blur" }], | ||
| 200 | authorizedGrantTypes: [{ required: true, message: "授权类型不能为空", trigger: "blur" }], | 192 | authorizedGrantTypes: [{ required: true, message: "授权类型不能为空", trigger: "blur" }], |
| 201 | } | 193 | } |
| 202 | }; | 194 | }; |
| @@ -235,9 +227,9 @@ export default { | @@ -235,9 +227,9 @@ export default { | ||
| 235 | accessTokenValiditySeconds: 30 * 60, | 227 | accessTokenValiditySeconds: 30 * 60, |
| 236 | refreshTokenValiditySeconds: 30 * 24 * 60, | 228 | refreshTokenValiditySeconds: 30 * 24 * 60, |
| 237 | redirectUris: [], | 229 | redirectUris: [], |
| 238 | - autoApprove: true, | ||
| 239 | authorizedGrantTypes: [], | 230 | authorizedGrantTypes: [], |
| 240 | scopes: [], | 231 | scopes: [], |
| 232 | + autoApproveScopes: [], | ||
| 241 | authorities: [], | 233 | authorities: [], |
| 242 | resourceIds: [], | 234 | resourceIds: [], |
| 243 | additionalInformation: undefined, | 235 | additionalInformation: undefined, |