From 21d5e02c67c1ce853c28053d67a006604de16388 Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Thu, 28 Nov 2024 08:26:58 +0800 Subject: [PATCH] =?UTF-8?q?feat(oauth2):=20=E6=96=B0=E5=A2=9E=20UnionId=20?= =?UTF-8?q?=E8=81=94=E5=90=88id=20=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sa-token-demo-oauth2-client-h5/index.html | 2 +- .../java/com/pj/mock/SaClientMockDao.java | 8 +-- .../oauth2/config/SaOAuth2ServerConfig.java | 20 ++++++++ .../satoken/oauth2/consts/SaOAuth2Consts.java | 4 ++ .../data/loader/SaOAuth2DataLoader.java | 13 ++++- .../data/model/loader/SaClientModel.java | 27 +++++++++- .../satoken/oauth2/scope/CommonScope.java | 5 ++ .../scope/handler/UnionIdScopeHandler.java | 51 +++++++++++++++++++ .../oauth2/strategy/SaOAuth2Strategy.java | 6 +-- 9 files changed, 126 insertions(+), 10 deletions(-) create mode 100644 sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/scope/handler/UnionIdScopeHandler.java diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-client-h5/index.html b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-client-h5/index.html index ab05cbf6..2a9ecd14 100644 --- a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-client-h5/index.html +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-client-h5/index.html @@ -187,7 +187,7 @@ client_id: '1001', client_secret: 'aaaa-bbbb-cccc-dddd-eeee', redirect_uri: location.href.split('?')[0].split('#')[0], - scope: 'userinfo,userid,openid,oidc', + scope: 'userinfo,userid,openid,unionid,oidc', username: 'sa', password: '123456' } diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/mock/SaClientMockDao.java b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/mock/SaClientMockDao.java index 7136bbaf..7120bad8 100644 --- a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/mock/SaClientMockDao.java +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/mock/SaClientMockDao.java @@ -27,7 +27,8 @@ public class SaClientMockDao { .setClientId("1001") // client id .setClientSecret("aaaa-bbbb-cccc-dddd-eeee") // client 秘钥 .addAllowRedirectUris("*") // 所有允许授权的 url - .addContractScopes("openid", "userid", "userinfo", "oidc") // 所有签约的权限 + .addContractScopes("openid", "unionid", "userid", "userinfo", "oidc") // 所有签约的权限 + .setSubjectId("1000001") // 主体 id (可选) .addAllowGrantTypes( // 所有允许的授权模式 GrantType.authorization_code, // 授权码式 GrantType.implicit, // 隐式式 @@ -43,7 +44,8 @@ public class SaClientMockDao { .setClientId("1002") .setClientSecret("aaaa-bbbb-cccc-dddd-eeee") .addAllowRedirectUris("*") - .addContractScopes("openid", "userid", "userinfo", "oidc") + .addContractScopes("openid", "unionid", "userid", "userinfo", "oidc") + .setSubjectId("1000001") // 主体 id (可选) .addAllowGrantTypes( GrantType.authorization_code, GrantType.implicit, @@ -58,7 +60,7 @@ public class SaClientMockDao { .setClientId("1003") .setClientSecret("aaaa-bbbb-cccc-dddd-eeee") .addAllowRedirectUris("*") - .addContractScopes("openid", "userid", "userinfo", "oidc") + .addContractScopes("openid", "unionid", "userid", "userinfo", "oidc") .addAllowGrantTypes( GrantType.authorization_code, GrantType.implicit, diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/config/SaOAuth2ServerConfig.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/config/SaOAuth2ServerConfig.java index a01b3c8c..540073f2 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/config/SaOAuth2ServerConfig.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/config/SaOAuth2ServerConfig.java @@ -69,6 +69,9 @@ public class SaOAuth2ServerConfig implements Serializable { /** 默认 openid 生成算法中使用的摘要前缀 */ public String openidDigestPrefix = SaOAuth2Consts.OPENID_DEFAULT_DIGEST_PREFIX; + /** 默认 unionid 生成算法中使用的摘要前缀 */ + public String unionidDigestPrefix = SaOAuth2Consts.UNIONID_DEFAULT_DIGEST_PREFIX; + /** 指定高级权限,多个用逗号隔开 */ public String higherScope; @@ -264,6 +267,22 @@ public class SaOAuth2ServerConfig implements Serializable { return this; } + /** + * @return unionidDigestPrefix + */ + public String getUnionidDigestPrefix() { + return unionidDigestPrefix; + } + + /** + * @param unionidDigestPrefix 要设置的 unionidDigestPrefix + * @return 对象自身 + */ + public SaOAuth2ServerConfig setUnionidDigestPrefix(String unionidDigestPrefix) { + this.unionidDigestPrefix = unionidDigestPrefix; + return this; + } + /** * 获取 指定高级权限,多个用逗号隔开 * @@ -403,6 +422,7 @@ public class SaOAuth2ServerConfig implements Serializable { ", clientTokenTimeout=" + clientTokenTimeout + ", lowerClientTokenTimeout=" + lowerClientTokenTimeout + ", openidDigestPrefix='" + openidDigestPrefix + + ", unionidDigestPrefix='" + unionidDigestPrefix + ", higherScope='" + higherScope + ", lowerScope='" + lowerScope + ", mode4ReturnAccessToken='" + mode4ReturnAccessToken + diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/consts/SaOAuth2Consts.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/consts/SaOAuth2Consts.java index 009467c1..1cefef2a 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/consts/SaOAuth2Consts.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/consts/SaOAuth2Consts.java @@ -90,6 +90,7 @@ public class SaOAuth2Consts { * 扩展字段 */ public static final class ExtraField { + public static String unionid = "unionid"; public static String openid = "openid"; public static String userid = "userid"; public static String id_token = "id_token"; @@ -99,6 +100,9 @@ public class SaOAuth2Consts { /** 默认 openid 生成算法中使用的前缀 */ public static final String OPENID_DEFAULT_DIGEST_PREFIX = "openid_default_digest_prefix"; + /** 默认 unionid 生成算法中使用的前缀 */ + public static final String UNIONID_DEFAULT_DIGEST_PREFIX = "unionid_default_digest_prefix"; + /** 表示OK的返回结果 */ public static final String OK = "ok"; diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/loader/SaOAuth2DataLoader.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/loader/SaOAuth2DataLoader.java index 635f07ad..665e7416 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/loader/SaOAuth2DataLoader.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/loader/SaOAuth2DataLoader.java @@ -57,7 +57,7 @@ public interface SaOAuth2DataLoader { } /** - * 根据ClientId 和 LoginId 获取openid + * 根据 ClientId 和 LoginId 获取openid * * @param clientId 应用id * @param loginId 账号id @@ -67,4 +67,15 @@ public interface SaOAuth2DataLoader { return SaSecureUtil.md5(SaOAuth2Manager.getServerConfig().getOpenidDigestPrefix() + "_" + clientId + "_" + loginId); } + /** + * 根据 SubjectId 和 LoginId 获取 unionid + * + * @param subjectId 应用主体id + * @param loginId 账号id + * @return 此账号在此Client下的openid + */ + default String getUnionid(String subjectId, Object loginId) { + return SaSecureUtil.md5(SaOAuth2Manager.getServerConfig().getUnionidDigestPrefix() + "_" + subjectId + "_" + loginId); + } + } diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/model/loader/SaClientModel.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/model/loader/SaClientModel.java index 393da7c8..f81d7e38 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/model/loader/SaClientModel.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/model/loader/SaClientModel.java @@ -24,7 +24,7 @@ import java.util.Arrays; import java.util.List; /** - * Client应用信息 Model + * Client 应用信息 Model * * @author click33 * @since 1.23.0 @@ -58,6 +58,11 @@ public class SaClientModel implements Serializable { */ public List allowGrantTypes = new ArrayList<>(); + /** + * 主体id + */ + public String subjectId; + /** 单独配置此Client:是否在每次 Refresh-Token 刷新 Access-Token 时,产生一个新的 Refresh-Token [默认取全局配置] */ public Boolean isNewRefresh; @@ -171,6 +176,25 @@ public class SaClientModel implements Serializable { return this; } + /** + * 获取 主体id + * + * @return subjectId 主体id + */ + public String getSubjectId() { + return this.subjectId; + } + + /** + * 设置 主体id + * + * @param subjectId 主体id + */ + public SaClientModel setSubjectId(String subjectId) { + this.subjectId = subjectId; + return this; + } + /** * @return 此Client:是否在每次 Refresh-Token 刷新 Access-Token 时,产生一个新的 Refresh-Token [默认取全局配置] */ @@ -261,6 +285,7 @@ public class SaClientModel implements Serializable { ", contractScopes=" + contractScopes + ", allowRedirectUris=" + allowRedirectUris + ", allowGrantTypes=" + allowGrantTypes + + ", subjectId=" + subjectId + ", isNewRefresh=" + isNewRefresh + ", accessTokenTimeout=" + accessTokenTimeout + ", refreshTokenTimeout=" + refreshTokenTimeout + diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/scope/CommonScope.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/scope/CommonScope.java index 8a73c090..ac13ba3c 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/scope/CommonScope.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/scope/CommonScope.java @@ -31,6 +31,11 @@ public final class CommonScope { */ public static final String OPENID = "openid"; + /** + * 获取 unionid + */ + public static final String UNIONID = "unionid"; + /** * 获取 userid */ diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/scope/handler/UnionIdScopeHandler.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/scope/handler/UnionIdScopeHandler.java new file mode 100644 index 00000000..aaf037f6 --- /dev/null +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/scope/handler/UnionIdScopeHandler.java @@ -0,0 +1,51 @@ +/* + * Copyright 2020-2099 sa-token.cc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cn.dev33.satoken.oauth2.scope.handler; + +import cn.dev33.satoken.oauth2.SaOAuth2Manager; +import cn.dev33.satoken.oauth2.consts.SaOAuth2Consts; +import cn.dev33.satoken.oauth2.data.loader.SaOAuth2DataLoader; +import cn.dev33.satoken.oauth2.data.model.AccessTokenModel; +import cn.dev33.satoken.oauth2.data.model.ClientTokenModel; +import cn.dev33.satoken.oauth2.data.model.loader.SaClientModel; +import cn.dev33.satoken.oauth2.scope.CommonScope; + +/** + * UnionId Scope 处理器,在返回的 AccessToken 中增加 unionid 字段 + * + * @author click33 + * @since 1.40.0 + */ +public class UnionIdScopeHandler implements SaOAuth2ScopeHandlerInterface { + + @Override + public String getHandlerScope() { + return CommonScope.UNIONID; + } + + @Override + public void workAccessToken(AccessTokenModel at) { + SaOAuth2DataLoader dataLoader = SaOAuth2Manager.getDataLoader(); + SaClientModel cm = dataLoader.getClientModelNotNull(at.clientId); + at.extraData.put(SaOAuth2Consts.ExtraField.unionid, dataLoader.getUnionid(cm.subjectId, at.loginId)); + } + + @Override + public void workClientToken(ClientTokenModel ct) { + + } + +} \ No newline at end of file diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/strategy/SaOAuth2Strategy.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/strategy/SaOAuth2Strategy.java index ec722221..7e9e58eb 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/strategy/SaOAuth2Strategy.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/strategy/SaOAuth2Strategy.java @@ -30,10 +30,7 @@ import cn.dev33.satoken.oauth2.granttype.handler.PasswordGrantTypeHandler; import cn.dev33.satoken.oauth2.granttype.handler.RefreshTokenGrantTypeHandler; import cn.dev33.satoken.oauth2.granttype.handler.SaOAuth2GrantTypeHandlerInterface; import cn.dev33.satoken.oauth2.scope.CommonScope; -import cn.dev33.satoken.oauth2.scope.handler.OidcScopeHandler; -import cn.dev33.satoken.oauth2.scope.handler.OpenIdScopeHandler; -import cn.dev33.satoken.oauth2.scope.handler.SaOAuth2ScopeHandlerInterface; -import cn.dev33.satoken.oauth2.scope.handler.UserIdScopeHandler; +import cn.dev33.satoken.oauth2.scope.handler.*; import cn.dev33.satoken.util.SaFoxUtil; import java.util.LinkedHashMap; @@ -70,6 +67,7 @@ public final class SaOAuth2Strategy { */ public void registerDefaultScopeHandler() { scopeHandlerMap.put(CommonScope.OPENID, new OpenIdScopeHandler()); + scopeHandlerMap.put(CommonScope.UNIONID, new UnionIdScopeHandler()); scopeHandlerMap.put(CommonScope.USERID, new UserIdScopeHandler()); scopeHandlerMap.put(CommonScope.OIDC, new OidcScopeHandler()); }