diff --git a/snowy-admin-web/package.json b/snowy-admin-web/package.json index fb0f217f..33284090 100644 --- a/snowy-admin-web/package.json +++ b/snowy-admin-web/package.json @@ -44,6 +44,7 @@ "js-pinyin": "0.2.7", "lodash-es": "4.17.21", "nprogress": "0.2.0", + "path-to-regexp": "8.2.0", "pinia": "2.2.2", "screenfull": "6.0.2", "qs": "6.13.0", diff --git a/snowy-admin-web/src/assets/images/snowy-iam.png b/snowy-admin-web/src/assets/images/snowy-iam.png new file mode 100644 index 00000000..0e721282 Binary files /dev/null and b/snowy-admin-web/src/assets/images/snowy-iam.png differ diff --git a/snowy-admin-web/src/router/index.js b/snowy-admin-web/src/router/index.js index 5d3155e6..2aac8595 100644 --- a/snowy-admin-web/src/router/index.js +++ b/snowy-admin-web/src/router/index.js @@ -23,6 +23,7 @@ import { NextLoading } from '@/utils/loading' import { useMenuStore } from '@/store/menu' import { useUserStore } from '@/store/user' import { useDictStore } from '@/store/dict' +import { pathToRegexp } from 'path-to-regexp' // 进度条配置 NProgress.configure({ showSpinner: false, speed: 500 }) @@ -51,7 +52,14 @@ const isGetRouter = ref(false) // 白名单校验 const exportWhiteListFromRouter = (router) => { const res = [] - for (const item of router) res.push(item.path) + for (const item of router) { + // 生成路由的路径正则表达式(解构出正则表达式对象) + const { regexp } = pathToRegexp(item.path) + res.push({ + path: item.path, + regex: regexp // 使用解构后的正则表达式 + }) + } return res } const whiteList = exportWhiteListFromRouter(whiteListRouters) @@ -66,7 +74,7 @@ router.beforeEach(async (to, from, next) => { : `${sysBaseConfig.SNOWY_SYS_NAME}` // 过滤白名单 - if (whiteList.includes(to.path)) { + if (whiteList.some(currentRoute => currentRoute.regex.test(to.path))) { next() // NProgress.done() return false diff --git a/snowy-admin-web/src/views/auth/login/callback.vue b/snowy-admin-web/src/views/auth/login/callback.vue index aa025aca..60d83aad 100644 --- a/snowy-admin-web/src/views/auth/login/callback.vue +++ b/snowy-admin-web/src/views/auth/login/callback.vue @@ -146,8 +146,8 @@ argLength += 1 params[key] = value }) - // 当然了,不可能只有一个参数 - if (argLength < 2) { + // 参数不能为空 + if (argLength === 0) { showError(proxy.$t('login.paramError'), true) return } diff --git a/snowy-admin-web/src/views/auth/login/threeLogin.vue b/snowy-admin-web/src/views/auth/login/threeLogin.vue index c0bfd9db..d9407a1e 100644 --- a/snowy-admin-web/src/views/auth/login/threeLogin.vue +++ b/snowy-admin-web/src/views/auth/login/threeLogin.vue @@ -2,6 +2,9 @@ {{ $t('login.signInOther') }}
+ + + diff --git a/snowy-admin-web/src/views/dev/config/thirdConfig/iamThirdForm.vue b/snowy-admin-web/src/views/dev/config/thirdConfig/iamThirdForm.vue new file mode 100644 index 00000000..ede7f6c3 --- /dev/null +++ b/snowy-admin-web/src/views/dev/config/thirdConfig/iamThirdForm.vue @@ -0,0 +1,120 @@ + + + diff --git a/snowy-admin-web/src/views/dev/config/thirdConfig/index.vue b/snowy-admin-web/src/views/dev/config/thirdConfig/index.vue index bf0d7b80..859b152e 100644 --- a/snowy-admin-web/src/views/dev/config/thirdConfig/index.vue +++ b/snowy-admin-web/src/views/dev/config/thirdConfig/index.vue @@ -1,16 +1,20 @@ diff --git a/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/enums/AuthThirdPlatformEnum.java b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/enums/AuthThirdPlatformEnum.java index 8d5b609a..f6825881 100644 --- a/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/enums/AuthThirdPlatformEnum.java +++ b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/enums/AuthThirdPlatformEnum.java @@ -24,6 +24,11 @@ import vip.xiaonuo.common.exception.CommonException; @Getter public enum AuthThirdPlatformEnum { + /** + * IAM + */ + IAM("IAM"), + /** * GITEE */ @@ -41,7 +46,7 @@ public enum AuthThirdPlatformEnum { } public static void validate(String value) { - boolean flag = GITEE.getValue().equals(value) || WECHAT.getValue().equals(value); + boolean flag = IAM.getValue().equals(value) || GITEE.getValue().equals(value) || WECHAT.getValue().equals(value); if(!flag) { throw new CommonException("不支持的第三方平台:{}", value); } diff --git a/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/param/AuthThirdCallbackParam.java b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/param/AuthThirdCallbackParam.java index 921eecc1..efef8467 100644 --- a/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/param/AuthThirdCallbackParam.java +++ b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/param/AuthThirdCallbackParam.java @@ -38,7 +38,6 @@ public class AuthThirdCallbackParam { private String code; /** 第三方回调state */ - @Schema(description = "第三方回调state", requiredMode = Schema.RequiredMode.REQUIRED) - @NotBlank(message = "state不能为空") + @Schema(description = "第三方回调state") private String state; } diff --git a/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/request/AuthThirdIamCommonSource.java b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/request/AuthThirdIamCommonSource.java new file mode 100644 index 00000000..70f6b907 --- /dev/null +++ b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/request/AuthThirdIamCommonSource.java @@ -0,0 +1,45 @@ +/* + * Copyright [2022] [https://www.xiaonuo.vip] + * + * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改Snowy源码头部的版权声明。 + * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip + * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。 + * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.auth.modular.third.request; + +import me.zhyd.oauth.config.AuthSource; +import me.zhyd.oauth.request.AuthDefaultRequest; + +/** + * 山信通认证源通用源 + * + * @author xuyuxiang + * @date 2025/2/6 17:07 + **/ +public record AuthThirdIamCommonSource(String authorizeUrl, String accessTokenUrl, String userInfoUrl) implements AuthSource { + + @Override + public String authorize() { + return this.authorizeUrl; + } + + @Override + public String accessToken() { + return this.accessTokenUrl; + } + + @Override + public String userInfo() { + return this.userInfoUrl; + } + + @Override + public Class getTargetClass() { + return AuthThirdIamRequest.class; + } +} diff --git a/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/request/AuthThirdIamRequest.java b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/request/AuthThirdIamRequest.java new file mode 100644 index 00000000..ac55bac6 --- /dev/null +++ b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/request/AuthThirdIamRequest.java @@ -0,0 +1,80 @@ +/* + * Copyright [2022] [https://www.xiaonuo.vip] + * + * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改Snowy源码头部的版权声明。 + * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip + * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。 + * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.auth.modular.third.request; + +import com.alibaba.fastjson.JSONObject; +import lombok.Getter; +import me.zhyd.oauth.config.AuthConfig; +import me.zhyd.oauth.enums.AuthUserGender; +import me.zhyd.oauth.enums.scope.AuthGiteeScope; +import me.zhyd.oauth.exception.AuthException; +import me.zhyd.oauth.model.AuthCallback; +import me.zhyd.oauth.model.AuthToken; +import me.zhyd.oauth.model.AuthUser; +import me.zhyd.oauth.request.AuthDefaultRequest; +import me.zhyd.oauth.utils.AuthScopeUtils; +import me.zhyd.oauth.utils.UrlBuilder; +import org.bouncycastle.jce.provider.BouncyCastleProvider; + +import java.security.Security; +import java.util.Map; + +/** + * 山信通认证源通用请求 + * + * @author xuyuxiang + * @date 2025/1/24 15:09 + **/ +@Getter +public class AuthThirdIamRequest extends AuthDefaultRequest { + + private final Map authSourceOidcBaseJson; + + static { + Security.addProvider(new BouncyCastleProvider()); + } + + public AuthThirdIamRequest(AuthConfig config, Map authSourceOidcBaseJson) { + super(config, new AuthThirdIamCommonSource(authSourceOidcBaseJson.get("authorizeUrl"), + authSourceOidcBaseJson.get("accessTokenUrl"), + authSourceOidcBaseJson.get("userInfoUrl"))); + this.authSourceOidcBaseJson = authSourceOidcBaseJson; + } + + @Override + public AuthToken getAccessToken(AuthCallback authCallback) { + String response = this.doPostAuthorizationCode(authCallback.getCode()); + com.alibaba.fastjson.JSONObject accessTokenObject = com.alibaba.fastjson.JSONObject.parseObject(response); + this.checkResponse(accessTokenObject); + return AuthToken.builder().accessToken(accessTokenObject.getString("access_token")).refreshToken(accessTokenObject.getString("refresh_token")).scope(accessTokenObject.getString("scope")).tokenType(accessTokenObject.getString("token_type")).expireIn(accessTokenObject.getIntValue("expires_in")).build(); + } + + @Override + public AuthUser getUserInfo(AuthToken authToken) { + String userInfo = this.doGetUserInfo(authToken); + com.alibaba.fastjson.JSONObject userInfoObject = com.alibaba.fastjson.JSONObject.parseObject(userInfo); + this.checkResponse(userInfoObject); + return AuthUser.builder().rawUserInfo(userInfoObject).uuid(userInfoObject.getString("sub")).nickname(userInfoObject.getString("name")).username(userInfoObject.getString("account")).avatar(userInfoObject.getString("picture")).email(userInfoObject.getString("email")).gender(AuthUserGender.UNKNOWN).token(authToken).source(this.source.toString()).build(); + } + + private void checkResponse(JSONObject object) { + if (object.getIntValue("code") != 200) { + throw new AuthException(object.getString("msg")); + } + } + + @Override + public String authorize(String state) { + return UrlBuilder.fromBaseUrl(super.authorize(state)).queryParam("scope", this.getScopes(" ", true, AuthScopeUtils.getDefaultScopes(AuthGiteeScope.values()))).build(); + } +} diff --git a/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/service/impl/AuthThirdServiceImpl.java b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/service/impl/AuthThirdServiceImpl.java index 259a724a..75dcc208 100644 --- a/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/service/impl/AuthThirdServiceImpl.java +++ b/snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/service/impl/AuthThirdServiceImpl.java @@ -13,6 +13,7 @@ package vip.xiaonuo.auth.modular.third.service.impl; import cn.dev33.satoken.stp.StpUtil; +import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONUtil; @@ -45,6 +46,7 @@ import vip.xiaonuo.auth.modular.third.param.AuthThirdBindAccountParam; import vip.xiaonuo.auth.modular.third.param.AuthThirdCallbackParam; import vip.xiaonuo.auth.modular.third.param.AuthThirdRenderParam; import vip.xiaonuo.auth.modular.third.param.AuthThirdUserPageParam; +import vip.xiaonuo.auth.modular.third.request.AuthThirdIamRequest; import vip.xiaonuo.auth.modular.third.result.AuthThirdRenderResult; import vip.xiaonuo.auth.modular.third.service.AuthThirdService; import vip.xiaonuo.common.cache.CommonCacheOperator; @@ -53,6 +55,8 @@ import vip.xiaonuo.common.exception.CommonException; import vip.xiaonuo.common.page.CommonPageRequest; import vip.xiaonuo.dev.api.DevConfigApi; +import java.util.Map; + /** * 第三方登录Service接口实现类 * @@ -65,6 +69,13 @@ public class AuthThirdServiceImpl extends ServiceImpl authResponse = authRequest.login(authCallback); if (authResponse.ok()) { @@ -240,12 +258,26 @@ public class AuthThirdServiceImpl extends ServiceImpl