From 54d29f0fb52e669d1436cb1305a1da7f5a01957d Mon Sep 17 00:00:00 2001 From: cuiguiyang Date: Tue, 9 Apr 2024 15:30:59 +0800 Subject: [PATCH 001/172] =?UTF-8?q?=E5=85=BC=E5=AE=B9=E8=AF=B7=E6=B1=82/oa?= =?UTF-8?q?uth2/token=E6=8E=A5=E5=8F=A3=E6=97=B6Basic=E4=B8=AD=E6=90=BA?= =?UTF-8?q?=E5=B8=A6clientId=E5=92=8CclientSecret=E7=9A=84=E5=9C=BA?= =?UTF-8?q?=E6=99=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../satoken/oauth2/logic/SaOAuth2Handle.java | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/logic/SaOAuth2Handle.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/logic/SaOAuth2Handle.java index 59c13a5f..99403d0d 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/logic/SaOAuth2Handle.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/logic/SaOAuth2Handle.java @@ -15,6 +15,7 @@ */ package cn.dev33.satoken.oauth2.logic; +import cn.dev33.satoken.basic.SaBasicUtil; import cn.dev33.satoken.context.SaHolder; import cn.dev33.satoken.context.model.SaRequest; import cn.dev33.satoken.context.model.SaResponse; @@ -32,6 +33,7 @@ import cn.dev33.satoken.oauth2.model.CodeModel; import cn.dev33.satoken.oauth2.model.RequestAuthModel; import cn.dev33.satoken.oauth2.model.SaClientModel; import cn.dev33.satoken.stp.StpUtil; +import cn.dev33.satoken.util.SaFoxUtil; import cn.dev33.satoken.util.SaResult; /** @@ -177,9 +179,20 @@ public class SaOAuth2Handle { */ public static Object token(SaRequest req, SaResponse res, SaOAuth2Config cfg) { // 获取参数 + String authorizationValue = SaBasicUtil.getAuthorizationValue(); + String clientId; + String clientSecret; + // gitlab回调token接口时,按照的是标准的oauth2协议的basic请求头,basic中会包含client_id和client_secret的信息 + if(SaFoxUtil.isEmpty(authorizationValue)){ + clientId = req.getParamNotNull(Param.client_id); + clientSecret = req.getParamNotNull(Param.client_secret); + } else { + String[] clientIdAndSecret = authorizationValue.split(":"); + clientId = clientIdAndSecret[0]; + clientSecret = clientIdAndSecret[1]; + } + String code = req.getParamNotNull(Param.code); - String clientId = req.getParamNotNull(Param.client_id); - String clientSecret = req.getParamNotNull(Param.client_secret); String redirectUri = req.getParam(Param.redirect_uri); // 校验参数 -- Gitee From 14a97a1fcb60337557156ca806d89a793a0076a1 Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Wed, 1 May 2024 05:53:18 +0800 Subject: [PATCH 002/172] =?UTF-8?q?sso=20=E6=A8=A1=E5=9D=97=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=20maxRegClient=20=E5=B1=9E=E6=80=A7=EF=BC=8C=E7=94=A8?= =?UTF-8?q?=E4=BA=8E=E6=8E=A7=E5=88=B6=E6=A8=A1=E5=BC=8F=E4=B8=89=E4=B8=8B?= =?UTF-8?q?=20client=20=E6=B3=A8=E5=86=8C=E6=95=B0=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/pj/SaSso2ClientApplication.java | 6 +- .../src/main/resources/application.yml | 6 +- .../java/com/pj/SaSso3ClientApplication.java | 6 +- .../src/main/resources/application.yml | 2 +- .../satoken/sso/model/SaSsoClientModel.java | 78 +++++---- .../sso/processor/SaSsoClientProcessor.java | 11 +- .../sso/template/SaSsoClientTemplate.java | 2 + .../sso/template/SaSsoServerTemplate.java | 161 ++++++++---------- .../satoken/sso/template/SaSsoTemplate.java | 4 - .../dev33/satoken/sso/template/SaSsoUtil.java | 2 +- 10 files changed, 142 insertions(+), 136 deletions(-) diff --git a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso2-client/src/main/java/com/pj/SaSso2ClientApplication.java b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso2-client/src/main/java/com/pj/SaSso2ClientApplication.java index d19c7528..2ab50f98 100644 --- a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso2-client/src/main/java/com/pj/SaSso2ClientApplication.java +++ b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso2-client/src/main/java/com/pj/SaSso2ClientApplication.java @@ -13,9 +13,9 @@ public class SaSso2ClientApplication { System.out.println(); System.out.println("---------------------- Sa-Token SSO 模式二 Client 端启动成功 ----------------------"); System.out.println("配置信息:" + SaSsoManager.getClientConfig()); - System.out.println("测试访问应用端一: http://sa-sso-client1.com:9001"); - System.out.println("测试访问应用端二: http://sa-sso-client2.com:9001"); - System.out.println("测试访问应用端三: http://sa-sso-client3.com:9001"); + System.out.println("测试访问应用端一: http://sa-sso-client1.com:9002"); + System.out.println("测试访问应用端二: http://sa-sso-client2.com:9002"); + System.out.println("测试访问应用端三: http://sa-sso-client3.com:9002"); System.out.println("测试前需要根据官网文档修改hosts文件,测试账号密码:sa / 123456"); System.out.println(); } diff --git a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso2-client/src/main/resources/application.yml b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso2-client/src/main/resources/application.yml index bf59d24b..f971cae7 100644 --- a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso2-client/src/main/resources/application.yml +++ b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso2-client/src/main/resources/application.yml @@ -1,9 +1,11 @@ # 端口 server: - port: 9001 + port: 9002 # sa-token配置 -sa-token: +sa-token: + # 每次登录时,产生不同的token + is-share: false # SSO-相关配置 sso-client: # SSO-Server端 统一认证地址 diff --git a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client/src/main/java/com/pj/SaSso3ClientApplication.java b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client/src/main/java/com/pj/SaSso3ClientApplication.java index cdcd78ce..ff233b83 100644 --- a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client/src/main/java/com/pj/SaSso3ClientApplication.java +++ b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client/src/main/java/com/pj/SaSso3ClientApplication.java @@ -13,9 +13,9 @@ public class SaSso3ClientApplication { System.out.println(); System.out.println("---------------------- Sa-Token SSO 模式三 Client 端启动成功 ----------------------"); System.out.println("配置信息:" + SaSsoManager.getClientConfig()); - System.out.println("测试访问应用端一: http://sa-sso-client1.com:9001"); - System.out.println("测试访问应用端二: http://sa-sso-client2.com:9001"); - System.out.println("测试访问应用端三: http://sa-sso-client3.com:9001"); + System.out.println("测试访问应用端一: http://sa-sso-client1.com:9003"); + System.out.println("测试访问应用端二: http://sa-sso-client2.com:9003"); + System.out.println("测试访问应用端三: http://sa-sso-client3.com:9003"); System.out.println("测试前需要根据官网文档修改hosts文件,测试账号密码:sa / 123456"); System.out.println(); } diff --git a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client/src/main/resources/application.yml b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client/src/main/resources/application.yml index 4d669453..f1bce7e4 100644 --- a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client/src/main/resources/application.yml +++ b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client/src/main/resources/application.yml @@ -1,6 +1,6 @@ # 端口 server: - port: 9001 + port: 9003 # sa-token配置 sa-token: diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/model/SaSsoClientModel.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/model/SaSsoClientModel.java index db3ed6a4..81e51ca3 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/model/SaSsoClientModel.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/model/SaSsoClientModel.java @@ -16,6 +16,8 @@ package cn.dev33.satoken.sso.model; +import cn.dev33.satoken.sso.util.SaSsoConsts; + /** * Sa-Token SSO Model * @@ -24,6 +26,11 @@ package cn.dev33.satoken.sso.model; */ public class SaSsoClientModel { + /* + * 只能记录模式三登录的 client 信息,模式一和模式二的信息即使记录上,也无法完成单点注销操作,遂不记录 + * 所以:mode、tokenValue 字段,仅留作扩展,暂时无用 + */ + /** * 此 client 登录模式(1=模式一,2=模式二,3=模式三) */ @@ -34,15 +41,15 @@ public class SaSsoClientModel { */ public String client; - /** - * 此次登录 token 值 - */ - public String tokenValue; +// /** +// * 此次登录 token 值 +// */ +// public String tokenValue; /** * 单点注销回调url */ - public String ssoLogoutCall; + public String sloCallbackUrl; /** * 此 client 注册信息的时间,13位时间戳 @@ -57,10 +64,15 @@ public class SaSsoClientModel { public SaSsoClientModel() { } - public SaSsoClientModel(String client, String ssoLogoutCall) { + /** + * 模式三构建 + */ + public SaSsoClientModel(String client, String sloCallbackUrl, int index) { + this.mode = SaSsoConsts.SSO_MODE_3; this.client = client; - this.ssoLogoutCall = ssoLogoutCall; + this.sloCallbackUrl = sloCallbackUrl; this.regTime = System.currentTimeMillis(); + this.index = index; } @@ -104,43 +116,43 @@ public class SaSsoClientModel { return this; } - /** - * 获取 此次登录 token 值 - * - * @return tokenValue 此次登录 token 值 - */ - public String getTokenValue() { - return this.tokenValue; - } - - /** - * 设置 此次登录 token 值 - * - * @param tokenValue 此次登录 token 值 - * @return / - */ - public SaSsoClientModel setTokenValue(String tokenValue) { - this.tokenValue = tokenValue; - return this; - } +// /** +// * 获取 此次登录 token 值 +// * +// * @return tokenValue 此次登录 token 值 +// */ +// public String getTokenValue() { +// return this.tokenValue; +// } +// +// /** +// * 设置 此次登录 token 值 +// * +// * @param tokenValue 此次登录 token 值 +// * @return / +// */ +// public SaSsoClientModel setTokenValue(String tokenValue) { +// this.tokenValue = tokenValue; +// return this; +// } /** * 获取 单点注销回调url * * @return ssoLogoutCall 单点注销回调url */ - public String getSsoLogoutCall() { - return this.ssoLogoutCall; + public String getSloCallbackUrl() { + return this.sloCallbackUrl; } /** * 设置 单点注销回调url * - * @param ssoLogoutCall 单点注销回调url + * @param sloCallbackUrl 单点注销回调url * @return / */ - public SaSsoClientModel setSsoLogoutCall(String ssoLogoutCall) { - this.ssoLogoutCall = ssoLogoutCall; + public SaSsoClientModel setSloCallbackUrl(String sloCallbackUrl) { + this.sloCallbackUrl = sloCallbackUrl; return this; } @@ -189,8 +201,8 @@ public class SaSsoClientModel { return "SaSsoClientModel{" + "mode=" + mode + ", client='" + client + '\'' + - ", tokenValue='" + tokenValue + '\'' + - ", ssoLogoutCall='" + ssoLogoutCall + '\'' + +// ", tokenValue='" + tokenValue + '\'' + + ", sloCallbackUrl='" + sloCallbackUrl + '\'' + ", regTime=" + regTime + ", index=" + index + '}'; diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/processor/SaSsoClientProcessor.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/processor/SaSsoClientProcessor.java index eb04a659..d5b65596 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/processor/SaSsoClientProcessor.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/processor/SaSsoClientProcessor.java @@ -227,10 +227,13 @@ public class SaSsoClientProcessor { // 获取参数 String loginId = req.getParamNotNull(paramName.loginId); + // String client = req.getParam(paramName.client); + // String autoLogout = req.getParam(paramName.autoLogout); // 校验参数签名 if(ssoConfig.getIsCheckSign()) { - ssoClientTemplate.getSignTemplate(ssoConfig.getClient()).checkRequest(req, paramName.loginId); + ssoClientTemplate.getSignTemplate(ssoConfig.getClient()). + checkRequest(req, paramName.loginId, paramName.client, paramName.autoLogout); } else { SaSsoManager.printNoCheckSignWarningByRuntime(); } @@ -289,7 +292,11 @@ public class SaSsoClientProcessor { } } else { // q2、使用模式二:直连Redis校验ticket - // return ssoClientTemplate.checkTicket(ticket); + // 注意此处调用了 SaSsoServerProcessor 处理器里的方法, + // 这意味着如果你的 sso-server 端重写了 SaSsoServerProcessor 里的部分方法, + // 而在当前 sso-client 没有按照相应格式重写 SaSsoClientProcessor 里的方法, + // 可能会导致调用失败(注意是可能,而非一定), + // 解决方案为:在当前 sso-client 端也按照 sso-server 端的格式重写 SaSsoClientProcessor 里的方法 return SaSsoServerProcessor.instance.ssoServerTemplate.checkTicket(ticket, cfg.getClient()); } } diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/template/SaSsoClientTemplate.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/template/SaSsoClientTemplate.java index 7a2034e5..80b4d603 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/template/SaSsoClientTemplate.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/template/SaSsoClientTemplate.java @@ -42,6 +42,8 @@ public class SaSsoClientTemplate extends SaSsoTemplate { return SaSsoManager.getClientConfig(); } + + // ------------------- SSO 模式三相关 ------------------- /** diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/template/SaSsoServerTemplate.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/template/SaSsoServerTemplate.java index d9d922fc..1c60027b 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/template/SaSsoServerTemplate.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/template/SaSsoServerTemplate.java @@ -17,12 +17,12 @@ package cn.dev33.satoken.sso.template; import cn.dev33.satoken.SaManager; import cn.dev33.satoken.session.SaSession; -import cn.dev33.satoken.sso.util.SaSsoConsts; import cn.dev33.satoken.sso.SaSsoManager; import cn.dev33.satoken.sso.config.SaSsoServerConfig; import cn.dev33.satoken.sso.error.SaSsoErrorCode; import cn.dev33.satoken.sso.exception.SaSsoException; import cn.dev33.satoken.sso.model.SaSsoClientModel; +import cn.dev33.satoken.sso.util.SaSsoConsts; import cn.dev33.satoken.strategy.SaStrategy; import cn.dev33.satoken.util.SaFoxUtil; @@ -266,44 +266,7 @@ public class SaSsoServerTemplate extends SaSsoTemplate { } - - // ------------------- SSO 模式三相关 ------------------- - - /** - * 为指定账号id注册单点注销回调URL - * @param loginId 账号id - * @param client 指定客户端标识,可为null - * @param sloCallbackUrl 单点注销时的回调URL - */ - public void registerSloCallbackUrl(Object loginId, String client, String sloCallbackUrl) { - // 如果提供的参数是空值,则直接返回,不进行任何操作 - if(SaFoxUtil.isEmpty(loginId) || SaFoxUtil.isEmpty(sloCallbackUrl)) { - return; - } - - SaSession session = getStpLogic().getSessionByLoginId(loginId); - - // 取出原来的 - List scmList = session.get(SaSsoConsts.SSO_CLIENT_MODEL_LIST_KEY_, ArrayList::new); - - // 将 新登录client 加入到集合中 - SaSsoClientModel scm = new SaSsoClientModel(); - scm.mode = 3; - scm.client = client; - scm.ssoLogoutCall = sloCallbackUrl; - scm.regTime = System.currentTimeMillis(); - scm.index = calcNextIndex(scmList); - scmList.add(scm); - - // 如果登录的client数量超过了限制,则将最早的一个登录进行清退 - if(scmList.size() > getServerConfig().getMaxRegClient()) { - SaSsoClientModel removeScm = scmList.remove(0); - notifyClientLogout(loginId, removeScm, true); - } - - // 存入持久库 - session.set(SaSsoConsts.SSO_CLIENT_MODEL_LIST_KEY_, scmList); - } + // ------------------- SSO ------------------- /** * 指定账号单点注销 @@ -328,73 +291,97 @@ public class SaSsoServerTemplate extends SaSsoTemplate { } /** - * 通知指定账号的指定客户端注销 - * @param loginId 指定账号 - * @param scm 客户端信息对象 - * @param autoLogout 是否为超过 maxRegClient 的自动注销 + * 计算下一个 index 值 + * @param scmList / + * @return / */ - public void notifyClientLogout(Object loginId, SaSsoClientModel scm, boolean autoLogout) { + public int calcNextIndex(List scmList) { + // 如果目前还没有任何登录记录,则直接返回0 + if(scmList == null || scmList.isEmpty()) { + return 0; + } + // 获取目前最大的index值 + int maxIndex = scmList.get(scmList.size() - 1).index; - // 如果给个null值,不进行任何操作 - if(scm == null) { - return; + // 如果已经是 int 最大值了,则直接返回0 + if(maxIndex == Integer.MAX_VALUE) { + return 0; } - // 如果是模式二登录的 - if(scm.mode == SaSsoConsts.SSO_MODE_2) { - // 获取登录 token - String tokenValue = scm.tokenValue; - if(SaFoxUtil.isEmpty(tokenValue)) { - return; - } + // 否则返回最大值+1 + maxIndex++; + return maxIndex; + } - // 注销此 token - getStpLogic().logoutByTokenValue(scm.tokenValue); + /** + * 为指定账号id注册单点注销回调信息(模式三) + * @param loginId 账号id + * @param client 指定客户端标识,可为null + * @param sloCallbackUrl 单点注销时的回调URL + */ + public void registerSloCallbackUrl(Object loginId, String client, String sloCallbackUrl) { + // 如果提供的参数是空值,则直接返回,不进行任何操作 + if(SaFoxUtil.isEmpty(loginId)) { + return; } - // 如果是模式三登录的 - else if(scm.mode != SaSsoConsts.SSO_MODE_3) { - // url - String sloCallUrl = scm.getSsoLogoutCall(); - if(SaFoxUtil.isEmpty(sloCallUrl)) { - return; - } + SaSession session = getStpLogic().getSessionByLoginId(loginId); - // 参数 - Map paramsMap = new TreeMap<>(); - paramsMap.put(paramName.client, scm.getClient()); - paramsMap.put(paramName.loginId, loginId); - paramsMap.put(paramName.autoLogout, autoLogout); - String signParamsStr = getSignTemplate(scm.getClient()).addSignParamsAndJoin(paramsMap); + // 取出原来的 + List scmList = session.get(SaSsoConsts.SSO_CLIENT_MODEL_LIST_KEY_, ArrayList::new); - // 拼接 - String finalUrl = SaFoxUtil.joinParam(sloCallUrl, signParamsStr); + // 将 新登录client 加入到集合中 + SaSsoClientModel scm = new SaSsoClientModel(client, sloCallbackUrl, calcNextIndex(scmList)); + scmList.add(scm); - // 发起请求 - getServerConfig().sendHttp.apply(finalUrl); + // 如果登录的client数量超过了限制,则从最早的一个登录开始清退 + int maxRegClient = getServerConfig().maxRegClient; + if(maxRegClient != -1) { + for (;;) { + if(scmList.size() > maxRegClient) { + SaSsoClientModel removeScm = scmList.remove(0); + notifyClientLogout(loginId, removeScm, true); + } else { + break; + } + } } + + // 存入持久库 + session.set(SaSsoConsts.SSO_CLIENT_MODEL_LIST_KEY_, scmList); } /** - * 计算下一个 index 值 - * @param scmList / - * @return / + * 通知指定账号的指定客户端注销 + * @param loginId 指定账号 + * @param scm 客户端信息对象 + * @param autoLogout 是否为超过 maxRegClient 的自动注销 */ - private int calcNextIndex(List scmList) { - // 如果目前还没有任何登录记录,则直接返回0 - if(scmList == null || scmList.isEmpty()) { - return 0; + public void notifyClientLogout(Object loginId, SaSsoClientModel scm, boolean autoLogout) { + + // 如果给个null值,不进行任何操作 + if(scm == null || scm.mode != SaSsoConsts.SSO_MODE_3) { + return; } - // 获取目前最大的index值 - int maxIndex = scmList.get(scmList.size() - 1).index; - // 如果已经是 int 最大值了,则直接返回0 - if(maxIndex == Integer.MAX_VALUE) { - return 0; + // url + String sloCallUrl = scm.getSloCallbackUrl(); + if(SaFoxUtil.isEmpty(sloCallUrl)) { + return; } - // 否则返回最大值+1 - return maxIndex++; + // 参数 + Map paramsMap = new TreeMap<>(); + paramsMap.put(paramName.client, scm.getClient()); + paramsMap.put(paramName.loginId, loginId); + paramsMap.put(paramName.autoLogout, autoLogout); + String signParamsStr = getSignTemplate(scm.getClient()).addSignParamsAndJoin(paramsMap); + + // 拼接 + String finalUrl = SaFoxUtil.joinParam(sloCallUrl, signParamsStr); + + // 发起请求 + getServerConfig().sendHttp.apply(finalUrl); } // ---------------------- 构建URL ---------------------- diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/template/SaSsoTemplate.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/template/SaSsoTemplate.java index 8a525e8d..39465185 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/template/SaSsoTemplate.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/template/SaSsoTemplate.java @@ -21,9 +21,6 @@ import cn.dev33.satoken.sso.name.ApiName; import cn.dev33.satoken.sso.name.ParamName; import cn.dev33.satoken.stp.StpLogic; import cn.dev33.satoken.stp.StpUtil; -import cn.dev33.satoken.util.SaResult; - -import java.util.Map; /** * Sa-Token SSO 模板方法类 (公共端) @@ -81,5 +78,4 @@ public class SaSsoTemplate { return SaManager.getSaSignTemplate(); } - } diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/template/SaSsoUtil.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/template/SaSsoUtil.java index 4ff93fc4..5b63e8af 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/template/SaSsoUtil.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/template/SaSsoUtil.java @@ -144,7 +144,7 @@ public class SaSsoUtil { } /** - * 指定账号单点注销 + * 指定账号单点注销 (以Server方发起) * @param loginId 指定账号 */ public static void ssoLogout(Object loginId) { -- Gitee From 818c1cb4eb175e87ca38848aa2bff6150a67dbb8 Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Wed, 1 May 2024 11:14:35 +0800 Subject: [PATCH 003/172] =?UTF-8?q?=E4=BC=98=E5=8C=96sso=E7=AB=A0=E8=8A=82?= =?UTF-8?q?=E6=96=87=E6=A1=A3=E5=92=8Cdemo=E7=A4=BA=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/pj/SaSsoServerApplication.java | 7 +- .../java/com/pj/sso/SsoServerController.java | 2 +- .../sa-token-demo-sso2-client/pom.xml | 9 +- .../java/com/pj/sso/SsoClientController.java | 15 ++ .../src/main/resources/application.yml | 16 +- .../.gitignore | 12 ++ .../sa-token-demo-sso3-client-test2/pom.xml | 68 ++++++++ .../com/pj/SaSso3ClientTest2Application.java | 23 +++ .../java/com/pj/sso/SsoClientController.java | 78 ++++++++++ .../src/main/resources/application.yml | 49 ++++++ .../src/main/resources/application.yml | 4 +- sa-token-doc/sso/sso-apidoc.md | 8 +- sa-token-doc/sso/sso-server.md | 39 +++-- sa-token-doc/sso/sso-type1.md | 28 ++-- sa-token-doc/sso/sso-type2.md | 139 +++++++++++++++-- sa-token-doc/sso/sso-type3.md | 147 ++---------------- .../sso/processor/SaSsoClientProcessor.java | 14 +- .../sso/processor/SaSsoServerProcessor.java | 2 +- 18 files changed, 461 insertions(+), 199 deletions(-) create mode 100644 sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-test2/.gitignore create mode 100644 sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-test2/pom.xml create mode 100644 sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-test2/src/main/java/com/pj/SaSso3ClientTest2Application.java create mode 100644 sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-test2/src/main/java/com/pj/sso/SsoClientController.java create mode 100644 sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-test2/src/main/resources/application.yml diff --git a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-server/src/main/java/com/pj/SaSsoServerApplication.java b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-server/src/main/java/com/pj/SaSsoServerApplication.java index b898ed83..1d2c46d2 100644 --- a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-server/src/main/java/com/pj/SaSsoServerApplication.java +++ b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-server/src/main/java/com/pj/SaSsoServerApplication.java @@ -1,5 +1,6 @@ package com.pj; +import cn.dev33.satoken.sso.SaSsoManager; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @@ -8,7 +9,11 @@ public class SaSsoServerApplication { public static void main(String[] args) { SpringApplication.run(SaSsoServerApplication.class, args); - System.out.println("\n------ Sa-Token-SSO 统一认证中心启动成功 "); + + System.out.println(); + System.out.println("---------------------- Sa-Token SSO 统一认证中心启动成功 ----------------------"); + System.out.println("配置信息:" + SaSsoManager.getServerConfig()); + System.out.println(); } } \ No newline at end of file diff --git a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-server/src/main/java/com/pj/sso/SsoServerController.java b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-server/src/main/java/com/pj/sso/SsoServerController.java index 7302afd7..6e2e64ee 100644 --- a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-server/src/main/java/com/pj/sso/SsoServerController.java +++ b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-server/src/main/java/com/pj/sso/SsoServerController.java @@ -25,7 +25,7 @@ public class SsoServerController { * http://{host}:{port}/sso/auth -- 单点登录授权地址,接受参数:redirect=授权重定向地址 * http://{host}:{port}/sso/doLogin -- 账号密码登录接口,接受参数:name、pwd * http://{host}:{port}/sso/checkTicket -- Ticket校验接口(isHttp=true时打开),接受参数:ticket=ticket码、ssoLogoutCall=单点注销回调地址 [可选] - * http://{host}:{port}/sso/signout -- 单点注销地址(isSlo=true时打开),接受参数:loginId=账号id、secretkey=接口调用秘钥 + * http://{host}:{port}/sso/signout -- 单点注销地址(isSlo=true时打开),接受参数:loginId=账号id、sign=参数签名 */ @RequestMapping("/sso/*") public Object ssoRequest() { diff --git a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso2-client/pom.xml b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso2-client/pom.xml index 513629c2..e2599ed8 100644 --- a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso2-client/pom.xml +++ b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso2-client/pom.xml @@ -60,7 +60,14 @@ sa-token-alone-redis ${sa-token.version} - + + + + com.dtflys.forest + forest-spring-boot-starter + 1.5.26 + + diff --git a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso2-client/src/main/java/com/pj/sso/SsoClientController.java b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso2-client/src/main/java/com/pj/sso/SsoClientController.java index 11383970..4eeed193 100644 --- a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso2-client/src/main/java/com/pj/sso/SsoClientController.java +++ b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso2-client/src/main/java/com/pj/sso/SsoClientController.java @@ -1,8 +1,11 @@ package com.pj.sso; +import cn.dev33.satoken.sso.config.SaSsoClientConfig; import cn.dev33.satoken.sso.processor.SaSsoClientProcessor; import cn.dev33.satoken.stp.StpUtil; import cn.dev33.satoken.util.SaResult; +import com.dtflys.forest.Forest; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -35,6 +38,18 @@ public class SsoClientController { return SaSsoClientProcessor.instance.dister(); } + // 配置SSO相关参数 + @Autowired + private void configSso(SaSsoClientConfig ssoClient) { + // 配置Http请求处理器 + ssoClient.sendHttp = url -> { + System.out.println("------ 发起请求:" + url); + String resStr = Forest.get(url).executeAsString(); + System.out.println("------ 请求结果:" + resStr); + return resStr; + }; + } + // 全局异常拦截 @ExceptionHandler public SaResult handlerException(Exception e) { diff --git a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso2-client/src/main/resources/application.yml b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso2-client/src/main/resources/application.yml index f971cae7..8ce56dd6 100644 --- a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso2-client/src/main/resources/application.yml +++ b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso2-client/src/main/resources/application.yml @@ -4,14 +4,15 @@ server: # sa-token配置 sa-token: - # 每次登录时,产生不同的token - is-share: false # SSO-相关配置 sso-client: - # SSO-Server端 统一认证地址 + # SSO-Server 端主机地址 server-url: http://sa-sso-server.com:9000 # 前后端分离时用这个 # auth-url: http://127.0.0.1:8848/sa-token-demo-sso-server-h5/sso-auth.html + sign: + # API 接口调用秘钥 + secret-key: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor # 配置Sa-Token单独使用的Redis连接 (此处需要和SSO-Server端连接同一个Redis) alone-redis: @@ -35,8 +36,7 @@ sa-token: max-idle: 10 # 连接池中的最小空闲连接 min-idle: 0 - - - - - \ No newline at end of file + +forest: + # 关闭 forest 请求日志打印 + log-enabled: false diff --git a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-test2/.gitignore b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-test2/.gitignore new file mode 100644 index 00000000..99a6e767 --- /dev/null +++ b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-test2/.gitignore @@ -0,0 +1,12 @@ +target/ + +node_modules/ +bin/ +.settings/ +unpackage/ +.classpath +.project + +.idea/ + +.factorypath \ No newline at end of file diff --git a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-test2/pom.xml b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-test2/pom.xml new file mode 100644 index 00000000..cd61b769 --- /dev/null +++ b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-test2/pom.xml @@ -0,0 +1,68 @@ + + 4.0.0 + cn.dev33 + sa-token-demo-sso3-client-test2 + 0.0.1-SNAPSHOT + + + + org.springframework.boot + spring-boot-starter-parent + 2.5.14 + + + + + + 1.37.0 + + + + + + + org.springframework.boot + spring-boot-starter-web + + + + + cn.dev33 + sa-token-spring-boot-starter + ${sa-token.version} + + + + + cn.dev33 + sa-token-sso + ${sa-token.version} + + + + + cn.dev33 + sa-token-redis-jackson + ${sa-token.version} + + + + + org.apache.commons + commons-pool2 + + + + + com.dtflys.forest + forest-spring-boot-starter + 1.5.26 + + + + + + + \ No newline at end of file diff --git a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-test2/src/main/java/com/pj/SaSso3ClientTest2Application.java b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-test2/src/main/java/com/pj/SaSso3ClientTest2Application.java new file mode 100644 index 00000000..1374e3b7 --- /dev/null +++ b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-test2/src/main/java/com/pj/SaSso3ClientTest2Application.java @@ -0,0 +1,23 @@ +package com.pj; + +import cn.dev33.satoken.sso.SaSsoManager; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SaSso3ClientTest2Application { + + public static void main(String[] args) { + SpringApplication.run(SaSso3ClientTest2Application.class, args); + + System.out.println(); + System.out.println("---------------------- Sa-Token SSO 模式三 Client 端启动成功 ----------------------"); + System.out.println("配置信息:" + SaSsoManager.getClientConfig()); + System.out.println("测试访问应用端一: http://sa-sso-client1.com:9032"); + System.out.println("测试访问应用端二: http://sa-sso-client2.com:9032"); + System.out.println("测试访问应用端三: http://sa-sso-client3.com:9032"); + System.out.println("测试前需要根据官网文档修改hosts文件,测试账号密码:sa / 123456"); + System.out.println(); + } + +} \ No newline at end of file diff --git a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-test2/src/main/java/com/pj/sso/SsoClientController.java b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-test2/src/main/java/com/pj/sso/SsoClientController.java new file mode 100644 index 00000000..2492688a --- /dev/null +++ b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-test2/src/main/java/com/pj/sso/SsoClientController.java @@ -0,0 +1,78 @@ +package com.pj.sso; + +import cn.dev33.satoken.sso.config.SaSsoClientConfig; +import cn.dev33.satoken.sso.processor.SaSsoClientProcessor; +import cn.dev33.satoken.sso.template.SaSsoUtil; +import cn.dev33.satoken.stp.StpUtil; +import cn.dev33.satoken.util.SaResult; +import com.dtflys.forest.Forest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.HashMap; +import java.util.Map; + +/** + * Sa-Token-SSO Client端 Controller + * @author click33 + */ +@RestController +public class SsoClientController { + + // SSO-Client端:首页 + @RequestMapping("/") + public String index() { + String str = "

Sa-Token SSO-Client 应用端

" + + "

当前会话是否登录:" + StpUtil.isLogin() + "

" + + "

登录" + + " 注销

"; + return str; + } + + /* + * SSO-Client端:处理所有SSO相关请求 + * http://{host}:{port}/sso/login -- Client端登录地址,接受参数:back=登录后的跳转地址 + * http://{host}:{port}/sso/logout -- Client端单点注销地址(isSlo=true时打开),接受参数:back=注销后的跳转地址 + * http://{host}:{port}/sso/logoutCall -- Client端单点注销回调地址(isSlo=true时打开),此接口为框架回调,开发者无需关心 + */ + @RequestMapping("/sso/*") + public Object ssoRequest() { + return SaSsoClientProcessor.instance.dister(); + } + + // 配置SSO相关参数 + @Autowired + private void configSso(SaSsoClientConfig ssoClient) { + // 配置Http请求处理器 + ssoClient.sendHttp = url -> { + System.out.println("------ 发起请求:" + url); + String resStr = Forest.get(url).executeAsString(); + System.out.println("------ 请求结果:" + resStr); + return resStr; + }; + } + + // 查询我的账号信息 + @RequestMapping("/sso/myInfo") + public Object myInfo() { + // 组织请求参数 + Map map = new HashMap<>(); + map.put("apiType", "userinfo"); + map.put("loginId", StpUtil.getLoginId()); + + // 发起请求 + Object resData = SaSsoUtil.getData(map); + System.out.println("sso-server 返回的信息:" + resData); + return resData; + } + + // 全局异常拦截 + @ExceptionHandler + public SaResult handlerException(Exception e) { + e.printStackTrace(); + return SaResult.error(e.getMessage()); + } + +} diff --git a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-test2/src/main/resources/application.yml b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-test2/src/main/resources/application.yml new file mode 100644 index 00000000..ebec4dde --- /dev/null +++ b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-test2/src/main/resources/application.yml @@ -0,0 +1,49 @@ +# 端口 +server: + port: 9032 + +# sa-token配置 +sa-token: + # sso-client 相关配置 + sso-client: + # client 标识 + client: sso-client3-test2 + # sso-server 端主机地址 + server-url: http://sa-sso-server.com:9000 + # 使用 Http 请求校验ticket (模式三) + is-http: true + + sign: + # API 接口调用秘钥 + secret-key: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor + +spring: + # 配置 Redis 连接 (此处与SSO-Server端连接不同的Redis) + redis: + # Redis数据库索引 + database: 4 + # Redis服务器地址 + host: 127.0.0.1 + # Redis服务器连接端口 + port: 6379 + # Redis服务器连接密码(默认为空) + password: + # 连接超时时间 + timeout: 10s + lettuce: + pool: + # 连接池最大连接数 + max-active: 200 + # 连接池最大阻塞等待时间(使用负值表示没有限制) + max-wait: -1ms + # 连接池中的最大空闲连接 + max-idle: 10 + # 连接池中的最小空闲连接 + min-idle: 0 + +forest: + # 关闭 forest 请求日志打印 + log-enabled: false + + + \ No newline at end of file diff --git a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client/src/main/resources/application.yml b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client/src/main/resources/application.yml index f1bce7e4..ef48e853 100644 --- a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client/src/main/resources/application.yml +++ b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client/src/main/resources/application.yml @@ -6,6 +6,8 @@ server: sa-token: # sso-client 相关配置 sso-client: + # client 标识 + client: sso-client3 # sso-server 端主机地址 server-url: http://sa-sso-server.com:9000 # 使用 Http 请求校验ticket (模式三) @@ -19,7 +21,7 @@ spring: # 配置 Redis 连接 (此处与SSO-Server端连接不同的Redis) redis: # Redis数据库索引 - database: 2 + database: 3 # Redis服务器地址 host: 127.0.0.1 # Redis服务器连接端口 diff --git a/sa-token-doc/sso/sso-apidoc.md b/sa-token-doc/sso/sso-apidoc.md index 8cb487b2..14d29257 100644 --- a/sa-token-doc/sso/sso-apidoc.md +++ b/sa-token-doc/sso/sso-apidoc.md @@ -22,6 +22,7 @@ http://{host}:{port}/sso/auth | :-------- | :-------- | :-------- | | redirect | 是 | 登录成功后的重定向地址,一般填写 location.href(从哪来回哪去) | | mode | 否 | 授权模式,取值 [simple, ticket],simple=登录后直接重定向,ticket=带着ticket参数重定向,默认值为ticket | +| client | 否 | 客户端标识,可不填,代表是一个匿名应用,若填写了,则校验 ticket 时也必须时这个 client 才可以校验成功 | 访问接口后有两种情况: - 情况一:当前会话在 SSO 认证中心未登录,会进入登录页开始登录。 @@ -40,7 +41,7 @@ http://{host}:{port}/sso/doLogin | name | 是 | 用户名 | | pwd | 是 | 密码 | -此接口属于 RestAPI (使用ajax访问),会进入后端配置的 `sso.setDoLoginHandle` 函数中,此函数的返回值即是此接口的响应值。 +此接口属于 RestAPI (使用ajax访问),会进入后端配置的 `ssoServer.doLoginHandle` 函数中,此函数的返回值即是此接口的响应值。 另外需要注意:此接口并非只能携带 name、pwd 参数,因为你可以在方法里通过 `SaHolder.getRequest().getParam("xxx")` 来获取前端提交的其它参数。 @@ -58,6 +59,7 @@ http://{host}:{port}/sso/checkTicket | :-------- | :-------- | :-------- | | ticket | 是 | 在步骤 1 中授权重定向时的 ticket 参数 | | ssoLogoutCall | 否 | 单点注销时的回调通知地址,只在SSO模式三单点注销时需要携带此参数| +| client | 否 | 客户端标识,可不填,代表是一个匿名应用,若填写了,则必须填写的和 `/sso/auth` 登录时填写的一致才可以校验成功 | 返回值场景: - 校验成功时: @@ -106,6 +108,7 @@ http://{host}:{port}/sso/signout?back=xxx | timestamp | 是 | 当前时间戳,13位 | | nonce | 是 | 随机字符串 | | sign | 是 | 签名,生成算法:`md5( loginId={账号id}&nonce={随机字符串}×tamp={13位时间戳}&key={secretkey秘钥} )` | +| client | 否 | 客户端标识,可不填,一般在帮助 “sso-server 端不同client不同秘钥” 的场景下找到对应秘钥时,才填写 | 例如: ``` url @@ -198,7 +201,8 @@ http://{host}:{port}/sso/logoutCall | loginId | 是 | 要注销的账号 id | | timestamp | 是 | 当前时间戳,13位 | | nonce | 是 | 随机字符串 | -| sign | 是 | 签名,生成算法:`md5( loginId={账号id}&nonce={随机字符串}×tamp={13位时间戳}&key={secretkey秘钥} )` | +| client | 否 | 客户端标识,如果你在登录时向 sso-server 端传递了 client 值,那么在此处 sso-server 也会给你回传过来,否则此参数无值 | +| sign | 是 | 签名,生成算法:`md5( loginId={账号id}&nonce={随机字符串}×tamp={13位时间戳}&key={secretkey秘钥} )` 如果 client 参数有值,则client也要参与签名,放在 loginId 参数签名(字典顺序)| 返回数据: diff --git a/sa-token-doc/sso/sso-server.md b/sa-token-doc/sso/sso-server.md index b87084ab..9193a8ab 100644 --- a/sa-token-doc/sso/sso-server.md +++ b/sa-token-doc/sso/sso-server.md @@ -90,21 +90,21 @@ implementation 'com.dtflys.forest:forest-spring-boot-starter:1.5.26' @RestController public class SsoServerController { - /* + /** * SSO-Server端:处理所有SSO相关请求 (下面的章节我们会详细列出开放的接口) */ @RequestMapping("/sso/*") public Object ssoRequest() { - return SaSsoProcessor.instance.serverDister(); + return SaSsoServerProcessor.instance.dister(); } /** * 配置SSO相关参数 */ @Autowired - private void configSso(SaSsoConfig sso) { + private void configSso(SaSsoServerConfig ssoServer) { // 配置:未登录时返回的View - sso.notLoginView = () -> { + ssoServer.notLoginView = () -> { String msg = "当前会话在SSO-Server端尚未登录,请先访问" + " doLogin登录 " + "进行登录之后,刷新页面开始授权"; @@ -112,7 +112,7 @@ public class SsoServerController { }; // 配置:登录处理函数 - sso.doLoginHandle = (name, pwd) -> { + ssoServer.doLoginHandle = (name, pwd) -> { // 此处仅做模拟登录,真实环境应该查询数据进行登录 if("sa".equals(name) && "123456".equals(pwd)) { StpUtil.login(10001); @@ -122,11 +122,12 @@ public class SsoServerController { }; // 配置 Http 请求处理器 (在模式三的单点注销功能下用到,如不需要可以注释掉) - sso.sendHttp = url -> { + ssoServer.sendHttp = url -> { try { - // 发起 http 请求 System.out.println("------ 发起请求:" + url); - return Forest.get(url).executeAsString(); + String resStr = Forest.get(url).executeAsString(); + System.out.println("------ 请求结果:" + resStr); + return resStr; } catch (Exception e) { e.printStackTrace(); return null; @@ -138,8 +139,8 @@ public class SsoServerController { ``` 注意: -- 在`setDoLoginHandle`函数里如果要获取name, pwd以外的参数,可通过`SaHolder.getRequest().getParam("xxx")`来获取 -- 在 `setSendHttp` 函数中,使用 `try-catch` 是为了提高整个注销流程的容错性,避免在一些极端情况下注销失败(例如:某个 Client 端上线之后又下线,导致 http 请求无法调用成功,从而阻断了整个注销流程) +- 在`doLoginHandle`函数里如果要获取name, pwd以外的参数,可通过`SaHolder.getRequest().getParam("xxx")`来获取 +- 在 `sendHttp` 函数中,使用 `try-catch` 是为了提高整个注销流程的容错性,避免在一些极端情况下注销失败(例如:某个 Client 端上线之后又下线,导致 http 请求无法调用成功,从而阻断了整个注销流程) 全局异常处理: ``` java @@ -173,7 +174,7 @@ sa-token: # domain: stp.com # ------- SSO-模式二相关配置 - sso: + sso-server: # Ticket有效期 (单位: 秒),默认五分钟 ticket-timeout: 300 # 所有允许的授权回调地址 @@ -215,13 +216,13 @@ server.port=9000 # ------- SSO-模式二相关配置 # Ticket有效期 (单位: 秒),默认五分钟 -sa-token.sso.ticket-timeout=300 +sa-token.sso-server.ticket-timeout=300 # 所有允许的授权回调地址 -sa-token.sso.allow-url=* +sa-token.sso-server.allow-url=* # ------- SSO-模式三相关配置 (下面的配置在使用SSO模式三时打开) # 是否打开模式三 -sa-token.sso.is-http=true +sa-token.sso-server.is-http=true # API 接口调用秘钥 sa-token.sign.secret-key=kQwIOrYvnXmSDkwEiFngrKidMcdrgKor @@ -243,7 +244,7 @@ forest.log-enabled: false -注意点:`sa-token.sso.allow-url`为了方便测试配置为`*`,线上生产环境一定要配置为详细URL地址 (之后的章节我们会详细阐述此配置项) +注意点:`sa-token.sso-server.allow-url`为了方便测试配置为`*`,线上生产环境一定要配置为详细URL地址 (之后的章节我们会详细阐述此配置项) ### 4、创建启动类 @@ -252,7 +253,11 @@ forest.log-enabled: false public class SaSsoServerApplication { public static void main(String[] args) { SpringApplication.run(SaSsoServerApplication.class, args); - System.out.println("\n------ Sa-Token-SSO 认证中心启动成功"); + + System.out.println(); + System.out.println("---------------------- Sa-Token SSO 统一认证中心启动成功 ----------------------"); + System.out.println("配置信息:" + SaSsoManager.getServerConfig()); + System.out.println(); } } ``` @@ -261,7 +266,7 @@ public class SaSsoServerApplication { ![sso-server-start](https://oss.dev33.cn/sa-token/doc/sso/sso-server-start.png 's-w-sh') -访问统一授权地址(仅测试SSO Server部署是否成功访问localhost,测试SSO模式一到模式三建议按照对应文档的域名进行配置并访问): +访问统一授权地址(仅测试 SSO-Server 是否部署成功,暂时还不需要点击登录): - [http://localhost:9000/sso/auth](http://localhost:9000/sso/auth) ![sso-server-init-login.png](https://oss.dev33.cn/sa-token/doc/sso/sso-server-init-login.png 's-w-sh') diff --git a/sa-token-doc/sso/sso-type1.md b/sa-token-doc/sso/sso-type1.md index f5e59fa7..2bac82ca 100644 --- a/sa-token-doc/sso/sso-type1.md +++ b/sa-token-doc/sso/sso-type1.md @@ -135,8 +135,8 @@ public class SsoClientController { // SSO-Client端:首页 @RequestMapping("/") public String index() { - String authUrl = SaSsoManager.getConfig().splicingAuthUrl(); - String solUrl = SaSsoManager.getConfig().splicingSloUrl(); + String authUrl = SaSsoManager.getClientConfig().splicingAuthUrl(); + String solUrl = SaSsoManager.getClientConfig().splicingSloUrl(); String str = "

Sa-Token SSO-Client 应用端

" + "

当前会话是否登录:" + StpUtil.isLogin() + "

" + "

登录 " + @@ -166,11 +166,9 @@ server: # Sa-Token 配置 sa-token: # SSO-相关配置 - sso: - # SSO-Server端-单点登录授权地址 - auth-url: http://sso.stp.com:9000/sso/auth - # SSO-Server端-单点注销地址 - slo-url: http://sso.stp.com:9000/sso/signout + sso-client: + # SSO-Server端主机地址 + server-url: http://sso.stp.com:9000 # 配置 Sa-Token 单独使用的Redis连接 (此处需要和SSO-Server端连接同一个Redis) alone-redis: @@ -192,10 +190,8 @@ server.port=9001 ######### Sa-Token 配置 ######### -# SSO-Server端-单点登录授权地址 -sa-token.sso.auth-url=http://sso.stp.com:9000/sso/auth -# SSO-Server端-单点注销地址 -sa-token.sso.slo-url=http://sso.stp.com:9000/sso/signout +# SSO-Server端主机地址 +sa-token.sso-client.server-url=http://sso.stp.com:9000 # 配置 Sa-Token 单独使用的Redis连接 (此处需要和SSO-Server端连接同一个Redis) # Redis数据库索引 @@ -222,7 +218,15 @@ sa-token.alone-redis.timeout=10s public class SaSso1ClientApplication { public static void main(String[] args) { SpringApplication.run(SaSso1ClientApplication.class, args); - System.out.println("\nSa-Token SSO模式一 Client端启动成功"); + + System.out.println(); + System.out.println("---------------------- Sa-Token SSO 模式一 Client 端启动成功 ----------------------"); + System.out.println("配置信息:" + SaSsoManager.getClientConfig()); + System.out.println("测试访问应用端一: http://s1.stp.com:9001"); + System.out.println("测试访问应用端二: http://s2.stp.com:9001"); + System.out.println("测试访问应用端三: http://s3.stp.com:9001"); + System.out.println("测试前需要根据官网文档修改hosts文件,测试账号密码:sa / 123456"); + System.out.println(); } } ``` diff --git a/sa-token-doc/sso/sso-type2.md b/sa-token-doc/sso/sso-type2.md index 4ebe1e87..32d115ea 100644 --- a/sa-token-doc/sso/sso-type2.md +++ b/sa-token-doc/sso/sso-type2.md @@ -64,7 +64,7 @@ sa-token.cookie.domain=stp.com ``` -此为模式一专属配置,现在我们将其注释掉 +此为模式一专属配置,现在我们将其注释掉**(一定要注释掉!)** #### 3.2、创建 SSO-Client 端项目 @@ -151,7 +151,7 @@ public class SsoClientController { */ @RequestMapping("/sso/*") public Object ssoRequest() { - return SaSsoProcessor.instance.clientDister(); + return SaSsoClientProcessor.instance.dister(); } } @@ -170,9 +170,9 @@ server: # sa-token配置 sa-token: # SSO-相关配置 - sso: - # SSO-Server端 统一认证地址 - auth-url: http://sa-sso-server.com:9000/sso/auth + sso-client: + # SSO-Server 端主机地址 + server-url: http://sa-sso-server.com:9000 # 配置Sa-Token单独使用的Redis连接 (此处需要和SSO-Server端连接同一个Redis) alone-redis: @@ -194,7 +194,7 @@ server.port=9001 ######### Sa-Token 配置 ######### # SSO-Server端 统一认证地址 -sa-token.sso.auth-url=http://sa-sso-server.com:9000/sso/auth +sa-token.sso-client.server-url=http://sa-sso-server.com:9000 # 配置 Sa-Token 单独使用的Redis连接 (此处需要和SSO-Server端连接同一个Redis) # Redis数据库索引 @@ -211,7 +211,7 @@ sa-token.alone-redis.timeout=10s -注意点:`sa-token.alone-redis` 的配置需要和SSO-Server端连接同一个Redis(database也要一样) +注意点:`sa-token.alone-redis` 的配置需要和SSO-Server端连接同一个Redis**(database 值也要一样!database 值也要一样!database 值也要一样!重说三!)** #### 3.5、写启动类 ``` java @@ -219,7 +219,15 @@ sa-token.alone-redis.timeout=10s public class SaSso2ClientApplication { public static void main(String[] args) { SpringApplication.run(SaSso2ClientApplication.class, args); - System.out.println("\nSa-Token-SSO Client端启动成功"); + + System.out.println(); + System.out.println("---------------------- Sa-Token SSO 模式二 Client 端启动成功 ----------------------"); + System.out.println("配置信息:" + SaSsoManager.getClientConfig()); + System.out.println("测试访问应用端一: http://sa-sso-client1.com:9001"); + System.out.println("测试访问应用端二: http://sa-sso-client2.com:9001"); + System.out.println("测试访问应用端三: http://sa-sso-client3.com:9001"); + System.out.println("测试前需要根据官网文档修改hosts文件,测试账号密码:sa / 123456"); + System.out.println(); } } ``` @@ -230,6 +238,8 @@ public class SaSso2ClientApplication { (1) 依次启动 `SSO-Server` 与 `SSO-Client`,然后从浏览器访问:[http://sa-sso-client1.com:9001/](http://sa-sso-client1.com:9001/) + + ![sso-client-index.png](https://oss.dev33.cn/sa-token/doc/sso/sso-client-index.png 's-w-sh') (2) 首次打开,提示当前未登录,我们点击 **`登录`** 按钮,页面会被重定向到登录中心 @@ -282,10 +292,121 @@ public class SaSso2ClientApplication { 默认测试密码:`sa / 123456`,其余流程保持不变 --> + + +### 5、无刷单点注销 + +有了单点登录,就必然伴随着单点注销(一处注销,全端下线) + +如果你的所有 client 都是基于 SSO 模式二来对接的,那么单点注销其实很简单: + +``` java +// 在 `sa-token.is-share=true` 的情况下,调用此代码即可单点注销: +StpUtil.logout(); + +// 在 `sa-token.is-share=false` 的情况下,调用此代码即可单点注销: +StpUtil.logout(StpUtil.getLoginId()); +``` + +你可能会比较疑惑,这不就是个普通的会话注销API吗,为什么会有单点注销的效果? + +因为模式二需要各个 sso-client 和 sso-server 连接同一个 redis,即使登录再多的 client,本质上对应的仍是同一个会话,因此可以做到任意一处调用注销,全端一起下线的效果。 + +而如果你的各个 client 架构各不相同,有的是模式二对接,有的是模式三对接,则需要麻烦一点才能做到单点注销。 + +这里的“麻烦”指两处:1、框架内部逻辑麻烦;2、开发者集成麻烦。 + +框架内部的麻烦 sa-token-sso 已经封装完毕,无需过多关注,而开发者的麻烦步骤也不是很多: + + +#### 5.1、增加 pom.xml 配置 + + + +``` xml + + + com.dtflys.forest + forest-spring-boot-starter + 1.5.26 + +``` + +``` gradle +// Http请求工具 +implementation 'com.dtflys.forest:forest-spring-boot-starter:1.5.26' +``` + + +Forest 是一个轻量级 http 请求工具,详情参考:[Forest](https://forest.dtflyx.com/) + +因为我们已经在控制台手动打印 url 请求日志了,所以此处 `forest.log-enabled=false` 关闭 Forest 框架自身的日志打印,这不是必须的,你可以将其打开。 + + +#### 5.2、SSO-Client 端新增配置:API调用秘钥 + +在 `application.yml` 增加: + + + +``` yaml +sa-token: + sign: + # API 接口调用秘钥 + secret-key: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor + +forest: + # 关闭 forest 请求日志打印 + log-enabled: false +``` + +``` properties +# 接口调用秘钥 +sa-token.sign.secret-key=kQwIOrYvnXmSDkwEiFngrKidMcdrgKor +``` + + +注意 secretkey 秘钥需要与SSO认证中心的一致 + +#### 5.3、SSO-Client 配置 http 请求处理器 +``` java +// 配置SSO相关参数 +@Autowired +private void configSso(SaSsoClientConfig ssoClient) { + // 配置Http请求处理器 + ssoClient.sendHttp = url -> { + System.out.println("------ 发起请求:" + url); + String resStr = Forest.get(url).executeAsString(); + System.out.println("------ 请求结果:" + resStr); + return resStr; + }; +} +``` + +#### 5.3、启动测试 +重启项目,依次登录三个 client: +- [http://sa-sso-client1.com:9001/](http://sa-sso-client1.com:9001/) +- [http://sa-sso-client2.com:9001/](http://sa-sso-client2.com:9001/) +- [http://sa-sso-client3.com:9001/](http://sa-sso-client3.com:9001/) + +![sso-type3-client-index.png](https://oss.dev33.cn/sa-token/doc/sso/sso-type3-client-index.png 's-w-sh') + +在任意一个 client 里,点击 **`[注销]`** 按钮,即可单点注销成功(打开另外两个client,刷新一下页面,登录态丢失)。 + + + +![sso-type3-slo-index.png](https://oss.dev33.cn/sa-token/doc/sso/sso-type3-slo-index.png 's-w-sh') + +PS:这里我们为了方便演示,使用的是超链接跳页面的形式,正式项目中使用 Ajax 调用接口即可做到无刷单点登录退出。 + +例如,我们使用 [Apifox 接口测试工具](https://www.apifox.cn/) 可以做到同样的效果: + +![sso-slo-apifox.png](https://oss.dev33.cn/sa-token/doc/sso/sso-slo-apifox.png 's-w-sh') +测试完毕! -### 5、跨 Redis 的单点登录 +### 6、跨 Redis 的单点登录 以上流程解决了跨域模式下的单点登录,但是后端仍然采用了共享Redis来同步会话,如果我们的架构设计中Client端与Server端无法共享Redis,又该怎么完成单点登录? 这就要采用模式三了,且往下看:[SSO模式三:Http请求获取会话](/sso/sso-type3) diff --git a/sa-token-doc/sso/sso-type3.md b/sa-token-doc/sso/sso-type3.md index c339424f..4d9d0a5b 100644 --- a/sa-token-doc/sso/sso-type3.md +++ b/sa-token-doc/sso/sso-type3.md @@ -10,7 +10,7 @@ 1. Client 端无法直连 Redis 校验 ticket,取出账号id。 2. Client 端无法与 Server 端共用一套会话,需要自行维护子会话。 -3. 由于不是一套会话,所以无法“一次注销,全端下线”,需要额外编写代码完成单点注销。 +3. 由于不是一套会话,所以无法“一次注销,全端下线”,需要额外编写代码完成单点注销(其实此处的“额外编写代码”已在SSO模式二“无刷单点注销”部分介绍完毕)。 所以模式三的主要目标:也就是在 模式二的基础上 解决上述 三个难题 @@ -20,76 +20,24 @@ ### 2、在Client 端更改 Ticket 校验方式 -#### 2.1、增加 pom.xml 配置 - - - -``` xml - - - com.dtflys.forest - forest-spring-boot-starter - 1.5.26 - -``` - -``` gradle -// Http请求工具 -implementation 'com.dtflys.forest:forest-spring-boot-starter:1.5.26' -``` - - - -> Forest 是一个轻量级 http 请求工具,详情参考:[Forest](https://forest.dtflyx.com/) - -#### 2.2、配置 http 请求处理器 -在SSO-Client端的 `SsoClientController` 中,新增以下配置 -``` java -// 配置SSO相关参数 -@Autowired -private void configSso(SaSsoConfig sso) { - // ... 其他代码 - - // 配置 Http 请求处理器 - sso.sendHttp = url -> { - System.out.println("------ 发起请求:" + url); - return Forest.get(url).executeAsString(); - }; -} -``` - -#### 2.3、application.yml 新增配置 +在 application.yml 新增配置: ``` yaml sa-token: - sso: + sso-client: # 打开模式三(使用Http请求校验ticket) is-http: true - # SSO-Server端 ticket校验地址 - check-ticket-url: http://sa-sso-server.com:9000/sso/checkTicket - -forest: - # 关闭 forest 请求日志打印 - log-enabled: false ``` ``` properties # 打开模式三(使用Http请求校验ticket) -sa-token.sso.is-http=true -# SSO-Server端 ticket校验地址 -sa-token.sso.check-ticket-url=http://sa-sso-server.com:9000/sso/checkTicket - -# 关闭 forest 请求日志打印 -forest.log-enabled: false +sa-token.sso-client.is-http=true ``` -因为我们已经在控制台手动打印 url 请求日志了,所以此处 `forest.log-enabled=false` 关闭 Forest 框架自身的日志打印,这不是必须的,你可以将其打开。 - -#### 2.4、启动项目测试 重启项目,访问测试: - [http://sa-sso-client1.com:9001/](http://sa-sso-client1.com:9001/) - [http://sa-sso-client2.com:9001/](http://sa-sso-client2.com:9001/) @@ -134,23 +82,7 @@ public SaResult getData(String apiType, String loginId) { #### 3.2、在 Client 端调用此接口查询数据 -首先在 application.yml 中配置接口地址: - - -``` yaml -sa-token: - sso: - # sso-server 端拉取数据地址 - get-data-url: http://sa-sso-server.com:9000/sso/getData -``` - -``` properties -# sso-server 端拉取数据地址 -sa-token.sso.get-data-url=http://sa-sso-server.com:9000/sso/getData -``` - - -然后在 `SsoClientController` 中新增接口 +在 `SsoClientController` 中新增接口 ``` java // 查询我的账号信息 @RequestMapping("/sso/myInfo") @@ -242,28 +174,19 @@ public Object getFansList(Long loginId) { } ``` -**注意:使用此方案时,需要在 client 端配置 `sa-token.sso.server-url` 地址,例如:** -``` yaml -sa-token: - sso: - # sso-server 端主机地址 - server-url: http://sa-sso-server.com:9000 -``` - #### 4.3、访问测试 访问测试:[http://sa-sso-client1.com:9001/sso/myFansList](http://sa-sso-client1.com:9001/sso/myFansList) +### 5、单点注销 -### 5、无刷单点注销 +有关 SSO 单点注销的步骤,在上一章节的“无刷单点注销”部分已讲解完毕(模式二和模式三通用),所以此处就不再赘述了。 -有了单点登录就必然要有单点注销,网上给出的大多数解决方案是将注销请求重定向至SSO-Server中心,逐个通知Client端下线 - -在某些场景下,页面的跳转可能造成不太好的用户体验,Sa-Token-SSO 允许你以 `REST API` 的形式构建接口,做到页面无刷新单点注销。 +此处简单介绍一下 SSO 模式三的单点注销链路过程: 1. Client 端在校验 ticket 时,将注销回调地址发送到 Server 端。 -2. Server 端将此 Client 的注销回调地址存储到 Set 集合。 +2. Server 端将此 Client 的注销回调回调信息存储到 List 集合。 3. Client 端向 Server 端发送单点注销请求。 4. Server 端遍历Set集合,逐个通知 Client 端下线。 5. Server 端注销下线。 @@ -273,57 +196,7 @@ sa-token: -这些逻辑 Sa-Token 内部已经封装完毕,你只需按照文档增加以下配置即可: - -#### 5.1、SSO-Client 端新增配置 - -在 `application.yml` 增加配置:`API调用秘钥` 和 `单点注销接口URL`。 - - - -``` yaml -sa-token: - sso: - # 单点注销地址 - slo-url: http://sa-sso-server.com:9000/sso/signout - sign: - # API 接口调用秘钥 - secret-key: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor -``` - -``` properties -# 单点注销地址 -sa-token.sso.slo-url=http://sa-sso-server.com:9000/sso/signout -# 接口调用秘钥 -sa-token.sign.secret-key=kQwIOrYvnXmSDkwEiFngrKidMcdrgKor -``` - - - -注意 secretkey 秘钥需要与SSO认证中心的一致 - - -#### 5.2、启动测试 -重启项目,访问测试:[http://sa-sso-client1.com:9001/](http://sa-sso-client1.com:9001/), -我们主要的测试点在于 `单点注销`,正常登录即可。 - -![sso-type3-client-index.png](https://oss.dev33.cn/sa-token/doc/sso/sso-type3-client-index.png 's-w-sh') - -点击 **`[注销]`** 按钮,即可单点注销成功。 - - - -![sso-type3-slo-index.png](https://oss.dev33.cn/sa-token/doc/sso/sso-type3-slo-index.png 's-w-sh') - -PS:这里我们为了方便演示,使用的是超链接跳页面的形式,正式项目中使用 Ajax 调用接口即可做到无刷单点登录退出。 - -例如,我们使用 [Apifox 接口测试工具](https://www.apifox.cn/) 可以做到同样的效果: - -![sso-slo-apifox.png](https://oss.dev33.cn/sa-token/doc/sso/sso-slo-apifox.png 's-w-sh') - -测试完毕! - - +这些逻辑 Sa-Token 内部已经封装完毕,你只需按照文档步骤集成即可。 ### 6、后记 diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/processor/SaSsoClientProcessor.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/processor/SaSsoClientProcessor.java index d5b65596..5401d2f8 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/processor/SaSsoClientProcessor.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/processor/SaSsoClientProcessor.java @@ -148,14 +148,10 @@ public class SaSsoClientProcessor { // 获取对象 SaSsoClientConfig cfg = ssoClientTemplate.getClientConfig(); - // ---------- SSO-Client端:单点注销 [模式二] + // 无论登录时选择的是模式二还是模式三 + // 在注销时都应该按照模式三的方法,通过 http 请求调用 sso-server 的单点注销接口来做到全端下线 if(cfg.getIsSlo() && ! cfg.getIsHttp()) { - return ssoLogoutType2(); - } - - // ---------- SSO-Client端:单点注销 [模式三] - if(cfg.getIsSlo() && cfg.getIsHttp()) { - return ssoLogoutType3(); + return ssoLogoutByMode3(); } // 默认返回 @@ -166,7 +162,7 @@ public class SaSsoClientProcessor { * SSO-Client端:单点注销 [模式二] * @return 处理结果 */ - public Object ssoLogoutType2() { + public Object ssoLogoutByMode2() { // 获取对象 SaRequest req = SaHolder.getRequest(); SaResponse res = SaHolder.getResponse(); @@ -185,7 +181,7 @@ public class SaSsoClientProcessor { * SSO-Client端:单点注销 [模式三] * @return 处理结果 */ - public Object ssoLogoutType3() { + public Object ssoLogoutByMode3() { // 获取对象 SaRequest req = SaHolder.getRequest(); SaResponse res = SaHolder.getResponse(); diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/processor/SaSsoServerProcessor.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/processor/SaSsoServerProcessor.java index 2f12fa92..22fea638 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/processor/SaSsoServerProcessor.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/processor/SaSsoServerProcessor.java @@ -223,7 +223,7 @@ public class SaSsoServerProcessor { // step.1 校验签名 if(ssoServerTemplate.getServerConfig().getIsCheckSign()) { - ssoServerTemplate.getSignTemplate(client).checkRequest(req, paramName.loginId); + ssoServerTemplate.getSignTemplate(client).checkRequest(req, paramName.client, paramName.loginId); } else { SaSsoManager.printNoCheckSignWarningByRuntime(); } -- Gitee From c52fd9c86f78a53f43b099f95f38bc534fdf4ae7 Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Wed, 1 May 2024 11:33:17 +0800 Subject: [PATCH 004/172] =?UTF-8?q?=E4=BC=98=E5=8C=96=20sso=20=E7=AB=A0?= =?UTF-8?q?=E8=8A=82=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sa-token-doc/sso/sso-check-domain.md | 4 +- sa-token-doc/sso/sso-custom-api.md | 16 ++--- sa-token-doc/sso/sso-custom-login.md | 6 +- sa-token-doc/sso/sso-h5.md | 6 +- sa-token-doc/sso/sso-home-jump.md | 2 +- sa-token-doc/sso/sso-questions.md | 100 ++++++++++++++------------- 6 files changed, 69 insertions(+), 65 deletions(-) diff --git a/sa-token-doc/sso/sso-check-domain.md b/sa-token-doc/sso/sso-check-domain.md index 96caff48..db82615d 100644 --- a/sa-token-doc/sso/sso-check-domain.md +++ b/sa-token-doc/sso/sso-check-domain.md @@ -3,7 +3,7 @@ --- ### 1、Ticket劫持攻击 -在前面章节的 SSO-Server 示例中,配置项 `sa-token.sso.allow-url=*` 意为配置所有允许的Client端授权地址,不在此配置项中的URL将无法单点登录成功 +在前面章节的 SSO-Server 示例中,配置项 `sa-token.sso-server.allow-url=*` 意为配置所有允许的Client端授权地址,不在此配置项中的URL将无法单点登录成功 为了方便测试,上述代码将其配置为`*`,但是,在生产环境中,此配置项绝对不能配置为 * ,否则会有被 Ticket 劫持的风险 @@ -26,7 +26,7 @@ ``` yaml sa-token: - sso: + sso-server: # 配置允许单点登录的 url allow-url: http://sa-sso-client1.com:9001/sso/login ``` diff --git a/sa-token-doc/sso/sso-custom-api.md b/sa-token-doc/sso/sso-custom-api.md index ea6d933e..fb734e82 100644 --- a/sa-token-doc/sso/sso-custom-api.md +++ b/sa-token-doc/sso/sso-custom-api.md @@ -15,7 +15,7 @@ public class SsoServerController { // SSO-Server端:处理所有SSO相关请求 @RequestMapping("/sso/*") public Object ssoRequest() { - return SaSsoProcessor.instance.serverDister(); + return SaSsoServerProcessor.instance.dister(); } // ... 其它代码 @@ -33,13 +33,13 @@ public class SsoServerController { ``` java // 配置SSO相关参数 @Autowired -private void configSso(SaSsoConfig sso) { +private void configSso(SaSsoServerConfig ssoServer) { // 自定义API地址 - SaSsoUtil.ssoTemplate.apiName.ssoAuth = "/sso/auth2"; + SaSsoServerProcessor.instance.ssoServerTemplate.apiName.ssoAuth = "/sso/auth2"; // ... // SSO 相关配置 - sso.setXxx ... ; + ssoServer.xxx ... ; } ``` @@ -61,25 +61,25 @@ public class SsoServerController { // SSO-Server:统一认证地址 @RequestMapping("/sso/auth") public Object ssoAuth() { - return SaSsoProcessor.instance.ssoAuth(); + return SaSsoServerProcessor.instance.ssoAuth(); } // SSO-Server:RestAPI 登录接口 @RequestMapping("/sso/doLogin") public Object ssoDoLogin() { - return SaSsoProcessor.instance.ssoDoLogin(); + return SaSsoServerProcessor.instance.ssoDoLogin(); } // SSO-Server:校验ticket 获取账号id @RequestMapping("/sso/checkTicket") public Object ssoCheckTicket() { - return SaSsoProcessor.instance.ssoCheckTicket(); + return SaSsoServerProcessor.instance.ssoCheckTicket(); } // SSO-Server:单点注销 @RequestMapping("/sso/signout") public Object ssoSignout() { - return SaSsoProcessor.instance.ssoSignout(); + return SaSsoServerProcessor.instance.ssoSignout(); } // ... 其它方法 diff --git a/sa-token-doc/sso/sso-custom-login.md b/sa-token-doc/sso/sso-custom-login.md index bf5dfbf1..95638d58 100644 --- a/sa-token-doc/sso/sso-custom-login.md +++ b/sa-token-doc/sso/sso-custom-login.md @@ -81,16 +81,16 @@ if(res.code == 401) { ``` java // 配置:未登录时返回的View -sso.setNotLoginView(() -> { +sso.notLoginView = () -> { return new ModelAndView("xxx.html"); -}) +} ``` ### 3、如何自定义登录API的接口地址? 根据需求点选择解决方案: -#### 3.1、如果只是想在 setDoLoginHandle 函数里获取除 name、pwd 以外的参数? +#### 3.1、如果只是想在 doLoginHandle 函数里获取除 name、pwd 以外的参数? ``` java // 在任意代码处获取前端提交的参数 String xxx = SaHolder.getRequest().getParam("xxx"); diff --git a/sa-token-doc/sso/sso-h5.md b/sa-token-doc/sso/sso-h5.md index 6f4f2eb6..22d8d8a3 100644 --- a/sa-token-doc/sso/sso-h5.md +++ b/sa-token-doc/sso/sso-h5.md @@ -30,7 +30,7 @@ public class H5Controller { // 根据ticket进行登录 @RequestMapping("/sso/doLoginByTicket") public SaResult doLoginByTicket(String ticket) { - Object loginId = SaSsoProcessor.instance.checkTicket(ticket, "/sso/doLoginByTicket"); + Object loginId = SaSsoClientProcessor.instance.checkTicketByMode2Or3(ticket, "/sso/doLoginByTicket"); if(loginId != null) { StpUtil.login(loginId); return SaResult.data(StpUtil.getTokenValue()); @@ -122,14 +122,14 @@ public class H5Controller { ``` yaml sa-token: - sso: + sso-client: # SSO-Server端 统一认证地址 auth-url: http://127.0.0.1:8848/sa-token-demo-sso-server-h5/sso-auth.html ``` ``` properties # SSO-Server端 统一认证地址 -sa-token.sso.auth-url=http://127.0.0.1:8848/sa-token-demo-sso-server-h5/sso-auth.html +sa-token.sso-client.auth-url=http://127.0.0.1:8848/sa-token-demo-sso-server-h5/sso-auth.html ``` diff --git a/sa-token-doc/sso/sso-home-jump.md b/sa-token-doc/sso/sso-home-jump.md index a5a8f8d3..a8c6e405 100644 --- a/sa-token-doc/sso/sso-home-jump.md +++ b/sa-token-doc/sso/sso-home-jump.md @@ -75,7 +75,7 @@ public Object ssoRequest() { if(req.isPath("/sso/auth") && req.hasParam("redirect") == false && StpUtil.isLogin()) { return SaHolder.getResponse().redirect("/home"); } - return SaSsoProcessor.instance.serverDister(); + return SaSsoServerProcessor.instance.serverDister(); } ``` diff --git a/sa-token-doc/sso/sso-questions.md b/sa-token-doc/sso/sso-questions.md index d3ef9aa9..cbd73f4a 100644 --- a/sa-token-doc/sso/sso-questions.md +++ b/sa-token-doc/sso/sso-questions.md @@ -212,19 +212,23 @@ public class SsoController { // 处理 SSO-Server 端所有请求 @RequestMapping({"/sso/auth", "/sso/doLogin", "/sso/checkTicket", "/sso/signout"}) public Object ssoServerRequest() { - return SaSsoProcessor.instance.serverDister(); + return SaSsoServerProcessor.instance.dister(); } // 处理 SSO-Client 端所有请求 @RequestMapping({"/sso/login", "/sso/logout", "/sso/logoutCall"}) public Object ssoClientRequest() { - return SaSsoProcessor.instance.clientDister(); + return SaSsoClientProcessor.instance.dister(); } // 配置SSO相关参数 @Autowired - private void configSso(SaSsoConfig sso) { - // SSO配置代码,参考文档前几章 ... + private void configSsoServer(SaSsoServerConfig ssoServer) { + // SSO Server 配置代码,参考文档前几章 ... + } + @Autowired + private void configSsoClient(SaSsoClientConfig ssoClient) { + // SSO Client 配置代码,参考文档前几章 ... } } @@ -248,58 +252,58 @@ public class SsoController { @RestController public class SsoUserServerController { - /** - * 新建一个 SaSsoProcessor 请求处理器 - */ - public static SaSsoProcessor ssoUserProcessor = new SaSsoProcessor(); - static { - // 自定义一个 SaSsoTemplate 对象 - SaSsoTemplate ssoUserTemplate = new SaSsoTemplate() { - // 使用的会话对象 是自定义的 StpUserUtil - @Override - public StpLogic getStpLogic() { - return StpUserUtil.stpLogic; - } + /** + * 新建一个 SaSsoServerProcessor 请求处理器 + */ + public static SaSsoServerProcessor ssoUserServerProcessor = new SaSsoServerProcessor(); + static { + // 自定义一个 SaSsoTemplate 对象 + SaSsoServerTemplate ssoUserTemplate = new SaSsoServerTemplate() { + // 使用的会话对象 是自定义的 StpUserUtil + @Override + public StpLogic getStpLogic() { + return StpUserUtil.stpLogic; + } // 使用自定义的签名秘钥 SaSignConfig signConfig = new SaSignConfig().setSecretKey("xxxx-新的秘钥-xxxx"); SaSignTemplate userSignTemplate = new SaSignTemplate().setSignConfig(signConfig); @Override - public SaSignTemplate getSignTemplate() { + public SaSignTemplate getSignTemplate(String client) { return userSignTemplate; } - }; - // 让这个SSO请求处理器,使用的路由前缀是 /sso-user,而不是原先的 /sso - ssoUserTemplate.apiName.replacePrefix("/sso-user"); - - // 给这个 SSO 请求处理器使用自定义的 SaSsoTemplate 对象 - ssoUserProcessor.ssoTemplate = ssoUserTemplate; - } + }; + // 让这个SSO请求处理器,使用的路由前缀是 /sso-user,而不是原先的 /sso + ssoUserTemplate.apiName.replacePrefix("/sso-user"); - /* - * 第二套 sso-server 服务:处理所有SSO相关请求 - * http://{host}:{port}/sso-user/auth -- 单点登录授权地址,接受参数:redirect=授权重定向地址 - * http://{host}:{port}/sso-user/doLogin -- 账号密码登录接口,接受参数:name、pwd - * http://{host}:{port}/sso-user/checkTicket -- Ticket校验接口(isHttp=true时打开),接受参数:ticket=ticket码、ssoLogoutCall=单点注销回调地址 [可选] - * http://{host}:{port}/sso-user/signout -- 单点注销地址(isSlo=true时打开),接受参数:loginId=账号id、secretkey=接口调用秘钥 - */ - @RequestMapping("/sso-user/*") - public Object ssoUserRequest() { - return ssoUserProcessor.serverDister(); - } + // 给这个 SSO 请求处理器使用自定义的 SaSsoTemplate 对象 + ssoUserServerProcessor.ssoServerTemplate = ssoUserTemplate; + } + + /* + * 第二套 sso-server 服务:处理所有SSO相关请求 + * http://{host}:{port}/sso-user/auth -- 单点登录授权地址,接受参数:redirect=授权重定向地址 + * http://{host}:{port}/sso-user/doLogin -- 账号密码登录接口,接受参数:name、pwd + * http://{host}:{port}/sso-user/checkTicket -- Ticket校验接口(isHttp=true时打开),接受参数:ticket=ticket码、ssoLogoutCall=单点注销回调地址 [可选] + * http://{host}:{port}/sso-user/signout -- 单点注销地址(isSlo=true时打开),接受参数:loginId=账号id、secretkey=接口调用秘钥 + */ + @RequestMapping("/sso-user/*") + public Object ssoUserRequest() { + return ssoUserServerProcessor.dister(); + } + + // 自定义 doLogin 方法 */ + // 注意点: + // 1、第2套 sso-server 对应的 RestApi 登录接口也应该更换为 /sso-user/doLogin,而不是原先的 /sso/doLogin + // 2、在这里,登录函数要使用自定义的 StpUserUtil.login(),而不是原先的 StpUtil.login() + @RequestMapping("/sso-user/doLogin") + public Object ssoUserRequest(String name, String pwd) { + if("sa".equals(name) && "123456".equals(pwd)) { + StpUserUtil.login(10001); + return SaResult.ok("登录成功!").setData(StpUserUtil.getTokenValue()); + } + return SaResult.error("登录失败!"); + } - // 自定义 doLogin 方法 */ - // 注意点: - // 1、第2套 sso-server 对应的 RestApi 登录接口也应该更换为 /sso-user/doLogin,而不是原先的 /sso/doLogin - // 2、在这里,登录函数要使用自定义的 StpUserUtil.login(),而不是原先的 StpUtil.login() - @RequestMapping("/sso-user/doLogin") - public Object ssoUserRequest(String name, String pwd) { - if("sa".equals(name) && "123456".equals(pwd)) { - StpUserUtil.login(10001); - return SaResult.ok("登录成功!").setData(StpUserUtil.getTokenValue()); - } - return SaResult.error("登录失败!"); - } - } ``` -- Gitee From 8d6b648d4bf1aad38a47620bc5451ba399f46241 Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Thu, 2 May 2024 07:17:11 +0800 Subject: [PATCH 005/172] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=B8=8D=E5=90=8C=20?= =?UTF-8?q?SSO=20Client=20=E9=85=8D=E7=BD=AE=E4=B8=8D=E5=90=8C=E7=A7=98?= =?UTF-8?q?=E9=92=A5=E7=9A=84=E6=96=B9=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cn/dev33/satoken/config/SaSignConfig.java | 11 ++ .../cn/dev33/satoken/sign/SaSignTemplate.java | 22 +++- .../src/main/resources/application.yml | 2 +- .../src/main/resources/application.yml | 2 +- .../src/main/resources/application.yml | 2 +- sa-token-doc/_sidebar.md | 13 +- sa-token-doc/sso/sso-apidoc.md | 6 +- sa-token-doc/sso/sso-diff-key.md | 119 ++++++++++++++++++ sa-token-doc/sso/sso-type2.md | 3 + .../sso/processor/SaSsoClientProcessor.java | 2 +- 10 files changed, 169 insertions(+), 13 deletions(-) create mode 100644 sa-token-doc/sso/sso-diff-key.md diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/config/SaSignConfig.java b/sa-token-core/src/main/java/cn/dev33/satoken/config/SaSignConfig.java index 1e5cd72d..995ab523 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/config/SaSignConfig.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/config/SaSignConfig.java @@ -37,6 +37,17 @@ public class SaSignConfig { private long timestampDisparity = 1000 * 60 * 15; + public SaSignConfig() { + } + + /** + * 构造函数 + * @param secretKey 秘钥 + */ + public SaSignConfig(String secretKey) { + this.secretKey = secretKey; + } + /** * 获取 API 调用签名秘钥 * diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/sign/SaSignTemplate.java b/sa-token-core/src/main/java/cn/dev33/satoken/sign/SaSignTemplate.java index 1076511d..4ea4f9d4 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/sign/SaSignTemplate.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/sign/SaSignTemplate.java @@ -26,6 +26,8 @@ import cn.dev33.satoken.util.SaFoxUtil; import java.util.Map; import java.util.TreeMap; +import static cn.dev33.satoken.SaManager.log; + /** * API 参数签名算法,在跨系统接口调用时防参数篡改、防重放攻击。 * @@ -42,6 +44,17 @@ import java.util.TreeMap; */ public class SaSignTemplate { + public SaSignTemplate() { + } + + /** + * 构造函数 + * @param signConfig 签名参数配置对象 + */ + public SaSignTemplate(SaSignConfig signConfig) { + this.signConfig = signConfig; + } + // ----------- 签名配置 SaSignConfig signConfig; @@ -160,7 +173,14 @@ public class SaSignTemplate { // 计算签名 String paramsStr = joinParamsDictSort(paramsMap); String fullStr = paramsStr + "&" + key + "=" + secretKey; - return abstractStr(fullStr); + String signStr = abstractStr(fullStr); + + // 输入日志,方便调试 + log.debug("fullStr:{}", fullStr); + log.debug("signStr:{}", signStr); + + // 返回 + return signStr; } /** diff --git a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-server/src/main/resources/application.yml b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-server/src/main/resources/application.yml index 04395bf6..901f2838 100644 --- a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-server/src/main/resources/application.yml +++ b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-server/src/main/resources/application.yml @@ -3,7 +3,7 @@ server: port: 9000 # Sa-Token 配置 -sa-token: +sa-token: # ------- SSO-模式一相关配置 (非模式一不需要配置) # cookie: # 配置 Cookie 作用域 diff --git a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-test2/src/main/resources/application.yml b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-test2/src/main/resources/application.yml index ebec4dde..f829c1fe 100644 --- a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-test2/src/main/resources/application.yml +++ b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-test2/src/main/resources/application.yml @@ -3,7 +3,7 @@ server: port: 9032 # sa-token配置 -sa-token: +sa-token: # sso-client 相关配置 sso-client: # client 标识 diff --git a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client/src/main/resources/application.yml b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client/src/main/resources/application.yml index ef48e853..b5426f8f 100644 --- a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client/src/main/resources/application.yml +++ b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client/src/main/resources/application.yml @@ -3,7 +3,7 @@ server: port: 9003 # sa-token配置 -sa-token: +sa-token: # sso-client 相关配置 sso-client: # client 标识 diff --git a/sa-token-doc/_sidebar.md b/sa-token-doc/_sidebar.md index 9a575347..c017ef18 100644 --- a/sa-token-doc/_sidebar.md +++ b/sa-token-doc/_sidebar.md @@ -41,12 +41,13 @@ - [SSO模式一 共享Cookie同步会话](/sso/sso-type1) - [SSO模式二 URL重定向传播会话](/sso/sso-type2) - [SSO模式三 Http请求获取会话](/sso/sso-type3) - - [SSO整合:配置域名校验](/sso/sso-check-domain) - - [SSO整合:定制化登录页面](/sso/sso-custom-login) - - [SSO整合:自定义API路由](/sso/sso-custom-api) - - [SSO扩展:前后端分离下的整合方案](/sso/sso-h5) - - [SSO扩展:平台中心跳转模式](/sso/sso-home-jump) - - [SSO扩展:常见问题总结](/sso/sso-questions) + - [配置域名校验](/sso/sso-check-domain) + - [定制化登录页面](/sso/sso-custom-login) + - [自定义API路由](/sso/sso-custom-api) + - [前后端分离下的整合方案](/sso/sso-h5) + - [平台中心跳转模式](/sso/sso-home-jump) + - [不同 Client 不同秘钥](/sso/sso-diff-key) + - [常见问题总结](/sso/sso-questions) - [Sa-Sso-Pro:单点登录商业版](/sso/sso-pro) - **OAuth2.0** diff --git a/sa-token-doc/sso/sso-apidoc.md b/sa-token-doc/sso/sso-apidoc.md index 14d29257..2d03713a 100644 --- a/sa-token-doc/sso/sso-apidoc.md +++ b/sa-token-doc/sso/sso-apidoc.md @@ -201,8 +201,10 @@ http://{host}:{port}/sso/logoutCall | loginId | 是 | 要注销的账号 id | | timestamp | 是 | 当前时间戳,13位 | | nonce | 是 | 随机字符串 | -| client | 否 | 客户端标识,如果你在登录时向 sso-server 端传递了 client 值,那么在此处 sso-server 也会给你回传过来,否则此参数无值 | -| sign | 是 | 签名,生成算法:`md5( loginId={账号id}&nonce={随机字符串}×tamp={13位时间戳}&key={secretkey秘钥} )` 如果 client 参数有值,则client也要参与签名,放在 loginId 参数签名(字典顺序)| +| sign | 是 | 签名,生成算法:`md5( loginId={账号id}&nonce={随机字符串}×tamp={13位时间戳}&key={secretkey秘钥} )` | +| client | 否 | 客户端标识,如果你在登录时向 sso-server 端传递了 client 值,那么在此处 sso-server 也会给你回传过来,否则此参数无值。如果此参数有值,则此参数也要参与签名,放在 loginId 参数前面(字典顺序) | +| autoLogout | 否 | 是否为“登录client超过最大数量”引起的自动注销(true=超限系统自动注销,false=用户主动发起注销)。如果此参数有值,则此参数也要参与签名,放在 client 参数前面(字典顺序) | + 返回数据: diff --git a/sa-token-doc/sso/sso-diff-key.md b/sa-token-doc/sso/sso-diff-key.md new file mode 100644 index 00000000..17fb0344 --- /dev/null +++ b/sa-token-doc/sso/sso-diff-key.md @@ -0,0 +1,119 @@ +# 不同 SSO Client 配置不同秘钥 + +在校验 ticket、单点注销等操作发起的 http 调用时,需要配置秘钥参数,像这样: + + + +``` yaml +sa-token: + sign: + # API 接口调用秘钥 + secret-key: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor +``` + +``` properties +# 接口调用秘钥 +sa-token.sign.secret-key=kQwIOrYvnXmSDkwEiFngrKidMcdrgKor +``` + + + +如果 SSO Client 端和 SSO Server 端配置的秘钥不同,则无法调通请求,显示无效签名: +``` js +{ + "code": 500, + "msg": "无效签名:9f1b453817bfeac56d2f772a66c01eb2", + "data": null +} +``` + +如果你有多个 SSO Client,你可能想让每个应用配置不同的秘钥,让它们彼此之间不能互相“冒充”,怎么做呢? + +### 1、首先在 SSO Client 端,你需要配置上不同的 Client 标识参数: + +例如在 client1 我们配置上: + + + +``` yaml +sa-token: + sso-client: + # 当前 client 标识 + client: sso-client1 + # ... + sign: + # sso-client1 使用的秘钥 + secret-key: secret-key-xxxx-1 +``` + +``` properties +# 当前 client 标识 +sa-token.sso-client.client=sso-client1 + +# sso-client1 使用的秘钥 +sa-token.sign.secret-key=secret-key-xxxx-1 +``` + + + +在 client2 我们配置上: + + + +``` yaml +sa-token: + sso-client: + # 当前 client 标识 + client: sso-client2 + # ... + sign: + # sso-client2 使用的秘钥 + secret-key: secret-key-xxxx-2 +``` + +``` properties +# 当前 client 标识 +sa-token.sso-client.client=sso-client2 + +# sso-client2 使用的秘钥 +sa-token.sign.secret-key=secret-key-xxxx-2 +``` + + + +### 2、然后在 SSO Server 端,重写获取秘钥的函数 + +在 SSO Server 端新建 `CustomSaSsoServerTemplate.java`,继承 `SaSsoServerTemplate`,重写其 `getSignTemplate` 函数: + +``` java +/** + * 自定义 SaSsoServerTemplate 子类 + */ +@Component +public class CustomSaSsoServerTemplate extends SaSsoServerTemplate { + + // 存储所有 client 的秘钥 + static Map signMap = new HashMap<>(); + static { + signMap.put("sso-client1", new SaSignTemplate(new SaSignConfig("secret-key-xxxx-1"))); + signMap.put("sso-client2", new SaSignTemplate(new SaSignConfig("secret-key-xxxx-2"))); + signMap.put("sso-client3", new SaSignTemplate(new SaSignConfig("secret-key-xxxx-3"))); + // ... + } + + @Override + public SaSignTemplate getSignTemplate(String client) { + // 先从自定义的 signMap 中获取 + SaSignTemplate saSignTemplate = signMap.get(client); + if (saSignTemplate != null) { + return saSignTemplate; + } + // 找不到就返回全局默认的 SaSignTemplate + return SaManager.getSaSignTemplate(); + } +} +``` + +至此完成,其它代码一切照旧。 + + diff --git a/sa-token-doc/sso/sso-type2.md b/sa-token-doc/sso/sso-type2.md index 32d115ea..b19cb153 100644 --- a/sa-token-doc/sso/sso-type2.md +++ b/sa-token-doc/sso/sso-type2.md @@ -363,6 +363,9 @@ forest: ``` properties # 接口调用秘钥 sa-token.sign.secret-key=kQwIOrYvnXmSDkwEiFngrKidMcdrgKor + +# 关闭 forest 请求日志打印 +forest.log-enabled=false ``` diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/processor/SaSsoClientProcessor.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/processor/SaSsoClientProcessor.java index 5401d2f8..6665ff9e 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/processor/SaSsoClientProcessor.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/processor/SaSsoClientProcessor.java @@ -150,7 +150,7 @@ public class SaSsoClientProcessor { // 无论登录时选择的是模式二还是模式三 // 在注销时都应该按照模式三的方法,通过 http 请求调用 sso-server 的单点注销接口来做到全端下线 - if(cfg.getIsSlo() && ! cfg.getIsHttp()) { + if(cfg.getIsSlo()) { return ssoLogoutByMode3(); } -- Gitee From 27618484dc4743d6579aebcc2df1b8625fec4985 Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Thu, 2 May 2024 07:42:20 +0800 Subject: [PATCH 006/172] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20sso=20nosdk=20demo?= =?UTF-8?q?=20=E4=B8=8D=E6=AD=A3=E7=A1=AE=E4=B9=8B=E5=A4=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ....java => SaSsoClientNoSdkApplication.java} | 14 ++++- .../java/com/pj/sso/SsoClientController.java | 31 ++++++---- .../main/java/com/pj/sso/SsoRequestUtil.java | 61 +++++++++++-------- .../src/main/resources/application.yml | 2 +- 4 files changed, 67 insertions(+), 41 deletions(-) rename sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-nosdk/src/main/java/com/pj/{SaSsoClientApplication.java => SaSsoClientNoSdkApplication.java} (31%) diff --git a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-nosdk/src/main/java/com/pj/SaSsoClientApplication.java b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-nosdk/src/main/java/com/pj/SaSsoClientNoSdkApplication.java similarity index 31% rename from sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-nosdk/src/main/java/com/pj/SaSsoClientApplication.java rename to sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-nosdk/src/main/java/com/pj/SaSsoClientNoSdkApplication.java index 5a6393e2..1bd993de 100644 --- a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-nosdk/src/main/java/com/pj/SaSsoClientApplication.java +++ b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-nosdk/src/main/java/com/pj/SaSsoClientNoSdkApplication.java @@ -4,11 +4,19 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication -public class SaSsoClientApplication { +public class SaSsoClientNoSdkApplication { public static void main(String[] args) { - SpringApplication.run(SaSsoClientApplication.class, args); + SpringApplication.run(SaSsoClientNoSdkApplication.class, args); System.out.println("\nSa-Token SSO模式三 Client端 (无SDK版本) 启动成功"); + + System.out.println(); + System.out.println("---------------------- Sa-Token SSO 模式三 NoSdk 模式 demo 启动成功 ----------------------"); + System.out.println("测试访问应用端一: http://sa-sso-client1.com:9004"); + System.out.println("测试访问应用端二: http://sa-sso-client2.com:9004"); + System.out.println("测试访问应用端三: http://sa-sso-client3.com:9004"); + System.out.println("测试前需要根据官网文档修改hosts文件,测试账号密码:sa / 123456"); + System.out.println(); } - + } \ No newline at end of file diff --git a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-nosdk/src/main/java/com/pj/sso/SsoClientController.java b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-nosdk/src/main/java/com/pj/sso/SsoClientController.java index 243a0550..15098acc 100644 --- a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-nosdk/src/main/java/com/pj/sso/SsoClientController.java +++ b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-nosdk/src/main/java/com/pj/sso/SsoClientController.java @@ -29,7 +29,7 @@ public class SsoClientController { "

当前会话登录账号:" + session.getAttribute("userId") + "

" + "

登录" + " 注销" + - " 获取资料

"; + " 获取资料

"; return str; } @@ -62,8 +62,16 @@ public class SsoClientController { ssoLogoutCall = request.getRequestURL().toString().replace("/sso/login", "/sso/logoutCall"); } - // 校验 ticket - String checkUrl = SsoRequestUtil.checkTicketUrl + "?ticket=" + ticket + "&ssoLogoutCall=" + ssoLogoutCall; + // 校验 ticket + String timestamp = String.valueOf(System.currentTimeMillis()); // 时间戳 + String nonce = SsoRequestUtil.getRandomString(20); // 随机字符串 + String sign = SsoRequestUtil.getSignByTicket(ticket, ssoLogoutCall, timestamp, nonce); // 参数签名 + String checkUrl = SsoRequestUtil.checkTicketUrl + + "?timestamp=" + timestamp + + "&nonce=" + nonce + + "&sign=" + sign + + "&ticket=" + ticket + + "&ssoLogoutCall=" + ssoLogoutCall; AjaxJson result = SsoRequestUtil.request(checkUrl); // 200 代表校验成功 @@ -97,7 +105,7 @@ public class SsoClientController { Object loginId = session.getAttribute("userId"); // 账号id String timestamp = String.valueOf(System.currentTimeMillis()); // 时间戳 String nonce = SsoRequestUtil.getRandomString(20); // 随机字符串 - String sign = SsoRequestUtil.getSign(loginId, timestamp, nonce, SsoRequestUtil.secretkey); // 参数签名 + String sign = SsoRequestUtil.getSign(loginId, timestamp, nonce); // 参数签名 String url = SsoRequestUtil.sloUrl + "?loginId=" + loginId + @@ -123,12 +131,13 @@ public class SsoClientController { // SSO-Client端:单点注销回调地址 @RequestMapping("/sso/logoutCall") - public Object ssoLogoutCall(String loginId, String timestamp, String nonce, String sign) { + public Object ssoLogoutCall(String loginId, String autoLogout, String timestamp, String nonce, String sign) { // 校验签名 - String calcSign = SsoRequestUtil.getSign(loginId, timestamp, nonce, SsoRequestUtil.secretkey); + String calcSign = SsoRequestUtil.getSignByLogoutCall(loginId, autoLogout, timestamp, nonce); if(calcSign.equals(sign) == false) { - return AjaxJson.getError("无效签名,拒绝应答"); + System.out.println("无效签名,拒绝应答:" + sign); + return AjaxJson.getError("无效签名,拒绝应答" + sign); } // 注销这个账号id @@ -143,8 +152,8 @@ public class SsoClientController { } // 查询我的账号信息 (调用此接口的前提是 sso-server 端开放了 /sso/userinfo 路由) - @RequestMapping("/sso/myinfo") - public Object myinfo(HttpSession session) { + @RequestMapping("/sso/myInfo") + public Object myInfo(HttpSession session) { // 如果尚未登录 if(session.getAttribute("userId") == null) { return "尚未登录,无法获取"; @@ -154,9 +163,9 @@ public class SsoClientController { Object loginId = session.getAttribute("userId"); // 账号id String timestamp = String.valueOf(System.currentTimeMillis()); // 时间戳 String nonce = SsoRequestUtil.getRandomString(20); // 随机字符串 - String sign = SsoRequestUtil.getSign(loginId, timestamp, nonce, SsoRequestUtil.secretkey); // 参数签名 + String sign = SsoRequestUtil.getSign(loginId, timestamp, nonce); // 参数签名 - String url = SsoRequestUtil.userinfoUrl + + String url = SsoRequestUtil.getDataUrl + "?loginId=" + loginId + "×tamp=" + timestamp + "&nonce=" + nonce + diff --git a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-nosdk/src/main/java/com/pj/sso/SsoRequestUtil.java b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-nosdk/src/main/java/com/pj/sso/SsoRequestUtil.java index 3ac8f181..0cbfa27d 100644 --- a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-nosdk/src/main/java/com/pj/sso/SsoRequestUtil.java +++ b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-nosdk/src/main/java/com/pj/sso/SsoRequestUtil.java @@ -1,14 +1,14 @@ package com.pj.sso; +import com.dtflys.forest.Forest; +import com.pj.sso.util.AjaxJson; + import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.security.MessageDigest; import java.util.Map; import java.util.Random; -import com.dtflys.forest.Forest; -import com.pj.sso.util.AjaxJson; - /** * 封装一些 sso 共用方法 * @@ -18,39 +18,40 @@ import com.pj.sso.util.AjaxJson; public class SsoRequestUtil { /** - * SSO-Server端 统一认证地址 + * SSO-Server端主机地址 */ - public static String authUrl = "http://sa-sso-server.com:9000/sso/auth"; - + public static String serverUrl = "http://sa-sso-server.com:9000"; + /** - * 使用 Http 请求校验ticket + * SSO-Server端 统一认证地址 */ -// public static boolean isHttp = true; - + public static String authUrl = serverUrl + "/sso/auth"; + /** * SSO-Server端 ticket校验地址 */ - public static String checkTicketUrl = "http://sa-sso-server.com:9000/sso/checkTicket"; - + public static String checkTicketUrl = serverUrl + "/sso/checkTicket"; + /** - * 打开单点注销功能 + * 单点注销地址 */ - public static boolean isSlo = true; - + public static String sloUrl = serverUrl + "/sso/signout"; + /** - * 单点注销地址 + * SSO-Server端 查询userinfo地址 */ - public static String sloUrl = "http://sa-sso-server.com:9000/sso/signout"; - + public static String getDataUrl = serverUrl + "/sso/getData"; + /** - * 接口调用秘钥 + * 打开单点注销功能 */ - public static String secretkey = "kQwIOrYvnXmSDkwEiFngrKidMcdrgKor"; - + public static boolean isSlo = true; + /** - * SSO-Server端 查询userinfo地址 + * 接口调用秘钥 */ - public static String userinfoUrl = "http://sa-sso-server.com:9000/sso/userinfo"; + public static String secretKey = "kQwIOrYvnXmSDkwEiFngrKidMcdrgKor"; + // -------------------------- 工具方法 @@ -69,12 +70,20 @@ public class SsoRequestUtil { * 根据参数计算签名 * @param loginId 账号id * @param timestamp 当前时间戳,13位 - * @param nonce 随机字符串 - * @param secretkey 账号id + * @param nonce 随机字符串 * @return 签名 */ - public static String getSign(Object loginId, String timestamp, String nonce, String secretkey) { - return md5("loginId=" + loginId + "&nonce=" + nonce + "×tamp=" + timestamp + "&key=" + secretkey); + public static String getSign(Object loginId, String timestamp, String nonce) { + return md5("loginId=" + loginId + "&nonce=" + nonce + "×tamp=" + timestamp + "&key=" + secretKey); + } + // 单点注销回调时构建签名 + public static String getSignByLogoutCall(Object loginId, String autoLogout, String timestamp, String nonce) { + System.out.println("autoLogout=" + autoLogout + "loginId=" + loginId + "&nonce=" + nonce + "×tamp=" + timestamp + "&key=" + secretKey); + return md5("autoLogout=" + autoLogout + "&loginId=" + loginId + "&nonce=" + nonce + "×tamp=" + timestamp + "&key=" + secretKey); + } + // 校验ticket 时构建签名 + public static String getSignByTicket(String ticket, String ssoLogoutCall, String timestamp, String nonce) { + return md5("nonce=" + nonce + "&ssoLogoutCall=" + ssoLogoutCall + "&ticket=" + ticket + "×tamp=" + timestamp + "&key=" + secretKey); } /** diff --git a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-nosdk/src/main/resources/application.yml b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-nosdk/src/main/resources/application.yml index bb9f457b..982a1264 100644 --- a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-nosdk/src/main/resources/application.yml +++ b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-nosdk/src/main/resources/application.yml @@ -1,6 +1,6 @@ # 端口 server: - port: 9001 + port: 9004 forest: # 打开/关闭Forest请求日志(默认为 true) -- Gitee From 7ee27add84ee992c3716a1f48f0a9bba81c920f0 Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Thu, 2 May 2024 07:42:54 +0800 Subject: [PATCH 007/172] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20sso=20nosdk=20demo?= =?UTF-8?q?=20=E4=B8=8D=E6=AD=A3=E7=A1=AE=E4=B9=8B=E5=A4=84..?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/pj/sso/SsoRequestUtil.java | 1 - 1 file changed, 1 deletion(-) diff --git a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-nosdk/src/main/java/com/pj/sso/SsoRequestUtil.java b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-nosdk/src/main/java/com/pj/sso/SsoRequestUtil.java index 0cbfa27d..0fc8c3ee 100644 --- a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-nosdk/src/main/java/com/pj/sso/SsoRequestUtil.java +++ b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-nosdk/src/main/java/com/pj/sso/SsoRequestUtil.java @@ -78,7 +78,6 @@ public class SsoRequestUtil { } // 单点注销回调时构建签名 public static String getSignByLogoutCall(Object loginId, String autoLogout, String timestamp, String nonce) { - System.out.println("autoLogout=" + autoLogout + "loginId=" + loginId + "&nonce=" + nonce + "×tamp=" + timestamp + "&key=" + secretKey); return md5("autoLogout=" + autoLogout + "&loginId=" + loginId + "&nonce=" + nonce + "×tamp=" + timestamp + "&key=" + secretKey); } // 校验ticket 时构建签名 -- Gitee From 4c216069aaf462a4961019dc4b41887c7b3fd247 Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Thu, 2 May 2024 10:36:54 +0800 Subject: [PATCH 008/172] =?UTF-8?q?=E5=8C=BF=E5=90=8D=20client=20=E5=B0=86?= =?UTF-8?q?=E4=B8=8D=E5=86=8D=E8=83=BD=E8=A7=A3=E6=9E=90=E5=87=BA=E6=89=80?= =?UTF-8?q?=E6=9C=89=E5=BA=94=E7=94=A8=E7=9A=84=20client?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../satoken/sso/error/SaSsoErrorCode.java | 3 +++ .../sso/processor/SaSsoServerProcessor.java | 25 +++++++++++++++---- .../sso/template/SaSsoServerTemplate.java | 17 +++++++++---- .../dev33/satoken/sso/util/SaSsoConsts.java | 5 ++++ 4 files changed, 40 insertions(+), 10 deletions(-) diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/error/SaSsoErrorCode.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/error/SaSsoErrorCode.java index 9f748ade..c27bfdff 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/error/SaSsoErrorCode.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/error/SaSsoErrorCode.java @@ -59,4 +59,7 @@ public interface SaSsoErrorCode { /** 当前缺少配置 server-url 地址 */ int CODE_30012 = 30012; + /** 提供的 client 参数值无效 */ + int CODE_30013 = 30013; + } diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/processor/SaSsoServerProcessor.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/processor/SaSsoServerProcessor.java index 22fea638..c99e5f5e 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/processor/SaSsoServerProcessor.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/processor/SaSsoServerProcessor.java @@ -20,6 +20,8 @@ import cn.dev33.satoken.context.model.SaRequest; import cn.dev33.satoken.context.model.SaResponse; import cn.dev33.satoken.sso.SaSsoManager; import cn.dev33.satoken.sso.config.SaSsoServerConfig; +import cn.dev33.satoken.sso.error.SaSsoErrorCode; +import cn.dev33.satoken.sso.exception.SaSsoException; import cn.dev33.satoken.sso.name.ApiName; import cn.dev33.satoken.sso.name.ParamName; import cn.dev33.satoken.sso.template.SaSsoServerTemplate; @@ -112,8 +114,16 @@ public class SaSsoServerProcessor { return res.redirect(redirect); } else { // 方式2:带着ticket参数重定向回Client端 (mode=ticket) + + // 校验提供的client是否为非法字符 + String client = req.getParam(paramName.client); + if(SaSsoConsts.CLIENT_WILDCARD.equals(client)) { + throw new SaSsoException("无效 client 标识:" + client).setCode(SaSsoErrorCode.CODE_30013); + } + + // 开始重定向 String redirectUrl = ssoServerTemplate.buildRedirectUrl( - stpLogic.getLoginId(), req.getParam(paramName.client), req.getParam(paramName.redirect)); + stpLogic.getLoginId(), client, req.getParam(paramName.redirect)); return res.redirect(redirectUrl); } } @@ -145,7 +155,12 @@ public class SaSsoServerProcessor { String ticket = req.getParamNotNull(paramName.ticket); String sloCallback = req.getParam(paramName.ssoLogoutCall); - // 2、校验签名 + // 2、校验提供的client是否为非法字符 + if(SaSsoConsts.CLIENT_WILDCARD.equals(client)) { + return SaResult.error("无效 client 标识:" + client); + } + + // 3、校验签名 if(ssoServerTemplate.getServerConfig().getIsCheckSign()) { ssoServerTemplate.getSignTemplate(client).checkRequest(req, paramName.client, paramName.ticket, paramName.ssoLogoutCall); @@ -153,16 +168,16 @@ public class SaSsoServerProcessor { SaSsoManager.printNoCheckSignWarningByRuntime(); } - // 3、校验ticket,获取 loginId + // 4、校验ticket,获取 loginId Object loginId = ssoServerTemplate.checkTicket(ticket, client); if(SaFoxUtil.isEmpty(loginId)) { return SaResult.error("无效ticket:" + ticket); } - // 4、注册此客户端的单点注销回调URL + // 5、注册此客户端的单点注销回调URL ssoServerTemplate.registerSloCallbackUrl(loginId, client, sloCallback); - // 5、给 client 端响应结果 + // 6、给 client 端响应结果 return SaResult.data(loginId); } diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/template/SaSsoServerTemplate.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/template/SaSsoServerTemplate.java index 1c60027b..42255ac0 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/template/SaSsoServerTemplate.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/template/SaSsoServerTemplate.java @@ -187,7 +187,7 @@ public class SaSsoServerTemplate extends SaSsoTemplate { * @return 账号id */ public Object checkTicket(String ticket) { - return checkTicket(ticket, null); + return checkTicket(ticket, SaSsoConsts.CLIENT_WILDCARD); } /** @@ -205,10 +205,17 @@ public class SaSsoServerTemplate extends SaSsoTemplate { // 解析出这个 ticket 关联的 Client String ticketClient = getTicketToClient(ticket); - // 如果指定了 client 标识,则校验一下 client 标识是否一致 - if(SaFoxUtil.isNotEmpty(client) && SaFoxUtil.notEquals(client, ticketClient)) { - throw new SaSsoException("该 ticket 不属于 client=" + client + ", ticket 值: " + ticket) - .setCode(SaSsoErrorCode.CODE_30011); + // 校验 client 参数是否正确,即:创建 ticket 的 client 和当前校验 ticket 的 client 是否一致 + if(SaSsoConsts.CLIENT_WILDCARD.equals(client)) { + // 如果提供的是通配符,直接越过 client 校验 + } else if (SaFoxUtil.isEmpty(client) && SaFoxUtil.isEmpty(ticketClient)) { + // 如果提供的和期望的两者均为空,则通过校验 + } else { + // 开始详细比对 + if(SaFoxUtil.notEquals(client, ticketClient)) { + throw new SaSsoException("该 ticket 不属于 client=" + client + ", ticket 值: " + ticket) + .setCode(SaSsoErrorCode.CODE_30011); + } } // 删除 ticket 信息,使其只有一次性有效 diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/util/SaSsoConsts.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/util/SaSsoConsts.java index 64050ae7..8eea64f4 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/util/SaSsoConsts.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/util/SaSsoConsts.java @@ -45,6 +45,9 @@ public class SaSsoConsts { /** 表示请求没有得到任何有效处理 {msg: "not handle"} */ public static final String NOT_HANDLE = "{\"msg\": \"not handle\"}"; + /** client 身份,* 代表通配,可以解析出所有 client 的 ticket */ + public static final String CLIENT_WILDCARD = "*"; + /** SSO 模式1 */ public static final int SSO_MODE_1 = 1; /** SSO 模式2 */ @@ -52,4 +55,6 @@ public class SaSsoConsts { /** SSO 模式3 */ public static final int SSO_MODE_3 = 3; + + } -- Gitee From cf1f255a4ae797760e829ff9624f3595ddb4249f Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Fri, 3 May 2024 04:04:52 +0800 Subject: [PATCH 009/172] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20homeRoute=20?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E9=A1=B9=EF=BC=9A=E5=9C=A8=20/sso/auth=20?= =?UTF-8?q?=E7=99=BB=E5=BD=95=E5=90=8E=E4=B8=8D=E6=8C=87=E5=AE=9A=20redire?= =?UTF-8?q?ct=20=E5=8F=82=E6=95=B0=E7=9A=84=E6=83=85=E5=86=B5=E4=B8=8B?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E8=B7=B3=E8=BD=AC=E7=9A=84=E8=B7=AF=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/pj/SaSsoServerApplication.java | 2 ++ .../satoken/sso/config/SaSsoServerConfig.java | 22 ++++++++++++++++++ .../satoken/sso/error/SaSsoErrorCode.java | 3 +++ .../sso/processor/SaSsoServerProcessor.java | 23 +++++++++++++++---- 4 files changed, 46 insertions(+), 4 deletions(-) diff --git a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-server/src/main/java/com/pj/SaSsoServerApplication.java b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-server/src/main/java/com/pj/SaSsoServerApplication.java index 1d2c46d2..f8559239 100644 --- a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-server/src/main/java/com/pj/SaSsoServerApplication.java +++ b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-server/src/main/java/com/pj/SaSsoServerApplication.java @@ -13,6 +13,8 @@ public class SaSsoServerApplication { System.out.println(); System.out.println("---------------------- Sa-Token SSO 统一认证中心启动成功 ----------------------"); System.out.println("配置信息:" + SaSsoManager.getServerConfig()); + System.out.println("统一认证登录地址:http://sa-sso-server.com:9000/sso/auth"); + System.out.println("测试前需要根据官网文档修改hosts文件,测试账号密码:sa / 123456"); System.out.println(); } diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/config/SaSsoServerConfig.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/config/SaSsoServerConfig.java index a0855c57..2ba6a8a3 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/config/SaSsoServerConfig.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/config/SaSsoServerConfig.java @@ -54,6 +54,11 @@ public class SaSsoServerConfig implements Serializable { */ public String allowUrl = "*"; + /** + * 主页路由:在 /sso/auth 登录后不指定 redirect 参数的情况下默认跳转的路由 + */ + public String homeRoute; + /** * 是否打开单点注销功能 */ @@ -127,6 +132,22 @@ public class SaSsoServerConfig implements Serializable { return this; } + /** + * @return 主页路由:在 /sso/auth 登录后不指定 redirect 参数的情况下默认跳转的路由 + */ + public String getHomeRoute() { + return homeRoute; + } + + /** + * @param homeRoute 主页路由:在 /sso/auth 登录后不指定 redirect 参数的情况下默认跳转的路由 + * @return 对象自身 + */ + public SaSsoServerConfig setHomeRoute(String homeRoute) { + this.homeRoute = homeRoute; + return this; + } + /** * @return 是否打开单点注销功能 */ @@ -210,6 +231,7 @@ public class SaSsoServerConfig implements Serializable { + "mode=" + mode + ", ticketTimeout=" + ticketTimeout + ", allowUrl=" + allowUrl + + ", homeRoute=" + homeRoute + ", isSlo=" + isSlo + ", isHttp=" + isHttp + ", maxRegClient=" + maxRegClient diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/error/SaSsoErrorCode.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/error/SaSsoErrorCode.java index c27bfdff..9d0c6b16 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/error/SaSsoErrorCode.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/error/SaSsoErrorCode.java @@ -62,4 +62,7 @@ public interface SaSsoErrorCode { /** 提供的 client 参数值无效 */ int CODE_30013 = 30013; + /** 在 /sso/auth 既没有指定 redirect 参数,也没有配置 homeRoute 路由 */ + int CODE_30014 = 30014; + } diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/processor/SaSsoServerProcessor.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/processor/SaSsoServerProcessor.java index c99e5f5e..e6d93561 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/processor/SaSsoServerProcessor.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/processor/SaSsoServerProcessor.java @@ -106,10 +106,18 @@ public class SaSsoServerProcessor { } // ---- 情况2:在SSO认证中心已经登录,需要重定向回 Client 端,而这又分为两种方式: String mode = req.getParam(paramName.mode, ""); + String redirect = req.getParam(paramName.redirect); // 方式1:直接重定向回Client端 (mode=simple) if(mode.equals(SaSsoConsts.MODE_SIMPLE)) { - String redirect = req.getParam(paramName.redirect); + + // 若 redirect 为空,则选择 homeRoute,若 homeRoute 也为空,则抛出异常 + if(SaFoxUtil.isEmpty(redirect)) { + if(SaFoxUtil.isEmpty(cfg.getHomeRoute())) { + throw new SaSsoException("未指定 redirect 参数,也未配置 homeRoute 路由,无法完成重定向操作").setCode(SaSsoErrorCode.CODE_30014); + } + return res.redirect(cfg.getHomeRoute()); + } ssoServerTemplate.checkRedirectUrl(redirect); return res.redirect(redirect); } else { @@ -121,9 +129,16 @@ public class SaSsoServerProcessor { throw new SaSsoException("无效 client 标识:" + client).setCode(SaSsoErrorCode.CODE_30013); } - // 开始重定向 - String redirectUrl = ssoServerTemplate.buildRedirectUrl( - stpLogic.getLoginId(), client, req.getParam(paramName.redirect)); + // 若 redirect 为空,则选择 homeRoute,若 homeRoute 也为空,则抛出异常 + if(SaFoxUtil.isEmpty(redirect)) { + if(SaFoxUtil.isEmpty(cfg.getHomeRoute())) { + throw new SaSsoException("未指定 redirect 参数,也未配置 homeRoute 路由,无法完成重定向操作").setCode(SaSsoErrorCode.CODE_30014); + } + return res.redirect(cfg.getHomeRoute()); + } + + // 构建并跳转 + String redirectUrl = ssoServerTemplate.buildRedirectUrl(stpLogic.getLoginId(), client, redirect); return res.redirect(redirectUrl); } } -- Gitee From 5a7463dc91ce11a6517968b9bdc46947b960c3f5 Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Fri, 3 May 2024 14:22:27 +0800 Subject: [PATCH 010/172] =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=99=BB=E5=BD=95?= =?UTF-8?q?=E6=9C=89=E6=95=88=E6=9C=9F=E7=AD=96=E7=95=A5=EF=BC=8CSSO=20Cli?= =?UTF-8?q?ent=20=E7=AB=AF=E7=99=BB=E5=BD=95=E6=97=B6=E5=B0=86=E5=BB=B6?= =?UTF-8?q?=E7=BB=AD=20SSO=20Server=20=E7=AB=AF=E7=9A=84=E4=BC=9A=E8=AF=9D?= =?UTF-8?q?=E5=89=A9=E4=BD=99=E6=9C=89=E6=95=88=E6=9C=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../satoken/sso/exception/SaSsoException.java | 33 +++++++++- .../function/TicketResultHandleFunction.java | 4 +- .../cn/dev33/satoken/sso/name/ParamName.java | 3 + .../sso/processor/SaSsoClientProcessor.java | 65 ++++++++++++++----- .../sso/processor/SaSsoServerProcessor.java | 4 +- 5 files changed, 88 insertions(+), 21 deletions(-) diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/exception/SaSsoException.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/exception/SaSsoException.java index a33a3453..b4c50995 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/exception/SaSsoException.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/exception/SaSsoException.java @@ -16,6 +16,7 @@ package cn.dev33.satoken.sso.exception; import cn.dev33.satoken.exception.SaTokenException; +import cn.dev33.satoken.util.SaFoxUtil; /** @@ -57,12 +58,38 @@ public class SaSsoException extends SaTokenException { super.setCode(code); return this; } - + + + /** + * 断言 flag 不为 true,否则抛出 message 异常 + * @param flag 标记 + * @param message 异常信息 + * @param code 异常细分状态码 + */ + public static void notTrue(boolean flag, String message, int code) { + if(flag) { + throw new SaSsoException(message).setCode(code); + } + } + + /** + * 断言 value 不为空,否则抛出 message 异常 + * @param value 值 + * @param message 异常信息 + * @param code 异常细分状态码 + */ + public static void notEmpty(Object value, String message, int code) { + if(SaFoxUtil.isEmpty(value)) { + throw new SaSsoException(message).setCode(code); + } + } + /** - * 如果flag==true,则抛出message异常 + * 如果flag==true,则抛出message异常 * @param flag 标记 - * @param message 异常信息 + * @param message 异常信息 */ + @Deprecated public static void throwBy(boolean flag, String message) { if(flag) { throw new SaSsoException(message); diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/function/TicketResultHandleFunction.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/function/TicketResultHandleFunction.java index 7fdc26e0..f1100000 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/function/TicketResultHandleFunction.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/function/TicketResultHandleFunction.java @@ -15,6 +15,8 @@ */ package cn.dev33.satoken.sso.function; +import cn.dev33.satoken.sso.processor.SaSsoClientProcessor; + import java.util.function.BiFunction; /** @@ -27,6 +29,6 @@ import java.util.function.BiFunction; * @since 1.38.0 */ @FunctionalInterface -public interface TicketResultHandleFunction extends BiFunction { +public interface TicketResultHandleFunction extends BiFunction { } \ No newline at end of file diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/name/ParamName.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/name/ParamName.java index ab456a06..c0f7b188 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/name/ParamName.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/name/ParamName.java @@ -57,4 +57,7 @@ public class ParamName { public String nonce = "nonce"; public String sign = "sign"; + /** Session 剩余有效期 参数名称 */ + public String remainSessionTimeout = "remainSessionTimeout"; + } diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/processor/SaSsoClientProcessor.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/processor/SaSsoClientProcessor.java index 6665ff9e..9e589c81 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/processor/SaSsoClientProcessor.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/processor/SaSsoClientProcessor.java @@ -121,22 +121,17 @@ public class SaSsoClientProcessor { String serverAuthUrl = ssoClientTemplate.buildServerAuthUrl(currSsoLoginUrl, back); return res.redirect(serverAuthUrl); } else { - // ------- 1、校验ticket,获取 loginId - Object loginId = checkTicketByMode2Or3(ticket, apiName.ssoLogin); + // 1、校验ticket,获取 loginId + CheckTicketResult ctr = checkTicketByMode2Or3(ticket, apiName.ssoLogin); - // Be: 如果开发者自定义了处理逻辑 + // 2、如果开发者自定义了ticket结果值处理函数,则使用自定义的函数 if(cfg.ticketResultHandle != null) { - return cfg.ticketResultHandle.apply(loginId, back); + return cfg.ticketResultHandle.apply(ctr, back); } - // ------- 2、如果 loginId 无值,说明 ticket 无效 - if(SaFoxUtil.isEmpty(loginId)) { - throw new SaSsoException("无效ticket:" + ticket).setCode(SaSsoErrorCode.CODE_30004); - } else { - // 3、如果 loginId 有值,说明 ticket 有效,此时进行登录并重定向至back地址 - stpLogic.login(loginId); - return res.redirect(back); - } + // 3、登录并重定向至back地址 + stpLogic.login(ctr.loginId, ctr.remainSessionTimeout); + return res.redirect(back); } } @@ -244,14 +239,15 @@ public class SaSsoClientProcessor { // 工具方法 /** - * 封装:校验ticket,取出loginId + * 封装:校验ticket,取出loginId,如果 ticket 无效则抛出异常 * @param ticket ticket码 * @param currUri 当前路由的uri,用于计算单点注销回调地址 * @return loginId */ - public Object checkTicketByMode2Or3(String ticket, String currUri) { + public CheckTicketResult checkTicketByMode2Or3(String ticket, String currUri) { SaSsoClientConfig cfg = ssoClientTemplate.getClientConfig(); ApiName apiName = ssoClientTemplate.apiName; + ParamName paramName = ssoClientTemplate.paramName; // --------- 两种模式 if(cfg.getIsHttp()) { @@ -281,7 +277,18 @@ public class SaSsoClientProcessor { // 校验 if(result.getCode() != null && result.getCode() == SaResult.CODE_SUCCESS) { - return result.getData(); + // 取出 loginId + Object loginId = result.getData(); + if(SaFoxUtil.isEmpty(loginId)) { + throw new SaSsoException("无效ticket:" + ticket).setCode(SaSsoErrorCode.CODE_30004); + } + // 取出 Session 剩余有效期 + Long remainSessionTimeout = result.get(paramName.remainSessionTimeout, Long.class); + if(remainSessionTimeout == null) { + remainSessionTimeout = ssoClientTemplate.getStpLogic().getConfig().getTimeout(); + } + // 构建返回 + return new CheckTicketResult(loginId, remainSessionTimeout); } else { // 将 sso-server 回应的消息作为异常抛出 throw new SaSsoException(result.getMsg()).setCode(SaSsoErrorCode.CODE_30005); @@ -293,7 +300,16 @@ public class SaSsoClientProcessor { // 而在当前 sso-client 没有按照相应格式重写 SaSsoClientProcessor 里的方法, // 可能会导致调用失败(注意是可能,而非一定), // 解决方案为:在当前 sso-client 端也按照 sso-server 端的格式重写 SaSsoClientProcessor 里的方法 - return SaSsoServerProcessor.instance.ssoServerTemplate.checkTicket(ticket, cfg.getClient()); + + // 取出 loginId + Object loginId = SaSsoServerProcessor.instance.ssoServerTemplate.checkTicket(ticket, cfg.getClient()); + if(SaFoxUtil.isEmpty(loginId)) { + throw new SaSsoException("无效ticket:" + ticket).setCode(SaSsoErrorCode.CODE_30004); + } + // 取出 Session 剩余有效期 + long remainSessionTimeout = ssoClientTemplate.getStpLogic().getSessionTimeoutByLoginId(loginId); + // 构建返回 + return new CheckTicketResult(loginId, remainSessionTimeout); } } @@ -307,4 +323,21 @@ public class SaSsoClientProcessor { return SaSsoProcessorHelper.ssoLogoutBack(req, res, ssoClientTemplate.paramName); } + + public static class CheckTicketResult { + public Object loginId; + public long remainSessionTimeout; + public CheckTicketResult(Object loginId, long remainSessionTimeout) { + this.loginId = loginId; + this.remainSessionTimeout = remainSessionTimeout; + } + @Override + public String toString() { + return "CheckTicketResult{" + + "loginId=" + loginId + + ", remainSessionTimeout=" + remainSessionTimeout + + '}'; + } + } + } diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/processor/SaSsoServerProcessor.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/processor/SaSsoServerProcessor.java index e6d93561..3a917376 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/processor/SaSsoServerProcessor.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/processor/SaSsoServerProcessor.java @@ -193,7 +193,9 @@ public class SaSsoServerProcessor { ssoServerTemplate.registerSloCallbackUrl(loginId, client, sloCallback); // 6、给 client 端响应结果 - return SaResult.data(loginId); + long remainSessionTimeout = ssoServerTemplate.getStpLogic().getSessionTimeoutByLoginId(loginId); + return SaResult.data(loginId) + .set(paramName.remainSessionTimeout, remainSessionTimeout); } /** -- Gitee From 64abd69715f90eab7dd3e0dc37f68ca941f7005d Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Fri, 3 May 2024 16:14:40 +0800 Subject: [PATCH 011/172] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20autoRenewTimeout?= =?UTF-8?q?=20=E9=85=8D=E7=BD=AE=E9=A1=B9=EF=BC=9A=E6=98=AF=E5=90=A6?= =?UTF-8?q?=E5=9C=A8=E6=AF=8F=E6=AC=A1=E4=B8=8B=E5=8F=91=20ticket=20?= =?UTF-8?q?=E6=97=B6=EF=BC=8C=E8=87=AA=E5=8A=A8=E7=BB=AD=E6=9C=9F=20token?= =?UTF-8?q?=20=E7=9A=84=E6=9C=89=E6=95=88=E6=9C=9F=EF=BC=88=E6=A0=B9?= =?UTF-8?q?=E6=8D=AE=E5=85=A8=E5=B1=80=20timeout=20=E5=80=BC=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../satoken/sso/config/SaSsoServerConfig.java | 22 +++++++++++++++++++ .../sso/processor/SaSsoClientProcessor.java | 2 +- .../sso/processor/SaSsoServerProcessor.java | 5 +++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/config/SaSsoServerConfig.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/config/SaSsoServerConfig.java index 2ba6a8a3..1830962f 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/config/SaSsoServerConfig.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/config/SaSsoServerConfig.java @@ -69,6 +69,11 @@ public class SaSsoServerConfig implements Serializable { */ public Boolean isHttp = false; + /** + * 是否在每次下发 ticket 时,自动续期 token 的有效期(根据全局 timeout 值) + */ + public Boolean autoRenewTimeout = false; + /** * 在 Access-Session 上记录 Client 信息的最高数量(-1=无限),超过此值将进行自动清退处理,先进先出 */ @@ -180,6 +185,22 @@ public class SaSsoServerConfig implements Serializable { return this; } + /** + * @return 是否在每次下发 ticket 时,自动续期 token 的有效期(根据全局 timeout 值) + */ + public Boolean getAutoRenewTimeout() { + return autoRenewTimeout; + } + + /** + * @param autoRenewTimeout 是否在每次下发 ticket 时,自动续期 token 的有效期(根据全局 timeout 值) + * @return 对象自身 + */ + public SaSsoServerConfig setAutoRenewTimeout(Boolean autoRenewTimeout) { + this.autoRenewTimeout = autoRenewTimeout; + return this; + } + /** * @return maxLoginClient 在 Access-Session 上记录 Client 信息的最高数量(-1=无限),超过此值将进行自动清退处理,先进先出 */ @@ -234,6 +255,7 @@ public class SaSsoServerConfig implements Serializable { + ", homeRoute=" + homeRoute + ", isSlo=" + isSlo + ", isHttp=" + isHttp + + ", autoRenewTimeout=" + autoRenewTimeout + ", maxRegClient=" + maxRegClient + ", isCheckSign=" + isCheckSign + "]"; diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/processor/SaSsoClientProcessor.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/processor/SaSsoClientProcessor.java index 9e589c81..9c8dc651 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/processor/SaSsoClientProcessor.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/processor/SaSsoClientProcessor.java @@ -285,7 +285,7 @@ public class SaSsoClientProcessor { // 取出 Session 剩余有效期 Long remainSessionTimeout = result.get(paramName.remainSessionTimeout, Long.class); if(remainSessionTimeout == null) { - remainSessionTimeout = ssoClientTemplate.getStpLogic().getConfig().getTimeout(); + remainSessionTimeout = ssoClientTemplate.getStpLogic().getConfigOrGlobal().getTimeout(); } // 构建返回 return new CheckTicketResult(loginId, remainSessionTimeout); diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/processor/SaSsoServerProcessor.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/processor/SaSsoServerProcessor.java index 3a917376..ec9e00c2 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/processor/SaSsoServerProcessor.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/processor/SaSsoServerProcessor.java @@ -139,6 +139,11 @@ public class SaSsoServerProcessor { // 构建并跳转 String redirectUrl = ssoServerTemplate.buildRedirectUrl(stpLogic.getLoginId(), client, redirect); + // 构建成功,说明 redirect 地址合法,此时需要更新一下该账号的Session有效期 + if(cfg.getAutoRenewTimeout()) { + stpLogic.renewTimeout(stpLogic.getConfigOrGlobal().getTimeout()); + } + // 跳转 return res.redirect(redirectUrl); } } -- Gitee From c850874e49c19f91af4b4772a53c3ef9592e9b81 Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Sat, 4 May 2024 04:49:51 +0800 Subject: [PATCH 012/172] =?UTF-8?q?=E9=A6=96=E9=A1=B5=E4=BB=8B=E7=BB=8D?= =?UTF-8?q?=E6=96=87=E5=AD=97=E5=A2=9E=E5=8A=A0=E9=80=90=E5=AD=97=E6=89=93?= =?UTF-8?q?=E5=8D=B0=E6=95=88=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sa-token-doc/index.html | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/sa-token-doc/index.html b/sa-token-doc/index.html index a7abea2c..da8dd5ee 100644 --- a/sa-token-doc/index.html +++ b/sa-token-doc/index.html @@ -1095,5 +1095,31 @@ } + + \ No newline at end of file -- Gitee From 21948fbf7e1329137889aa8b577aa166201e988c Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Sat, 4 May 2024 05:31:48 +0800 Subject: [PATCH 013/172] =?UTF-8?q?=E6=96=87=E5=AD=97=E9=80=90=E5=AD=97?= =?UTF-8?q?=E6=89=93=E5=8D=B0=E6=95=88=E6=9E=9C..?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sa-token-doc/index.html | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/sa-token-doc/index.html b/sa-token-doc/index.html index da8dd5ee..1ad4665d 100644 --- a/sa-token-doc/index.html +++ b/sa-token-doc/index.html @@ -13,6 +13,10 @@ + @@ -110,7 +114,10 @@

Sa-Tokenv1.37.0

-
一个轻量级 java 权限认证框架,让鉴权变得简单、优雅!
+
+ 一个轻量级 java 权限认证框架,让鉴权变得简单、优雅! +
 
+
Gitee GitHub @@ -1098,7 +1105,7 @@ diff --git a/sa-token-doc/static/index.css b/sa-token-doc/static/index.css index 20c0c712..b860c3cc 100644 --- a/sa-token-doc/static/index.css +++ b/sa-token-doc/static/index.css @@ -50,6 +50,9 @@ body{font-size: 16px; color: #34495E; font-family: "Source Sans Pro","Helvetica /* .content-box p{line-height: 30px; padding: 0px 1em;} */ /* 角标位置修复 */ .badge-box a:nth-child(-n+2) img{position: relative; top: 1px;} +/* 模拟副标题的光标闪烁 */ +.gb-cursor {display: inline-block;width: 2px;height: 22px;position: relative;top: 4px;left: -4px;background-color: black;animation: blink 0.7s infinite alternate;} +@keyframes blink { from {opacity: 0;} to {opacity: 1;} } /* .main-box{background-image: url(https://oss.dev33.cn/sa-token/home-bg.jpg);} */ .main-box{background-image: url(https://oss.dev33.cn/sa-token/home-bg3.png); background-size: 120% 100%;} @@ -172,6 +175,12 @@ body{font-size: 16px; color: #34495E; font-family: "Source Sans Pro","Helvetica .com-box-you a{flex: 0 0 14.5%; line-height: 60px; height: 60px; margin: 10px;} .com-box-you a img{min-width: 60%; max-width: 85%; vertical-align: middle; max-height: 100%;} +/* -------- Dromara 成员项目 --------- */ +.table-show-pj{border: 1px #ddd solid; border-width: 1px 0 0 1px ;} +.table-show-pj a{flex: 0 0 16.5%; border: 1px #ddd solid; margin: 0; padding: 7px 0; overflow: hidden;} +.table-show-pj a{border-width: 0 1px 1px 0px;} +.table-show-pj a img{min-width: 60%; max-width: 70%; } + /* -------- 底部 - 连接 --------- */ #footer{background-color: #181818;} #footer h3{font-weight: 400; font-size: 16px; color: #ccc; margin-top: 20px; margin-bottom: 20px;} -- Gitee From 9680d6cb9f713cdc67cf731f9d657d242aa9498f Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Sun, 5 May 2024 05:47:34 +0800 Subject: [PATCH 019/172] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20=E8=B5=9E=E5=8A=A9?= =?UTF-8?q?=E8=80=85=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sa-token-doc/index.html | 12 ++++++------ sa-token-doc/static/donate/donate-list.js | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/sa-token-doc/index.html b/sa-token-doc/index.html index c96404be..e0acb5ed 100644 --- a/sa-token-doc/index.html +++ b/sa-token-doc/index.html @@ -214,14 +214,14 @@

Dromara 组织顶尖项目(之一)

- +
+

GitHub stars 超 15k+

+
diff --git a/sa-token-doc/static/donate/donate-list.js b/sa-token-doc/static/donate/donate-list.js index 7944be70..84c1465f 100644 --- a/sa-token-doc/static/donate/donate-list.js +++ b/sa-token-doc/static/donate/donate-list.js @@ -930,7 +930,7 @@ var donateList = [ "name": "MetaLowCode", "link": "https://gitee.com/meta_low_code_admin", "money": 220.0, - "msg": "可能是最适合Java程序员的低代码平台 -- 美乐低代码 https://melecode.com/", + "msg": '可能是最适合Java程序员的低代码平台 -- 美乐低代码 https://melecode.com/', "date": "2023-11-23" }, { @@ -951,7 +951,7 @@ var donateList = [ "name": "Justin Chia", "link": "https://gitee.com/justin-chia", "money": 218.0, - "msg": "可以二开的国产低代码表单 https://vform666.com/", + "msg": '可以二开的国产低代码表单 https://vform666.com/', "date": "2023-12-05" }, { -- Gitee From 1ba8d6f8d482744c3af8bca26514d8c7d986009d Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Sun, 5 May 2024 06:44:28 +0800 Subject: [PATCH 020/172] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E9=97=AE=E5=8D=B7?= =?UTF-8?q?=E8=B0=83=E6=9F=A5=E5=BC=B9=E7=AA=97=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sa-token-doc/doc.html | 4 +- sa-token-doc/index.html | 2 +- sa-token-doc/static/is-fill-in-wj-plugin.js | 114 ++++++++++++++++++++ 3 files changed, 118 insertions(+), 2 deletions(-) create mode 100644 sa-token-doc/static/is-fill-in-wj-plugin.js diff --git a/sa-token-doc/doc.html b/sa-token-doc/doc.html index 92ac71ce..b0a61f7e 100644 --- a/sa-token-doc/doc.html +++ b/sa-token-doc/doc.html @@ -166,6 +166,7 @@ + @@ -388,5 +389,6 @@ $('.zk-btn--1').show(); } + diff --git a/sa-token-doc/index.html b/sa-token-doc/index.html index e0acb5ed..93789c4f 100644 --- a/sa-token-doc/index.html +++ b/sa-token-doc/index.html @@ -922,7 +922,7 @@ diff --git a/sa-token-doc/static/is-fill-in-wj-plugin.js b/sa-token-doc/static/is-fill-in-wj-plugin.js new file mode 100644 index 00000000..d9def006 --- /dev/null +++ b/sa-token-doc/static/is-fill-in-wj-plugin.js @@ -0,0 +1,114 @@ +// + +// 声明 docsify 插件 +var isFillInWjPlugin = function(hook, vm) { + + // 钩子函数:解析之前执行 + hook.beforeEach(function(content) { + return content; + }); + + // 钩子函数:每次路由切换时,解析内容之后执行 + hook.afterEach(function(html) { + return html; + }); + + // 钩子函数:每次路由切换时数据全部加载完成后调用,没有参数。 + hook.doneEach(function() { + isFillIn(vm); + }); + + // 钩子函数:初始化并第一次加载完成数据后调用,没有参数。 + hook.ready(function() { + + }); + +} + + +// 检查成功后,多少天不再检查 +const wjAllowDisparity = 1000 * 60 * 60 * 24 * 30 * 3; +// const allowDisparity = 1000 * 10; + + +// 判断当前是否已填写 +function isFillIn(vm) { + // 非PC端不检查 + if(document.body.offsetWidth < 800) { + console.log('small screen ... wj '); + return; + } + + // 白名单路由不判断 + const whiteList = ['/', '/more/link', '/more/demand-commit', '/more/join-group', '/more/sa-token-donate', '/more/wenjuan', + '/sso/sso-pro', '/more/update-log', '/more/common-questions', '/fun/sa-token-test', '/fun/issue-template']; + if(whiteList.indexOf(vm.route.path) >= 0) { + console.log('white route ... wj'); + return; + } + + // 判断是否近期已经判断过了 + try{ + const isFillIn = localStorage.isFillIn; + if(isFillIn) { + // 记录 star 的时间,和当前时间的差距 + const disparity = new Date().getTime() - parseInt(isFillIn); + + // 差距小于一月,不再检测,大于一月,再检测一下 + if(disparity < wjAllowDisparity) { + console.log('checked ... wj '); + return; + } + } + }catch(e){ + console.error(e); + } + + // 本次打开页面的内存内已经弹出了的话,也不再弹了 + if(window.isYtcXsjfkasjda) { + return; + } + window.isYtcXsjfkasjda = true; + + // 弹出弹框,邀请填写 + const tipStr = ` +
+

+ 嗨,同学你好! +

+

+ 我们想以运营一款产品的心态来运营一个开源框架,所以我们迫切希望您能够填写这份问卷,这有 6 道选择题, + 应该只会略微占用您 1~3 分钟的时间。 +

+

问卷地址:https://wj.qq.com/s2/14587150/b5b4/

+

Sa-Token 将会非常重视每一位粉丝的宝贵意见!😇😇😇

+
+ `; + + const index = layer.confirm(tipStr, { + title: '问卷调查填写邀请', + btn: ['我已填写 (1月内不再弹出)', '暂时不要 (1天内不再弹出)'], + // btn: ['同意授权检测', '暂时不要,我先看看文档'], + area: '480px', + offset: '30%' + }, + // 点击确定 + function(index) { + layer.close(index); + localStorage.isFillIn = new Date().getTime(); + + layer.msg('感谢你的支持,Sa-Token 将努力变得更加完善! ❤️ ❤️ ❤️ ') + }, + // 点击取消 + function(){ + // 一天内不再检查 + const ygTime = allowDisparity - (1000 * 60 * 60 * 24); + localStorage.isFillIn = new Date().getTime() - ygTime; + + layer.alert('你可以随时在右上角 [ 相关资源 -> 问卷调查 ] 处找到问卷链接', function(index) { + layer.close(index); + }) + } + ); +} + -- Gitee From 42d94827c1e2e8ae30ae8047259fdbfb4fee67e6 Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Sun, 5 May 2024 12:27:49 +0800 Subject: [PATCH 021/172] =?UTF-8?q?=E5=AE=8C=E5=96=84=20Http=20Digest=20?= =?UTF-8?q?=E8=AE=A4=E8=AF=81=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sa-token-doc/_sidebar.md | 2 +- sa-token-doc/up/basic-auth.md | 51 +++++++++++++++++++++++++++++------ 2 files changed, 44 insertions(+), 9 deletions(-) diff --git a/sa-token-doc/_sidebar.md b/sa-token-doc/_sidebar.md index f471adac..230e3f31 100644 --- a/sa-token-doc/_sidebar.md +++ b/sa-token-doc/_sidebar.md @@ -29,7 +29,7 @@ - [账号封禁](/up/disable) - [密码加密](/up/password-secure) - [会话查询](/up/search-session) - - [Http Basic 认证](/up/basic-auth) + - [Http Basic/Digest 认证](/up/basic-auth) - [全局侦听器](/up/global-listener) - [全局过滤器](/up/global-filter) - [多账号认证](/up/many-account) diff --git a/sa-token-doc/up/basic-auth.md b/sa-token-doc/up/basic-auth.md index d9e4a178..f28121ce 100644 --- a/sa-token-doc/up/basic-auth.md +++ b/sa-token-doc/up/basic-auth.md @@ -14,7 +14,7 @@ Http Basic 是 http 协议中最基础的认证方式,其有两个特点: ``` java @RequestMapping("test3") public SaResult test3() { - SaBasicUtil.check("sa:123456"); + SaHttpBasicUtil.check("sa:123456"); // ... 其它代码 return SaResult.ok(); } @@ -44,14 +44,14 @@ public class GlobalExceptionHandler { ### 2、其它启用方式 ``` java -// 对当前会话进行 Basic 校验,账号密码为 yml 配置的值(例如:sa-token.basic=sa:123456) -SaBasicUtil.check(); +// 对当前会话进行 Http Basic 校验,账号密码为 yml 配置的值(例如:sa-token.http-basic=sa:123456) +SaHttpBasicUtil.check(); -// 对当前会话进行 Basic 校验,账号密码为:`sa / 123456` -SaBasicUtil.check("sa:123456"); +// 对当前会话进行 Http Basic 校验,账号密码为:`sa / 123456` +SaHttpBasicUtil.check("sa:123456"); -// 以注解方式启用 Basic 校验 -@SaCheckBasic(account = "sa:123456") +// 以注解方式启用 Http Basic 校验 +@SaCheckHttpBasic(account = "sa:123456") @RequestMapping("test3") public SaResult test3() { return SaResult.ok(); @@ -63,7 +63,7 @@ public SaServletFilter getSaServletFilter() { return new SaServletFilter() .addInclude("/**").addExclude("/favicon.ico") .setAuth(obj -> { - SaRouter.match("/test/**", () -> SaBasicUtil.check("sa:123456")); + SaRouter.match("/test/**", () -> SaHttpBasicUtil.check("sa:123456")); }); } ``` @@ -75,6 +75,41 @@ http://sa:123456@127.0.0.1:8081/test/test3 ``` +### 4、Http Digest 认证 + +Http Digest 认证是 Http Basic 认证的升级版,Http Digest 在提交请求时不会使用明文方式传输认证信息,而是使用一定的规则加密后提交。 +不过对于开发者来讲,开启 Http Digest 认证校验的流程与 Http Basic 认证基本是一致的。 + +``` java +// 测试 Http Digest 认证 浏览器访问: http://localhost:8081/test/testDigest +@RequestMapping("testDigest") +public SaResult testDigest() { + SaHttpDigestUtil.check("sa", "123456"); + return SaResult.ok(); +} + +// 使用注解方式开启 Http Digest 认证 +@SaCheckHttpDigest("sa:123456") +@RequestMapping("testDigest2") +public SaResult testDigest() { + return SaResult.ok(); +} + + +// 对当前会话进行 Http Digest 校验,账号密码为 yml 配置的值(例如:sa-token.http-digest=sa:123456) +SaHttpDigestUtil.check(); +``` + +与上面的 Http Basic 认证一致,在访问这个路由时,浏览器会强制弹出一个表单,客户端输入正确的账号密码后即可通过校验。 + +同样的,Http Digest 也支持在浏览器访问接口时直接使用 @ 符号拼接账号密码信息,使客户端直接通过校验。 + +``` url +http://sa:123456@127.0.0.1:8081/test/testDigest +``` + + + --- Date: Sun, 5 May 2024 12:30:14 +0800 Subject: [PATCH 022/172] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sa-token-doc/plugin/dao-extend.md | 1 + 1 file changed, 1 insertion(+) diff --git a/sa-token-doc/plugin/dao-extend.md b/sa-token-doc/plugin/dao-extend.md index 8e3db5ea..04b0eca4 100644 --- a/sa-token-doc/plugin/dao-extend.md +++ b/sa-token-doc/plugin/dao-extend.md @@ -14,6 +14,7 @@ - sa-token-redis-fastjson2:Redis集成包,使用 fastjson2 序列化方式。 - sa-token-redisson-jackson:Redis集成包,Redisson客户端使用,jackson 序列化方式。 - sa-token-redisson-jackson2:通用 redisson 集成方案 (spring, solon, jfinal 等都可用)。 +- sa-token-hutool-timed-cache:集成 hutool 框架的 Timed-Cache 缓存方案(基于内存)。 有关 Redis 集成,详细参考:[集成Redis](/up/integ-redis),更多存储方式欢迎提交PR -- Gitee From 9e37896bb08b1cfd6b08136fad3f7dfd45cda6a5 Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Sun, 5 May 2024 12:39:54 +0800 Subject: [PATCH 023/172] =?UTF-8?q?=E8=A1=A5=E5=85=A8=E7=BC=BA=E5=A4=B1?= =?UTF-8?q?=E7=9A=84=20clean=20=E9=A1=B9=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mvn clean.bat | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mvn clean.bat b/mvn clean.bat index baf093d2..d4044c83 100644 --- a/mvn clean.bat +++ b/mvn clean.bat @@ -26,6 +26,7 @@ cd sa-token-demo-websocket-spring & call mvn clean & cd .. cd sa-token-demo-bom-import & call mvn clean & cd .. cd sa-token-demo-hutool-timed-cache & call mvn clean & cd .. +cd sa-token-demo-ssm & call mvn clean & cd .. @@ -34,6 +35,7 @@ cd sa-token-demo-sso-server & call mvn clean & cd .. cd sa-token-demo-sso1-client & call mvn clean & cd .. cd sa-token-demo-sso2-client & call mvn clean & cd .. cd sa-token-demo-sso3-client & call mvn clean & cd .. +cd sa-token-demo-sso3-client-test2 & call mvn clean & cd .. cd sa-token-demo-sso3-client-nosdk & call mvn clean & cd .. cd .. -- Gitee From 78fd26e9db3738009d2a62c7e1616ee17397bff2 Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Sun, 5 May 2024 12:40:05 +0800 Subject: [PATCH 024/172] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sa-token-doc/api/stp-util.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sa-token-doc/api/stp-util.md b/sa-token-doc/api/stp-util.md index 8395090f..d7ca8f94 100644 --- a/sa-token-doc/api/stp-util.md +++ b/sa-token-doc/api/stp-util.md @@ -38,6 +38,8 @@ StpUtil.login(10001, new SaLoginModel() .setToken("xxxx-xxxx-xxxx-xxxx") // 预定此次登录生成的Token .setExtra("name", "zhangsan") // Token挂载的扩展参数 (此方法只有在集成jwt插件时才会生效) .setIsWriteHeader(false) // 是否在登录后将 Token 写入到响应头 + .setActiveTimeout(300) // 指定此次登录token的最低活跃频率, 单位:秒,设置此参数需要在配置文件打开dynamicActiveTimeout=true + .setTokenSignTag("xxx") // 指定此次登录挂载在 TokenSign 上的 tag 值, 任意值 ); ``` @@ -139,6 +141,7 @@ StpUtil.getTokenValueByLoginId(10001, "PC"); // 获取指定账号id指定设 StpUtil.getTokenValueListByLoginId(10001); // 获取指定账号id的tokenValue集合 StpUtil.getTokenValueListByLoginId(10001, "APP"); // 获取指定账号id指定设备类型端的tokenValue 集合 StpUtil.getLoginDevice(); // 返回当前会话的登录设备类型 +StpUtil.getLoginDeviceByToken(xxx); // // 返回任意 token 的登录设备类型 ``` -- Gitee From f4dda6eb6c17ed6e5f8767c06b962773cbeb982b Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Sun, 5 May 2024 12:45:04 +0800 Subject: [PATCH 025/172] =?UTF-8?q?=E5=AE=8C=E5=96=84=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sa-token-doc/start/download.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/sa-token-doc/start/download.md b/sa-token-doc/start/download.md index 607676aa..68636eef 100644 --- a/sa-token-doc/start/download.md +++ b/sa-token-doc/start/download.md @@ -238,13 +238,6 @@ Maven依赖一直无法加载成功?[参考解决方案](https://sa-token.cc/d ├── sa-token-demo-oauth2 // [示例] Sa-Token 集成 OAuth2.0 ├── sa-token-demo-oauth2-server // [示例] Sa-Token 集成 OAuth2.0 (服务端) ├── sa-token-demo-oauth2-client // [示例] Sa-Token 集成 OAuth2.0 (客户端) - ├── sa-token-demo-cross // [示例] Sa-Token 跨域示例 - ├── sa-token-demo-cross-header-server // [示例] Sa-Token 跨域测试 - Header 参数版,后端接口 - ├── sa-token-demo-cross-header-h5 // [示例] Sa-Token 跨域测试 - Header 参数版,h5 页面(jquery请求) - ├── sa-token-demo-cross-header-vue3 // [示例] Sa-Token 跨域测试 - Header 参数版,vue3 页面 - ├── sa-token-demo-cross-cookie-server // [示例] Sa-Token 跨域测试 - 第三方 Cookie 版,后端接口 - ├── sa-token-demo-cross-cookie-h5 // [示例] Sa-Token 跨域测试 - 第三方 Cookie 版,h5 页面(jquery请求) - ├── sa-token-demo-cross-cookie-vue3 // [示例] Sa-Token 跨域测试 - 第三方 Cookie 版,vue3 页面 ├── sa-token-demo-dubbo // [示例] Sa-Token 集成 dubbo ├── sa-token-demo-dubbo-consumer // [示例] Sa-Token 集成 dubbo 鉴权,消费端(调用端) ├── sa-token-demo-dubbo-provider // [示例] Sa-Token 集成 dubbo 鉴权,生产端(被调用端) @@ -263,6 +256,12 @@ Maven依赖一直无法加载成功?[参考解决方案](https://sa-token.cc/d ├──pom.xml // [依赖] 顶级pom文件 ``` +其它: + +- [sa-token-demo-cross](https://gitee.com/sa-tokens/sa-token-demo-cross):Sa-Token 处理跨域示例。 +- [sa-token-three-plugin](https://gitee.com/sa-tokens/sa-token-three-plugin):Sa-Token 第三方插件合集。 +- [sa-token-study](https://gitee.com/sa-tokens/sa-token-study):Sa-Token 涉及知识点学习。 + ## 运行示例 -- Gitee From 83ac2036318ae9e0536fdf0863968a3d01def6ed Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Mon, 6 May 2024 10:33:42 +0800 Subject: [PATCH 026/172] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sa-token-doc/api/stp-util.md | 1 + sa-token-doc/sso/sso-apidoc.md | 8 ++- sa-token-doc/static/index.css | 4 +- sa-token-doc/use/config.md | 108 ++++++++++++++++++++++++++------- 4 files changed, 94 insertions(+), 27 deletions(-) diff --git a/sa-token-doc/api/stp-util.md b/sa-token-doc/api/stp-util.md index d7ca8f94..f9be25de 100644 --- a/sa-token-doc/api/stp-util.md +++ b/sa-token-doc/api/stp-util.md @@ -94,6 +94,7 @@ StpUtil.getSessionBySessionId("xxxx-xxxx-xxxx"); // 获取指定key的Session, ``` java // Token 最低活跃频率 StpUtil.getTokenActiveTimeout(); // 获取当前 token 距离被冻结还剩多少时间 (单位: 秒) +StpUtil.getTokenLastActiveTime(); // 获取当前 token 最后活跃时间 StpUtil.checkActiveTimeout(); // 检查当前token 是否已经被冻结,如果是则抛出异常 StpUtil.updateLastActiveToNow(); // 续签当前token:(将 [最后操作时间] 更新为当前时间戳) diff --git a/sa-token-doc/sso/sso-apidoc.md b/sa-token-doc/sso/sso-apidoc.md index 2d03713a..53d47ef4 100644 --- a/sa-token-doc/sso/sso-apidoc.md +++ b/sa-token-doc/sso/sso-apidoc.md @@ -22,7 +22,7 @@ http://{host}:{port}/sso/auth | :-------- | :-------- | :-------- | | redirect | 是 | 登录成功后的重定向地址,一般填写 location.href(从哪来回哪去) | | mode | 否 | 授权模式,取值 [simple, ticket],simple=登录后直接重定向,ticket=带着ticket参数重定向,默认值为ticket | -| client | 否 | 客户端标识,可不填,代表是一个匿名应用,若填写了,则校验 ticket 时也必须时这个 client 才可以校验成功 | +| client | 否 | 客户端标识,可不填,代表是一个匿名应用,若填写了,则校验 ticket 时也必须是这个 client 才可以校验成功 | 访问接口后有两种情况: - 情况一:当前会话在 SSO 认证中心未登录,会进入登录页开始登录。 @@ -60,6 +60,9 @@ http://{host}:{port}/sso/checkTicket | ticket | 是 | 在步骤 1 中授权重定向时的 ticket 参数 | | ssoLogoutCall | 否 | 单点注销时的回调通知地址,只在SSO模式三单点注销时需要携带此参数| | client | 否 | 客户端标识,可不填,代表是一个匿名应用,若填写了,则必须填写的和 `/sso/auth` 登录时填写的一致才可以校验成功 | +| timestamp | 是 | 当前时间戳,13位 | +| nonce | 是 | 随机字符串 | +| sign | 是 | 签名,生成算法:`md5( [client={client值}&]nonce={随机字符串}&[ssoLogoutCall={单点注销回调地址}&]ticket={ticket值}×tamp={13位时间戳}&key={secretkey秘钥} )` 注:[]内容代表可选 | 返回值场景: - 校验成功时: @@ -68,7 +71,8 @@ http://{host}:{port}/sso/checkTicket { "code": 200, "msg": "ok", - "data": "10001" // 此 ticket 指向的 loginId + "data": "10001", // 此 ticket 指向的 loginId + "remainSessionTimeout": 7200, // 此账号在 sso-server 端的会话剩余有效期(单位:s) } ``` diff --git a/sa-token-doc/static/index.css b/sa-token-doc/static/index.css index b860c3cc..08cfc6bc 100644 --- a/sa-token-doc/static/index.css +++ b/sa-token-doc/static/index.css @@ -176,8 +176,8 @@ body{font-size: 16px; color: #34495E; font-family: "Source Sans Pro","Helvetica .com-box-you a img{min-width: 60%; max-width: 85%; vertical-align: middle; max-height: 100%;} /* -------- Dromara 成员项目 --------- */ -.table-show-pj{border: 1px #ddd solid; border-width: 1px 0 0 1px ;} -.table-show-pj a{flex: 0 0 16.5%; border: 1px #ddd solid; margin: 0; padding: 7px 0; overflow: hidden;} +.table-show-pj{border: 1px #d5d5d5 solid; border-width: 1px 0 0 1px ;} +.table-show-pj a{flex: 0 0 16.5%; border: 1px #d5d5d5 solid; margin: 0; padding: 7px 0; overflow: hidden;} .table-show-pj a{border-width: 0 1px 1px 0px;} .table-show-pj a img{min-width: 60%; max-width: 70%; } diff --git a/sa-token-doc/use/config.md b/sa-token-doc/use/config.md index 6ea30755..d9dbc48c 100644 --- a/sa-token-doc/use/config.md +++ b/sa-token-doc/use/config.md @@ -4,7 +4,9 @@ --- -### 方式1、在 application.yml 配置 +### 1、配置方式 + +##### 方式1、在 application.yml 配置 @@ -52,7 +54,7 @@ sa-token.is-log=true -### 方式2、通过代码配置 +##### 方式2、通过代码配置 @@ -108,7 +110,7 @@ public class SaTokenConfigure { --- -### 所有可配置项 +### 2、核心包所有可配置项 **你不必立刻掌握整个表格,只需要在用到某个功能时再详细查阅它即可** @@ -141,7 +143,8 @@ public class SaTokenConfigure { | basic | String | "" | Http Basic 认证的账号和密码 [参考:Http Basic 认证](/up/basic-auth) | | currDomain | String | null | 配置当前项目的网络访问地址 | | checkSameToken | Boolean | false | 是否校验Same-Token(部分rpc插件有效) | -| cookie | Object | new SaCookieConfig() | Cookie配置对象 | +| cookie | Object | new SaCookieConfig() | Cookie 配置对象 | +| sign | Object | new SaSignConfig() | API 签名配置对象 | Cookie相关配置: @@ -179,35 +182,90 @@ sa-token.cookie.sameSite=Lax ``` +Sign 参数签名相关配置: + +| 参数名称 | 类型 | 默认值 | 说明 | +| :-------- | :-------- | :-------- | :-------- | +| secretKey | String | null | API 调用签名秘钥 | +| timestampDisparity | long | 900000 | 接口调用时的时间戳允许的差距(单位:ms),-1 代表不校验差距,默认15分钟 | + +示例: + + + +``` yaml +# Sa-Token 配置 +sa-token: + # 参数签名配置 + sign: + # API 接口调用签名秘钥 + secret-key: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor +``` + +``` properties +# API 接口调用签名秘钥 +sa-token.sign.secret-key=kQwIOrYvnXmSDkwEiFngrKidMcdrgKor +``` + + + + -### 单点登录相关配置 +### 3、单点登录相关配置 -Server 端配置: +**SSO-Server 端配置:** | 参数名称 | 类型 | 默认值 | 说明 | | :-------- | :-------- | :-------- | :-------- | +| mode | String | | 指定当前系统集成 SSO 时使用的模式(约定型配置项,不对代码逻辑产生任何影响) | | ticketTimeout | long | 300 | ticket 有效期 (单位: 秒) | | allowUrl | String | * | 所有允许的授权回调地址,多个用逗号隔开(不在此列表中的URL将禁止下放ticket),参考:[SSO整合:配置域名校验](/sso/sso-check-domain) | +| homeRoute | String | | 主页路由:在 /sso/auth 登录后不指定 redirect 参数的情况下默认跳转的路由 | | isSlo | Boolean | false | 是否打开单点注销功能 | | isHttp | Boolean | false | 是否打开模式三(此值为 true 时将使用 http 请求:校验 ticket 值、单点注销、获取 userinfo),参考:[详解](/use/config?id=配置项详解:isHttp) | -| secretkey | String | null | 调用秘钥 (用于SSO模式三单点注销的接口通信身份校验) | +| autoRenewTimeout | Bolean | false | 是否在每次下发 ticket 时,自动续期 token 的有效期(根据全局 timeout 值) | +| maxRegClient | int | 32 | 在 Access-Session 上记录 Client 信息的最高数量(-1=无限),超过此值将进行自动清退处理,先进先出 | +| isCheckSign | Boolean | true | 是否校验参数签名(方便本地调试用的一个配置项,生产环境请务必为true) | +配置示例: + + + +``` yaml +# Sa-Token 配置 +sa-token: + # SSO 单点登录服务端配置 + sso-server: + # Ticket有效期 (单位: 秒),默认五分钟 + ticket-timeout: 300 + # 所有允许的授权回调地址 + allow-url: "*" +``` + +``` properties +# Ticket有效期 (单位: 秒),默认五分钟 +sa-token.sso-server.ticket-timeout=300 +# 所有允许的授权回调地址 +sa-token.sso-server.allow-url="*" +``` + -Client 端配置: +**SSO-Client 端配置:** | 参数名称 | 类型 | 默认值 | 说明 | | :-------- | :-------- | :-------- | :-------- | +| mode | String | | 指定当前系统集成 SSO 时使用的模式(约定型配置项,不对代码逻辑产生任何影响) | +| client | String | "" | 当前 Client 名称标识,用于和 ticket 码的互相锁定 | +| serverUrl | String | null | 配置 Server 端主机总地址,拼接在 `authUrl`、`checkTicketUrl`、`userinfoUrl`、`sloUrl` 属性前面,用以简化各种 url 配置,参考:[详解](/sso/sso-questions?id=问:模式三配置一堆-xxx-url-,有办法简化一下吗?) | | authUrl | String | /sso/auth | 配置 Server 端单点登录授权地址 | -| isSlo | Boolean | false | 是否打开单点注销功能 | -| isHttp | Boolean | false | 是否打开模式三(此值为 true 时将使用 http 请求:校验 ticket 值、单点注销、获取 userinfo),参考:[详解](/use/config?id=配置项详解:isHttp) | | checkTicketUrl| String | /sso/checkTicket | 配置 Server 端的 `ticket` 校验地址 | -| userinfoUrl | String | /sso/userinfo | 配置 Server 端查询 `userinfo` 地址 | +| getDataUrl | String | /sso/getData | 配置 Server 端的 拉取数据 地址 | | sloUrl | String | /sso/signout | 配置 Server 端单点注销地址 | -| ssoLogoutCall | String | null | 配置当前 Client 端的单点注销回调URL (为空时自动获取) | -| secretkey | String | null | 接口调用秘钥 (用于SSO模式三单点注销的接口通信身份校验) | -| serverUrl | String | null | 配置 Server 端主机总地址,拼接在 `authUrl`、`checkTicketUrl`、`userinfoUrl`、`sloUrl` 属性前面,用以简化各种 url 配置,参考:[详解](/sso/sso-questions?id=问:模式三配置一堆-xxx-url-,有办法简化一下吗?) | -| client | String | "" | 当前 Client 名称标识,用于和 ticket 码的互相锁定 | - +| currSsoLogin | String | null | 配置当前 Client 端的登录地址(为空时自动获取) | +| currSsoLogoutCall | String | null | 配置当前 Client 端的单点注销回调URL (为空时自动获取) | +| isSlo | Boolean | true | 是否打开单点注销功能 | +| isHttp | Boolean | false | 是否打开模式三(此值为 true 时将使用 http 请求:校验 ticket 值、单点注销、拉取数据getData),参考:[详解](/use/config?id=配置项详解:isHttp) | +| isCheckSign | Boolean | true | 是否校验参数签名(方便本地调试用的一个配置项,生产环境请务必为true) | 配置示例: @@ -217,21 +275,25 @@ Client 端配置: # Sa-Token 配置 sa-token: # SSO-相关配置 - sso: - # SSO-Server端 单点登录授权地址 - auth-url: http://sa-sso-server.com:9000/sso/auth + sso-client: + # sso-server 端主机地址 + server-url: http://sa-sso-server.com:9000 + # 是否打开单点注销功能 + is-slo: true ``` ``` properties -# SSO-Server端 单点登录授权地址 -sa-token.sso.auth-url=http://sa-sso-server.com:9000/sso/auth +# sso-server 端主机地址 +sa-token.sso-client.server-url=http://sa-sso-server.com:9000 +# 是否打开单点注销功能 +sa-token.sso-client.is-slo=true ``` -### OAuth2.0相关配置 +### 4、OAuth2.0相关配置 | 参数名称 | 类型 | 默认值 | 说明 | | :-------- | :-------- | :-------- | :-------- | | isCode | Boolean | true | 是否打开模式:授权码(`Authorization Code`) | @@ -292,7 +354,7 @@ sa-token.oauth2.is-client=true -### 部分配置项详解 +### 5、部分配置项详解 对部分配置项做一下详解 -- Gitee From b926bad13d3e0275372a5dd86808ac854febba96 Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Mon, 6 May 2024 10:34:00 +0800 Subject: [PATCH 027/172] =?UTF-8?q?=E7=A7=BB=E9=99=A4=E8=BF=87=E6=9C=9F?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../satoken/sso/config/SaSsoClientConfig.java | 35 ++----------------- 1 file changed, 3 insertions(+), 32 deletions(-) diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/config/SaSsoClientConfig.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/config/SaSsoClientConfig.java index 097adfb5..ccdf9c73 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/config/SaSsoClientConfig.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/config/SaSsoClientConfig.java @@ -64,11 +64,6 @@ public class SaSsoClientConfig implements Serializable { */ public String getDataUrl = "/sso/getData"; - /** - * 单独配置 Server 端查询 userinfo 地址 - */ - public String userinfoUrl = "/sso/userinfo"; - /** * 单独配置 Server 端单点注销地址 */ @@ -90,7 +85,7 @@ public class SaSsoClientConfig implements Serializable { public Boolean isSlo = true; /** - * 是否打开模式三(此值为 true 时将使用 http 请求:校验ticket值、单点注销、获取userinfo) + * 是否打开模式三(此值为 true 时将使用 http 请求:校验ticket值、单点注销、拉取数据getData) */ public Boolean isHttp = false; @@ -137,14 +132,14 @@ public class SaSsoClientConfig implements Serializable { } /** - * @return isHttp 是否打开模式三(此值为 true 时将使用 http 请求:校验ticket值、单点注销、获取userinfo) + * @return isHttp 是否打开模式三(此值为 true 时将使用 http 请求:校验ticket值、单点注销、拉取数据getData) */ public Boolean getIsHttp() { return isHttp; } /** - * @param isHttp 是否打开模式三(此值为 true 时将使用 http 请求:校验ticket值、单点注销、获取userinfo) + * @param isHttp 是否打开模式三(此值为 true 时将使用 http 请求:校验ticket值、单点注销、拉取数据getData) * @return 对象自身 */ public SaSsoClientConfig setIsHttp(Boolean isHttp) { @@ -215,22 +210,6 @@ public class SaSsoClientConfig implements Serializable { return this; } - /** - * @return 配置的 Server 端查询 userinfo 地址 - */ - public String getUserinfoUrl() { - return userinfoUrl; - } - - /** - * @param userinfoUrl 配置 Server 端查询 userinfo 地址 - * @return 对象自身 - */ - public SaSsoClientConfig setUserinfoUrl(String userinfoUrl) { - this.userinfoUrl = userinfoUrl; - return this; - } - /** * @return 配置 Server 端单点注销地址 */ @@ -323,7 +302,6 @@ public class SaSsoClientConfig implements Serializable { + ", authUrl=" + authUrl + ", checkTicketUrl=" + checkTicketUrl + ", getDataUrl=" + getDataUrl - + ", userinfoUrl=" + userinfoUrl + ", sloUrl=" + sloUrl + ", currSsoLogin=" + currSsoLogin + ", currSsoLogoutCall=" + currSsoLogoutCall @@ -356,13 +334,6 @@ public class SaSsoClientConfig implements Serializable { return SaFoxUtil.spliceTwoUrl(getServerUrl(), getGetDataUrl()); } - /** - * @return 获取拼接url:Server 端查询 userinfo 地址 - */ - public String splicingUserinfoUrl() { - return SaFoxUtil.spliceTwoUrl(getServerUrl(), getUserinfoUrl()); - } - /** * @return 获取拼接url:Server 端单点注销地址 */ -- Gitee From cc9fe4c08bd0ca56f0e593ff208ac52581365dd0 Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Mon, 6 May 2024 10:57:57 +0800 Subject: [PATCH 028/172] =?UTF-8?q?=E8=A1=A5=E5=85=A8=E9=81=97=E6=BC=8F?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mvn clean.bat | 48 +++++++++++++++++++----------------------------- 1 file changed, 19 insertions(+), 29 deletions(-) diff --git a/mvn clean.bat b/mvn clean.bat index d4044c83..3f051542 100644 --- a/mvn clean.bat +++ b/mvn clean.bat @@ -2,33 +2,48 @@ :: 整体clean call mvn clean + :: demo模块clean cd sa-token-demo cd sa-token-demo-alone-redis & call mvn clean & cd .. cd sa-token-demo-alone-redis-cluster & call mvn clean & cd .. +cd sa-token-demo-beetl & call mvn clean & cd .. +cd sa-token-demo-bom-import & call mvn clean & cd .. cd sa-token-demo-case & call mvn clean & cd .. cd sa-token-demo-grpc & call mvn clean & cd .. +cd sa-token-demo-hutool-timed-cache & call mvn clean & cd .. cd sa-token-demo-jwt & call mvn clean & cd .. cd sa-token-demo-quick-login & call mvn clean & cd .. cd sa-token-demo-solon & call mvn clean & cd .. +cd sa-token-demo-solon-redisson & call mvn clean & cd .. cd sa-token-demo-springboot & call mvn clean & cd .. cd sa-token-demo-springboot3-redis & call mvn clean & cd .. cd sa-token-demo-springboot-redis & call mvn clean & cd .. cd sa-token-demo-springboot-redisson & call mvn clean & cd .. +cd sa-token-demo-ssm & call mvn clean & cd .. cd sa-token-demo-test & call mvn clean & cd .. cd sa-token-demo-thymeleaf & call mvn clean & cd .. -cd sa-token-demo-beetl & call mvn clean & cd .. cd sa-token-demo-webflux & call mvn clean & cd .. cd sa-token-demo-webflux-springboot3 & call mvn clean & cd .. cd sa-token-demo-websocket & call mvn clean & cd .. cd sa-token-demo-websocket-spring & call mvn clean & cd .. -cd sa-token-demo-bom-import & call mvn clean & cd .. -cd sa-token-demo-hutool-timed-cache & call mvn clean & cd .. -cd sa-token-demo-ssm & call mvn clean & cd .. +cd sa-token-demo-dubbo +cd sa-token-demo-dubbo-consumer & call mvn clean & cd .. +cd sa-token-demo-dubbo-provider & call mvn clean & cd .. +cd sa-token-demo-dubbo3-consumer & call mvn clean & cd .. +cd sa-token-demo-dubbo3-provider & call mvn clean & cd .. +cd .. +cd sa-token-demo-oauth2 +cd sa-token-demo-oauth2-client & call mvn clean & cd .. +cd sa-token-demo-oauth2-server & call mvn clean & cd .. +cd .. +cd sa-token-demo-remember-me +cd sa-token-demo-remember-me-server & call mvn clean & cd .. +cd .. cd sa-token-demo-sso cd sa-token-demo-sso-server & call mvn clean & cd .. @@ -46,31 +61,6 @@ cd sa-token-demo-sso3-client-solon & call mvn clean & cd .. cd sa-token-demo-sso-server-solon & call mvn clean & cd .. cd .. -cd sa-token-demo-oauth2 -cd sa-token-demo-oauth2-client & call mvn clean & cd .. -cd sa-token-demo-oauth2-server & call mvn clean & cd .. -cd .. - -:: cd sa-token-demo-cross -:: cd sa-token-demo-cross-header-server & call mvn clean & cd .. -:: cd sa-token-demo-cross-cookie-server & call mvn clean & cd .. -:: cd .. - -cd sa-token-demo-dubbo -cd sa-token-demo-dubbo-consumer & call mvn clean & cd .. -cd sa-token-demo-dubbo-provider & call mvn clean & cd .. -cd sa-token-demo-dubbo3-consumer & call mvn clean & cd .. -cd sa-token-demo-dubbo3-provider & call mvn clean & cd .. -cd .. - -cd sa-token-demo-remember-me -cd sa-token-demo-remember-me-server & call mvn clean & cd .. -cd .. - - - - - cd .. -- Gitee From ce40fe2dad33abce834d6a659b51e609697c9638 Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Mon, 6 May 2024 12:01:07 +0800 Subject: [PATCH 029/172] v1.38.0 update --- README.md | 2 +- pom.xml | 2 +- sa-token-bom/pom.xml | 2 +- .../cn/dev33/satoken/util/SaTokenConsts.java | 2 +- .../sa-token-demo-alone-redis-cluster/pom.xml | 2 +- .../sa-token-demo-alone-redis/pom.xml | 2 +- sa-token-demo/sa-token-demo-beetl/pom.xml | 2 +- .../sa-token-demo-bom-import/pom.xml | 4 +- sa-token-demo/sa-token-demo-case/pom.xml | 2 +- .../sa-token-demo-dubbo-consumer/pom.xml | 2 +- .../sa-token-demo-dubbo-provider/pom.xml | 2 +- .../sa-token-demo-dubbo3-consumer/pom.xml | 2 +- .../sa-token-demo-dubbo3-provider/pom.xml | 2 +- sa-token-demo/sa-token-demo-grpc/pom.xml | 2 +- .../sa-token-demo-hutool-timed-cache/pom.xml | 2 +- sa-token-demo/sa-token-demo-jwt/pom.xml | 2 +- .../sa-token-demo-oauth2-client/pom.xml | 2 +- .../sa-token-demo-oauth2-server/pom.xml | 2 +- .../sa-token-demo-quick-login/pom.xml | 2 +- .../sa-token-demo-remember-me-server/pom.xml | 2 +- .../sa-token-demo-solon-redisson/pom.xml | 2 +- sa-token-demo/sa-token-demo-solon/pom.xml | 2 +- .../sa-token-demo-springboot-redis/pom.xml | 2 +- .../sa-token-demo-springboot-redisson/pom.xml | 2 +- .../sa-token-demo-springboot/pom.xml | 2 +- .../sa-token-demo-springboot3-redis/pom.xml | 2 +- sa-token-demo/sa-token-demo-ssm/pom.xml | 2 +- .../sa-token-demo-sso-server-solon/pom.xml | 2 +- .../sa-token-demo-sso1-client-solon/pom.xml | 2 +- .../sa-token-demo-sso2-client-solon/pom.xml | 2 +- .../sa-token-demo-sso3-client-solon/pom.xml | 2 +- .../sa-token-demo-sso-server/pom.xml | 2 +- .../sa-token-demo-sso1-client/pom.xml | 2 +- .../sa-token-demo-sso2-client/pom.xml | 2 +- .../sa-token-demo-sso3-client-test2/pom.xml | 2 +- .../sa-token-demo-sso3-client/pom.xml | 2 +- sa-token-demo/sa-token-demo-test/pom.xml | 2 +- sa-token-demo/sa-token-demo-thymeleaf/pom.xml | 2 +- .../sa-token-demo-webflux-springboot3/pom.xml | 2 +- sa-token-demo/sa-token-demo-webflux/pom.xml | 2 +- .../sa-token-demo-websocket-spring/pom.xml | 2 +- sa-token-demo/sa-token-demo-websocket/pom.xml | 2 +- sa-token-dependencies/pom.xml | 2 +- sa-token-doc/README.md | 2 +- sa-token-doc/doc.html | 10 ++-- sa-token-doc/index.html | 2 +- sa-token-doc/more/update-log.md | 46 +++++++++++++++++++ sa-token-doc/start/new-version.md | 4 +- sa-token-doc/up/disable.md | 2 +- sa-token-doc/up/global-filter.md | 2 +- sa-token-doc/up/global-listener.md | 2 +- sa-token-doc/up/many-account.md | 2 +- sa-token-doc/up/mock-person.md | 2 +- sa-token-doc/up/mutex-login.md | 2 +- sa-token-doc/up/not-cookie.md | 2 +- sa-token-doc/up/password-secure.md | 2 +- sa-token-doc/up/remember-me.md | 2 +- sa-token-doc/up/safe-auth.md | 2 +- sa-token-doc/up/search-session.md | 2 +- sa-token-doc/use/at-check.md | 2 +- sa-token-doc/use/jur-auth.md | 2 +- sa-token-doc/use/kick.md | 2 +- sa-token-doc/use/login-auth.md | 2 +- sa-token-doc/use/route-check.md | 2 +- sa-token-doc/use/session.md | 2 +- 65 files changed, 117 insertions(+), 69 deletions(-) diff --git a/README.md b/README.md index f67bf874..55e7a24f 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@

logo

-

Sa-Token v1.37.0

+

Sa-Token v1.38.0

一个轻量级 Java 权限认证框架,让鉴权变得简单、优雅!

diff --git a/pom.xml b/pom.xml index 4e3a16df..9dce4b56 100644 --- a/pom.xml +++ b/pom.xml @@ -37,7 +37,7 @@ - 1.37.0 + 1.38.0 1.8 utf-8 utf-8 diff --git a/sa-token-bom/pom.xml b/sa-token-bom/pom.xml index 3eb7454d..872a1252 100644 --- a/sa-token-bom/pom.xml +++ b/sa-token-bom/pom.xml @@ -13,7 +13,7 @@ https://github.com/dromara/sa-token - 1.37.0 + 1.38.0 diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/util/SaTokenConsts.java b/sa-token-core/src/main/java/cn/dev33/satoken/util/SaTokenConsts.java index 05be26a0..baebafe7 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/util/SaTokenConsts.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/util/SaTokenConsts.java @@ -36,7 +36,7 @@ public class SaTokenConsts { /** * Sa-Token 当前版本号 */ - public static final String VERSION_NO = "v1.37.0"; + public static final String VERSION_NO = "v1.38.0"; /** * Sa-Token 开源地址 Gitee diff --git a/sa-token-demo/sa-token-demo-alone-redis-cluster/pom.xml b/sa-token-demo/sa-token-demo-alone-redis-cluster/pom.xml index 0981fe02..240b63a3 100644 --- a/sa-token-demo/sa-token-demo-alone-redis-cluster/pom.xml +++ b/sa-token-demo/sa-token-demo-alone-redis-cluster/pom.xml @@ -16,7 +16,7 @@ - 1.37.0 + 1.38.0 diff --git a/sa-token-demo/sa-token-demo-alone-redis/pom.xml b/sa-token-demo/sa-token-demo-alone-redis/pom.xml index 21b48f89..8d24f792 100644 --- a/sa-token-demo/sa-token-demo-alone-redis/pom.xml +++ b/sa-token-demo/sa-token-demo-alone-redis/pom.xml @@ -17,7 +17,7 @@ - 1.37.0 + 1.38.0 diff --git a/sa-token-demo/sa-token-demo-beetl/pom.xml b/sa-token-demo/sa-token-demo-beetl/pom.xml index e5d3de42..a51ea399 100644 --- a/sa-token-demo/sa-token-demo-beetl/pom.xml +++ b/sa-token-demo/sa-token-demo-beetl/pom.xml @@ -16,7 +16,7 @@ - 1.37.0 + 1.38.0 diff --git a/sa-token-demo/sa-token-demo-bom-import/pom.xml b/sa-token-demo/sa-token-demo-bom-import/pom.xml index c2dc16f4..0e0ee7f4 100644 --- a/sa-token-demo/sa-token-demo-bom-import/pom.xml +++ b/sa-token-demo/sa-token-demo-bom-import/pom.xml @@ -16,7 +16,7 @@ - 1.37.0 + 1.38.0 @@ -73,7 +73,7 @@ cn.dev33 sa-token-bom - 1.37.0 + 1.38.0 pom import diff --git a/sa-token-demo/sa-token-demo-case/pom.xml b/sa-token-demo/sa-token-demo-case/pom.xml index fb56e1f7..c8972fea 100644 --- a/sa-token-demo/sa-token-demo-case/pom.xml +++ b/sa-token-demo/sa-token-demo-case/pom.xml @@ -17,7 +17,7 @@ - 1.37.0 + 1.38.0 diff --git a/sa-token-demo/sa-token-demo-dubbo/sa-token-demo-dubbo-consumer/pom.xml b/sa-token-demo/sa-token-demo-dubbo/sa-token-demo-dubbo-consumer/pom.xml index fcfdc51d..6f3fd5f4 100644 --- a/sa-token-demo/sa-token-demo-dubbo/sa-token-demo-dubbo-consumer/pom.xml +++ b/sa-token-demo/sa-token-demo-dubbo/sa-token-demo-dubbo-consumer/pom.xml @@ -17,7 +17,7 @@ 1.8 3.1.1 - 1.37.0 + 1.38.0 2.7.21 1.4.2 diff --git a/sa-token-demo/sa-token-demo-dubbo/sa-token-demo-dubbo-provider/pom.xml b/sa-token-demo/sa-token-demo-dubbo/sa-token-demo-dubbo-provider/pom.xml index 2b4ff53a..aeb6f90d 100644 --- a/sa-token-demo/sa-token-demo-dubbo/sa-token-demo-dubbo-provider/pom.xml +++ b/sa-token-demo/sa-token-demo-dubbo/sa-token-demo-dubbo-provider/pom.xml @@ -17,7 +17,7 @@ 1.8 3.1.1 - 1.37.0 + 1.38.0 2.7.21 1.4.2 diff --git a/sa-token-demo/sa-token-demo-dubbo/sa-token-demo-dubbo3-consumer/pom.xml b/sa-token-demo/sa-token-demo-dubbo/sa-token-demo-dubbo3-consumer/pom.xml index 9f55d960..9a7a1af2 100644 --- a/sa-token-demo/sa-token-demo-dubbo/sa-token-demo-dubbo3-consumer/pom.xml +++ b/sa-token-demo/sa-token-demo-dubbo/sa-token-demo-dubbo3-consumer/pom.xml @@ -17,7 +17,7 @@ 1.8 3.1.1 - 1.37.0 + 1.38.0 3.2.2 2.2.2 diff --git a/sa-token-demo/sa-token-demo-dubbo/sa-token-demo-dubbo3-provider/pom.xml b/sa-token-demo/sa-token-demo-dubbo/sa-token-demo-dubbo3-provider/pom.xml index dee3179f..c420e235 100644 --- a/sa-token-demo/sa-token-demo-dubbo/sa-token-demo-dubbo3-provider/pom.xml +++ b/sa-token-demo/sa-token-demo-dubbo/sa-token-demo-dubbo3-provider/pom.xml @@ -17,7 +17,7 @@ 1.8 3.1.1 - 1.37.0 + 1.38.0 3.2.2 2.2.2 diff --git a/sa-token-demo/sa-token-demo-grpc/pom.xml b/sa-token-demo/sa-token-demo-grpc/pom.xml index 5ef2e36c..df0cc5ca 100644 --- a/sa-token-demo/sa-token-demo-grpc/pom.xml +++ b/sa-token-demo/sa-token-demo-grpc/pom.xml @@ -27,7 +27,7 @@ UTF-8 UTF-8 1.18.10 - 1.37.0 + 1.38.0 diff --git a/sa-token-demo/sa-token-demo-hutool-timed-cache/pom.xml b/sa-token-demo/sa-token-demo-hutool-timed-cache/pom.xml index 10250bae..18897f60 100644 --- a/sa-token-demo/sa-token-demo-hutool-timed-cache/pom.xml +++ b/sa-token-demo/sa-token-demo-hutool-timed-cache/pom.xml @@ -17,7 +17,7 @@ - 1.37.0 + 1.38.0 diff --git a/sa-token-demo/sa-token-demo-jwt/pom.xml b/sa-token-demo/sa-token-demo-jwt/pom.xml index bbe04ad1..2359117a 100644 --- a/sa-token-demo/sa-token-demo-jwt/pom.xml +++ b/sa-token-demo/sa-token-demo-jwt/pom.xml @@ -16,7 +16,7 @@ - 1.37.0 + 1.38.0 diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-client/pom.xml b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-client/pom.xml index d4d0e7f6..57ef7f5d 100644 --- a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-client/pom.xml +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-client/pom.xml @@ -17,7 +17,7 @@ 1.8 3.1.1 - 1.37.0 + 1.38.0 diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/pom.xml b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/pom.xml index c4016193..0363705c 100644 --- a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/pom.xml +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/pom.xml @@ -17,7 +17,7 @@ 1.8 3.1.1 - 1.37.0 + 1.38.0 diff --git a/sa-token-demo/sa-token-demo-quick-login/pom.xml b/sa-token-demo/sa-token-demo-quick-login/pom.xml index 8baf9914..51bb3868 100644 --- a/sa-token-demo/sa-token-demo-quick-login/pom.xml +++ b/sa-token-demo/sa-token-demo-quick-login/pom.xml @@ -16,7 +16,7 @@ - 1.37.0 + 1.38.0 diff --git a/sa-token-demo/sa-token-demo-remember-me/sa-token-demo-remember-me-server/pom.xml b/sa-token-demo/sa-token-demo-remember-me/sa-token-demo-remember-me-server/pom.xml index 91a781cc..ccc15d35 100644 --- a/sa-token-demo/sa-token-demo-remember-me/sa-token-demo-remember-me-server/pom.xml +++ b/sa-token-demo/sa-token-demo-remember-me/sa-token-demo-remember-me-server/pom.xml @@ -17,7 +17,7 @@ - 1.37.0 + 1.38.0 diff --git a/sa-token-demo/sa-token-demo-solon-redisson/pom.xml b/sa-token-demo/sa-token-demo-solon-redisson/pom.xml index 5a9fdf68..c08a7b8d 100644 --- a/sa-token-demo/sa-token-demo-solon-redisson/pom.xml +++ b/sa-token-demo/sa-token-demo-solon-redisson/pom.xml @@ -16,7 +16,7 @@ - 1.37.0 + 1.38.0 UTF-8 UTF-8 diff --git a/sa-token-demo/sa-token-demo-solon/pom.xml b/sa-token-demo/sa-token-demo-solon/pom.xml index 4d41d909..01b44aa5 100644 --- a/sa-token-demo/sa-token-demo-solon/pom.xml +++ b/sa-token-demo/sa-token-demo-solon/pom.xml @@ -16,7 +16,7 @@ - 1.37.0 + 1.38.0 UTF-8 UTF-8 diff --git a/sa-token-demo/sa-token-demo-springboot-redis/pom.xml b/sa-token-demo/sa-token-demo-springboot-redis/pom.xml index 8fab3f09..f60c4ed8 100644 --- a/sa-token-demo/sa-token-demo-springboot-redis/pom.xml +++ b/sa-token-demo/sa-token-demo-springboot-redis/pom.xml @@ -17,7 +17,7 @@ - 1.37.0 + 1.38.0 diff --git a/sa-token-demo/sa-token-demo-springboot-redisson/pom.xml b/sa-token-demo/sa-token-demo-springboot-redisson/pom.xml index 6dbb9a1d..b3a373cd 100644 --- a/sa-token-demo/sa-token-demo-springboot-redisson/pom.xml +++ b/sa-token-demo/sa-token-demo-springboot-redisson/pom.xml @@ -17,7 +17,7 @@ - 1.37.0 + 1.38.0 diff --git a/sa-token-demo/sa-token-demo-springboot/pom.xml b/sa-token-demo/sa-token-demo-springboot/pom.xml index d76bfed3..a6f41c64 100644 --- a/sa-token-demo/sa-token-demo-springboot/pom.xml +++ b/sa-token-demo/sa-token-demo-springboot/pom.xml @@ -17,7 +17,7 @@ - 1.37.0 + 1.38.0 diff --git a/sa-token-demo/sa-token-demo-springboot3-redis/pom.xml b/sa-token-demo/sa-token-demo-springboot3-redis/pom.xml index e745f7dd..8cd82921 100644 --- a/sa-token-demo/sa-token-demo-springboot3-redis/pom.xml +++ b/sa-token-demo/sa-token-demo-springboot3-redis/pom.xml @@ -16,7 +16,7 @@ - 1.37.0 + 1.38.0 diff --git a/sa-token-demo/sa-token-demo-ssm/pom.xml b/sa-token-demo/sa-token-demo-ssm/pom.xml index fea3458d..0e35d6e1 100644 --- a/sa-token-demo/sa-token-demo-ssm/pom.xml +++ b/sa-token-demo/sa-token-demo-ssm/pom.xml @@ -27,7 +27,7 @@ 5.3.7 2.16.1 - 1.37.0 + 1.38.0 diff --git a/sa-token-demo/sa-token-demo-sso-for-solon/sa-token-demo-sso-server-solon/pom.xml b/sa-token-demo/sa-token-demo-sso-for-solon/sa-token-demo-sso-server-solon/pom.xml index dd081fd9..a5df50ba 100644 --- a/sa-token-demo/sa-token-demo-sso-for-solon/sa-token-demo-sso-server-solon/pom.xml +++ b/sa-token-demo/sa-token-demo-sso-for-solon/sa-token-demo-sso-server-solon/pom.xml @@ -16,7 +16,7 @@ - 1.37.0 + 1.38.0 2.2.3 diff --git a/sa-token-demo/sa-token-demo-sso-for-solon/sa-token-demo-sso1-client-solon/pom.xml b/sa-token-demo/sa-token-demo-sso-for-solon/sa-token-demo-sso1-client-solon/pom.xml index 4f663aa4..b98ecd47 100644 --- a/sa-token-demo/sa-token-demo-sso-for-solon/sa-token-demo-sso1-client-solon/pom.xml +++ b/sa-token-demo/sa-token-demo-sso-for-solon/sa-token-demo-sso1-client-solon/pom.xml @@ -16,7 +16,7 @@ - 1.37.0 + 1.38.0 diff --git a/sa-token-demo/sa-token-demo-sso-for-solon/sa-token-demo-sso2-client-solon/pom.xml b/sa-token-demo/sa-token-demo-sso-for-solon/sa-token-demo-sso2-client-solon/pom.xml index 91bfb5b0..8ae7b7d4 100644 --- a/sa-token-demo/sa-token-demo-sso-for-solon/sa-token-demo-sso2-client-solon/pom.xml +++ b/sa-token-demo/sa-token-demo-sso-for-solon/sa-token-demo-sso2-client-solon/pom.xml @@ -16,7 +16,7 @@ - 1.37.0 + 1.38.0 diff --git a/sa-token-demo/sa-token-demo-sso-for-solon/sa-token-demo-sso3-client-solon/pom.xml b/sa-token-demo/sa-token-demo-sso-for-solon/sa-token-demo-sso3-client-solon/pom.xml index d07642aa..2750f9cd 100644 --- a/sa-token-demo/sa-token-demo-sso-for-solon/sa-token-demo-sso3-client-solon/pom.xml +++ b/sa-token-demo/sa-token-demo-sso-for-solon/sa-token-demo-sso3-client-solon/pom.xml @@ -16,7 +16,7 @@ - 1.37.0 + 1.38.0 diff --git a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-server/pom.xml b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-server/pom.xml index 5a462047..dc7c6767 100644 --- a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-server/pom.xml +++ b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-server/pom.xml @@ -16,7 +16,7 @@ - 1.37.0 + 1.38.0 diff --git a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso1-client/pom.xml b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso1-client/pom.xml index 38d8109a..50ef2657 100644 --- a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso1-client/pom.xml +++ b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso1-client/pom.xml @@ -16,7 +16,7 @@ - 1.37.0 + 1.38.0 diff --git a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso2-client/pom.xml b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso2-client/pom.xml index e2599ed8..cd026558 100644 --- a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso2-client/pom.xml +++ b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso2-client/pom.xml @@ -16,7 +16,7 @@ - 1.37.0 + 1.38.0 diff --git a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-test2/pom.xml b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-test2/pom.xml index cd61b769..3c32f669 100644 --- a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-test2/pom.xml +++ b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-test2/pom.xml @@ -16,7 +16,7 @@ - 1.37.0 + 1.38.0 diff --git a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client/pom.xml b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client/pom.xml index 8abbcc9c..d45bff0b 100644 --- a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client/pom.xml +++ b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client/pom.xml @@ -16,7 +16,7 @@ - 1.37.0 + 1.38.0 diff --git a/sa-token-demo/sa-token-demo-test/pom.xml b/sa-token-demo/sa-token-demo-test/pom.xml index de93da1f..df740e04 100644 --- a/sa-token-demo/sa-token-demo-test/pom.xml +++ b/sa-token-demo/sa-token-demo-test/pom.xml @@ -18,7 +18,7 @@ - 1.37.0 + 1.38.0 com.pj.SaTokenApplication diff --git a/sa-token-demo/sa-token-demo-thymeleaf/pom.xml b/sa-token-demo/sa-token-demo-thymeleaf/pom.xml index dae8f76f..4175c35c 100644 --- a/sa-token-demo/sa-token-demo-thymeleaf/pom.xml +++ b/sa-token-demo/sa-token-demo-thymeleaf/pom.xml @@ -16,7 +16,7 @@ - 1.37.0 + 1.38.0 diff --git a/sa-token-demo/sa-token-demo-webflux-springboot3/pom.xml b/sa-token-demo/sa-token-demo-webflux-springboot3/pom.xml index 44bf8932..609b90c7 100644 --- a/sa-token-demo/sa-token-demo-webflux-springboot3/pom.xml +++ b/sa-token-demo/sa-token-demo-webflux-springboot3/pom.xml @@ -16,7 +16,7 @@ - 1.37.0 + 1.38.0 diff --git a/sa-token-demo/sa-token-demo-webflux/pom.xml b/sa-token-demo/sa-token-demo-webflux/pom.xml index 2ed92f33..058edaa9 100644 --- a/sa-token-demo/sa-token-demo-webflux/pom.xml +++ b/sa-token-demo/sa-token-demo-webflux/pom.xml @@ -16,7 +16,7 @@ - 1.37.0 + 1.38.0 diff --git a/sa-token-demo/sa-token-demo-websocket-spring/pom.xml b/sa-token-demo/sa-token-demo-websocket-spring/pom.xml index a0c31faa..44134e1c 100644 --- a/sa-token-demo/sa-token-demo-websocket-spring/pom.xml +++ b/sa-token-demo/sa-token-demo-websocket-spring/pom.xml @@ -17,7 +17,7 @@ - 1.37.0 + 1.38.0 diff --git a/sa-token-demo/sa-token-demo-websocket/pom.xml b/sa-token-demo/sa-token-demo-websocket/pom.xml index 4b90b853..a15d39b8 100644 --- a/sa-token-demo/sa-token-demo-websocket/pom.xml +++ b/sa-token-demo/sa-token-demo-websocket/pom.xml @@ -17,7 +17,7 @@ - 1.37.0 + 1.38.0 diff --git a/sa-token-dependencies/pom.xml b/sa-token-dependencies/pom.xml index 5fcdd718..5be7f46f 100644 --- a/sa-token-dependencies/pom.xml +++ b/sa-token-dependencies/pom.xml @@ -12,7 +12,7 @@ Sa-Token Dependencies - 1.37.0 + 1.38.0 2.5.15 diff --git a/sa-token-doc/README.md b/sa-token-doc/README.md index d5c29289..62a4c9b8 100644 --- a/sa-token-doc/README.md +++ b/sa-token-doc/README.md @@ -1,7 +1,7 @@

logo

-

Sa-Token v1.37.0

+

Sa-Token v1.38.0

一个轻量级 Java 权限认证框架,让鉴权变得简单、优雅!

diff --git a/sa-token-doc/doc.html b/sa-token-doc/doc.html index b0a61f7e..d6a3717f 100644 --- a/sa-token-doc/doc.html +++ b/sa-token-doc/doc.html @@ -18,7 +18,7 @@

Sa-Token

- v1.37.0 + v1.38.0
@@ -28,6 +28,7 @@ 密码: - http://sa-oauth-server.com:8001/oauth2/token?grant_type=password&client_id={value}&client_secret={value}&username={value}&password={value} + http://sa-oauth-server.com:8000/oauth2/token?grant_type=password&client_id={value}&client_secret={value}&username={value}&password={value}

模式四:凭证式(Client Credentials)

@@ -87,7 +87,7 @@ 保证了服务的高可用

- http://sa-oauth-server.com:8001/oauth2/client_token?grant_type=client_credentials&client_id={value}&client_secret={value} + http://sa-oauth-server.com:8000/oauth2/client_token?grant_type=client_credentials&client_id={value}&client_secret={value}

更多资料请参考 Sa-Token 官方文档地址: diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/current/GlobalExceptionHandler.java b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/current/GlobalExceptionHandler.java new file mode 100644 index 00000000..f251e478 --- /dev/null +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/current/GlobalExceptionHandler.java @@ -0,0 +1,22 @@ +package com.pj.current; + +import cn.dev33.satoken.util.SaResult; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +/** + * 全局异常处理 + * @author click33 + * + */ +@RestControllerAdvice +public class GlobalExceptionHandler { + + // 全局异常拦截 + @ExceptionHandler + public SaResult handlerException(Exception e) { + e.printStackTrace(); + return SaResult.error(e.getMessage()); + } + +} diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2DataLoaderImpl.java b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2DataLoaderImpl.java index e8358a24..72948364 100644 --- a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2DataLoaderImpl.java +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2DataLoaderImpl.java @@ -12,22 +12,22 @@ import org.springframework.stereotype.Component; @Component public class SaOAuth2DataLoaderImpl implements SaOAuth2DataLoader { - // 根据 client_id 获取 Client 信息 + // 根据 clientId 获取 Client 信息 @Override public SaClientModel getClientModel(String clientId) { // 此为模拟数据,真实环境需要从数据库查询 if("1001".equals(clientId)) { return new SaClientModel() - .setClientId("1001") - .setClientSecret("aaaa-bbbb-cccc-dddd-eeee") - .addAllowUrls("*") - .addContractScopes("openid", "userid", "userinfo") - .setIsAutoMode(true); + .setClientId("1001") // client id + .setClientSecret("aaaa-bbbb-cccc-dddd-eeee") // client 秘钥 + .addAllowUrls("*") // 所有允许授权的 url + .addContractScopes("openid", "userid", "userinfo") // 所有签约的权限 + .setIsAutoMode(true); // 是否自动判断开放的授权模式 } return null; } - // 根据ClientId 和 LoginId 获取openid + // 根据 clientId 和 loginId 获取 openid @Override public String getOpenid(String clientId, Object loginId) { // 此处使用框架默认算法生成 openid,真实环境建议改为从数据库查询 diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java index 03fbbf60..44ddce9d 100644 --- a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java @@ -33,12 +33,13 @@ public class SaOAuth2ServerController { // Sa-OAuth2 定制化配置 @Autowired - public void setSaOAuth2Config(SaOAuth2Config cfg) { + public void configOAuth2Server(SaOAuth2Config cfg) { // 未登录的视图 cfg.notLoginView = ()->{ return new ModelAndView("login.html"); }; - // 登录处理函数 + + // 登录处理函数 cfg.doLoginHandle = (name, pwd) -> { if("sa".equals(name) && "123456".equals(pwd)) { StpUtil.login(10001); @@ -46,11 +47,12 @@ public class SaOAuth2ServerController { } return SaResult.error("账号名或密码错误"); }; - // 授权确认视图 - cfg.confirmView = (clientId, scope)->{ + + // 授权确认视图 + cfg.confirmView = (clientId, scopes)->{ Map map = new HashMap<>(); map.put("clientId", clientId); - map.put("scope", scope); + map.put("scope", scopes); return new ModelAndView("confirm.html", map); }; } diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/resources/application.yml b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/resources/application.yml index f745a8d9..0d263504 100644 --- a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/resources/application.yml +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/resources/application.yml @@ -1,5 +1,5 @@ server: - port: 8001 + port: 8000 # sa-token配置 sa-token: diff --git a/sa-token-doc/_sidebar.md b/sa-token-doc/_sidebar.md index e07e2cc9..e15f364c 100644 --- a/sa-token-doc/_sidebar.md +++ b/sa-token-doc/_sidebar.md @@ -37,7 +37,7 @@ - **单点登录** - [单点登录简述](/sso/readme) - [搭建统一认证中心:SSO-Server](/sso/sso-server) - - [SSO-Server 认证中心开放接口](/sso/sso-apidoc) + - [SSO-Server 认证中心开放 API 接口](/sso/sso-apidoc) - [SSO模式一 共享Cookie同步会话](/sso/sso-type1) - [SSO模式二 URL重定向传播会话](/sso/sso-type2) - [SSO模式三 Http请求获取会话](/sso/sso-type3) @@ -54,9 +54,18 @@ - **OAuth2.0** - [OAuth2.0简述](/oauth2/readme) - [OAuth2-Server搭建](/oauth2/oauth2-server) - - [OAuth2-Server端-API列表](/oauth2/oauth2-api) - - [OAuth2-二次开发说明](/oauth2/oauth2-dev) + - [OAuth2-Server端开放 API 接口](/oauth2/oauth2-apidoc) + - [配置 client 域名校验 ](/oauth2/oauth2-check-domain) + - [定制化登录页面与授权页面](/oauth2/oauth2-custom-login) + - [拆分式路由](/oauth2/3) + - [前后端分离模式整合方案](/oauth2/4) + - [平台中心模式开发](/oauth2/5) + - [自定义 Scope 权限以处理器](/oauth2/6) + - [为 Scope 划分等级](/oauth2/7) - [OAuth2-与登录会话实现数据互通](/oauth2/oauth2-interworking) + - [OAuth2 代码 API 参考](/oauth2/oauth2-dev) + - [常见问题说明](/oauth2/8) + - - **微服务** - [分布式Session会话](/micro/dcs-session) diff --git a/sa-token-doc/fun/sso-vs-oauth2.md b/sa-token-doc/fun/sso-vs-oauth2.md index a3cd5764..e8dd8b0f 100644 --- a/sa-token-doc/fun/sso-vs-oauth2.md +++ b/sa-token-doc/fun/sso-vs-oauth2.md @@ -14,6 +14,7 @@ QQ群库经常有小伙伴提问:项目需要搭建统一认证中心,是用 | 自有系统授权管理 | 支持度高 | 支持度低 | | Client级的权限校验 | 不支持 | 支持度高 | | 集成简易度 | 比较简单 | 难度中等 | +| 适合项目 | 企业内部项目整合 | 企业搭建统一认证授权平台,对外开放服务 | 注:以上仅为在 Sa-Token 中两种技术的差异度比较,不同框架的实现可能略有差异,但整体思想是一致的。 diff --git a/sa-token-doc/oauth2/oauth2-api.md b/sa-token-doc/oauth2/oauth2-apidoc.md similarity index 40% rename from sa-token-doc/oauth2/oauth2-api.md rename to sa-token-doc/oauth2/oauth2-apidoc.md index a214c271..eedcdf04 100644 --- a/sa-token-doc/oauth2/oauth2-api.md +++ b/sa-token-doc/oauth2/oauth2-apidoc.md @@ -9,75 +9,79 @@ 根据以下格式构建URL,引导用户访问 (复制时请注意删减掉相应空格和换行符) ``` url -http://sa-oauth-server.com:8001/oauth2/authorize +http://{host}:{port}/oauth2/authorize ?response_type=code - &client_id={value} - &redirect_uri={value} - &scope={value} - &state={value} + &client_id={client_id} + &redirect_uri={redirect_uri} + &scope={scope} + &state={state} ``` 参数详解: | 参数 | 是否必填 | 说明 | | :-------- | :-------- | :-------- | -| response_type | 是 | 返回类型,这里请填写:code | -| client_id | 是 | 应用id | -| redirect_uri | 是 | 用户确认授权后,重定向的url地址 | -| scope | 否 | 具体请求的权限,多个用逗号隔开 | -| state | 否 | 随机值,此参数会在重定向时追加到url末尾,不填不追加 | +| response_type | 是 | 返回类型,这里请填写:`code` | +| client_id | 是 | 应用 id | +| redirect_uri | 是 | 用户确认授权后,重定向的 url 地址 | +| scope | 否 | 具体请求的权限,多个用逗号(或空格)隔开 | +| state | 否 | 随机值,此参数会在重定向时追加到url末尾,不填不追加,如果填写则每次填写的值不可以重复 | 注意点: -1. 如果用户在Server端尚未登录:会被转发到登录视图,你可以参照文档或官方示例自定义登录页面 -2. 如果scope参数为空,或者请求的权限用户近期已确认过,则无需用户再次确认,达到静默授权的效果,否则需要用户手动确认,服务器才可以下放code授权码 +1. 如果用户在 `OAuth-Server` 端尚未登录:会被转发到登录视图,你可以参照文档或官方示例自定义登录页面。 +2. 如果 `scope` 参数为空,或者请求的 `scope` 用户近期已确认授权过,则无需用户再次确认,达到静默授权的效果,否则需要用户手动确认,服务器才可以下放 `code` 授权码。 -用户确认授权之后,会被重定向至`redirect_uri`,并追加code参数与state参数,形如: +用户确认授权之后,会被重定向至`redirect_uri`,并追加 `code` 参数与 `state` 参数,形如: ``` url redirect_uri?code={code}&state={state} ``` -Code授权码具有以下特点: -1. 每次授权产生的Code码都不一样 -2. Code码用完即废,不能二次使用 -3. 一个Code的有效期默认为五分钟,超时自动作废 -4. 每次授权产生新Code码,会导致旧Code码立即作废,即使旧Code码尚未使用 +`Code` 授权码具有以下特点: +1. 每次授权产生的 `Code` 码都不一样。 +2. `Code` 码用完即废,不能二次使用。 +3. 一个 `Code` 的有效期默认为五分钟,超时自动作废。 +4. 每次授权产生新 `Code` 码,会导致旧 `Code` 码立即作废,即使旧 `Code` 码尚未使用。 -### 1.2、根据授权码获取Access-Token -获得Code码后,我们可以通过以下接口,获取到用户的`Access-Token`、`Refresh-Token`、`openid`等关键信息 +### 1.2、根据授权码获取 Access-Token +获得 `Code` 码后,我们可以通过以下接口,获取到用户的 `Access-Token`、`Refresh-Token` 等信息。 ``` url -http://sa-oauth-server.com:8001/oauth2/token +http://{host}:{port}/oauth2/token ?grant_type=authorization_code - &client_id={value} - &client_secret={value} - &code={value} + &client_id={client_id} + &client_secret={client_secret} + &code={code} ``` 参数详解: | 参数 | 是否必填 | 说明 | | :-------- | :-------- | :-------- | -| grant_type | 是 | 授权类型,这里请填写:authorization_code | -| client_id | 是 | 应用id | +| grant_type | 是 | 授权类型,这里请填写:`authorization_code` | +| client_id | 是 | 应用 id | | client_secret | 是 | 应用秘钥 | -| code | 是 | 步骤1.1中获取到的授权码 | +| code | 是 | 步骤 1.1 中获取到的授权码 | + +也可以通过 `Basic Authorization` 方式提交 `client` 信息,格式为在请求 `header` 头添加 `Authorization` 参数: +``` js +header['Authorization'] = base64(`${client_id}:${client_secret}`); +``` 接口返回示例: ``` js { - "code": 200, // 200表示请求成功,非200标识请求失败, 以下不再赘述 + "code": 200, // 200表示请求成功,非200标识请求失败, 以下不再赘述 "msg": "ok", - "data": { - "access_token": "7Ngo1Igg6rieWwAmWMe4cxT7j8o46mjyuabuwLETuAoN6JpPzPO2i3PVpEVJ", // Access-Token值 - "refresh_token": "ZMG7QbuCVtCIn1FAJuDbgEjsoXt5Kqzii9zsPeyahAmoir893ARA4rbmeR66", // Refresh-Token值 - "expires_in": 7199, // Access-Token剩余有效期,单位秒 - "refresh_expires_in": 2591999, // Refresh-Token剩余有效期,单位秒 - "client_id": "1001", // 应用id - "scope": "userinfo", // 此令牌包含的权限 - "openid": "gr_SwoIN0MC1ewxHX_vfCW3BothWDZMMtx__" // openid - } + "data": null, + "token_type": "bearer", + "access_token": "Gly7mnnXSdCxkOqmOwcA5SbG6ZtPmJVX7ZgSn1pidhRmnenBEgxbWJS8VWxA", // Access-Token值 + "refresh_token": "EuYNwpxdc18MpaZLPyhFeyAyzr2IOWEr4q3QUGgPWqdJujQqvohjQEDJpwOm", // Refresh-Token值 + "expires_in": 7199, // Access-Token剩余有效期,单位秒 + "refresh_expires_in": 2591999, // Refresh-Token剩余有效期,单位秒 + "client_id": "1001", // 应用 id + "scope": "userinfo" // 此令牌包含的权限 } ``` @@ -86,40 +90,40 @@ http://sa-oauth-server.com:8001/oauth2/token Access-Token的有效期较短,如果每次过期都需要重新授权的话,会比较影响用户体验,因此我们可以在后台通过`Refresh-Token` 刷新 `Access-Token` ``` url -http://sa-oauth-server.com:8001/oauth2/refresh +http://{host}:{port}/oauth2/refresh ?grant_type=refresh_token - &client_id={value} - &client_secret={value} - &refresh_token={value} + &client_id={client_id} + &client_secret={client_secret} + &refresh_token={refresh_token} ``` 参数详解: | 参数 | 是否必填 | 说明 | | :-------- | :-------- | :-------- | -| grant_type | 是 | 授权类型,这里请填写:refresh_token | -| client_id | 是 | 应用id | +| grant_type | 是 | 授权类型,这里请填写:`refresh_token` | +| client_id | 是 | 应用 id | | client_secret | 是 | 应用秘钥 | -| refresh_token | 是 | 步骤1.2中获取到的`Refresh-Token`值 | +| refresh_token | 是 | 步骤1.2中获取到的 `Refresh-Token` 值 | 接口返回值同章节1.2,此处不再赘述 ### 1.4、回收 Access-Token (如果需要的话) -在Access-Token过期前主动将其回收 +在A ccess-Token 过期之前主动将其回收 ``` url -http://sa-oauth-server.com:8001/oauth2/revoke - ?client_id={value} - &client_secret={value} - &access_token={value} +http://{host}:{port}/oauth2/revoke + ?client_id={client_id} + &client_secret={client_secret} + &access_token={access_token} ``` 参数详解: | 参数 | 是否必填 | 说明 | | :-------- | :-------- | :-------- | -| client_id | 是 | 应用id | +| client_id | 是 | 应用 id | | client_secret | 是 | 应用秘钥 | | access_token | 是 | 步骤1.2中获取到的`Access-Token`值 | @@ -137,7 +141,7 @@ http://sa-oauth-server.com:8001/oauth2/revoke 注:此接口为官方仓库模拟接口,正式项目中大家可以根据此样例,自定义需要的接口及参数 ``` url -http://sa-oauth-server.com:8001/oauth2/userinfo?access_token={value} +http://{host}:{port}/oauth2/userinfo?access_token={access_token} ``` 返回值样例: @@ -145,13 +149,11 @@ http://sa-oauth-server.com:8001/oauth2/userinfo?access_token={value} { "code": 200, "msg": "ok", - "data": { - "nickname": "shengzhang_", // 账号昵称 - "avatar": "http://xxx.com/1.jpg", // 头像地址 - "age": "18", // 年龄 - "sex": "男", // 性别 - "address": "山东省 青岛市 城阳区" // 所在城市 - } + "nickname": "shengzhang_", // 账号昵称 + "avatar": "http://xxx.com/1.jpg", // 头像地址 + "age": "18", // 年龄 + "sex": "男", // 性别 + "address": "山东省 青岛市 城阳区" // 所在城市 } ``` @@ -160,50 +162,53 @@ http://sa-oauth-server.com:8001/oauth2/userinfo?access_token={value} 根据以下格式构建URL,引导用户访问: ``` url -http://sa-oauth-server.com:8001/oauth2/authorize +http://{host}:{port}/oauth2/authorize ?response_type=token - &client_id={value} - &redirect_uri={value} - &scope={value} - $state={value} + &client_id={client_id} + &redirect_uri={redirect_uri} + &scope={scope} + &state={state} ``` 参数详解: | 参数 | 是否必填 | 说明 | | :-------- | :-------- | :-------- | -| response_type | 是 | 返回类型,这里请填写:token | -| client_id | 是 | 应用id | +| response_type | 是 | 返回类型,这里请填写:`token` | +| client_id | 是 | 应用 id | | redirect_uri | 是 | 用户确认授权后,重定向的url地址 | -| scope | 否 | 具体请求的权限,多个用逗号隔开 | -| state | 否 | 随机值,此参数会在重定向时追加到url末尾,不填不追加 | +| scope | 否 | 具体请求的权限,多个用逗号(或空格)隔开 | +| state | 否 | 随机值,此参数会在重定向时追加到url末尾,不填不追加,如果填写则每次填写的值不可以重复 | -此模式会越过授权码的步骤,直接返回Access-Token到前端页面,形如: +此模式会越过授权码的步骤,直接返回 `Access-Token` 到前端页面,形如: ``` url redirect_uri#token=xxxx-xxxx-xxxx-xxxx ``` +注意 token 是以 `#` 锚参数的形式拼接到 url 上的。 + ## 3、模式三:密码式(Password) -首先在Client端构建表单,让用户输入Server端的账号和密码,然后在Client端访问接口 +首先在Client端构建表单,让用户输入 Server 端的账号和密码,然后在 Client 端访问接口 ``` url -http://sa-oauth-server.com:8001/oauth2/token +http://{host}:{port}/oauth2/token ?grant_type=password - &client_id={value} - &client_secret={value} - &username={value} - &password={value} + &client_id={client_id} + &client_secret={client_secret} + &username={username} + &password={password} + &scope={scope} ``` 参数详解: | 参数 | 是否必填 | 说明 | | :-------- | :-------- | :-------- | -| grant_type | 是 | 返回类型,这里请填写:password| -| client_id | 是 | 应用id | +| grant_type | 是 | 返回类型,这里请填写:`password`| +| client_id | 是 | 应用 id | | client_secret | 是 | 应用秘钥 | -| username | 是 | 用户的Server端账号 | -| password | 是 | 用户的Server端密码 | -| scope | 否 | 具体请求的权限,多个用逗号隔开 | +| username | 是 | 用户的 `OAuth2-Server` 端账号 | +| password | 是 | 用户的 `OAuth2-Server` 端密码 | +| scope | 否 | 具体请求的权限,多个用逗号(或空格)隔开 | 接口返回示例: @@ -211,15 +216,12 @@ http://sa-oauth-server.com:8001/oauth2/token { "code": 200, // 200表示请求成功,非200标识请求失败, 以下不再赘述 "msg": "ok", - "data": { - "access_token": "7Ngo1Igg6rieWwAmWMe4cxT7j8o46mjyuabuwLETuAoN6JpPzPO2i3PVpEVJ", // Access-Token值 - "refresh_token": "ZMG7QbuCVtCIn1FAJuDbgEjsoXt5Kqzii9zsPeyahAmoir893ARA4rbmeR66", // Refresh-Token值 - "expires_in": 7199, // Access-Token剩余有效期,单位秒 - "refresh_expires_in": 2591999, // Refresh-Token剩余有效期,单位秒 - "client_id": "1001", // 应用id - "scope": "", // 此令牌包含的权限 - "openid": "gr_SwoIN0MC1ewxHX_vfCW3BothWDZMMtx__" // openid - } + "access_token": "7Ngo1Igg6rieWwAmWMe4cxT7j8o46mjyuabuwLETuAoN6JpPzPO2i3PVpEVJ", // Access-Token 值 + "refresh_token": "ZMG7QbuCVtCIn1FAJuDbgEjsoXt5Kqzii9zsPeyahAmoir893ARA4rbmeR66", // Refresh-Token 值 + "expires_in": 7199, // Access-Token 剩余有效期,单位秒 + "refresh_expires_in": 2591999, // Refresh-Token 剩余有效期,单位秒 + "client_id": "1001", // 应用 id + "scope": "", // 此令牌包含的权限 } ``` @@ -228,38 +230,37 @@ http://sa-oauth-server.com:8001/oauth2/token 以上三种模式获取的都是用户的 `Access-Token`,代表用户对第三方应用的授权, 在OAuth2.0中还有一种针对 Client级别的授权, 即:`Client-Token`,代表应用自身的资源授权 -在Client端的后台访问以下接口: +在 Client 端的后台访问以下接口: ``` url -http://sa-oauth-server.com:8001/oauth2/client_token +http://{host}:{port}/oauth2/client_token ?grant_type=client_credentials - &client_id={value} - &client_secret={value} + &client_id={client_id} + &client_secret={client_secret} + &scope={scope} ``` 参数详解: | 参数 | 是否必填 | 说明 | | :-------- | :-------- | :-------- | -| grant_type | 是 | 返回类型,这里请填写:client_credentials| -| client_id | 是 | 应用id | +| grant_type | 是 | 返回类型,这里请填写:`client_credentials`| +| client_id | 是 | 应用 id | | client_secret | 是 | 应用秘钥 | -| scope | 否 | 申请权限 | +| scope | 否 | 具体请求的权限,多个用逗号(或空格)隔开 | 接口返回值样例: ``` js { "code": 200, "msg": "ok", - "data": { - "client_token": "HmzPtaNuIqGrOdudWLzKJRSfPadN497qEJtanYwE7ZvHQWDy0jeoZJuDIiqO", // Client-Token 值 - "expires_in": 7199, // Token剩余有效时间,单位秒 - "client_id": "1001", // 应用id - "scope": null // 包含权限 - } + "client_token": "HmzPtaNuIqGrOdudWLzKJRSfPadN497qEJtanYwE7ZvHQWDy0jeoZJuDIiqO", // Client-Token 值 + "expires_in": 7199, // Token剩余有效时间,单位秒 + "client_id": "1001", // 应用 id + "scope": null // 包含权限 } ``` 注:`Client-Token`具有延迟作废特性,即:在每次获取最新`Client-Token`的时候,旧`Client-Token`不会立即过期,而是作为`Past-Token`再次储存起来, -资源请求方只要携带其中之一便可通过Token校验,这种特性保证了在大量并发请求时不会出现“新旧Token交替造成的授权失效”, 保证了服务的高可用 +资源请求方只要携带其中之一便可通过Token校验,这种特性保证了在大量并发请求时不会出现“新旧Token交替造成的授权失效”, 保证了服务的高可用。 diff --git a/sa-token-doc/oauth2/oauth2-check-domain.md b/sa-token-doc/oauth2/oauth2-check-domain.md new file mode 100644 index 00000000..f1cca9e3 --- /dev/null +++ b/sa-token-doc/oauth2/oauth2-check-domain.md @@ -0,0 +1,93 @@ +# OAuth2 整合-配置域名校验 + +--- + +### 1、code 劫持攻击 +在前面章节的 OAuth-Server 搭建示例中: + +``` java +@Component +public class SaOAuth2DataLoaderImpl implements SaOAuth2DataLoader { + // 根据 clientId 获取 Client 信息 + @Override + public SaClientModel getClientModel(String clientId) { + if("1001".equals(clientId)) { + return new SaClientModel() + // ... + .addAllowUrls("*") // 所有允许授权的 url + // ... + } + return null; + } + // 其它代码 ... +} +``` + +配置项 `AllowUrls` 意为配置此 `Client` 端所有允许的授权地址,不在此配置项中的 URL 将无法下发 `code` 授权码。 + +为了方便测试,上述代码将其配置为`*`,但是,在生产环境中,此配置项绝对不能配置为 * ,否则会有被 `code` 劫持的风险。 + +假设攻击者根据模仿我们的授权地址,巧妙的构造一个URL: + +> [http://sa-oauth-server.com:8000/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=https://www.baidu.com](http://sa-oauth-server.com:8000/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=https://www.baidu.com) + +当不知情的小红被诱导访问了这个 URL 时,它将被重定向至百度首页。 + +![oauth2-ticket-jc](https://oss.dev33.cn/sa-token/doc/oauth2-new/oauth2-ticket-jc.png 's-w-sh') + +可以看到,代表着用户身份的 code 授权码也显现到了URL之中,借此漏洞,攻击者完全可以构建一个 URL 将小红的 code 授权码自动提交到攻击者自己的服务器,伪造小红身份登录网站。 + + +### 2、防范方法 + +造成此漏洞的直接原因就是我们对此 client 配置了过于宽泛的 `AllowUrls` 允许授权地址,防范的方法也很简单,就是缩小 `AllowUrls` 授权范围。 + +我们将其配置为一个具体的URL: + +``` java +@Component +public class SaOAuth2DataLoaderImpl implements SaOAuth2DataLoader { + // 根据 clientId 获取 Client 信息 + @Override + public SaClientModel getClientModel(String clientId) { + if("1001".equals(clientId)) { + return new SaClientModel() + // ... + .addAllowUrls("http://sa-oauth-client.com:8002/") // 所有允许授权的 url + // ... + } + return null; + } + // 其它代码 ... +} +``` + +再次访问上述链接: + +![oauth2-feifa-rf](https://oss.dev33.cn/sa-token/doc/oauth2-new/oauth2-feifa-rf.png 's-w-sh') + +域名没有通过校验,拒绝授权! + + +### 3、配置安全性参考表 + +| 配置方式 | 举例 | 安全性 | 建议 | +| :-------- | :-------- | :-------- | :-------- | +| 配置为* | `*` | | **禁止在生产环境下使用** | +| 配置到域名 | `http://sa-oauth-client.com:8002/*` | | 不建议在生产环境下使用 | +| 配置到详细地址 | `http://sa-oauth-client.com:8002/xxx/xxx` | | 可以在生产环境下使用 | + + +### 4、其它规则 + +1、AllowUrls 配置的地址不允许出现 `@` 字符。 +*详见源码:[SaOAuth2Template.java](https://gitee.com/dromara/sa-token/blob/master/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java) +`checkRightUrl` 方法。* + +2、AllowUrls 配置的地址 `*` 通配符只允许出现在字符串末尾,不允许出现在字符串中间位置。 +*详见源码: [SaOAuth2Template.java](https://gitee.com/dromara/sa-token/blob/master/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java) +`checkAllowUrlListStaticMethod` 方法。* + +参考:[github/issue/529](https://github.com/dromara/Sa-Token/issues/529) +感谢这位 `@m4ra7h0n` 用户反馈的漏洞。 + diff --git a/sa-token-doc/oauth2/oauth2-custom-login.md b/sa-token-doc/oauth2/oauth2-custom-login.md new file mode 100644 index 00000000..25cb0bb1 --- /dev/null +++ b/sa-token-doc/oauth2/oauth2-custom-login.md @@ -0,0 +1,94 @@ +# OAuth2 定制化登录页面 + +--- + + + +### 1、如何自定义 OAuth-Server 端的登录视图? + +重写 `cfg.notLoginView` 策略: + +``` java +@Autowired +public void configOAuth2Server(SaOAuth2Config cfg) { + // 配置:未登录时返回的View + cfg.notLoginView = ()->{ + return new ModelAndView("xxx.html"); + }; +} +``` + +在以上返回的视图中 ajax 方式调用 `/oauth2/doLogin` 接口,该接口接受以下参数: + +| 参数 | 是否必填 | 说明 | +| :-------- | :-------- | :-------- | +| name | 否 | 账号 | +| pwd | 否 | 密码 | + +接口返回值根据你重写的 `cfg.doLoginHandle` 策略进行自由决定。 + + + +### 2、如何自定义登录API的接口地址? +根据需求点选择解决方案: + +#### 2.1、如果只是想在 doLoginHandle 函数里获取除 name、pwd 以外的参数? +``` java +// 在任意代码处获取前端提交的参数 +String xxx = SaHolder.getRequest().getParam("xxx"); +``` + +#### 2.2、想完全自定义一个接口来接受前端登录请求? +``` java +// 直接定义一个拦截路由为 `/oauth2/doLogin` 的接口即可 +@RequestMapping("/oauth2/doLogin") +public SaResult ss(String name, String pwd) { + System.out.println("------ 请求进入了自定义的API接口 ---------- "); + if("sa".equals(name) && "123456".equals(pwd)) { + StpUtil.login(10001); + return SaResult.ok("登录成功!"); + } + return SaResult.error("登录失败!"); +} +``` + +#### 2.3、不想使用`/oauth2/doLogin`这个接口,想自定义一个API地址? + +答:直接在前端更改点击按钮时 Ajax 的请求地址即可 + + + +### 3、如何自定义 OAuth-Server 端的确认授权视图? + +重写 `cfg.confirmView` 策略: + +``` java +@Autowired +public void configOAuth2Server(SaOAuth2Config cfg) { + // 配置:授权确认视图 + cfg.confirmView = (clientId, scopes)->{ + Map map = new HashMap<>(); + map.put("clientId", clientId); + map.put("scope", scopes); + return new ModelAndView("confirm.html", map); + }; +} +``` + +在以上返回的视图中 ajax 方式调用 `/oauth2/doConfirm` 接口,即可完成授权,该接口接受以下参数: + +| 参数 | 是否必填 | 说明 | +| :-------- | :-------- | :-------- | +| client_id | 是 | 应用 id | +| scope | 是 | 具体授予的权限,多个用逗号(或空格)隔开 | + +接口返回值样例: +``` js +{ + "code": 200, + "msg": "ok", + "data": null, +} +``` + + diff --git a/sa-token-doc/oauth2/oauth2-server.md b/sa-token-doc/oauth2/oauth2-server.md index 9f2b631f..ad4a1724 100644 --- a/sa-token-doc/oauth2/oauth2-server.md +++ b/sa-token-doc/oauth2/oauth2-server.md @@ -11,7 +11,7 @@ ### 2、引入依赖 -创建SpringBoot项目 `sa-token-demo-oauth2-server`(不会的同学自行百度或参考仓库示例),添加pom依赖: +创建SpringBoot项目 `sa-token-demo-oauth2-server`(不会的同学自行百度或参考仓库示例),引入 `pom.xml` 依赖: @@ -43,36 +43,37 @@ implementation 'cn.dev33:sa-token-oauth2:${sa.top.version}' ### 3、开放服务 -1、新建 `SaOAuth2TemplateImpl` +1、自定义数据加载器:新建 `SaOAuth2DataLoaderImpl` 实现 `SaOAuth2DataLoader` 接口。 + ``` java /** - * Sa-Token OAuth2.0 整合实现 + * Sa-Token OAuth2:自定义数据加载器 */ @Component -public class SaOAuth2TemplateImpl extends SaOAuth2Template { +public class SaOAuth2DataLoaderImpl implements SaOAuth2DataLoader { - // 根据 id 获取 Client 信息 + // 根据 clientId 获取 Client 信息 @Override public SaClientModel getClientModel(String clientId) { // 此为模拟数据,真实环境需要从数据库查询 if("1001".equals(clientId)) { return new SaClientModel() - .setClientId("1001") - .setClientSecret("aaaa-bbbb-cccc-dddd-eeee") - .setAllowUrl("*") - .setContractScope("userinfo") - .setIsAutoMode(true); + .setClientId("1001") // client id + .setClientSecret("aaaa-bbbb-cccc-dddd-eeee") // client 秘钥 + .addAllowUrls("*") // 所有允许授权的 url + .addContractScopes("openid", "userid", "userinfo") // 所有签约的权限 + .setIsAutoMode(true); // 是否自动判断开放的授权模式 } return null; } - // 根据ClientId 和 LoginId 获取openid + // 根据 clientId 和 loginId 获取 openid @Override public String getOpenid(String clientId, Object loginId) { - // 此为模拟数据,真实环境需要从数据库查询 - return "gr_SwoIN0MC1ewxHX_vfCW3BothWDZMMtx__"; + // 此处使用框架默认算法生成 openid,真实环境建议改为从数据库查询 + return SaOAuth2DataLoader.super.getOpenid(clientId, loginId); } - + } ``` @@ -97,30 +98,32 @@ public class SaOAuth2ServerController { // Sa-OAuth2 定制化配置 @Autowired public void setSaOAuth2Config(SaOAuth2Config cfg) { - cfg. - // 配置:未登录时返回的View - setNotLoginView(() -> { - String msg = "当前会话在OAuth-Server端尚未登录,请先访问" - + " doLogin登录 " - + "进行登录之后,刷新页面开始授权"; - return msg; - }). - // 配置:登录处理函数 - setDoLoginHandle((name, pwd) -> { - if("sa".equals(name) && "123456".equals(pwd)) { - StpUtil.login(10001); - return SaResult.ok(); - } - return SaResult.error("账号名或密码错误"); - }). - // 配置:确认授权时返回的View - setConfirmView((clientId, scope) -> { - String msg = "

应用 " + clientId + " 请求授权:" + scope + "

" - + "

请确认: 确认授权

" - + "

确认之后刷新页面

"; - return msg; - }) - ; + + // 配置:未登录时返回的View + cfg.notLoginView = () -> { + String msg = "当前会话在OAuth-Server端尚未登录,请先访问" + + " doLogin登录 " + + "进行登录之后,刷新页面开始授权"; + return msg; + }; + + // 配置:登录处理函数 + cfg.doLoginHandle = (name, pwd) -> { + if("sa".equals(name) && "123456".equals(pwd)) { + StpUtil.login(10001); + return SaResult.ok(); + } + return SaResult.error("账号名或密码错误"); + }; + + // 配置:确认授权时返回的 view + cfg.confirmView = (clientId, scopes) -> { + String scopeStr = SaFoxUtil.convertListToString(scopes); + String msg = "

应用 " + clientId + " 请求授权:" + scopeStr + "

" + + "

请确认: 确认授权

" + + "

确认之后刷新页面

"; + return msg; + }; } // 全局异常拦截 @@ -134,7 +137,19 @@ public class SaOAuth2ServerController { ``` 注意:在`setDoLoginHandle`函数里如果要获取name, pwd以外的参数,可通过`SaHolder.getRequest().getParam("xxx")`来获取 -3、创建启动类: +3、全局异常处理 +``` java +@RestControllerAdvice +public class GlobalExceptionHandler { + @ExceptionHandler + public SaResult handlerException(Exception e) { + e.printStackTrace(); + return SaResult.error(e.getMessage()); + } +} +``` + +4、创建启动类: ``` java /** * 启动:Sa-OAuth2 Server端 @@ -154,28 +169,44 @@ public class SaOAuth2ServerApplication { 1、由于暂未搭建Client端,我们可以使用Sa-Token官网作为重定向URL进行测试: ``` url -http://sa-oauth-server.com:8001/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=https://sa-token.cc&scope=userinfo +http://sa-oauth-server.com:8000/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=https://sa-token.cc&scope=openid ``` 2、由于首次访问,我们在OAuth-Server端暂未登录,会被转发到登录视图 -![sa-oauth2-server-login-view](https://oss.dev33.cn/sa-token/doc/oauth2/sa-oauth2-server-login-view.png 's-w-sh') +![sa-oauth2-server-login-view](https://oss.dev33.cn/sa-token/doc/oauth2-new/sa-oauth2-server-login-view.png 's-w-sh') 3、点击doLogin进行登录之后刷新页面,会提示我们确认授权 -![sa-oauth2-server-login-view](https://oss.dev33.cn/sa-token/doc/oauth2/sa-oauth2-server-scope.png 's-w-sh') +![sa-oauth2-server-scope](https://oss.dev33.cn/sa-token/doc/oauth2-new/sa-oauth2-server-scope.png 's-w-sh') 4、点击确认授权之后刷新页面,我们会被重定向至 redirect_uri 页面,并携带了code参数 -![sa-oauth2-server-code](https://oss.dev33.cn/sa-token/doc/oauth2/sa-oauth2-server-code.png 's-w-sh') +![sa-oauth2-server-code](https://oss.dev33.cn/sa-token/doc/oauth2-new/sa-oauth2-server-code.png 's-w-sh') 4、我们拿着code参数,访问以下地址: ``` url -http://sa-oauth-server.com:8001/oauth2/token?grant_type=authorization_code&client_id=1001&client_secret=aaaa-bbbb-cccc-dddd-eeee&code={code} +http://sa-oauth-server.com:8000/oauth2/token?grant_type=authorization_code&client_id=1001&client_secret=aaaa-bbbb-cccc-dddd-eeee&code={code} ``` 将得到 `Access-Token`、`Refresh-Token`、`openid`等授权信息 -![sa-oauth2-server-token](https://oss.dev33.cn/sa-token/doc/oauth2/sa-oauth2-server-token.png 's-w-sh') +``` js +{ + "code": 200, + "msg": "ok", + "data": null, + "token_type": "bearer", + "access_token": "cAls8jnBLmeo5yuCUMwb8zxaSsQPPzGINXF3NOCjCqFHplr6hagdT6A5HeR2", + "refresh_token": "L2rPbJ3aaOXwaB4Zu0EGWNz5EjVNpw5u2oMP9CS2IEap7rR3Hb76ZqqHS07J", + "expires_in": 7199, + "refresh_expires_in": 2591999, + "client_id": "1001", + "scope": "openid", + "openid": "ded91dc189a437dd1bac2274be167d50" +} +``` + + 测试完毕 @@ -188,7 +219,7 @@ http://sa-oauth-server.com:8001/oauth2/token?grant_type=authorization_code&clien 依次启动`OAuth2-Server` 与 `OAuth2-Client`,然后从浏览器访问:[http://sa-oauth-client.com:8002](http://sa-oauth-client.com:8002) -![sa-oauth2-client-index](https://oss.dev33.cn/sa-token/doc/oauth2/sa-oauth2-client-index.png 's-w-sh') +![sa-oauth2-client-index](https://oss.dev33.cn/sa-token/doc/oauth2-new/sa-oauth2-client-index.png 's-w-sh') 如图,可以针对OAuth2.0四种模式进行详细测试 diff --git a/sa-token-doc/oauth2/readme.md b/sa-token-doc/oauth2/readme.md index 59d9e4b0..5776fb42 100644 --- a/sa-token-doc/oauth2/readme.md +++ b/sa-token-doc/oauth2/readme.md @@ -2,12 +2,12 @@ --- -### 什么是OAuth2.0?解决什么问题? +### 什么是 OAuth2.0 ?解决什么问题? -简单来讲,OAuth2.0的应用场景可以理解为单点登录的升级版,单点登录解决了多个系统间会话的共享,OAuth2.0在此基础上增加了应用之间的权限控制 -(SO:有些系统采用OAuth2.0模式实现了单点登录,但这总给人一种“杀鸡焉用宰牛刀”的感觉) +简单来讲,OAuth2.0的应用场景可以理解为单点登录的升级版,单点登录解决了多个系统间会话的共享,OAuth2.0 在此基础上增加了应用之间的权限控制 +(SO:有些系统采用 OAuth2.0 模式实现了单点登录,但这总给人一种 “杀鸡焉用宰牛刀” 的感觉) -有关OAuth2.0的设计思想网上教程较多,此处不再重复赘述,详细可参考博客: +有关 OAuth2.0 的设计思想网上教程较多,此处不再重复赘述,详细可参考博客: [OAuth2.0 简单解释](https://www.ruanyifeng.com/blog/2019/04/oauth_design.html) @@ -19,14 +19,40 @@ 基于不同的使用场景,OAuth2.0设计了四种模式: -1. 授权码(Authorization Code):OAuth2.0标准授权步骤,Server端向Client端下放Code码,Client端再用Code码换取授权Token -2. 隐藏式(Implicit):无法使用授权码模式时的备用选择,Server端使用URL重定向方式直接将Token下放到Client端页面 -3. 密码式(Password):Client直接拿着用户的账号密码换取授权Token -4. 客户端凭证(Client Credentials):Server端针对Client级别的Token,代表应用自身的资源授权 +1. 授权码(Authorization Code):OAuth2.0 标准授权步骤,Server 端向 Client 端下放 `Code` 码,Client 端再用 `Code` 码换取授权 `Access-Token`。 +2. 隐藏式(Implicit):无法使用授权码模式时的备用选择,Server 端使用 URL 重定向方式直接将 `Access-Token` 下放到 Client 端页面。 +3. 密码式(Password):Client 端直接拿着用户的账号密码换取授权 `Access-Token`。 +4. 客户端凭证(Client Credentials):Server 端针对 Client 级别的 Token,代表应用自身的资源授权。 ![https://oss.dev33.cn/sa-token/doc/oauth2/sa-oauth2-setup.png](https://oss.dev33.cn/sa-token/doc/oauth2/sa-oauth2-setup.png) -接下来我们将通过简单示例演示如何在 Sa-OAuth2 中完成这四种模式的对接: [搭建OAuth2-Server](/oauth2/oauth2-server) - +接下来我们将通过简单示例演示如何在 Sa-Token-OAuth2 中完成这四种模式的对接: [搭建OAuth2-Server](/oauth2/oauth2-server) + + +### OAuth2.0 第三方开放平台完整开发流程参考 + +1. oauth2-server 平台端 + 1. 搭建 oauth2-server 数据后台管理端(后台人员对底层数据增删改查维护的地方)。 + 2. 搭建 oauth2-server 数据前台申请端(给第三方公司提供一个申请注册 client 的地方)。 + 3. 搭建 oauth2-server 授权端 以及其接口文档(让第三方公司拿到 access_token)。 + 4. 搭建 oauth2-server 资源端 以及其接口文档(让第三方公司通过 access_token 拿到对应的资源数据)。 + 5. 以上四端可以为一个项目,也可以为四个独立的项目。 + +2. oauth2-client 第三方公司端 + 1. 第三方公司登录 oauth-server 数据前台申请端,申请注册应用,拿到 `clientId`、`clientSecret` 等数据。 + 2. 在自己系统通过 `clientId`、`clientSecret` 等参数对接 oauth2-server 授权端,拿到 `access_token`。 + 3. 通过 `access_token` 调用 oauth2-server 资源端接口,拿到对应资源数据。 + +3. 用户端操作 + 1. 打开第三方公司开发的网站或APP等程序。 + 2. 一般有个“通过xx第三方登录”的按钮,点它。 + 3. 跳转到了 oauth2-server 端的网站,在此网站用 oauth2-server 的账号开始登录。 + 4. 登录完成,继续跳转到授权页,点击确认授权。 + 5. 授权完成,oauth2-server 端生成一个 code 码,重定向回 oauth2-client 的网站,把 code 参数挂到对应的 url 上。 + 6. oauth2-client 从 url 中读取 code 参数,提交到 oauth2-client 的后端。 + 7. oauth2-client 后端拿着 `code`、`clientId`、`clientSecret` 等信息调用 oauth2-server 授权端 的接口,得到 `access_token`。 + 8. 继续拿着 `access_token` 调用 oauth2-server 资源端获取此用户对应的数据。 + 9. 一般最终目的拿到一个 openid 值,oauth2-client 根据 openid 进行登录。生成自己的会话 token ,返回到数据到前端。 + 10. 前端拿到自己 oauth2-client 生成的会话 token ,完成登录。开始进行业务操作。 diff --git a/sa-token-doc/sso/sso-apidoc.md b/sa-token-doc/sso/sso-apidoc.md index 53d47ef4..093a6e4f 100644 --- a/sa-token-doc/sso/sso-apidoc.md +++ b/sa-token-doc/sso/sso-apidoc.md @@ -28,6 +28,11 @@ http://{host}:{port}/sso/auth - 情况一:当前会话在 SSO 认证中心未登录,会进入登录页开始登录。 - 情况二:当前会话在 SSO 认证中心已登录,会被重定向至 `redirect` 地址,并携带 `ticket` 参数。 +`ticket` 码具有以下特点: +1. 每次授权产生的 `ticket` 码都不一样。 +2. `ticket` 码用完即废,不能二次使用。 +3. 一个 `ticket` 的有效期默认为五分钟,超时自动作废。 +4. 每次授权产生新 `ticket` 码,会导致旧 `ticket` 码立即作废,即使旧 `ticket` 码尚未使用。 ### 2、RestAPI 登录接口 ``` url diff --git a/sa-token-doc/use/config.md b/sa-token-doc/use/config.md index 0e80c9cb..89a97a97 100644 --- a/sa-token-doc/use/config.md +++ b/sa-token-doc/use/config.md @@ -297,9 +297,9 @@ sa-token.sso-client.is-slo=true | 参数名称 | 类型 | 默认值 | 说明 | | :-------- | :-------- | :-------- | :-------- | | isCode | Boolean | true | 是否打开模式:授权码(`Authorization Code`) | -| isImplicit | Boolean | false | 是否打开模式:隐藏式(`Implicit`) | -| isPassword | Boolean | false | 是否打开模式:密码式(`Password`) | -| isClient | Boolean | false | 是否打开模式:凭证式(`Client Credentials`) | +| isImplicit | Boolean | true | 是否打开模式:隐藏式(`Implicit`) | +| isPassword | Boolean | true | 是否打开模式:密码式(`Password`) | +| isClient | Boolean | true | 是否打开模式:凭证式(`Client Credentials`) | | isNewRefresh | Boolean | false | 是否在每次 `Refresh-Token` 刷新 `Access-Token` 时,产生一个新的 Refresh-Token | | codeTimeout | long | 300 | Code授权码 保存的时间(单位:秒) 默认五分钟 | | accessTokenTimeout | long | 7200 | `Access-Token` 保存的时间(单位:秒)默认两个小时 | diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerateDefaultImpl.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerateDefaultImpl.java index a88b6b7a..7c6f5ff6 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerateDefaultImpl.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerateDefaultImpl.java @@ -19,14 +19,18 @@ import cn.dev33.satoken.oauth2.SaOAuth2Manager; import cn.dev33.satoken.oauth2.consts.SaOAuth2Consts; import cn.dev33.satoken.oauth2.dao.SaOAuth2Dao; import cn.dev33.satoken.oauth2.data.convert.SaOAuth2DataConverter; -import cn.dev33.satoken.oauth2.data.model.*; -import cn.dev33.satoken.oauth2.data.model.request.RequestAuthModel; +import cn.dev33.satoken.oauth2.data.model.AccessTokenModel; +import cn.dev33.satoken.oauth2.data.model.ClientTokenModel; +import cn.dev33.satoken.oauth2.data.model.CodeModel; +import cn.dev33.satoken.oauth2.data.model.RefreshTokenModel; import cn.dev33.satoken.oauth2.data.model.loader.SaClientModel; +import cn.dev33.satoken.oauth2.data.model.request.RequestAuthModel; import cn.dev33.satoken.oauth2.error.SaOAuth2ErrorCode; import cn.dev33.satoken.oauth2.exception.SaOAuth2Exception; import cn.dev33.satoken.oauth2.strategy.SaOAuth2Strategy; import cn.dev33.satoken.util.SaFoxUtil; +import java.util.LinkedHashMap; import java.util.List; /** @@ -211,6 +215,7 @@ public class SaOAuth2DataGenerateDefaultImpl implements SaOAuth2DataGenerate { String clientTokenValue = SaOAuth2Strategy.instance.createClientToken.execute(clientId, scopes); ClientTokenModel ct = new ClientTokenModel(clientTokenValue, clientId, scopes); ct.expiresTime = System.currentTimeMillis() + (cm.getClientTokenTimeout() * 1000); + ct.extraData = new LinkedHashMap<>(); SaOAuth2Strategy.instance.workClientTokenByScope.accept(ct); // 3、保存新Client-Token -- Gitee From 174a94db01c4d1cb84ffff4e0e507414f5f321a8 Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Mon, 19 Aug 2024 23:29:05 +0800 Subject: [PATCH 138/172] =?UTF-8?q?=E5=AE=8C=E6=95=B4=E9=80=82=E9=85=8D?= =?UTF-8?q?=E6=8B=86=E5=88=86=E5=BC=8F=E8=B7=AF=E7=94=B1=E5=86=99=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/pj/SaOAuth2ServerApplication.java | 4 +- .../com/pj/oauth2/SaOAuth2DataLoaderImpl.java | 6 +- .../pj/oauth2/SaOAuth2ServerController.java | 16 +- .../src/main/resources/application.yml | 20 ++- sa-token-doc/_sidebar.md | 2 +- sa-token-doc/oauth2/oauth2-custom-api.md | 104 ++++++++++++ .../satoken/oauth2/config/SaOAuth2Config.java | 63 +++---- .../SaOAuth2DataConverterDefaultImpl.java | 4 +- .../SaOAuth2DataGenerateDefaultImpl.java | 12 +- .../data/model/loader/SaClientModel.java | 104 ++++++------ .../SaOAuth2DataResolverDefaultImpl.java | 2 +- .../oauth2/error/SaOAuth2ErrorCode.java | 3 + .../processor/SaOAuth2ServerProcessor.java | 155 ++++++++++++------ 13 files changed, 335 insertions(+), 160 deletions(-) create mode 100644 sa-token-doc/oauth2/oauth2-custom-api.md diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/SaOAuth2ServerApplication.java b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/SaOAuth2ServerApplication.java index 4accb81c..4cf9200b 100644 --- a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/SaOAuth2ServerApplication.java +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/SaOAuth2ServerApplication.java @@ -1,5 +1,6 @@ package com.pj; +import cn.dev33.satoken.oauth2.SaOAuth2Manager; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @@ -12,7 +13,8 @@ public class SaOAuth2ServerApplication { public static void main(String[] args) { SpringApplication.run(SaOAuth2ServerApplication.class, args); - System.out.println("\nSa-Token-OAuth Server端启动成功"); + System.out.println("\nSa-Token-OAuth Server端启动成功,配置如下:"); + System.out.println(SaOAuth2Manager.getConfig()); } } diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2DataLoaderImpl.java b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2DataLoaderImpl.java index 72948364..a7d10925 100644 --- a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2DataLoaderImpl.java +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2DataLoaderImpl.java @@ -22,7 +22,11 @@ public class SaOAuth2DataLoaderImpl implements SaOAuth2DataLoader { .setClientSecret("aaaa-bbbb-cccc-dddd-eeee") // client 秘钥 .addAllowUrls("*") // 所有允许授权的 url .addContractScopes("openid", "userid", "userinfo") // 所有签约的权限 - .setIsAutoMode(true); // 是否自动判断开放的授权模式 + .setEnableCode(true) // 是否开启授权码模式 + .setEnableImplicit(true) // 是否开启隐式模式 + .setEnablePassword(true) // 是否开启密码模式 + .setEnableClient(true) // 是否开启客户端模式 + ; } return null; } diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java index 44ddce9d..594b8126 100644 --- a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java @@ -7,7 +7,6 @@ import cn.dev33.satoken.oauth2.template.SaOAuth2Util; import cn.dev33.satoken.stp.StpUtil; import cn.dev33.satoken.util.SaResult; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.ModelAndView; @@ -17,20 +16,20 @@ import java.util.LinkedHashMap; import java.util.Map; /** - * Sa-OAuth2 Server端 控制器 + * Sa-Token-OAuth2 Server端 Controller + * * @author click33 - * */ @RestController public class SaOAuth2ServerController { - // 处理所有OAuth相关请求 + // OAuth2-Server 端:处理所有OAuth相关请求 @RequestMapping("/oauth2/*") public Object request() { System.out.println("------- 进入请求: " + SaHolder.getRequest().getUrl()); return SaOAuth2ServerProcessor.instance.dister(); } - + // Sa-OAuth2 定制化配置 @Autowired public void configOAuth2Server(SaOAuth2Config cfg) { @@ -57,13 +56,6 @@ public class SaOAuth2ServerController { }; } - // 全局异常拦截 - @ExceptionHandler - public SaResult handlerException(Exception e) { - e.printStackTrace(); - return SaResult.error(e.getMessage()); - } - // ---------- 开放相关资源接口: Client端根据 Access-Token ,置换相关资源 ------------ diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/resources/application.yml b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/resources/application.yml index 0d263504..44f39cd0 100644 --- a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/resources/application.yml +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/resources/application.yml @@ -3,15 +3,19 @@ server: # sa-token配置 sa-token: - # token名称 (同时也是cookie名称) - token-name: satoken-server + # token名称 (同时也是 Cookie 名称) + token-name: satoken-oauth2-server # OAuth2.0 配置 - oauth2: - is-code: true - is-implicit: true - is-password: true - is-client: true - + oauth2: + # 是否全局开启授权码模式 + enable-code: true + # 是否全局开启 Implicit 模式 + enable-implicit: true + # 是否全局开启密码模式 + enable-password: true + # 是否全局开启客户端模式 + enable-client: true + spring: # redis配置 redis: diff --git a/sa-token-doc/_sidebar.md b/sa-token-doc/_sidebar.md index e15f364c..432eb727 100644 --- a/sa-token-doc/_sidebar.md +++ b/sa-token-doc/_sidebar.md @@ -57,7 +57,7 @@ - [OAuth2-Server端开放 API 接口](/oauth2/oauth2-apidoc) - [配置 client 域名校验 ](/oauth2/oauth2-check-domain) - [定制化登录页面与授权页面](/oauth2/oauth2-custom-login) - - [拆分式路由](/oauth2/3) + - [自定义 API 路由 ](/oauth2/oauth2-custom-api) - [前后端分离模式整合方案](/oauth2/4) - [平台中心模式开发](/oauth2/5) - [自定义 Scope 权限以处理器](/oauth2/6) diff --git a/sa-token-doc/oauth2/oauth2-custom-api.md b/sa-token-doc/oauth2/oauth2-custom-api.md new file mode 100644 index 00000000..a398c37d --- /dev/null +++ b/sa-token-doc/oauth2/oauth2-custom-api.md @@ -0,0 +1,104 @@ +# OAuth2-自定义 API 路由 + +--- + +### 方式一:修改全局变量 + +在之前的章节中,我们演示了如何搭建一个 OAuth2 认证中心: +``` java +/** + * Sa-Token-OAuth2 Server端 Controller + */ +@RestController +public class SaOAuth2ServerController { + + // OAuth2-Server 端:处理所有 OAuth 相关请求 + @RequestMapping("/oauth2/*") + public Object request() { + return SaOAuth2ServerProcessor.instance.dister(); + } + + // ... 其它代码 + +} +``` + +这种写法集成简单但却不够灵活。例如获取 code 授权码地址只能是:`http://{host}:{port}/oauth2/authorize`,如果我们想要自定义其API地址,应该怎么做呢? + +打开 OAuth2 模块相关源码,有关 API 的设计都定义在: +[SaOAuth2Consts.java](https://gitee.com/dromara/sa-token/blob/master/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/consts/SaOAuth2Consts.java) +中,我们可以对其进行二次修改。 + +例如,我们可以在 Main 方法启动类或者 OAuth2 配置方法中修改变量值: +``` java +// 配置 OAuth2 相关参数 +@Autowired +private void configOAuth2Server(SaOAuth2Config cfg) { + // 自定义API地址 + SaOAuth2Consts.Api.authorize = "/oauth2/authorize2"; + // ... +} +``` + +启动项目,统一认证地址就被我们修改成了:`http://{host}:{port}/oauth2/authorize2` + + +### 方式二:拆分路由入口 +根据上述路由入口:`@RequestMapping("/oauth2/*")`,我们给它起一个合适的名字 —— 聚合式路由。 + +与之对应的,我们可以将其修改为拆分式路由: + +``` java +/** + * Sa-Token-OAuth2 Server端 Controller + */ +@RestController +public class SaOAuth2ServerController { + + // 模式一:Code授权码 || 模式二:隐藏式 + @RequestMapping("/oauth2/authorize") + public Object authorize() { + return SaOAuth2ServerProcessor.instance.authorize(); + } + + // 用户登录 + @RequestMapping("/oauth2/doLogin") + public Object doLogin() { + return SaOAuth2ServerProcessor.instance.doLogin(); + } + + // 用户确认授权 + @RequestMapping("/oauth2/doConfirm") + public Object doConfirm() { + return SaOAuth2ServerProcessor.instance.doConfirm(); + } + + // Code 换 Access-Token || 模式三:密码式 + @RequestMapping("/oauth2/token") + public Object token() { + return SaOAuth2ServerProcessor.instance.tokenOrPassword(); + } + + // Refresh-Token 刷新 Access-Token + @RequestMapping("/oauth2/refresh") + public Object refresh() { + return SaOAuth2ServerProcessor.instance.refresh(); + } + + // 回收 Access-Token + @RequestMapping("/oauth2/revoke") + public Object revoke() { + return SaOAuth2ServerProcessor.instance.revoke(); + } + + // 模式四:凭证式 + @RequestMapping("/oauth2/client_token") + public Object clientToken() { + return SaOAuth2ServerProcessor.instance.clientToken(); + } + +} +``` + +拆分式路由 与 聚合式路由 在功能上完全等价,且提供了更为细致的路由管控。 + diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/config/SaOAuth2Config.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/config/SaOAuth2Config.java index 3114e650..41cab136 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/config/SaOAuth2Config.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/config/SaOAuth2Config.java @@ -34,16 +34,16 @@ public class SaOAuth2Config implements Serializable { private static final long serialVersionUID = -6541180061782004705L; /** 是否打开模式:授权码(Authorization Code) */ - public Boolean isCode = true; + public Boolean enableCode = true; /** 是否打开模式:隐藏式(Implicit) */ - public Boolean isImplicit = true; + public Boolean enableImplicit = true; /** 是否打开模式:密码式(Password) */ - public Boolean isPassword = true; + public Boolean enablePassword = true; /** 是否打开模式:凭证式(Client Credentials) */ - public Boolean isClient = true; + public Boolean enableClient = true; /** 是否在每次 Refresh-Token 刷新 Access-Token 时,产生一个新的 Refresh-Token */ public Boolean isNewRefresh = false; @@ -69,59 +69,59 @@ public class SaOAuth2Config implements Serializable { /** - * @return isCode + * @return enableCode */ - public Boolean getIsCode() { - return isCode; + public Boolean getEnableCode() { + return enableCode; } /** - * @param isCode 要设置的 isCode + * @param enableCode 要设置的 enableCode */ - public void setIsCode(Boolean isCode) { - this.isCode = isCode; + public void setEnableCode(Boolean enableCode) { + this.enableCode = enableCode; } /** - * @return isImplicit + * @return enableImplicit */ - public Boolean getIsImplicit() { - return isImplicit; + public Boolean getEnableImplicit() { + return enableImplicit; } /** - * @param isImplicit 要设置的 isImplicit + * @param enableImplicit 要设置的 enableImplicit */ - public void setIsImplicit(Boolean isImplicit) { - this.isImplicit = isImplicit; + public void setEnableImplicit(Boolean enableImplicit) { + this.enableImplicit = enableImplicit; } /** - * @return isPassword + * @return enablePassword */ - public Boolean getIsPassword() { - return isPassword; + public Boolean getEnablePassword() { + return enablePassword; } /** - * @param isPassword 要设置的 isPassword + * @param enablePassword 要设置的 enablePassword */ - public void setIsPassword(Boolean isPassword) { - this.isPassword = isPassword; + public void setEnablePassword(Boolean enablePassword) { + this.enablePassword = enablePassword; } /** - * @return isClient + * @return enableClient */ - public Boolean getIsClient() { - return isClient; + public Boolean getEnableClient() { + return enableClient; } /** - * @param isClient 要设置的 isClient + * @param enableClient 要设置的 enableClient */ - public void setIsClient(Boolean isClient) { - this.isClient = isClient; + public void setEnableClient(Boolean enableClient) { + this.enableClient = enableClient; } /** @@ -254,8 +254,11 @@ public class SaOAuth2Config implements Serializable { @Override public String toString() { - return "SaOAuth2Config [isCode=" + isCode + ", isImplicit=" + isImplicit + ", isPassword=" + isPassword - + ", isClient=" + isClient + return "SaOAuth2Config [" + + "enableCode=" + enableCode + + ", enableImplicit=" + enableImplicit + + ", enablePassword=" + enablePassword + + ", enableClient=" + enableClient + ", isNewRefresh=" + isNewRefresh + ", codeTimeout=" + codeTimeout + ", accessTokenTimeout=" + accessTokenTimeout diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/convert/SaOAuth2DataConverterDefaultImpl.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/convert/SaOAuth2DataConverterDefaultImpl.java index 37ec58b3..5212fc43 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/convert/SaOAuth2DataConverterDefaultImpl.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/convert/SaOAuth2DataConverterDefaultImpl.java @@ -100,8 +100,8 @@ public class SaOAuth2DataConverterDefaultImpl implements SaOAuth2DataConverter { rt.expiresTime = System.currentTimeMillis() + (clientModel.getRefreshTokenTimeout() * 1000); rt.extraData = new LinkedHashMap<>(at.extraData); // 改变 at 属性 - at.refreshToken = rt.refreshToken; - at.refreshExpiresTime = rt.expiresTime; +// at.refreshToken = rt.refreshToken; +// at.refreshExpiresTime = rt.expiresTime; return rt; } diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerateDefaultImpl.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerateDefaultImpl.java index 7c6f5ff6..1ff51e79 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerateDefaultImpl.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerateDefaultImpl.java @@ -87,10 +87,10 @@ public class SaOAuth2DataGenerateDefaultImpl implements SaOAuth2DataGenerate { // 3、生成token AccessTokenModel at = dataConverter.convertCodeToAccessToken(cm); + SaOAuth2Strategy.instance.workAccessTokenByScope.accept(at); RefreshTokenModel rt = dataConverter.convertAccessTokenToRefreshToken(at); at.refreshToken = rt.refreshToken; at.refreshExpiresTime = rt.expiresTime; - SaOAuth2Strategy.instance.workAccessTokenByScope.accept(at); // 4、保存token dao.saveAccessToken(at); @@ -166,14 +166,20 @@ public class SaOAuth2DataGenerateDefaultImpl implements SaOAuth2DataGenerate { // 2、生成 新Access-Token String newAtValue = SaOAuth2Strategy.instance.createAccessToken.execute(ra.clientId, ra.loginId, ra.scopes); AccessTokenModel at = new AccessTokenModel(newAtValue, ra.clientId, ra.loginId, ra.scopes); - // TODO 此处的 openid 应该怎么加载? - // at.openid = SaOAuth2Manager.getDataLoader().getOpenid(ra.clientId, ra.loginId); + + // 3、根据权限构建额外参数 + at.extraData = new LinkedHashMap<>(); + SaOAuth2Strategy.instance.workAccessTokenByScope.accept(at); + SaClientModel clientModel = SaOAuth2Manager.getDataLoader().getClientModelNotNull(ra.clientId); at.expiresTime = System.currentTimeMillis() + (clientModel.getAccessTokenTimeout() * 1000); // 3、生成&保存 Refresh-Token if(isCreateRt) { RefreshTokenModel rt = SaOAuth2Manager.getDataConverter().convertAccessTokenToRefreshToken(at); + at.refreshToken = rt.refreshToken; + at.refreshExpiresTime = rt.expiresTime; + dao.saveRefreshToken(rt); dao.saveRefreshTokenIndex(rt); } 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 2cc8dc5f..248c1aef 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 @@ -54,23 +54,23 @@ public class SaClientModel implements Serializable { public List allowUrls; /** 此 Client 是否打开模式:授权码(Authorization Code) */ - public Boolean isCode = false; + public Boolean enableCode = false; /** 此 Client 是否打开模式:隐藏式(Implicit) */ - public Boolean isImplicit = false; + public Boolean enableImplicit = false; /** 此 Client 是否打开模式:密码式(Password) */ - public Boolean isPassword = false; + public Boolean enablePassword = false; /** 此 Client 是否打开模式:凭证式(Client Credentials) */ - public Boolean isClient = false; + public Boolean enableClient = false; - /** - * 是否自动判断此 Client 开放的授权模式 - *
此值为true时:四种模式(isCode、isImplicit、isPassword、isClient)是否生效,依靠全局设置 - *
此值为false时:四种模式(isCode、isImplicit、isPassword、isClient)是否生效,依靠局部配置+全局配置 - */ - public Boolean isAutoMode = true; +// /** +// * 是否自动判断此 Client 开放的授权模式 +// *
此值为true时:四种模式(isCode、isImplicit、isPassword、isClient)是否生效,依靠全局设置 +// *
此值为false时:四种模式(isCode、isImplicit、isPassword、isClient)是否生效,依靠局部配置+全局配置 +// */ +// public Boolean isAutoMode = true; /** 单独配置此Client:是否在每次 Refresh-Token 刷新 Access-Token 时,产生一个新的 Refresh-Token [默认取全局配置] */ public Boolean isNewRefresh; @@ -171,83 +171,83 @@ public class SaClientModel implements Serializable { /** * @return 此 Client 是否打开模式:授权码(Authorization Code) */ - public Boolean getIsCode() { - return isCode; + public Boolean getEnableCode() { + return enableCode; } /** - * @param isCode 此 Client 是否打开模式:授权码(Authorization Code) + * @param enableCode 此 Client 是否打开模式:授权码(Authorization Code) * @return 对象自身 */ - public SaClientModel setIsCode(Boolean isCode) { - this.isCode = isCode; + public SaClientModel setEnableCode(Boolean enableCode) { + this.enableCode = enableCode; return this; } /** * @return 此 Client 是否打开模式:隐藏式(Implicit) */ - public Boolean getIsImplicit() { - return isImplicit; + public Boolean getEnableImplicit() { + return enableImplicit; } /** - * @param isImplicit 此 Client 是否打开模式:隐藏式(Implicit) + * @param enableImplicit 此 Client 是否打开模式:隐藏式(Implicit) * @return 对象自身 */ - public SaClientModel setIsImplicit(Boolean isImplicit) { - this.isImplicit = isImplicit; + public SaClientModel setEnableImplicit(Boolean enableImplicit) { + this.enableImplicit = enableImplicit; return this; } /** * @return 此 Client 是否打开模式:密码式(Password) */ - public Boolean getIsPassword() { - return isPassword; + public Boolean getEnablePassword() { + return enablePassword; } /** - * @param isPassword 此 Client 是否打开模式:密码式(Password) + * @param enablePassword 此 Client 是否打开模式:密码式(Password) * @return 对象自身 */ - public SaClientModel setIsPassword(Boolean isPassword) { - this.isPassword = isPassword; + public SaClientModel setEnablePassword(Boolean enablePassword) { + this.enablePassword = enablePassword; return this; } /** * @return 此 Client 是否打开模式:凭证式(Client Credentials) */ - public Boolean getIsClient() { - return isClient; - } - - /** - * @param isClient 此 Client 是否打开模式:凭证式(Client Credentials) - * @return 对象自身 - */ - public SaClientModel setIsClient(Boolean isClient) { - this.isClient = isClient; - return this; - } - - /** - * @return 是否自动判断此 Client 开放的授权模式 - */ - public Boolean getIsAutoMode() { - return isAutoMode; + public Boolean getEnableClient() { + return enableClient; } /** - * @param isAutoMode 是否自动判断此 Client 开放的授权模式 + * @param enableClient 此 Client 是否打开模式:凭证式(Client Credentials) * @return 对象自身 */ - public SaClientModel setIsAutoMode(Boolean isAutoMode) { - this.isAutoMode = isAutoMode; + public SaClientModel setEnableClient(Boolean enableClient) { + this.enableClient = enableClient; return this; } - +// +// /** +// * @return 是否自动判断此 Client 开放的授权模式 +// */ +// public Boolean getIsAutoMode() { +// return isAutoMode; +// } +// +// /** +// * @param isAutoMode 是否自动判断此 Client 开放的授权模式 +// * @return 对象自身 +// */ +// public SaClientModel setIsAutoMode(Boolean isAutoMode) { +// this.isAutoMode = isAutoMode; +// return this; +// } +// /** * @return 此Client:是否在每次 Refresh-Token 刷新 Access-Token 时,产生一个新的 Refresh-Token [默认取全局配置] @@ -338,11 +338,11 @@ public class SaClientModel implements Serializable { ", clientSecret='" + clientSecret + '\'' + ", contractScopes=" + contractScopes + ", allowUrls=" + allowUrls + - ", isCode=" + isCode + - ", isImplicit=" + isImplicit + - ", isPassword=" + isPassword + - ", isClient=" + isClient + - ", isAutoMode=" + isAutoMode + + ", isCode=" + enableCode + + ", isImplicit=" + enableImplicit + + ", isPassword=" + enablePassword + + ", isClient=" + enableClient + +// ", isAutoMode=" + isAutoMode + ", isNewRefresh=" + isNewRefresh + ", accessTokenTimeout=" + accessTokenTimeout + ", refreshTokenTimeout=" + refreshTokenTimeout + diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/resolver/SaOAuth2DataResolverDefaultImpl.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/resolver/SaOAuth2DataResolverDefaultImpl.java index 97375f88..339ac51a 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/resolver/SaOAuth2DataResolverDefaultImpl.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/resolver/SaOAuth2DataResolverDefaultImpl.java @@ -113,7 +113,7 @@ public class SaOAuth2DataResolverDefaultImpl implements SaOAuth2DataResolver { public Map buildClientTokenReturnValue(ClientTokenModel ct) { Map map = new LinkedHashMap<>(); map.put("client_token", ct.clientToken); - map.put("access_token", ct.clientToken); // 兼容 OAuth2 协议 + // map.put("access_token", ct.clientToken); // 兼容 OAuth2 协议 map.put("expires_in", ct.getExpiresIn()); map.put("client_id", ct.clientId); map.put("scope", SaOAuth2Manager.getDataConverter().convertScopeListToString(ct.scopes)); diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/error/SaOAuth2ErrorCode.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/error/SaOAuth2ErrorCode.java index cd6a3fa4..fa15e0d4 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/error/SaOAuth2ErrorCode.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/error/SaOAuth2ErrorCode.java @@ -98,6 +98,9 @@ public interface SaOAuth2ErrorCode { /** 无效response_type */ int CODE_30125 = 30125; + /** 无效grant_type */ + int CODE_30126 = 30126; + /** 暂未开放授权码模式 */ int CODE_30131 = 30131; diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/processor/SaOAuth2ServerProcessor.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/processor/SaOAuth2ServerProcessor.java index 39a2446c..55857340 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/processor/SaOAuth2ServerProcessor.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/processor/SaOAuth2ServerProcessor.java @@ -71,28 +71,24 @@ public class SaOAuth2ServerProcessor { // ------------------ 路由分发 ------------------ - // 模式一:Code授权码 - if(req.isPath(Api.authorize) && req.isParam(Param.response_type, ResponseType.code)) { - SaClientModel cm = currClientModel(); - if(cfg.getIsCode() && (cm.isCode || cm.isAutoMode)) { - return authorize(); - } - throw new SaOAuth2Exception("暂未开放的授权模式").setCode(SaOAuth2ErrorCode.CODE_30131); + // 模式一:Code授权码 || 模式二:隐藏式 + if(req.isPath(Api.authorize)) { + return authorize(); } - - // Code授权码 获取 Access-Token - if(req.isPath(Api.token) && req.isParam(Param.grant_type, GrantType.authorization_code)) { - return token(); + + // Code 换 Access-Token || 模式三:密码式 + if(req.isPath(Api.token)) { + return tokenOrPassword(); } // Refresh-Token 刷新 Access-Token - if(req.isPath(Api.refresh) && req.isParam(Param.grant_type, GrantType.refresh_token)) { - return refreshToken(); + if(req.isPath(Api.refresh)) { + return refresh(); } // 回收 Access-Token if(req.isPath(Api.revoke)) { - return revokeToken(); + return revoke(); } // doLogin 登录接口 @@ -105,31 +101,9 @@ public class SaOAuth2ServerProcessor { return doConfirm(); } - // 模式二:隐藏式 - if(req.isPath(Api.authorize) && req.isParam(Param.response_type, ResponseType.token)) { - SaClientModel cm = currClientModel(); - if(cfg.getIsImplicit() && (cm.isImplicit || cm.isAutoMode)) { - return authorize(); - } - throw new SaOAuth2Exception("暂未开放的授权模式").setCode(SaOAuth2ErrorCode.CODE_30132); - } - - // 模式三:密码式 - if(req.isPath(Api.token) && req.isParam(Param.grant_type, GrantType.password)) { - SaClientModel cm = currClientModel(); - if(cfg.getIsPassword() && (cm.isPassword || cm.isAutoMode)) { - return password(); - } - throw new SaOAuth2Exception("暂未开放的授权模式").setCode(SaOAuth2ErrorCode.CODE_30133); - } - // 模式四:凭证式 - if(req.isPath(Api.client_token) && req.isParam(Param.grant_type, GrantType.client_credentials)) { - SaClientModel cm = currClientModel(); - if(cfg.getIsClient() && (cm.isClient || cm.isAutoMode)) { - return clientToken(); - } - throw new SaOAuth2Exception("暂未开放的授权模式").setCode(SaOAuth2ErrorCode.CODE_30134); + if(req.isPath(Api.client_token)) { + return clientToken(); } // 默认返回 @@ -141,42 +115,67 @@ public class SaOAuth2ServerProcessor { * @return 处理结果 */ public Object authorize() { + // 获取变量 SaRequest req = SaHolder.getRequest(); SaResponse res = SaHolder.getResponse(); SaOAuth2Config cfg = SaOAuth2Manager.getConfig(); SaOAuth2DataGenerate dataGenerate = SaOAuth2Manager.getDataGenerate(); + String responseType = req.getParamNotNull(Param.response_type); - // 1、如果尚未登录, 则先去登录 + // 1、先判断是否开启了指定的授权模式 + // 模式一:Code授权码 + if(responseType.equals(ResponseType.code)) { + if(!cfg.enableCode) { + throwErrorSystemNotEnableModel(); + } + if(!currClientModel().enableCode) { + throwErrorClientNotEnableModel(); + } + } + // 模式二:隐藏式 + else if(responseType.equals(ResponseType.token)) { + if(!cfg.enableImplicit) { + throwErrorSystemNotEnableModel(); + } + if(!currClientModel().enableImplicit) { + throwErrorClientNotEnableModel(); + } + } + // 其它 + else { + throw new SaOAuth2Exception("无效 response_type: " + req.getParam(Param.response_type)).setCode(SaOAuth2ErrorCode.CODE_30125); + } + + // 2、如果尚未登录, 则先去登录 if( ! getStpLogic().isLogin()) { return cfg.notLoginView.get(); } - // 2、构建请求 Model + // 3、构建请求 Model RequestAuthModel ra = SaOAuth2Manager.getDataResolver().readRequestAuthModel(req, getStpLogic().getLoginId()); - // 3、校验:重定向域名是否合法 + // 4、校验:重定向域名是否合法 oauth2Template.checkRightUrl(ra.clientId, ra.redirectUri); - // 4、校验:此次申请的Scope,该Client是否已经签约 + // 5、校验:此次申请的Scope,该Client是否已经签约 oauth2Template.checkContract(ra.clientId, ra.scopes); - // 5、判断:如果此次申请的Scope,该用户尚未授权,则转到授权页面 + // 6、判断:如果此次申请的Scope,该用户尚未授权,则转到授权页面 boolean isGrant = oauth2Template.isGrant(ra.loginId, ra.clientId, ra.scopes); if( ! isGrant) { return cfg.confirmView.apply(ra.clientId, ra.scopes); } - - // 6、判断授权类型 - // 如果是 授权码式,则:开始重定向授权,下放code + // 7、判断授权类型,重定向到不同地址 + // 如果是 授权码式,则:开始重定向授权,下放code if(ResponseType.code.equals(ra.responseType)) { CodeModel codeModel = dataGenerate.generateCode(ra); String redirectUri = dataGenerate.buildRedirectUri(ra.redirectUri, codeModel.code, ra.state); return res.redirect(redirectUri); } - // 如果是 隐藏式,则:开始重定向授权,下放 token + // 如果是 隐藏式,则:开始重定向授权,下放 token if(ResponseType.token.equals(ra.responseType)) { AccessTokenModel at = dataGenerate.generateAccessToken(ra, false); String redirectUri = dataGenerate.buildImplicitRedirectUri(ra.redirectUri, at.accessToken, ra.state); @@ -187,6 +186,34 @@ public class SaOAuth2ServerProcessor { throw new SaOAuth2Exception("无效response_type: " + ra.responseType).setCode(SaOAuth2ErrorCode.CODE_30125); } + /** + * Code 换 Access-Token || 模式三:密码式 + * @return 处理结果 + */ + public Object tokenOrPassword() { + + String grantType = SaHolder.getRequest().getParamNotNull(Param.grant_type); + + // Code 换 Access-Token + if(grantType.equals(GrantType.authorization_code)) { + return token(); + } + + // 模式三:密码式 + if(grantType.equals(GrantType.password)) { + SaOAuth2Config cfg = SaOAuth2Manager.getConfig(); + if(!cfg.enablePassword) { + throwErrorSystemNotEnableModel(); + } + if(!currClientModel().enablePassword) { + throwErrorClientNotEnableModel(); + } + return password(); + } + + throw new SaOAuth2Exception("无效 grant_type:" + grantType); + } + /** * Code授权码 获取 Access-Token * @return 处理结果 @@ -216,9 +243,13 @@ public class SaOAuth2ServerProcessor { * Refresh-Token 刷新 Access-Token * @return 处理结果 */ - public Object refreshToken() { + public Object refresh() { // 获取变量 SaRequest req = SaHolder.getRequest(); + String grantType = req.getParamNotNull(Param.grant_type); + if(!grantType.equals(GrantType.refresh_token)) { + throw new SaOAuth2Exception("无效 grant_type:" + grantType).setCode(SaOAuth2ErrorCode.CODE_30126); + } // 获取参数 @@ -241,7 +272,7 @@ public class SaOAuth2ServerProcessor { * 回收 Access-Token * @return 处理结果 */ - public Object revokeToken() { + public Object revoke() { // 获取变量 SaRequest req = SaHolder.getRequest(); @@ -344,6 +375,18 @@ public class SaOAuth2ServerProcessor { public Object clientToken() { // 获取变量 SaRequest req = SaHolder.getRequest(); + SaOAuth2Config cfg = SaOAuth2Manager.getConfig(); + + String grantType = req.getParamNotNull(Param.grant_type); + if(!grantType.equals(GrantType.client_credentials)) { + throw new SaOAuth2Exception("无效 grant_type:" + grantType).setCode(SaOAuth2ErrorCode.CODE_30126); + } + if(!cfg.enableClient) { + throwErrorSystemNotEnableModel(); + } + if(!currClientModel().enableClient) { + throwErrorClientNotEnableModel(); + } // 获取参数 ClientIdAndSecretModel clientIdAndSecret = SaOAuth2Manager.getDataResolver().readClientIdAndSecret(req); @@ -383,4 +426,18 @@ public class SaOAuth2ServerProcessor { return StpUtil.stpLogic; } + /** + * 系统未开放此授权模式时抛出异常 + */ + public void throwErrorSystemNotEnableModel() { + throw new SaOAuth2Exception("系统暂未开放此授权模式").setCode(SaOAuth2ErrorCode.CODE_30131); + } + + /** + * 应用未开放此授权模式时抛出异常 + */ + public void throwErrorClientNotEnableModel() { + throw new SaOAuth2Exception("应用暂未开放此授权模式").setCode(SaOAuth2ErrorCode.CODE_30131); + } + } -- Gitee From c4b6a6381e6652314be486ba82a6d805e4ce5899 Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Tue, 20 Aug 2024 12:58:56 +0800 Subject: [PATCH 139/172] =?UTF-8?q?=E5=AE=8C=E5=96=84=20OAuth2-=E8=87=AA?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=E6=9D=83=E9=99=90=E5=A4=84=E7=90=86=E5=99=A8?= =?UTF-8?q?=20=E7=AB=A0=E8=8A=82=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pj/oauth2/SaOAuth2ServerController.java | 14 +- .../com/pj/oauth2/UserinfoScopeHandler.java | 41 ++++ sa-token-doc/_sidebar.md | 6 +- .../oauth2/oauth2-custom-scope-handler.md | 178 ++++++++++++++++++ .../scope/handler/OidcScopeHandler.java | 2 +- .../scope/handler/OpenIdScopeHandler.java | 2 +- ...ava => SaOAuth2ScopeHandlerInterface.java} | 2 +- .../scope/handler/UserIdScopeHandler.java | 2 +- .../oauth2/strategy/SaOAuth2Strategy.java | 10 +- .../spring/oauth2/SaOAuth2BeanInject.java | 64 ++++++- 10 files changed, 300 insertions(+), 21 deletions(-) create mode 100644 sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/UserinfoScopeHandler.java create mode 100644 sa-token-doc/oauth2/oauth2-custom-scope-handler.md rename sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/scope/handler/{SaOAuth2ScopeAbstractHandler.java => SaOAuth2ScopeHandlerInterface.java} (96%) diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java index 594b8126..58d75726 100644 --- a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java @@ -8,6 +8,7 @@ import cn.dev33.satoken.stp.StpUtil; import cn.dev33.satoken.util.SaResult; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.ModelAndView; @@ -59,11 +60,10 @@ public class SaOAuth2ServerController { // ---------- 开放相关资源接口: Client端根据 Access-Token ,置换相关资源 ------------ - // 获取Userinfo信息:昵称、头像、性别等等 + // 获取 userinfo 信息:昵称、头像、性别等等 @RequestMapping("/oauth2/userinfo") - public SaResult userinfo() { - // 获取 Access-Token 对应的账号id - String accessToken = SaHolder.getRequest().getParamNotNull("access_token"); + public SaResult userinfo(@RequestParam("access_token") String accessToken) { + // 获取 Access-Token 对应的账号id Object loginId = SaOAuth2Util.getLoginIdByAccessToken(accessToken); System.out.println("-------- 此Access-Token对应的账号id: " + loginId); @@ -71,9 +71,9 @@ public class SaOAuth2ServerController { SaOAuth2Util.checkScope(accessToken, "userinfo"); // 模拟账号信息 (真实环境需要查询数据库获取信息) - Map map = new LinkedHashMap(); - map.put("userId", "10008"); - map.put("nickname", "shengzhang_"); + Map map = new LinkedHashMap<>(); + // map.put("userId", loginId); 一般原则下,oauth2-server 不能把 userId 返回给 oauth2-client + map.put("nickname", "林小林"); map.put("avatar", "http://xxx.com/1.jpg"); map.put("age", "18"); map.put("sex", "男"); diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/UserinfoScopeHandler.java b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/UserinfoScopeHandler.java new file mode 100644 index 00000000..afca1436 --- /dev/null +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/UserinfoScopeHandler.java @@ -0,0 +1,41 @@ +package com.pj.oauth2; + +import cn.dev33.satoken.oauth2.data.model.AccessTokenModel; +import cn.dev33.satoken.oauth2.data.model.ClientTokenModel; +import cn.dev33.satoken.oauth2.scope.handler.SaOAuth2ScopeHandlerInterface; +import org.springframework.stereotype.Component; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * @author click33 + * @since 2024/8/20 + */ +@Component +public class UserinfoScopeHandler implements SaOAuth2ScopeHandlerInterface { + + @Override + public String getHandlerScope() { + return "userinfo"; + } + + @Override + public void workAccessToken(AccessTokenModel at) { + System.out.println("--------- userinfo 权限,加工 AccessTokenModel --------- "); + // 模拟账号信息 (真实环境需要查询数据库获取信息) + Map map = new LinkedHashMap(); + map.put("userId", "10008"); + map.put("nickname", "shengzhang_"); + map.put("avatar", "http://xxx.com/1.jpg"); + map.put("age", "18"); + map.put("sex", "男"); + map.put("address", "山东省 青岛市 城阳区"); + at.extraData.put("userinfo", map); + } + + @Override + public void workClientToken(ClientTokenModel ct) { + } + +} \ No newline at end of file diff --git a/sa-token-doc/_sidebar.md b/sa-token-doc/_sidebar.md index 432eb727..f742c13f 100644 --- a/sa-token-doc/_sidebar.md +++ b/sa-token-doc/_sidebar.md @@ -58,10 +58,10 @@ - [配置 client 域名校验 ](/oauth2/oauth2-check-domain) - [定制化登录页面与授权页面](/oauth2/oauth2-custom-login) - [自定义 API 路由 ](/oauth2/oauth2-custom-api) - - [前后端分离模式整合方案](/oauth2/4) - - [平台中心模式开发](/oauth2/5) - - [自定义 Scope 权限以处理器](/oauth2/6) + - [自定义 Scope 权限以处理器](/oauth2/oauth2-custom-scope-handler) - [为 Scope 划分等级](/oauth2/7) + + - [平台中心模式开发](/oauth2/5) - [OAuth2-与登录会话实现数据互通](/oauth2/oauth2-interworking) - [OAuth2 代码 API 参考](/oauth2/oauth2-dev) - [常见问题说明](/oauth2/8) diff --git a/sa-token-doc/oauth2/oauth2-custom-scope-handler.md b/sa-token-doc/oauth2/oauth2-custom-scope-handler.md new file mode 100644 index 00000000..2a391c80 --- /dev/null +++ b/sa-token-doc/oauth2/oauth2-custom-scope-handler.md @@ -0,0 +1,178 @@ +# OAuth2-自定义权限处理器 + +--- + +### 1、需求场景 +一般情况下,对于第三方 oauth2-client 来讲,仅仅拿到用户的 access_token 是不够的,还需要拿到更多的信息,比如用户昵称、头像等资料。 + +sa-token-oauth2 提供两种模式,让 access_token 可以得到更多信息。 + +- 自定义接口模式:在 oauth2-server 端开放一个资料查询接口,在 oauth2-client 得到 `access_token` 后,再次调用这个接口来获取 `userinfo` 信息。 +- 自定义权限处理器模式:自定义一个 `ScopeHandler`,直接在返回 `access_token` 时追加字段,将 `userinfo` 信息和 `access_token` 一并返回到 oauth2-client。 + + +### 2、自定义接口模式 + +#### 1、新建查询接口 + +在 oauth2-server 新建接口,查询指定 `access_token` 代表的 `userId` 其 `userinfo`: + +``` java +// 获取 userinfo 信息:昵称、头像、性别等等 +@RequestMapping("/oauth2/userinfo") +public SaResult userinfo(@RequestParam("access_token") String accessToken) { + // 获取 Access-Token 对应的账号id + Object loginId = SaOAuth2Util.getLoginIdByAccessToken(accessToken); + System.out.println("-------- 此Access-Token对应的账号id: " + loginId); + + // 校验 Access-Token 是否具有权限: userinfo + SaOAuth2Util.checkScope(accessToken, "userinfo"); + + // 模拟账号信息 (真实环境需要查询数据库获取信息) + Map map = new LinkedHashMap<>(); + // map.put("userId", loginId); 一般原则下,oauth2-server 不能把 userId 返回给 oauth2-client + map.put("nickname", "林小林"); + map.put("avatar", "http://xxx.com/1.jpg"); + map.put("age", "18"); + map.put("sex", "男"); + map.put("address", "山东省 青岛市 城阳区"); + return SaResult.ok().setMap(map); +} +``` + + +#### 2、申请 code 时指定权限 +oauth2-client 申请 `code` 时,一定需要加上 `userinfo` 权限 + +``` url +http://sa-oauth-server.com:8000/oauth2/authorize + ?response_type=code + &client_id=1001 + &redirect_uri=http://sa-oauth-client.com:8002/ + &scope=userinfo +``` + + +#### 3、code 换 access_token +访问上述链接后,得到 `code` 授权码,然后我们拿着 `code` 换 `access_token` + +``` url +http://sa-oauth-server.com:8000/oauth2/token + ?grant_type=authorization_code + &client_id=1001 + &client_secret=aaaa-bbbb-cccc-dddd-eeee + &code=${code} +``` + +#### 4、access_token 取 userinfo +使用返回的 `access_token` 再次访问接口 `/oauth2/userinfo` + +``` url +http://sa-oauth-server.com:8000/oauth2/userinfo?access_token=${access_token} +``` + +返回以下结果: +``` js +{ + "code": 200, + "msg": "ok", + "data": null, + "nickname": "林小林", + "avatar": "http://xxx.com/1.jpg", + "age": "18", + "sex": "男", + "address": "山东省 青岛市 城阳区" +} +``` + +拿到 userinfo。 + + + +### 3、自定义权限处理器模式 + +#### 1、新建权限处理器 +在 oauth2-server 新建 `UserinfoScopeHandler.java` 实现 `SaOAuth2ScopeHandlerInterface` 接口: + +``` java +@Component +public class UserinfoScopeHandler implements SaOAuth2ScopeHandlerInterface { + + @Override + public String getHandlerScope() { + return "userinfo"; + } + + @Override + public void workAccessToken(AccessTokenModel at) { + System.out.println("--------- userinfo 权限,加工 AccessTokenModel --------- "); + // 模拟账号信息 (真实环境需要查询数据库获取信息) + Map map = new LinkedHashMap(); + map.put("userId", "10008"); + map.put("nickname", "shengzhang_"); + map.put("avatar", "http://xxx.com/1.jpg"); + map.put("age", "18"); + map.put("sex", "男"); + map.put("address", "山东省 青岛市 城阳区"); + at.extraData.putAll(map); + } + + @Override + public void workClientToken(ClientTokenModel ct) { + } + +} +``` + +如上所述,所有写入到 `extraData` 中的数据,都将追加返回到 oauth2-client 端。 + + +#### 2、申请 code 时指定权限 +oauth2-client 申请 `code` 时,一定需要加上 `userinfo` 权限 +``` url +http://sa-oauth-server.com:8000/oauth2/authorize + ?response_type=code + &client_id=1001 + &redirect_uri=http://sa-oauth-client.com:8002/ + &scope=userinfo +``` + + +#### 3、code 换 access_token +3、访问上述链接后,得到 `code` 授权码,然后我们拿着 `code` 换 `access_token` +``` url +http://sa-oauth-server.com:8000/oauth2/token + ?grant_type=authorization_code + &client_id=1001 + &client_secret=aaaa-bbbb-cccc-dddd-eeee + &code=${code} +``` + +返回结果如下 +``` js +{ + "code": 200, + "msg": "ok", + "data": null, + "token_type": "bearer", + "access_token": "LQ24xI0hX25vIzvciHPA0PNsnGCweSFM1Bzl8783li07VAXpw8sEfn9xsta2", + "refresh_token": "rKB8mby1Mw8yZXHbWzliHx6lmatcLcULLw5C5cUMBhMMRx72DFg5u0owZgrA", + "expires_in": 7199, + "refresh_expires_in": 2591999, + "client_id": "1001", + "scope": "openid,userid,userinfo", + "userinfo": { + "userId": "10008", + "nickname": "shengzhang_", + "avatar": "http://xxx.com/1.jpg", + "age": "18", + "sex": "男", + "address": "山东省 青岛市 城阳区" + } +} +``` + +拿到 userinfo。 + +#### 总结 +相比于自定义接口模式,自定义权限处理器模式可以少一次网络请求,提前拿到 `userinfo` 信息。 \ No newline at end of file diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/scope/handler/OidcScopeHandler.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/scope/handler/OidcScopeHandler.java index 71961833..7adb42d6 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/scope/handler/OidcScopeHandler.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/scope/handler/OidcScopeHandler.java @@ -25,7 +25,7 @@ import cn.dev33.satoken.oauth2.scope.CommonScope; * @author click33 * @since 1.39.0 */ -public class OidcScopeHandler implements SaOAuth2ScopeAbstractHandler { +public class OidcScopeHandler implements SaOAuth2ScopeHandlerInterface { public String getHandlerScope() { return CommonScope.OIDC; diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/scope/handler/OpenIdScopeHandler.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/scope/handler/OpenIdScopeHandler.java index 0bc100e7..cfc07a2b 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/scope/handler/OpenIdScopeHandler.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/scope/handler/OpenIdScopeHandler.java @@ -27,7 +27,7 @@ import cn.dev33.satoken.oauth2.scope.CommonScope; * @author click33 * @since 1.39.0 */ -public class OpenIdScopeHandler implements SaOAuth2ScopeAbstractHandler { +public class OpenIdScopeHandler implements SaOAuth2ScopeHandlerInterface { public String getHandlerScope() { return CommonScope.OPENID; diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/scope/handler/SaOAuth2ScopeAbstractHandler.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/scope/handler/SaOAuth2ScopeHandlerInterface.java similarity index 96% rename from sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/scope/handler/SaOAuth2ScopeAbstractHandler.java rename to sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/scope/handler/SaOAuth2ScopeHandlerInterface.java index 32aa8110..d06ed40b 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/scope/handler/SaOAuth2ScopeAbstractHandler.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/scope/handler/SaOAuth2ScopeHandlerInterface.java @@ -24,7 +24,7 @@ import cn.dev33.satoken.oauth2.data.model.ClientTokenModel; * @author click33 * @since 1.39.0 */ -public interface SaOAuth2ScopeAbstractHandler { +public interface SaOAuth2ScopeHandlerInterface { /** * 获取所要处理的权限 diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/scope/handler/UserIdScopeHandler.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/scope/handler/UserIdScopeHandler.java index b062a3df..18fad7d2 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/scope/handler/UserIdScopeHandler.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/scope/handler/UserIdScopeHandler.java @@ -26,7 +26,7 @@ import cn.dev33.satoken.oauth2.scope.CommonScope; * @author click33 * @since 1.39.0 */ -public class UserIdScopeHandler implements SaOAuth2ScopeAbstractHandler { +public class UserIdScopeHandler implements SaOAuth2ScopeHandlerInterface { public String getHandlerScope() { return CommonScope.USERID; 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 97fbab77..05aa59f5 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 @@ -20,7 +20,7 @@ import cn.dev33.satoken.oauth2.function.strategy.*; 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.SaOAuth2ScopeAbstractHandler; +import cn.dev33.satoken.oauth2.scope.handler.SaOAuth2ScopeHandlerInterface; import cn.dev33.satoken.oauth2.scope.handler.UserIdScopeHandler; import cn.dev33.satoken.util.SaFoxUtil; @@ -49,7 +49,7 @@ public final class SaOAuth2Strategy { /** * 权限处理器集合 */ - public Map scopeHandlerMap = new LinkedHashMap<>(); + public Map scopeHandlerMap = new LinkedHashMap<>(); /** * 注册所有默认的权限处理器 @@ -63,7 +63,7 @@ public final class SaOAuth2Strategy { /** * 注册一个权限处理器 */ - public void registerScopeHandler(SaOAuth2ScopeAbstractHandler handler) { + public void registerScopeHandler(SaOAuth2ScopeHandlerInterface handler) { scopeHandlerMap.put(handler.getHandlerScope(), handler); // TODO 优化日志输出 SaManager.getLog().info("新增权限处理器:" + handler.getHandlerScope()); @@ -86,7 +86,7 @@ public final class SaOAuth2Strategy { public SaOAuth2ScopeWorkAccessTokenFunction workAccessTokenByScope = (at) -> { if(at.scopes != null && !at.scopes.isEmpty()) { for (String scope : at.scopes) { - SaOAuth2ScopeAbstractHandler handler = scopeHandlerMap.get(scope); + SaOAuth2ScopeHandlerInterface handler = scopeHandlerMap.get(scope); if(handler != null) { handler.workAccessToken(at); } @@ -100,7 +100,7 @@ public final class SaOAuth2Strategy { public SaOAuth2ScopeWorkClientTokenFunction workClientTokenByScope = (ct) -> { if(ct.scopes != null && !ct.scopes.isEmpty()) { for (String scope : ct.scopes) { - SaOAuth2ScopeAbstractHandler handler = scopeHandlerMap.get(scope); + SaOAuth2ScopeHandlerInterface handler = scopeHandlerMap.get(scope); if(handler != null) { handler.workClientToken(ct); } diff --git a/sa-token-starter/sa-token-spring-boot-autoconfig/src/main/java/cn/dev33/satoken/spring/oauth2/SaOAuth2BeanInject.java b/sa-token-starter/sa-token-spring-boot-autoconfig/src/main/java/cn/dev33/satoken/spring/oauth2/SaOAuth2BeanInject.java index 0f4e1767..3ff3dda9 100644 --- a/sa-token-starter/sa-token-spring-boot-autoconfig/src/main/java/cn/dev33/satoken/spring/oauth2/SaOAuth2BeanInject.java +++ b/sa-token-starter/sa-token-spring-boot-autoconfig/src/main/java/cn/dev33/satoken/spring/oauth2/SaOAuth2BeanInject.java @@ -17,12 +17,20 @@ package cn.dev33.satoken.spring.oauth2; import cn.dev33.satoken.oauth2.SaOAuth2Manager; import cn.dev33.satoken.oauth2.config.SaOAuth2Config; +import cn.dev33.satoken.oauth2.dao.SaOAuth2Dao; +import cn.dev33.satoken.oauth2.data.convert.SaOAuth2DataConverter; +import cn.dev33.satoken.oauth2.data.generate.SaOAuth2DataGenerate; import cn.dev33.satoken.oauth2.data.loader.SaOAuth2DataLoader; +import cn.dev33.satoken.oauth2.data.resolver.SaOAuth2DataResolver; import cn.dev33.satoken.oauth2.processor.SaOAuth2ServerProcessor; +import cn.dev33.satoken.oauth2.scope.handler.SaOAuth2ScopeHandlerInterface; +import cn.dev33.satoken.oauth2.strategy.SaOAuth2Strategy; import cn.dev33.satoken.oauth2.template.SaOAuth2Template; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import java.util.List; + // 小提示:如果你在 idea 中运行源码时出现异常:java: 程序包cn.dev33.satoken.oauth2不存在。 // 在项目根目录进入 cmd,执行 mvn package 即可解决 @@ -53,7 +61,7 @@ public class SaOAuth2BeanInject { * @param saOAuth2Template 模板代码类 */ @Autowired(required = false) - public void setSaOAuth2Interface(SaOAuth2Template saOAuth2Template) { + public void setSaOAuth2Template(SaOAuth2Template saOAuth2Template) { SaOAuth2ServerProcessor.instance.oauth2Template = saOAuth2Template; } @@ -63,8 +71,60 @@ public class SaOAuth2BeanInject { * @param dataLoader / */ @Autowired(required = false) - public void setSaOAuth2Interface(SaOAuth2DataLoader dataLoader) { + public void setSaOAuth2DataLoader(SaOAuth2DataLoader dataLoader) { SaOAuth2Manager.setDataLoader(dataLoader); } + /** + * 注入 OAuth2 数据解析器 Bean + * + * @param dataResolver / + */ + @Autowired(required = false) + public void setSaOAuth2DataResolver(SaOAuth2DataResolver dataResolver) { + SaOAuth2Manager.setDataResolver(dataResolver); + } + + /** + * 注入 OAuth2 数据格式转换器 Bean + * + * @param dataConverter / + */ + @Autowired(required = false) + public void setSaOAuth2DataConverter(SaOAuth2DataConverter dataConverter) { + SaOAuth2Manager.setDataConverter(dataConverter); + } + + /** + * 注入 OAuth2 数据构建器 Bean + * + * @param dataGenerate / + */ + @Autowired(required = false) + public void setSaOAuth2DataGenerate(SaOAuth2DataGenerate dataGenerate) { + SaOAuth2Manager.setDataGenerate(dataGenerate); + } + + /** + * 注入 OAuth2 数据持久 Bean + * + * @param dao / + */ + @Autowired(required = false) + public void setSaOAuth2Dao(SaOAuth2Dao dao) { + SaOAuth2Manager.setDao(dao); + } + + /** + * 注入自定义 scope 处理器 + * + * @param handlerList 自定义 scope 处理器集合 + */ + @Autowired(required = false) + public void setSaOAuth2ScopeHandler(List handlerList) { + for (SaOAuth2ScopeHandlerInterface handler : handlerList) { + SaOAuth2Strategy.instance.registerScopeHandler(handler); + } + } + } -- Gitee From 0ca8a1ab2d7cbb2cc7ca25dae7a982282734b006 Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Tue, 20 Aug 2024 13:00:22 +0800 Subject: [PATCH 140/172] =?UTF-8?q?=E6=B3=A8=E8=A7=A3=E5=A4=84=E7=90=86?= =?UTF-8?q?=E5=99=A8=E7=88=B6=E6=8E=A5=E5=8F=A3=E9=87=8D=E5=91=BD=E5=90=8D?= =?UTF-8?q?=20SaAnnotationAbstractHandler=20->=20SaAnnotationHandlerInterf?= =?UTF-8?q?ace?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...tHandler.java => SaAnnotationHandlerInterface.java} | 2 +- .../annotation/handler/SaCheckDisableHandler.java | 2 +- .../annotation/handler/SaCheckHttpBasicHandler.java | 2 +- .../annotation/handler/SaCheckHttpDigestHandler.java | 2 +- .../annotation/handler/SaCheckLoginHandler.java | 2 +- .../satoken/annotation/handler/SaCheckOrHandler.java | 2 +- .../annotation/handler/SaCheckPermissionHandler.java | 2 +- .../satoken/annotation/handler/SaCheckRoleHandler.java | 2 +- .../satoken/annotation/handler/SaCheckSafeHandler.java | 2 +- .../satoken/annotation/handler/SaIgnoreHandler.java | 2 +- .../cn/dev33/satoken/listener/SaTokenEventCenter.java | 4 ++-- .../cn/dev33/satoken/listener/SaTokenListener.java | 4 ++-- .../dev33/satoken/listener/SaTokenListenerForLog.java | 4 ++-- .../dev33/satoken/strategy/SaAnnotationStrategy.java | 10 +++++----- .../custom_annotation/handler/CheckAccountHandler.java | 4 ++-- .../handler/SaUserCheckLoginHandler.java | 4 ++-- .../handler/SaUserCheckPermissionHandler.java | 4 ++-- .../handler/SaUserCheckRoleHandler.java | 4 ++-- .../handler/SaUserCheckSafeHandler.java | 4 ++-- .../custom_annotation/handler/CheckAccountHandler.java | 4 ++-- .../satoken/aop/SaAopPointcutAdvisorBeanRegister.java | 8 ++++---- .../main/java/cn/dev33/satoken/solon/XPluginImp.java | 4 ++-- .../java/cn/dev33/satoken/spring/SaBeanInject.java | 6 +++--- 23 files changed, 42 insertions(+), 42 deletions(-) rename sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/{SaAnnotationAbstractHandler.java => SaAnnotationHandlerInterface.java} (95%) diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaAnnotationAbstractHandler.java b/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaAnnotationHandlerInterface.java similarity index 95% rename from sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaAnnotationAbstractHandler.java rename to sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaAnnotationHandlerInterface.java index 8db94fc9..fd91d396 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaAnnotationAbstractHandler.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaAnnotationHandlerInterface.java @@ -24,7 +24,7 @@ import java.lang.reflect.Method; * @author click33 * @since 2024/8/2 */ -public interface SaAnnotationAbstractHandler { +public interface SaAnnotationHandlerInterface { /** * 获取所要处理的注解类型 diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckDisableHandler.java b/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckDisableHandler.java index 67817621..d1faa35d 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckDisableHandler.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckDisableHandler.java @@ -27,7 +27,7 @@ import java.lang.reflect.Method; * @author click33 * @since 2024/8/2 */ -public class SaCheckDisableHandler implements SaAnnotationAbstractHandler { +public class SaCheckDisableHandler implements SaAnnotationHandlerInterface { @Override public Class getHandlerAnnotationClass() { diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckHttpBasicHandler.java b/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckHttpBasicHandler.java index 2c809b60..7e15ce6b 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckHttpBasicHandler.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckHttpBasicHandler.java @@ -26,7 +26,7 @@ import java.lang.reflect.Method; * @author click33 * @since 2024/8/2 */ -public class SaCheckHttpBasicHandler implements SaAnnotationAbstractHandler { +public class SaCheckHttpBasicHandler implements SaAnnotationHandlerInterface { @Override public Class getHandlerAnnotationClass() { diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckHttpDigestHandler.java b/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckHttpDigestHandler.java index 1a688492..1829ce81 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckHttpDigestHandler.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckHttpDigestHandler.java @@ -28,7 +28,7 @@ import java.lang.reflect.Method; * @author click33 * @since 2024/8/2 */ -public class SaCheckHttpDigestHandler implements SaAnnotationAbstractHandler { +public class SaCheckHttpDigestHandler implements SaAnnotationHandlerInterface { @Override public Class getHandlerAnnotationClass() { diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckLoginHandler.java b/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckLoginHandler.java index ab83f39e..d549763a 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckLoginHandler.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckLoginHandler.java @@ -27,7 +27,7 @@ import java.lang.reflect.Method; * @author click33 * @since 2024/8/2 */ -public class SaCheckLoginHandler implements SaAnnotationAbstractHandler { +public class SaCheckLoginHandler implements SaAnnotationHandlerInterface { @Override public Class getHandlerAnnotationClass() { diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckOrHandler.java b/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckOrHandler.java index 3aafbfb5..bb7b9c1d 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckOrHandler.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckOrHandler.java @@ -31,7 +31,7 @@ import java.util.List; * @author click33 * @since 2024/8/2 */ -public class SaCheckOrHandler implements SaAnnotationAbstractHandler { +public class SaCheckOrHandler implements SaAnnotationHandlerInterface { @Override public Class getHandlerAnnotationClass() { diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckPermissionHandler.java b/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckPermissionHandler.java index 64e79b01..62321098 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckPermissionHandler.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckPermissionHandler.java @@ -30,7 +30,7 @@ import java.lang.reflect.Method; * @author click33 * @since 2024/8/2 */ -public class SaCheckPermissionHandler implements SaAnnotationAbstractHandler { +public class SaCheckPermissionHandler implements SaAnnotationHandlerInterface { @Override public Class getHandlerAnnotationClass() { diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckRoleHandler.java b/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckRoleHandler.java index e5c88b14..f5297a3d 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckRoleHandler.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckRoleHandler.java @@ -28,7 +28,7 @@ import java.lang.reflect.Method; * @author click33 * @since 2024/8/2 */ -public class SaCheckRoleHandler implements SaAnnotationAbstractHandler { +public class SaCheckRoleHandler implements SaAnnotationHandlerInterface { @Override public Class getHandlerAnnotationClass() { diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckSafeHandler.java b/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckSafeHandler.java index b07358b1..c4052385 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckSafeHandler.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaCheckSafeHandler.java @@ -27,7 +27,7 @@ import java.lang.reflect.Method; * @author click33 * @since 2024/8/2 */ -public class SaCheckSafeHandler implements SaAnnotationAbstractHandler { +public class SaCheckSafeHandler implements SaAnnotationHandlerInterface { @Override public Class getHandlerAnnotationClass() { diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaIgnoreHandler.java b/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaIgnoreHandler.java index 93319cd4..1fdef904 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaIgnoreHandler.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/annotation/handler/SaIgnoreHandler.java @@ -26,7 +26,7 @@ import java.lang.reflect.Method; * @author click33 * @since 2024/8/2 */ -public class SaIgnoreHandler implements SaAnnotationAbstractHandler { +public class SaIgnoreHandler implements SaAnnotationHandlerInterface { @Override public Class getHandlerAnnotationClass() { diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/listener/SaTokenEventCenter.java b/sa-token-core/src/main/java/cn/dev33/satoken/listener/SaTokenEventCenter.java index 29ed5d89..53c18dd8 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/listener/SaTokenEventCenter.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/listener/SaTokenEventCenter.java @@ -18,7 +18,7 @@ package cn.dev33.satoken.listener; import java.util.ArrayList; import java.util.List; -import cn.dev33.satoken.annotation.handler.SaAnnotationAbstractHandler; +import cn.dev33.satoken.annotation.handler.SaAnnotationHandlerInterface; import cn.dev33.satoken.config.SaTokenConfig; import cn.dev33.satoken.error.SaErrorCode; import cn.dev33.satoken.exception.SaTokenException; @@ -292,7 +292,7 @@ public class SaTokenEventCenter { * 事件发布:有新的注解处理器载入到框架中 * @param handler 注解处理器 */ - public static void doRegisterAnnotationHandler(SaAnnotationAbstractHandler handler) { + public static void doRegisterAnnotationHandler(SaAnnotationHandlerInterface handler) { for (SaTokenListener listener : listenerList) { listener.doRegisterAnnotationHandler(handler); } diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/listener/SaTokenListener.java b/sa-token-core/src/main/java/cn/dev33/satoken/listener/SaTokenListener.java index 6a4e3564..1d867f2c 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/listener/SaTokenListener.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/listener/SaTokenListener.java @@ -15,7 +15,7 @@ */ package cn.dev33.satoken.listener; -import cn.dev33.satoken.annotation.handler.SaAnnotationAbstractHandler; +import cn.dev33.satoken.annotation.handler.SaAnnotationHandlerInterface; import cn.dev33.satoken.config.SaTokenConfig; import cn.dev33.satoken.stp.SaLoginModel; import cn.dev33.satoken.stp.StpLogic; @@ -130,7 +130,7 @@ public interface SaTokenListener { * 注册了自定义注解处理器 * @param handler 注解处理器 */ - default void doRegisterAnnotationHandler(SaAnnotationAbstractHandler handler) {} + default void doRegisterAnnotationHandler(SaAnnotationHandlerInterface handler) {} /** * StpLogic 对象替换 diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/listener/SaTokenListenerForLog.java b/sa-token-core/src/main/java/cn/dev33/satoken/listener/SaTokenListenerForLog.java index 7bb32ef4..c1b61118 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/listener/SaTokenListenerForLog.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/listener/SaTokenListenerForLog.java @@ -15,7 +15,7 @@ */ package cn.dev33.satoken.listener; -import cn.dev33.satoken.annotation.handler.SaAnnotationAbstractHandler; +import cn.dev33.satoken.annotation.handler.SaAnnotationHandlerInterface; import cn.dev33.satoken.config.SaTokenConfig; import cn.dev33.satoken.stp.SaLoginModel; import cn.dev33.satoken.stp.StpLogic; @@ -136,7 +136,7 @@ public class SaTokenListenerForLog implements SaTokenListener { * @param handler 注解处理器 */ @Override - public void doRegisterAnnotationHandler(SaAnnotationAbstractHandler handler) { + public void doRegisterAnnotationHandler(SaAnnotationHandlerInterface handler) { if(handler != null) { log.info("注解扩展 @{} (处理器: {})", handler.getHandlerAnnotationClass().getSimpleName(), handler.getClass().getCanonicalName()); } diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/strategy/SaAnnotationStrategy.java b/sa-token-core/src/main/java/cn/dev33/satoken/strategy/SaAnnotationStrategy.java index 9c8b88ba..e0ffc23a 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/strategy/SaAnnotationStrategy.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/strategy/SaAnnotationStrategy.java @@ -49,7 +49,7 @@ public final class SaAnnotationStrategy { /** * 注解处理器集合 */ - public Map, SaAnnotationAbstractHandler> annotationHandlerMap = new LinkedHashMap<>(); + public Map, SaAnnotationHandlerInterface> annotationHandlerMap = new LinkedHashMap<>(); /** * 注册所有默认的注解处理器 @@ -69,7 +69,7 @@ public final class SaAnnotationStrategy { /** * 注册一个注解处理器 */ - public void registerAnnotationHandler(SaAnnotationAbstractHandler handler) { + public void registerAnnotationHandler(SaAnnotationHandlerInterface handler) { annotationHandlerMap.put(handler.getHandlerAnnotationClass(), handler); SaTokenEventCenter.doRegisterAnnotationHandler(handler); } @@ -77,8 +77,8 @@ public final class SaAnnotationStrategy { /** * 注册一个注解处理器,到首位 */ - public void registerAnnotationHandlerToFirst(SaAnnotationAbstractHandler handler) { - Map, SaAnnotationAbstractHandler> newMap = new LinkedHashMap<>(); + public void registerAnnotationHandlerToFirst(SaAnnotationHandlerInterface handler) { + Map, SaAnnotationHandlerInterface> newMap = new LinkedHashMap<>(); newMap.put(handler.getHandlerAnnotationClass(), handler); newMap.putAll(annotationHandlerMap); this.annotationHandlerMap = newMap; @@ -98,7 +98,7 @@ public final class SaAnnotationStrategy { @SuppressWarnings("unchecked") public SaCheckMethodAnnotationFunction checkMethodAnnotation = (method) -> { // 遍历所有的注解处理器,检查此 method 是否具有这些指定的注解 - for (Map.Entry, SaAnnotationAbstractHandler> entry: annotationHandlerMap.entrySet()) { + for (Map.Entry, SaAnnotationHandlerInterface> entry: annotationHandlerMap.entrySet()) { // 先校验 Method 所属 Class 上的注解 Annotation classTakeAnnotation = instance.getAnnotation.apply(method.getDeclaringClass(), (Class)entry.getKey()); diff --git a/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/custom_annotation/handler/CheckAccountHandler.java b/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/custom_annotation/handler/CheckAccountHandler.java index 96cb659e..2b49badf 100644 --- a/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/custom_annotation/handler/CheckAccountHandler.java +++ b/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/custom_annotation/handler/CheckAccountHandler.java @@ -1,6 +1,6 @@ package com.pj.satoken.custom_annotation.handler; -import cn.dev33.satoken.annotation.handler.SaAnnotationAbstractHandler; +import cn.dev33.satoken.annotation.handler.SaAnnotationHandlerInterface; import cn.dev33.satoken.context.SaHolder; import cn.dev33.satoken.exception.SaTokenException; import com.pj.satoken.custom_annotation.CheckAccount; @@ -15,7 +15,7 @@ import java.lang.reflect.Method; * */ @Component -public class CheckAccountHandler implements SaAnnotationAbstractHandler { +public class CheckAccountHandler implements SaAnnotationHandlerInterface { // 指定这个处理器要处理哪个注解 @Override diff --git a/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/custom_annotation/handler/SaUserCheckLoginHandler.java b/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/custom_annotation/handler/SaUserCheckLoginHandler.java index 46401f9e..73c51865 100644 --- a/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/custom_annotation/handler/SaUserCheckLoginHandler.java +++ b/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/custom_annotation/handler/SaUserCheckLoginHandler.java @@ -1,6 +1,6 @@ package com.pj.satoken.custom_annotation.handler; -import cn.dev33.satoken.annotation.handler.SaAnnotationAbstractHandler; +import cn.dev33.satoken.annotation.handler.SaAnnotationHandlerInterface; import cn.dev33.satoken.annotation.handler.SaCheckLoginHandler; import com.pj.satoken.StpUserUtil; import com.pj.satoken.custom_annotation.SaUserCheckLogin; @@ -14,7 +14,7 @@ import java.lang.reflect.Method; * @author click33 */ @Component -public class SaUserCheckLoginHandler implements SaAnnotationAbstractHandler { +public class SaUserCheckLoginHandler implements SaAnnotationHandlerInterface { @Override public Class getHandlerAnnotationClass() { diff --git a/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/custom_annotation/handler/SaUserCheckPermissionHandler.java b/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/custom_annotation/handler/SaUserCheckPermissionHandler.java index 9759d884..35ef3a44 100644 --- a/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/custom_annotation/handler/SaUserCheckPermissionHandler.java +++ b/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/custom_annotation/handler/SaUserCheckPermissionHandler.java @@ -1,6 +1,6 @@ package com.pj.satoken.custom_annotation.handler; -import cn.dev33.satoken.annotation.handler.SaAnnotationAbstractHandler; +import cn.dev33.satoken.annotation.handler.SaAnnotationHandlerInterface; import cn.dev33.satoken.annotation.handler.SaCheckPermissionHandler; import com.pj.satoken.StpUserUtil; import com.pj.satoken.custom_annotation.SaUserCheckPermission; @@ -14,7 +14,7 @@ import java.lang.reflect.Method; * @author click33 */ @Component -public class SaUserCheckPermissionHandler implements SaAnnotationAbstractHandler { +public class SaUserCheckPermissionHandler implements SaAnnotationHandlerInterface { @Override public Class getHandlerAnnotationClass() { diff --git a/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/custom_annotation/handler/SaUserCheckRoleHandler.java b/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/custom_annotation/handler/SaUserCheckRoleHandler.java index 633a756b..c430fb1e 100644 --- a/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/custom_annotation/handler/SaUserCheckRoleHandler.java +++ b/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/custom_annotation/handler/SaUserCheckRoleHandler.java @@ -1,6 +1,6 @@ package com.pj.satoken.custom_annotation.handler; -import cn.dev33.satoken.annotation.handler.SaAnnotationAbstractHandler; +import cn.dev33.satoken.annotation.handler.SaAnnotationHandlerInterface; import cn.dev33.satoken.annotation.handler.SaCheckRoleHandler; import com.pj.satoken.StpUserUtil; import com.pj.satoken.custom_annotation.SaUserCheckRole; @@ -14,7 +14,7 @@ import java.lang.reflect.Method; * @author click33 */ @Component -public class SaUserCheckRoleHandler implements SaAnnotationAbstractHandler { +public class SaUserCheckRoleHandler implements SaAnnotationHandlerInterface { @Override public Class getHandlerAnnotationClass() { diff --git a/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/custom_annotation/handler/SaUserCheckSafeHandler.java b/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/custom_annotation/handler/SaUserCheckSafeHandler.java index 8c046506..88504e06 100644 --- a/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/custom_annotation/handler/SaUserCheckSafeHandler.java +++ b/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/custom_annotation/handler/SaUserCheckSafeHandler.java @@ -1,6 +1,6 @@ package com.pj.satoken.custom_annotation.handler; -import cn.dev33.satoken.annotation.handler.SaAnnotationAbstractHandler; +import cn.dev33.satoken.annotation.handler.SaAnnotationHandlerInterface; import cn.dev33.satoken.annotation.handler.SaCheckSafeHandler; import com.pj.satoken.StpUserUtil; import com.pj.satoken.custom_annotation.SaUserCheckSafe; @@ -14,7 +14,7 @@ import java.lang.reflect.Method; * @author click33 */ @Component -public class SaUserCheckSafeHandler implements SaAnnotationAbstractHandler { +public class SaUserCheckSafeHandler implements SaAnnotationHandlerInterface { @Override public Class getHandlerAnnotationClass() { diff --git a/sa-token-demo/sa-token-demo-solon/src/main/java/com/pj/satoken/custom_annotation/handler/CheckAccountHandler.java b/sa-token-demo/sa-token-demo-solon/src/main/java/com/pj/satoken/custom_annotation/handler/CheckAccountHandler.java index f7f9c6e6..e7c608f2 100644 --- a/sa-token-demo/sa-token-demo-solon/src/main/java/com/pj/satoken/custom_annotation/handler/CheckAccountHandler.java +++ b/sa-token-demo/sa-token-demo-solon/src/main/java/com/pj/satoken/custom_annotation/handler/CheckAccountHandler.java @@ -1,6 +1,6 @@ package com.pj.satoken.custom_annotation.handler; -import cn.dev33.satoken.annotation.handler.SaAnnotationAbstractHandler; +import cn.dev33.satoken.annotation.handler.SaAnnotationHandlerInterface; import cn.dev33.satoken.context.SaHolder; import cn.dev33.satoken.exception.SaTokenException; import com.pj.satoken.custom_annotation.CheckAccount; @@ -15,7 +15,7 @@ import java.lang.reflect.Method; * */ @Component -public class CheckAccountHandler implements SaAnnotationAbstractHandler { +public class CheckAccountHandler implements SaAnnotationHandlerInterface { // 指定这个处理器要处理哪个注解 @Override diff --git a/sa-token-plugin/sa-token-spring-aop/src/main/java/cn/dev33/satoken/aop/SaAopPointcutAdvisorBeanRegister.java b/sa-token-plugin/sa-token-spring-aop/src/main/java/cn/dev33/satoken/aop/SaAopPointcutAdvisorBeanRegister.java index d379ebdd..8fe1a649 100644 --- a/sa-token-plugin/sa-token-spring-aop/src/main/java/cn/dev33/satoken/aop/SaAopPointcutAdvisorBeanRegister.java +++ b/sa-token-plugin/sa-token-spring-aop/src/main/java/cn/dev33/satoken/aop/SaAopPointcutAdvisorBeanRegister.java @@ -1,6 +1,6 @@ package cn.dev33.satoken.aop; -import cn.dev33.satoken.annotation.handler.SaAnnotationAbstractHandler; +import cn.dev33.satoken.annotation.handler.SaAnnotationHandlerInterface; import cn.dev33.satoken.strategy.SaAnnotationStrategy; import org.springframework.aop.aspectj.AspectJExpressionPointcut; import org.springframework.context.annotation.Bean; @@ -30,7 +30,7 @@ public class SaAopPointcutAdvisorBeanRegister { public static SaAroundAnnotationPointcutAdvisor saAroundAnnoAdvisor; @Bean - public SaAroundAnnotationPointcutAdvisor saAroundAnnotationHandlePointcutAdvisor (List> handlerList) { + public SaAroundAnnotationPointcutAdvisor saAroundAnnotationHandlePointcutAdvisor (List> handlerList) { SaAroundAnnotationPointcutAdvisor advisor = new SaAroundAnnotationPointcutAdvisor(); // 定义切入规则 @@ -52,14 +52,14 @@ public class SaAopPointcutAdvisorBeanRegister { * @param appendHandlerList 追加的 SaAnnotationAbstractHandler 处理器 * @return / */ - public static String calcExpression(List> appendHandlerList) { + public static String calcExpression(List> appendHandlerList) { // 框架内置的 List> list = new ArrayList<>(SaAnnotationStrategy.instance.annotationHandlerMap.keySet()); // 额外追加的 if(appendHandlerList != null) { - for (SaAnnotationAbstractHandler handler : appendHandlerList) { + for (SaAnnotationHandlerInterface handler : appendHandlerList) { Class cls = handler.getHandlerAnnotationClass(); if(!list.contains(cls)) { list.add(handler.getHandlerAnnotationClass()); diff --git a/sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/XPluginImp.java b/sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/XPluginImp.java index bbff6026..a6bb293e 100644 --- a/sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/XPluginImp.java +++ b/sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/XPluginImp.java @@ -16,7 +16,7 @@ package cn.dev33.satoken.solon; import cn.dev33.satoken.SaManager; -import cn.dev33.satoken.annotation.handler.SaAnnotationAbstractHandler; +import cn.dev33.satoken.annotation.handler.SaAnnotationHandlerInterface; import cn.dev33.satoken.config.SaTokenConfig; import cn.dev33.satoken.context.second.SaTokenSecondContextCreator; import cn.dev33.satoken.dao.SaTokenDao; @@ -98,7 +98,7 @@ public class XPluginImp implements Plugin { }); // 注入自定义注解处理器 Bean (可以有多个) - context.subBeansOfType(SaAnnotationAbstractHandler.class, sl -> { + context.subBeansOfType(SaAnnotationHandlerInterface.class, sl -> { SaAnnotationStrategy.instance.registerAnnotationHandler(sl); }); diff --git a/sa-token-starter/sa-token-spring-boot-autoconfig/src/main/java/cn/dev33/satoken/spring/SaBeanInject.java b/sa-token-starter/sa-token-spring-boot-autoconfig/src/main/java/cn/dev33/satoken/spring/SaBeanInject.java index 69d4777e..ce9ff0d3 100644 --- a/sa-token-starter/sa-token-spring-boot-autoconfig/src/main/java/cn/dev33/satoken/spring/SaBeanInject.java +++ b/sa-token-starter/sa-token-spring-boot-autoconfig/src/main/java/cn/dev33/satoken/spring/SaBeanInject.java @@ -16,7 +16,7 @@ package cn.dev33.satoken.spring; import cn.dev33.satoken.SaManager; -import cn.dev33.satoken.annotation.handler.SaAnnotationAbstractHandler; +import cn.dev33.satoken.annotation.handler.SaAnnotationHandlerInterface; import cn.dev33.satoken.config.SaTokenConfig; import cn.dev33.satoken.context.SaTokenContext; import cn.dev33.satoken.context.second.SaTokenSecondContextCreator; @@ -126,8 +126,8 @@ public class SaBeanInject { * @param handlerList 自定义注解处理器集合 */ @Autowired(required = false) - public void setSaAnnotationHandler(List> handlerList) { - for (SaAnnotationAbstractHandler handler : handlerList) { + public void setSaAnnotationHandler(List> handlerList) { + for (SaAnnotationHandlerInterface handler : handlerList) { SaAnnotationStrategy.instance.registerAnnotationHandler(handler); } } -- Gitee From 1bc59dc14cb3bb5ce0d7928b54f40f9415f1fab3 Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Tue, 20 Aug 2024 17:34:18 +0800 Subject: [PATCH 141/172] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20=5FFINALLY=5FWORK?= =?UTF-8?q?=5FSCOPE=20=E6=9C=80=E7=BB=88=E6=9D=83=E9=99=90=E5=A4=84?= =?UTF-8?q?=E7=90=86=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oauth2/oauth2-custom-scope-handler.md | 45 ++++++++++++++++++- .../satoken/oauth2/consts/SaOAuth2Consts.java | 7 ++- .../oauth2/strategy/SaOAuth2Strategy.java | 9 ++++ 3 files changed, 59 insertions(+), 2 deletions(-) diff --git a/sa-token-doc/oauth2/oauth2-custom-scope-handler.md b/sa-token-doc/oauth2/oauth2-custom-scope-handler.md index 2a391c80..17e9548e 100644 --- a/sa-token-doc/oauth2/oauth2-custom-scope-handler.md +++ b/sa-token-doc/oauth2/oauth2-custom-scope-handler.md @@ -175,4 +175,47 @@ http://sa-oauth-server.com:8000/oauth2/token 拿到 userinfo。 #### 总结 -相比于自定义接口模式,自定义权限处理器模式可以少一次网络请求,提前拿到 `userinfo` 信息。 \ No newline at end of file +相比于自定义接口模式,自定义权限处理器模式可以少一次网络请求,让 oauth2-client 端提前拿到 `userinfo` 信息。 + + + +### 4、最终权限处理器 +当一个自定义权限处理器,监听的 scope 字符串为 `_FINALLY_WORK_SCOPE` 时,则代表这个权限处理器为“最终权限处理器”。 + +最终权限处理器会永远在所有权限处理器工作完成之后执行一次,即使 oauth2-client 端没有申请任何 scope,最终权限处理器也会固定执行。 + +示例: +``` java +/** + * 最终权限处理器:在所有权限处理器工作完成之后,执行此权限处理器 + */ +@Component +public class FinallyWorkScopeHandler implements SaOAuth2ScopeHandlerInterface { + + @Override + public String getHandlerScope() { + return SaOAuth2Consts._FINALLY_WORK_SCOPE; + } + + @Override + public void workAccessToken(AccessTokenModel at) { + // 在所有权限处理器工作完成之后,执行此处方法加工 AccessToken + // System.out.println(123); + } + + @Override + public void workClientToken(ClientTokenModel ct) { + // System.out.println(456); + } +} +``` + + + + + + + + + + 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 a6e104c4..9311a0a3 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 @@ -111,5 +111,10 @@ public class SaOAuth2Consts { /** 表示请求没有得到任何有效处理 {msg: "not handle"} */ public static final String NOT_HANDLE = "{\"msg\": \"not handle\"}"; - + + /** + * 最终权限处理器标识符:在所有权限处理器执行之后,执行此 scope 标识符代表的权限处理器 + */ + public static final String _FINALLY_WORK_SCOPE = "_FINALLY_WORK_SCOPE"; + } 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 05aa59f5..f1dc0187 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 @@ -16,6 +16,7 @@ package cn.dev33.satoken.oauth2.strategy; import cn.dev33.satoken.SaManager; +import cn.dev33.satoken.oauth2.consts.SaOAuth2Consts; import cn.dev33.satoken.oauth2.function.strategy.*; import cn.dev33.satoken.oauth2.scope.CommonScope; import cn.dev33.satoken.oauth2.scope.handler.OidcScopeHandler; @@ -92,6 +93,10 @@ public final class SaOAuth2Strategy { } } } + SaOAuth2ScopeHandlerInterface finallyWorkScopeHandler = scopeHandlerMap.get(SaOAuth2Consts._FINALLY_WORK_SCOPE); + if(finallyWorkScopeHandler != null) { + finallyWorkScopeHandler.workAccessToken(at); + } }; /** @@ -106,6 +111,10 @@ public final class SaOAuth2Strategy { } } } + SaOAuth2ScopeHandlerInterface finallyWorkScopeHandler = scopeHandlerMap.get(SaOAuth2Consts._FINALLY_WORK_SCOPE); + if(finallyWorkScopeHandler != null) { + finallyWorkScopeHandler.workClientToken(ct); + } }; /** -- Gitee From 4aa494159806b73f3fe0ced0ccfe1e4dfb46ba8c Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Wed, 21 Aug 2024 13:57:05 +0800 Subject: [PATCH 142/172] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20scope=20=E7=AD=89?= =?UTF-8?q?=E7=BA=A7=E5=88=92=E5=88=86=EF=BC=8C=E5=8F=AF=E6=8C=87=E5=AE=9A?= =?UTF-8?q?=E5=93=AA=E4=BA=9B=E6=9D=83=E9=99=90=E9=9C=80=E8=A6=81=E5=BC=BA?= =?UTF-8?q?=E5=88=B6=E6=AF=8F=E6=AC=A1=E6=89=8B=E5=8A=A8=E6=8E=88=E6=9D=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../satoken/context/model/SaRequest.java | 21 ++- .../java/cn/dev33/satoken/util/SaFoxUtil.java | 59 ++++++++ .../pj/oauth2/SaOAuth2ServerController.java | 2 +- .../src/main/resources/application.yml | 4 + .../src/main/resources/templates/confirm.html | 19 ++- sa-token-doc/_sidebar.md | 9 +- sa-token-doc/oauth2/oauth2-apidoc.md | 78 +++++++++- .../oauth2/oauth2-custom-scope-handler.md | 2 +- sa-token-doc/oauth2/oauth2-scope-level.md | 137 ++++++++++++++++++ sa-token-doc/oauth2/oauth2-server.md | 14 +- sa-token-doc/static/doc.css | 10 +- .../satoken/oauth2/config/SaOAuth2Config.java | 98 ++++++++++--- .../satoken/oauth2/consts/SaOAuth2Consts.java | 1 + .../oauth2/error/SaOAuth2ErrorCode.java | 5 +- .../processor/SaOAuth2ServerProcessor.java | 105 ++++++++++---- .../oauth2/template/SaOAuth2Template.java | 52 +++++++ 16 files changed, 545 insertions(+), 71 deletions(-) create mode 100644 sa-token-doc/oauth2/oauth2-scope-level.md diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/context/model/SaRequest.java b/sa-token-core/src/main/java/cn/dev33/satoken/context/model/SaRequest.java index 7c0c0fa9..8a0b3d45 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/context/model/SaRequest.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/context/model/SaRequest.java @@ -17,6 +17,7 @@ package cn.dev33.satoken.context.model; import cn.dev33.satoken.error.SaErrorCode; import cn.dev33.satoken.exception.SaTokenException; +import cn.dev33.satoken.router.SaHttpMethod; import cn.dev33.satoken.util.SaFoxUtil; import java.util.List; @@ -156,7 +157,25 @@ public interface SaRequest { * @return / */ String getMethod(); - + + /** + * 返回当前请求 Method 是否为指定值 + * @param method method + * @return / + */ + default boolean isMethod(String method) { + return getMethod().equals(method); + } + + /** + * 返回当前请求 Method 是否为指定值 + * @param method method + * @return / + */ + default boolean isMethod(SaHttpMethod method) { + return getMethod().equals(method.name()); + } + /** * 判断此请求是否为 Ajax 异步请求 * @return / diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/util/SaFoxUtil.java b/sa-token-core/src/main/java/cn/dev33/satoken/util/SaFoxUtil.java index 530e1fbd..4994bde0 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/util/SaFoxUtil.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/util/SaFoxUtil.java @@ -678,4 +678,63 @@ public class SaFoxUtil { return false; } + /** + * list1 是否完全包含 list2 中所有元素 + * @param list1 集合1 + * @param list2 集合2 + * @return / + */ + public static boolean list1ContainList2AllElement(List list1, List list2){ + if(list2 == null || list2.isEmpty()) { + return true; + } + if(list1 == null || list1.isEmpty()) { + return false; + } + for (String str : list2) { + if(!list1.contains(str)) { + return false; + } + } + return true; + } + + /** + * list1 是否包含 list2 中任意一个元素 + * @param list1 集合1 + * @param list2 集合2 + * @return / + */ + public static boolean list1ContainList2AnyElement(List list1, List list2){ + if(list1 == null || list1.isEmpty() || list2 == null || list2.isEmpty()) { + return false; + } + for (String str : list2) { + if(list1.contains(str)) { + return true; + } + } + return false; + } + + /** + * 从 list1 中剔除 list2 所包含的元素 (克隆副本操作,不影响 list1) + * @param list1 集合1 + * @param list2 集合2 + * @return / + */ + public static List list1RemoveByList2(List list1, List list2){ + if(list1 == null) { + return null; + } + if(list1.isEmpty() || list2 == null || list2.isEmpty()) { + return new ArrayList<>(list1); + } + List listX = new ArrayList<>(list1); + for (String str : list2) { + listX.remove(str); + } + return listX; + } + } diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java index 58d75726..37261fd0 100644 --- a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java @@ -57,7 +57,7 @@ public class SaOAuth2ServerController { }; } - + // ---------- 开放相关资源接口: Client端根据 Access-Token ,置换相关资源 ------------ // 获取 userinfo 信息:昵称、头像、性别等等 diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/resources/application.yml b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/resources/application.yml index 44f39cd0..073f9d06 100644 --- a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/resources/application.yml +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/resources/application.yml @@ -15,6 +15,10 @@ sa-token: enable-password: true # 是否全局开启客户端模式 enable-client: true + # 定义哪些 scope 是高级权限,多个用逗号隔开 + # higher-scope: openid,userid + # 定义哪些 scope 是低级权限,多个用逗号隔开 + # lower-scope: userinfo spring: # redis配置 diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/resources/templates/confirm.html b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/resources/templates/confirm.html index 7ede1714..cfeaf099 100644 --- a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/resources/templates/confirm.html +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/resources/templates/confirm.html @@ -33,20 +33,31 @@ console.log('-----------'); $.ajax({ url: '/oauth2/doConfirm', + method: "POST", data: { client_id: getParam('client_id'), - scope: getParam('scope') + scope: getParam('scope'), + // 以下四个参数必须一起出现 + build_redirect_uri: true, + response_type: getParam('response_type'), + redirect_uri: getParam('redirect_uri'), + state: getParam('state'), }, dataType: 'json', success: function(res) { - if(res.code == 200) { + console.log('res:', res); + if(res.code === 200) { layer.msg('授权成功!'); setTimeout(function() { - location.reload(true); + if (res.redirect_uri) { + location.href = res.redirect_uri; + } else { + location.reload(); + } }, 800); } else { // 重定向至授权失败URL - layer.alert('授权失败!'); + layer.alert('授权失败:' + res.msg); } }, error: function(e) { diff --git a/sa-token-doc/_sidebar.md b/sa-token-doc/_sidebar.md index f742c13f..cdd29799 100644 --- a/sa-token-doc/_sidebar.md +++ b/sa-token-doc/_sidebar.md @@ -59,13 +59,14 @@ - [定制化登录页面与授权页面](/oauth2/oauth2-custom-login) - [自定义 API 路由 ](/oauth2/oauth2-custom-api) - [自定义 Scope 权限以处理器](/oauth2/oauth2-custom-scope-handler) - - [为 Scope 划分等级](/oauth2/7) - - - [平台中心模式开发](/oauth2/5) + - [为 Scope 划分等级](/oauth2/oauth2-scope-level) + - [自定义 grant_type](/oauth2/oauth2-custom-grant_type) - [OAuth2-与登录会话实现数据互通](/oauth2/oauth2-interworking) - [OAuth2 代码 API 参考](/oauth2/oauth2-dev) - [常见问题说明](/oauth2/8) - - + + + - - **微服务** - [分布式Session会话](/micro/dcs-session) diff --git a/sa-token-doc/oauth2/oauth2-apidoc.md b/sa-token-doc/oauth2/oauth2-apidoc.md index eedcdf04..a1b7b10f 100644 --- a/sa-token-doc/oauth2/oauth2-apidoc.md +++ b/sa-token-doc/oauth2/oauth2-apidoc.md @@ -43,6 +43,82 @@ redirect_uri?code={code}&state={state} 4. 每次授权产生新 `Code` 码,会导致旧 `Code` 码立即作废,即使旧 `Code` 码尚未使用。 +
+RestAPI 登录接口:/oauth2/doLogin + +如果用户在 OAuth-Server 端尚未登录,则会被阻塞在登录界面,开始登录,需要在页面上调用`/oauth2/doLogin`完成登录(此接口非 OAuth2 标准协议接口) + +``` url +http://{host}:{port}/oauth2/doLogin + ?name={name} + &pwd={pwd} +``` +参数详解: + +| 参数 | 是否必填 | 说明 | +| :-------- | :-------- | :-------- | +| name | 否 | 账号 | +| pwd | 否 | 密码 | + +访问此接口将进入自定义的 `cfg.doLoginHandle` 函数开始登录,你只要在此函数内调用 `StpUtil.login(xxx)` 即代表登录成功。 + +另外需要注意:此接口并非只能携带 `name`、`pwd` 参数,因为你可以在方法里通过 `SaHolder.getRequest().getParam("xxx")` 来获取前端提交的其它参数。 + +
+ + +
+RestAPI 确认授权接口:/oauth2/doConfirm + +如果 oauth-client 端申请的 scope 在 OAuth-Server 端需要用户手动确认授权,则会被阻塞在授权界面, +需要在页面上调用`/oauth2/doConfirm`完成授权(此接口非 OAuth2 标准协议接口) + +``` url +http://{host}:{port}/oauth2/doConfirm + ?client={value} + &scope={value} + &build_redirect_uri={true|false} + &response_type={value} + &redirect_uri={value} + &state={value} +``` +参数详解: + +| 参数 | 是否必填 | 说明 | +| :-------- | :-------- | :-------- | +| client_id | 是 | 应用 id | +| scope | 是 | 具体确认的权限,多个用逗号(或空格)隔开 | +| build_redirect_uri | 否 | 是否立即构建 `redirect_uri` 授权地址,取值:true | false | +| response_type | 否 | 取 url 上的 `response_type` 参数来提交 | +| redirect_uri | 否 | 取 url 上的 `redirect_uri` 参数来提交 | +| state | 否 | 取 url 上的 `state` 参数来提交 | + +此接口有两种调用方式,一种只提供 `client_id`、`scope` 两个参数,此时返回结果代表是否确认授权成功: +``` js +{ + code: 200, + msg: 'ok', + data: null, +} +``` + +一种是指定 `build_redirect_uri: true`,并同时提供 `client_id`、`scope`、`response_type`、`redirect_uri`、`state` 全部参数, +此时返回结果包括最终的 code 授权地址: +``` js +{ + code: 200, + msg: 'ok', + data: null, + redirect_uri: 'http://sa-oauth-client.com:8002/?code=n12TTc1M9REfJVqKm0wewDz0tNZDBhE1A90irOJmxD0zb92pdhUK8NghJfuC' +} +``` + +前端在 ajax 回调函数中直接使用 `location.href=res.redirect_uri` 跳转即可,无需再重复访问 `/oauth2/authorize` 接口。 + +
+ + + ### 1.2、根据授权码获取 Access-Token 获得 `Code` 码后,我们可以通过以下接口,获取到用户的 `Access-Token`、`Refresh-Token` 等信息。 @@ -138,7 +214,7 @@ http://{host}:{port}/oauth2/revoke ### 1.5、根据 Access-Token 获取相应用户的账号信息 -注:此接口为官方仓库模拟接口,正式项目中大家可以根据此样例,自定义需要的接口及参数 +注:此接口非 OAuth2 标准协议接口,为官方仓库 demo 模拟接口,正式项目中大家可以根据此样例,自定义需要的接口及参数 ``` url http://{host}:{port}/oauth2/userinfo?access_token={access_token} diff --git a/sa-token-doc/oauth2/oauth2-custom-scope-handler.md b/sa-token-doc/oauth2/oauth2-custom-scope-handler.md index 17e9548e..58207223 100644 --- a/sa-token-doc/oauth2/oauth2-custom-scope-handler.md +++ b/sa-token-doc/oauth2/oauth2-custom-scope-handler.md @@ -139,7 +139,7 @@ http://sa-oauth-server.com:8000/oauth2/authorize #### 3、code 换 access_token -3、访问上述链接后,得到 `code` 授权码,然后我们拿着 `code` 换 `access_token` +访问上述链接后,得到 `code` 授权码,然后我们拿着 `code` 换 `access_token` ``` url http://sa-oauth-server.com:8000/oauth2/token ?grant_type=authorization_code diff --git a/sa-token-doc/oauth2/oauth2-scope-level.md b/sa-token-doc/oauth2/oauth2-scope-level.md new file mode 100644 index 00000000..161065e7 --- /dev/null +++ b/sa-token-doc/oauth2/oauth2-scope-level.md @@ -0,0 +1,137 @@ +# OAuth2 - 为 Scope 划分等级 + + +### 1、划分等级 + +我们可以通过配置文件来为 scope 划分等级 + + + +``` yaml +# sa-token 配置 +sa-token: + # OAuth2.0 配置 + oauth2: + # 定义哪些 scope 是高级权限,多个用逗号隔开 + higher-scope: openid,userid + # 定义哪些 scope 是低级权限,多个用逗号隔开 + lower-scope: userinfo +``` + +``` properties +# 定义哪些 scope 是高级权限,多个用逗号隔开 +sa-token.oauth2.higher-scope=openid,userid +# 定义哪些 scope 是低级权限,多个用逗号隔开 +sa-token.oauth2.lower-scope=userinfo +``` + + +如上所示: +- 通过 `sa-token.oauth2.higher-scope` 配置项指定的 `scope` 将变成 **高级权限**。 +- 通过 `sa-token.oauth2.lower-scope` 配置项指定的 `scope` 将变成 **低级权限**。 +- 其它未指定的 `scope` 将默认为 **一般权限**。 + +不同的权限等级其差异主要表现在:oauth2-client 授权时是否需要用户手动确认授权。 + +| 权限等级 | 申请授权时表现 | +| :-------- | :-------- | +| 高级权限 | 申请授权时:每次都需要用户手动点击确认授权按钮,才会下放 code 授权码 | +| 一般权限 | 申请授权时:如果申请的 scope 用户近期授权过,则静默授权,如果近期未授权过,则需要手动点击确认授权按钮 | +| 低级权限 | 申请授权时:不需要用户手动点击确认授权,程序自动完成静默授权 | + + +### 2、详细举例 + +1、如下例子,oauth2-client 申请的 `openid` 权限为**高级权限**,每次都需要用户手动点击确认授权按钮,才会下放 code 授权码。 + +``` url +http://{host}:{port}/oauth2/authorize + ?response_type=code + &client_id=1001 + &redirect_uri=http://sa-oauth-client.com:8002/ + &scope=openid +``` + +2、如下例子,oauth2-client 申请的 `userinfo` 权限为**低级权限**,此时不需要用户手动点击确认授权,程序自动完成静默授权。 + +``` url +http://{host}:{port}/oauth2/authorize + ?response_type=code + &client_id=1001 + &redirect_uri=http://sa-oauth-client.com:8002/ + &scope=userinfo +``` + +3、如下例子,oauth2-client 申请的 `fans_list` 权限为**一般权限**,首次申请时,需要用户手动点击确认授权,第二次再申请则是静默授权。 + +``` url +http://{host}:{port}/oauth2/authorize + ?response_type=code + &client_id=1001 + &redirect_uri=http://sa-oauth-client.com:8002/ + &scope=fans_list +``` + +4、如下例子,oauth2-client 申请的 `openid,userid,userinfo,fans_list` 权限同时包括 **高级权限**、**低级权限**、**一般权限**: + +``` url +http://{host}:{port}/oauth2/authorize + ?response_type=code + &client_id=1001 + &redirect_uri=http://sa-oauth-client.com:8002/ + &scope=openid,userid,userinfo,fans_list +``` + +此时是否需要用户手动点击确认授权按钮?具体规则表现为: +- 如果请求的 scope 列表包括高级权限,则必须用户手动点击确认授权。 +- 如果 scope 列表不包括高级权限,则将 scope 列表中的所有低级权限剔除。 +- 剔除后的 list 大小如果为零,则直接静默授权通过。 +- 剔除后的 list 大小如果不为零,则判断剩余的这些 scope 是否全部已近期授权过: + - 如果是,则静默授权。 + - 如果否,则需要用户手动点击确认授权。 + + +### 3、申请高级权限时 `/oauth2/authorize` 无法通过验证 + +由于申请高级权限时,每次都必须用户手动点击确认授权,`/oauth2/authorize` 路由接口是无法完成权限验证操作的。 + +此时需要将构建 `redirect_uri` 的动作提前,在 `/oauth2/doConfirm` 确认授权接口时额外追加 `build_redirect_uri: true` 等参数: +``` url +http://{host}:{port}/oauth2/doConfirm + ?client={value} + &scope={value} + &build_redirect_uri=true + &response_type={value} + &redirect_uri={value} + &state={value} +``` + +返回结果示例: +``` js +{ + code: 200, + msg: 'ok', + data: null, + redirect_uri: 'http://sa-oauth-client.com:8002/?code=n12TTc1M9REfJVqKm0wewDz0tNZDBhE1A90irOJmxD0zb92pdhUK8NghJfuC' +} +``` + +其中 `redirect_uri` 参数为授权挂载code地址,直接在 ajax 回调函数中使用 `location.href=res.redirect_uri` 跳转即可。 + +自定义确认授权视图修改参考: +``` java +// 授权确认视图 +cfg.confirmView = (clientId, scopes)->{ + String scopeStr = SaFoxUtil.convertListToString(scopes); + String yesCode = + "fetch('/oauth2/doConfirm' + location.search + '&build_redirect_uri=true', {method: 'POST'})" + + ".then(res => res.json())" + + ".then(res => location.href=res.redirect_uri)"; + String res = "

应用 " + clientId + " 请求授权:" + scopeStr + ",是否同意?

" + + "

" + + " " + + " " + + "

"; + return res; +}; +``` \ No newline at end of file diff --git a/sa-token-doc/oauth2/oauth2-server.md b/sa-token-doc/oauth2/oauth2-server.md index ad4a1724..de684040 100644 --- a/sa-token-doc/oauth2/oauth2-server.md +++ b/sa-token-doc/oauth2/oauth2-server.md @@ -119,10 +119,16 @@ public class SaOAuth2ServerController { // 配置:确认授权时返回的 view cfg.confirmView = (clientId, scopes) -> { String scopeStr = SaFoxUtil.convertListToString(scopes); - String msg = "

应用 " + clientId + " 请求授权:" + scopeStr + "

" - + "

请确认: 确认授权

" - + "

确认之后刷新页面

"; - return msg; + String yesCode = + "fetch('/oauth2/doConfirm?client_id=" + clientId + "&scope=" + scopeStr + "', {method: 'POST'})" + + ".then(res => res.json())" + + ".then(res => location.reload())"; + String res = "

应用 " + clientId + " 请求授权:" + scopeStr + ",是否同意?

" + + "

" + + " " + + " " + + "

"; + return res; }; } diff --git a/sa-token-doc/static/doc.css b/sa-token-doc/static/doc.css index 7806a12d..fe7b8c93 100644 --- a/sa-token-doc/static/doc.css +++ b/sa-token-doc/static/doc.css @@ -413,13 +413,14 @@ body { background-color: #f4fdef; overflow: hidden; max-height: 44px; + margin-bottom: 1em; /* transition: all 1s; */ } -.main-box details[open]{ /* max-height: 1000px; */ overflow: auto; animation: slideDown 0.4s linear both;} +.main-box details[open]{ /* max-height: 1000px; */ overflow: auto; animation: slideDown 0.6s linear both;} @keyframes slideDown { 0% { max-height: 44px; overflow: hidden; } - 99% { max-height: 1000px; overflow: hidden; } - 100% { max-height: 1000px; overflow: auto; } + 99% { max-height: 1500px; overflow: hidden; } + 100% { max-height: 1500px; overflow: auto; } } .main-box details summary{ padding: 11px 14px; @@ -429,8 +430,9 @@ body { } .main-box details pre{ margin-left: 1em; - margin-right: 14px; + margin-right: 1em; } +.main-box details table{margin-left: 1em !important; margin-right: 1em; width: auto;} .main-box details p{padding: 0 14px;} /* 广告盒子 */ diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/config/SaOAuth2Config.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/config/SaOAuth2Config.java index 41cab136..0bb39073 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/config/SaOAuth2Config.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/config/SaOAuth2Config.java @@ -66,7 +66,11 @@ public class SaOAuth2Config implements Serializable { /** 默认 openid 生成算法中使用的摘要前缀 */ public String openidDigestPrefix = SaOAuth2Consts.OPENID_DEFAULT_DIGEST_PREFIX; + /** 指定高级权限,多个用逗号隔开 */ + public String higherScope; + /** 指定低级权限,多个用逗号隔开 */ + public String lowerScope; /** * @return enableCode @@ -77,9 +81,11 @@ public class SaOAuth2Config implements Serializable { /** * @param enableCode 要设置的 enableCode + * @return / */ - public void setEnableCode(Boolean enableCode) { + public SaOAuth2Config setEnableCode(Boolean enableCode) { this.enableCode = enableCode; + return this; } /** @@ -91,9 +97,11 @@ public class SaOAuth2Config implements Serializable { /** * @param enableImplicit 要设置的 enableImplicit + * @return / */ - public void setEnableImplicit(Boolean enableImplicit) { + public SaOAuth2Config setEnableImplicit(Boolean enableImplicit) { this.enableImplicit = enableImplicit; + return this; } /** @@ -106,8 +114,9 @@ public class SaOAuth2Config implements Serializable { /** * @param enablePassword 要设置的 enablePassword */ - public void setEnablePassword(Boolean enablePassword) { + public SaOAuth2Config setEnablePassword(Boolean enablePassword) { this.enablePassword = enablePassword; + return this; } /** @@ -119,9 +128,11 @@ public class SaOAuth2Config implements Serializable { /** * @param enableClient 要设置的 enableClient + * @return / */ - public void setEnableClient(Boolean enableClient) { + public SaOAuth2Config setEnableClient(Boolean enableClient) { this.enableClient = enableClient; + return this; } /** @@ -133,9 +144,11 @@ public class SaOAuth2Config implements Serializable { /** * @param isNewRefresh 要设置的 isNewRefresh + * @return / */ - public void setIsNewRefresh(Boolean isNewRefresh) { + public SaOAuth2Config setIsNewRefresh(Boolean isNewRefresh) { this.isNewRefresh = isNewRefresh; + return this; } /** @@ -229,13 +242,53 @@ public class SaOAuth2Config implements Serializable { * @param openidDigestPrefix 要设置的 openidDigestPrefix * @return 对象自身 */ - public SaOAuth2Config setOpenidMd5Prefix(String openidDigestPrefix) { + public SaOAuth2Config setOpenidDigestPrefix(String openidDigestPrefix) { this.openidDigestPrefix = openidDigestPrefix; return this; } - - // -------------------- SaOAuth2Handle 所有回调函数 -------------------- + /** + * 获取 指定高级权限,多个用逗号隔开 + * + * @return higherScope 指定高级权限,多个用逗号隔开 + */ + public String getHigherScope() { + return this.higherScope; + } + + /** + * 设置 指定高级权限,多个用逗号隔开 + * + * @param higherScope 指定高级权限,多个用逗号隔开 + * @return / + */ + public SaOAuth2Config setHigherScope(String higherScope) { + this.higherScope = higherScope; + return this; + } + + /** + * 获取 指定低级权限,多个用逗号隔开 + * + * @return lowerScope 指定低级权限,多个用逗号隔开 + */ + public String getLowerScope() { + return this.lowerScope; + } + + /** + * 设置 指定低级权限,多个用逗号隔开 + * + * @param lowerScope 指定低级权限,多个用逗号隔开 + * @return / + */ + public SaOAuth2Config setLowerScope(String lowerScope) { + this.lowerScope = lowerScope; + return this; + } + + + // -------------------- SaOAuth2Handle 所有回调函数 -------------------- /** * OAuth-Server端:未登录时返回的View @@ -254,19 +307,20 @@ public class SaOAuth2Config implements Serializable { @Override public String toString() { - return "SaOAuth2Config [" + - "enableCode=" + enableCode - + ", enableImplicit=" + enableImplicit - + ", enablePassword=" + enablePassword - + ", enableClient=" + enableClient - + ", isNewRefresh=" + isNewRefresh - + ", codeTimeout=" + codeTimeout - + ", accessTokenTimeout=" + accessTokenTimeout - + ", refreshTokenTimeout=" + refreshTokenTimeout - + ", clientTokenTimeout=" + clientTokenTimeout - + ", pastClientTokenTimeout=" + pastClientTokenTimeout - + ", openidDigestPrefix=" + openidDigestPrefix - +"]"; + return "SaOAuth2Config{" + + "enableCode=" + enableCode + + ", enableImplicit=" + enableImplicit + + ", enablePassword=" + enablePassword + + ", enableClient=" + enableClient + + ", isNewRefresh=" + isNewRefresh + + ", codeTimeout=" + codeTimeout + + ", accessTokenTimeout=" + accessTokenTimeout + + ", refreshTokenTimeout=" + refreshTokenTimeout + + ", clientTokenTimeout=" + clientTokenTimeout + + ", pastClientTokenTimeout=" + pastClientTokenTimeout + + ", openidDigestPrefix='" + openidDigestPrefix + + ", higherScope='" + higherScope + + ", lowerScope='" + lowerScope + + '}'; } - } 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 9311a0a3..1ecd5425 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 @@ -57,6 +57,7 @@ public class SaOAuth2Consts { public static String password = "password"; public static String name = "name"; public static String pwd = "pwd"; + public static String build_redirect_uri = "build_redirect_uri"; } /** diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/error/SaOAuth2ErrorCode.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/error/SaOAuth2ErrorCode.java index fa15e0d4..b6f3cbd7 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/error/SaOAuth2ErrorCode.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/error/SaOAuth2ErrorCode.java @@ -112,5 +112,8 @@ public interface SaOAuth2ErrorCode { /** 暂未开放凭证式模式 */ int CODE_30134 = 30134; - + + /** 无效的请求 Method */ + int CODE_30141 = 30141; + } diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/processor/SaOAuth2ServerProcessor.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/processor/SaOAuth2ServerProcessor.java index 55857340..e5d3af01 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/processor/SaOAuth2ServerProcessor.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/processor/SaOAuth2ServerProcessor.java @@ -35,6 +35,7 @@ import cn.dev33.satoken.oauth2.data.model.request.RequestAuthModel; import cn.dev33.satoken.oauth2.error.SaOAuth2ErrorCode; import cn.dev33.satoken.oauth2.exception.SaOAuth2Exception; import cn.dev33.satoken.oauth2.template.SaOAuth2Template; +import cn.dev33.satoken.router.SaHttpMethod; import cn.dev33.satoken.stp.StpLogic; import cn.dev33.satoken.stp.StpUtil; import cn.dev33.satoken.util.SaResult; @@ -124,28 +125,7 @@ public class SaOAuth2ServerProcessor { String responseType = req.getParamNotNull(Param.response_type); // 1、先判断是否开启了指定的授权模式 - // 模式一:Code授权码 - if(responseType.equals(ResponseType.code)) { - if(!cfg.enableCode) { - throwErrorSystemNotEnableModel(); - } - if(!currClientModel().enableCode) { - throwErrorClientNotEnableModel(); - } - } - // 模式二:隐藏式 - else if(responseType.equals(ResponseType.token)) { - if(!cfg.enableImplicit) { - throwErrorSystemNotEnableModel(); - } - if(!currClientModel().enableImplicit) { - throwErrorClientNotEnableModel(); - } - } - // 其它 - else { - throw new SaOAuth2Exception("无效 response_type: " + req.getParam(Param.response_type)).setCode(SaOAuth2ErrorCode.CODE_30125); - } + checkAuthorizeResponseType(responseType, req, cfg); // 2、如果尚未登录, 则先去登录 if( ! getStpLogic().isLogin()) { @@ -162,8 +142,8 @@ public class SaOAuth2ServerProcessor { oauth2Template.checkContract(ra.clientId, ra.scopes); // 6、判断:如果此次申请的Scope,该用户尚未授权,则转到授权页面 - boolean isGrant = oauth2Template.isGrant(ra.loginId, ra.clientId, ra.scopes); - if( ! isGrant) { + boolean isNeedCarefulConfirm = oauth2Template.isNeedCarefulConfirm(ra.loginId, ra.clientId, ra.scopes); + if(isNeedCarefulConfirm) { return cfg.confirmView.apply(ra.clientId, ra.scopes); } @@ -306,7 +286,7 @@ public class SaOAuth2ServerProcessor { SaRequest req = SaHolder.getRequest(); SaOAuth2Config cfg = SaOAuth2Manager.getConfig(); - return cfg.doLoginHandle.apply(req.getParamNotNull(Param.name), req.getParamNotNull(Param.pwd)); + return cfg.doLoginHandle.apply(req.getParam(Param.name), req.getParam(Param.pwd)); } /** @@ -316,13 +296,51 @@ public class SaOAuth2ServerProcessor { public Object doConfirm() { // 获取变量 SaRequest req = SaHolder.getRequest(); - String clientId = req.getParamNotNull(Param.client_id); + Object loginId = getStpLogic().getLoginId(); String scope = req.getParamNotNull(Param.scope); List scopes = SaOAuth2Manager.getDataConverter().convertScopeStringToList(scope); - Object loginId = getStpLogic().getLoginId(); + SaOAuth2DataGenerate dataGenerate = SaOAuth2Manager.getDataGenerate(); + + // 此请求只允许 POST 方式 + if(!req.isMethod(SaHttpMethod.POST)) { + throw new SaOAuth2Exception("无效请求方式:" + req.getMethod()).setCode(SaOAuth2ErrorCode.CODE_30141); + } + + // 确认授权 oauth2Template.saveGrantScope(clientId, loginId, scopes); - return SaResult.ok(); + + // 判断所需的返回结果模式 + boolean buildRedirectUri = req.isParam(Param.build_redirect_uri, "true"); + + // -------- 情况1:只返回确认结果即可 + if( ! buildRedirectUri ) { + oauth2Template.saveGrantScope(clientId, loginId, scopes); + return SaResult.ok(); + } + + // -------- 情况2:需要返回最终的 redirect_uri 地址 + + // s3、构建请求 Model + RequestAuthModel ra = SaOAuth2Manager.getDataResolver().readRequestAuthModel(req, loginId); + + // 7、判断授权类型,构建不同的重定向地址 + // 如果是 授权码式,则:开始重定向授权,下放code + if(ResponseType.code.equals(ra.responseType)) { + CodeModel codeModel = dataGenerate.generateCode(ra); + String redirectUri = dataGenerate.buildRedirectUri(ra.redirectUri, codeModel.code, ra.state); + return SaResult.ok().set(Param.redirect_uri, redirectUri); + } + + // 如果是 隐藏式,则:开始重定向授权,下放 token + if(ResponseType.token.equals(ra.responseType)) { + AccessTokenModel at = dataGenerate.generateAccessToken(ra, false); + String redirectUri = dataGenerate.buildImplicitRedirectUri(ra.redirectUri, at.accessToken, ra.state); + return SaResult.ok().set(Param.redirect_uri, redirectUri); + } + + // 默认返回 + throw new SaOAuth2Exception("无效response_type: " + ra.responseType).setCode(SaOAuth2ErrorCode.CODE_30125); } /** @@ -408,6 +426,9 @@ public class SaOAuth2ServerProcessor { return SaOAuth2Manager.getDataResolver().buildClientTokenReturnValue(ct); } + + // ----------- 代码块封装 -------------- + /** * 根据当前请求提交的 client_id 参数获取 SaClientModel 对象 * @return / @@ -417,6 +438,34 @@ public class SaOAuth2ServerProcessor { return oauth2Template.checkClientModel(clientIdAndSecret.clientId); } + /** + * 校验 authorize 路由的 ResponseType 参数 + */ + public void checkAuthorizeResponseType(String responseType, SaRequest req, SaOAuth2Config cfg) { + // 模式一:Code授权码 + if(responseType.equals(ResponseType.code)) { + if(!cfg.enableCode) { + throwErrorSystemNotEnableModel(); + } + if(!currClientModel().enableCode) { + throwErrorClientNotEnableModel(); + } + } + // 模式二:隐藏式 + else if(responseType.equals(ResponseType.token)) { + if(!cfg.enableImplicit) { + throwErrorSystemNotEnableModel(); + } + if(!currClientModel().enableImplicit) { + throwErrorClientNotEnableModel(); + } + } + // 其它 + else { + throw new SaOAuth2Exception("无效 response_type: " + req.getParam(Param.response_type)).setCode(SaOAuth2ErrorCode.CODE_30125); + } + } + /** * 获取底层使用的会话对象 * diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java index 73422fae..3062f525 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java @@ -123,6 +123,7 @@ public class SaOAuth2Template { // ------------------- check 数据校验 + /** * 判断:指定 loginId 是否对一个 Client 授权给了指定 Scope * @param loginId 账号id @@ -135,6 +136,39 @@ public class SaOAuth2Template { List grantScopeList = dao.getGrantScope(clientId, loginId); return scopes.isEmpty() || new HashSet<>(grantScopeList).containsAll(scopes); } + + /** + * 判断:指定 loginId 在指定 Client 请求指定 Scope 时,是否需要手动确认授权 + * @param loginId 账号id + * @param clientId 应用id + * @param scopes 权限 + * @return 是否已经授权 + */ + public boolean isNeedCarefulConfirm(Object loginId, String clientId, List scopes) { + // 如果请求的权限为空,则不需要确认 + if(scopes == null || scopes.isEmpty()) { + return false; + } + + // 如果包含高级权限,则必须手动确认授权 + List higherScopeList = getHigherScopeList(); + if(SaFoxUtil.list1ContainList2AnyElement(scopes, higherScopeList)) { + return true; + } + + // 如果包含低级权限,则先将低级权限剔除掉 + List lowerScopeList = getLowerScopeList(); + scopes = SaFoxUtil.list1RemoveByList2(scopes, lowerScopeList); + + // 如果剔除后的权限为空,则不需要确认 + if(scopes.isEmpty()) { + return false; + } + + // 根据近期授权记录,判断是否需要确认 + return !isGrant(loginId, clientId, scopes); + } + /** * 校验:该Client是否签约了指定的Scope * @param clientId 应用id @@ -362,6 +396,24 @@ public class SaOAuth2Template { SaOAuth2Manager.getDao().saveGrantScope(clientId, loginId, scopes); } + /** + * 获取高级权限列表 + * @return / + */ + public List getHigherScopeList() { + String higherScope = SaOAuth2Manager.getConfig().getHigherScope(); + return SaOAuth2Manager.getDataConverter().convertScopeStringToList(higherScope); + } + + /** + * 获取低级权限列表 + * @return / + */ + public List getLowerScopeList() { + String lowerScope = SaOAuth2Manager.getConfig().getLowerScope(); + return SaOAuth2Manager.getDataConverter().convertScopeStringToList(lowerScope); + } + -- Gitee From 3345e3aaf98be0f06a6c3d8e3af472d687781ce6 Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Fri, 23 Aug 2024 03:24:30 +0800 Subject: [PATCH 143/172] =?UTF-8?q?sa-token-oauth2=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E8=87=AA=E5=AE=9A=E4=B9=89=20grant=5Ftype=20=E8=83=BD=E5=8A=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/cn/dev33/satoken/util/SaFoxUtil.java | 11 ++ .../com/pj/oauth2/SaOAuth2DataLoaderImpl.java | 13 +- .../custom/PhoneCodeGrantTypeHandler.java | 56 ++++++ .../oauth2/custom/PhoneLoginController.java | 26 +++ .../{ => custom}/UserinfoScopeHandler.java | 2 +- .../src/main/resources/application.yml | 2 + sa-token-doc/_sidebar.md | 2 +- .../oauth2/oauth2-custom-grant_type.md | 174 ++++++++++++++++++ .../dev33/satoken/oauth2/SaOAuth2Manager.java | 39 ++++ .../satoken/oauth2/consts/GrantType.java | 12 ++ .../satoken/oauth2/consts/SaOAuth2Consts.java | 11 -- .../data/generate/SaOAuth2DataGenerate.java | 18 +- .../data/model/loader/SaClientModel.java | 126 +++---------- .../SaOAuth2GrantTypeAuthFunction.java | 35 ++++ .../AuthorizationCodeGrantTypeHandler.java | 57 ++++++ .../handler/PasswordGrantTypeHandler.java | 76 ++++++++ .../handler/RefreshTokenGrantTypeHandler.java | 59 ++++++ .../SaOAuth2GrantTypeHandlerInterface.java | 46 +++++ .../processor/SaOAuth2ServerProcessor.java | 151 ++------------- .../oauth2/strategy/SaOAuth2Strategy.java | 84 ++++++++- .../satoken/oauth2/template/SaOAuth2Util.java | 30 +-- .../spring/oauth2/SaOAuth2BeanInject.java | 25 ++- 22 files changed, 778 insertions(+), 277 deletions(-) create mode 100644 sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/custom/PhoneCodeGrantTypeHandler.java create mode 100644 sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/custom/PhoneLoginController.java rename sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/{ => custom}/UserinfoScopeHandler.java (97%) create mode 100644 sa-token-doc/oauth2/oauth2-custom-grant_type.md create mode 100644 sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/consts/GrantType.java create mode 100644 sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/function/strategy/SaOAuth2GrantTypeAuthFunction.java create mode 100644 sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/granttype/handler/AuthorizationCodeGrantTypeHandler.java create mode 100644 sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/granttype/handler/PasswordGrantTypeHandler.java create mode 100644 sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/granttype/handler/RefreshTokenGrantTypeHandler.java create mode 100644 sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/granttype/handler/SaOAuth2GrantTypeHandlerInterface.java diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/util/SaFoxUtil.java b/sa-token-core/src/main/java/cn/dev33/satoken/util/SaFoxUtil.java index 4994bde0..8044a223 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/util/SaFoxUtil.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/util/SaFoxUtil.java @@ -75,6 +75,17 @@ public class SaFoxUtil { return sb.toString(); } + /** + * 生成指定区间的 int 值 + * + * @param min 最小值(包括) + * @param max 最大值(包括) + * @return / + */ + public static int getRandomNumber(int min, int max) { + return ThreadLocalRandom.current().nextInt(min, max + 1); + } + /** * 指定元素是否为null或者空字符串 * @param str 指定元素 diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2DataLoaderImpl.java b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2DataLoaderImpl.java index a7d10925..fbcc2863 100644 --- a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2DataLoaderImpl.java +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2DataLoaderImpl.java @@ -1,5 +1,6 @@ package com.pj.oauth2; +import cn.dev33.satoken.oauth2.consts.GrantType; import cn.dev33.satoken.oauth2.data.loader.SaOAuth2DataLoader; import cn.dev33.satoken.oauth2.data.model.loader.SaClientModel; import org.springframework.stereotype.Component; @@ -22,10 +23,14 @@ public class SaOAuth2DataLoaderImpl implements SaOAuth2DataLoader { .setClientSecret("aaaa-bbbb-cccc-dddd-eeee") // client 秘钥 .addAllowUrls("*") // 所有允许授权的 url .addContractScopes("openid", "userid", "userinfo") // 所有签约的权限 - .setEnableCode(true) // 是否开启授权码模式 - .setEnableImplicit(true) // 是否开启隐式模式 - .setEnablePassword(true) // 是否开启密码模式 - .setEnableClient(true) // 是否开启客户端模式 + .addAllowGrantTypes( // 所有允许的授权模式 + GrantType.authorization_code, // 授权码式 + GrantType.implicit, // 隐式式 + GrantType.refresh_token, // 刷新令牌 + GrantType.password, // 密码式 + GrantType.client_credentials, // 客户端模式 + "phone_code" // 自定义授权模式 手机号验证码登录 + ) ; } return null; diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/custom/PhoneCodeGrantTypeHandler.java b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/custom/PhoneCodeGrantTypeHandler.java new file mode 100644 index 00000000..9bad7d29 --- /dev/null +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/custom/PhoneCodeGrantTypeHandler.java @@ -0,0 +1,56 @@ +package com.pj.oauth2.custom; + +import cn.dev33.satoken.SaManager; +import cn.dev33.satoken.context.model.SaRequest; +import cn.dev33.satoken.oauth2.SaOAuth2Manager; +import cn.dev33.satoken.oauth2.data.model.AccessTokenModel; +import cn.dev33.satoken.oauth2.data.model.request.RequestAuthModel; +import cn.dev33.satoken.oauth2.exception.SaOAuth2Exception; +import cn.dev33.satoken.oauth2.granttype.handler.SaOAuth2GrantTypeHandlerInterface; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * 自定义 phone_code 授权模式处理器 + * + * @author click33 + * @since 2024/8/23 + */ +@Component +public class PhoneCodeGrantTypeHandler implements SaOAuth2GrantTypeHandlerInterface { + + @Override + public String getHandlerGrantType() { + return "phone_code"; + } + + @Override + public AccessTokenModel getAccessTokenModel(SaRequest req, String clientId, List scopes) { + + String phone = req.getParamNotNull("phone"); + String code = req.getParamNotNull("code"); + String realCode = SaManager.getSaTokenDao().get("phone_code:" + phone); + + // 1、校验验证码是否正确 + if(!code.equals(realCode)) { + throw new SaOAuth2Exception("验证码错误"); + } + + // 2、校验通过,删除验证码 + SaManager.getSaTokenDao().delete("phone_code:" + phone); + + // 3、登录 + long userId = 10001; // 模拟 userId,真实项目应该根据手机号从数据库查询 + + // 4、构建 ra 对象 + RequestAuthModel ra = new RequestAuthModel(); + ra.clientId = clientId; + ra.loginId = userId; + ra.scopes = scopes; + + // 5、生成 Access-Token + AccessTokenModel at = SaOAuth2Manager.getDataGenerate().generateAccessToken(ra, true); + return at; + } +} \ No newline at end of file diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/custom/PhoneLoginController.java b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/custom/PhoneLoginController.java new file mode 100644 index 00000000..92cb3d99 --- /dev/null +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/custom/PhoneLoginController.java @@ -0,0 +1,26 @@ +package com.pj.oauth2.custom; + +import cn.dev33.satoken.SaManager; +import cn.dev33.satoken.util.SaFoxUtil; +import cn.dev33.satoken.util.SaResult; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 自定义手机登录接口 + * + * @author click33 + * @since 2024/8/23 + */ +@RestController +public class PhoneLoginController { + + @RequestMapping("/oauth2/sendPhoneCode") + public SaResult sendCode(String phone) { + String code = SaFoxUtil.getRandomNumber(100000, 999999) + ""; + SaManager.getSaTokenDao().set("phone_code:" + phone, code, 60 * 5); + System.out.println("手机号:" + phone + ",验证码:" + code + ",已发送成功"); + return SaResult.ok("验证码发送成功"); + } + +} \ No newline at end of file diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/UserinfoScopeHandler.java b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/custom/UserinfoScopeHandler.java similarity index 97% rename from sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/UserinfoScopeHandler.java rename to sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/custom/UserinfoScopeHandler.java index afca1436..088e9f57 100644 --- a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/UserinfoScopeHandler.java +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/custom/UserinfoScopeHandler.java @@ -1,4 +1,4 @@ -package com.pj.oauth2; +package com.pj.oauth2.custom; import cn.dev33.satoken.oauth2.data.model.AccessTokenModel; import cn.dev33.satoken.oauth2.data.model.ClientTokenModel; diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/resources/application.yml b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/resources/application.yml index 073f9d06..0cd51d5c 100644 --- a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/resources/application.yml +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/resources/application.yml @@ -5,6 +5,8 @@ server: sa-token: # token名称 (同时也是 Cookie 名称) token-name: satoken-oauth2-server + # 是否打印操作日志 + is-log: true # OAuth2.0 配置 oauth2: # 是否全局开启授权码模式 diff --git a/sa-token-doc/_sidebar.md b/sa-token-doc/_sidebar.md index cdd29799..f87b8192 100644 --- a/sa-token-doc/_sidebar.md +++ b/sa-token-doc/_sidebar.md @@ -63,7 +63,7 @@ - [自定义 grant_type](/oauth2/oauth2-custom-grant_type) - [OAuth2-与登录会话实现数据互通](/oauth2/oauth2-interworking) - [OAuth2 代码 API 参考](/oauth2/oauth2-dev) - - [常见问题说明](/oauth2/8) + - diff --git a/sa-token-doc/oauth2/oauth2-custom-grant_type.md b/sa-token-doc/oauth2/oauth2-custom-grant_type.md new file mode 100644 index 00000000..071fab88 --- /dev/null +++ b/sa-token-doc/oauth2/oauth2-custom-grant_type.md @@ -0,0 +1,174 @@ +# OAuth2-自定义权限处理器 + + +### 1、需求场景 + +OAuth2 协议的 `/oauth2/token` 接口定义了两种获取 `access_token` 的 `grant_type`,分别是: +- `authorization_code`:使用用户授权的授权码获取 access_token。 +- `password`:使用用户提交的账号、密码来获取 access_token。 + +除此之外,你还可以自定义 `grant_type`,来支持更多的场景。 + +假设有以下需求:通过 手机号+验证码 登录,返回 `access_token`。 + +--- + + +### 2、实现步骤 + +#### 2.1、新增验证码发送接口 + +首先在 oauth2-server 端开放一个接口,为指定手机号发送验证码。 + +``` java +/** + * 自定义手机登录接口 + */ +@RestController +public class PhoneLoginController { + + @RequestMapping("/oauth2/sendPhoneCode") + public SaResult sendCode(String phone) { + String code = SaFoxUtil.getRandomNumber(100000, 999999) + ""; + SaManager.getSaTokenDao().set("phone_code:" + phone, code, 60 * 5); + System.out.println("手机号:" + phone + ",验证码:" + code + ",已发送成功"); + return SaResult.ok("验证码发送成功"); + } + +} +``` + +真实项目肯定是要对接短信服务商的,此处我们仅做模拟代码,将发送的验证码打印在控制台上。 + + +#### 2.2、自定义 grant_type 处理器 + +在 oauth2-server 新建 `PhoneCodeGrantTypeHandler` 实现 `SaOAuth2GrantTypeHandlerInterface` 接口: + +``` java +/** + * 自定义 phone_code 授权模式处理器 + */ +@Component +public class PhoneCodeGrantTypeHandler implements SaOAuth2GrantTypeHandlerInterface { + + @Override + public String getHandlerGrantType() { + return "phone_code"; + } + + @Override + public AccessTokenModel getAccessTokenModel(SaRequest req, String clientId, List scopes) { + + String phone = req.getParamNotNull("phone"); + String code = req.getParamNotNull("code"); + String realCode = SaManager.getSaTokenDao().get("phone_code:" + phone); + + // 1、校验验证码是否正确 + if(!code.equals(realCode)) { + throw new SaOAuth2Exception("验证码错误"); + } + + // 2、校验通过,删除验证码 + SaManager.getSaTokenDao().delete("phone_code:" + phone); + + // 3、登录 + long userId = 10001; // 模拟 userId,真实项目应该根据手机号从数据库查询 + + // 4、构建 ra 对象 + RequestAuthModel ra = new RequestAuthModel(); + ra.clientId = clientId; + ra.loginId = userId; + ra.scopes = scopes; + + // 5、生成 Access-Token + AccessTokenModel at = SaOAuth2Manager.getDataGenerate().generateAccessToken(ra, true); + return at; + } +} +``` + +#### 2.3、为应用添加允许的授权类型 + +在 `SaOAuth2DataLoader` 实现类中,为 client 的允许授权类型增加自定义的 `phone_code` + +``` java +// Sa-Token OAuth2:自定义数据加载器 +@Component +public class SaOAuth2DataLoaderImpl implements SaOAuth2DataLoader { + @Override + public SaClientModel getClientModel(String clientId) { + if("1001".equals(clientId)) { + return new SaClientModel() + .setClientId("1001") + .setClientSecret("aaaa-bbbb-cccc-dddd-eeee") + .addAllowUrls("*") + .addContractScopes("openid", "userid", "userinfo") + .addAllowGrantTypes( + GrantType.authorization_code, + GrantType.implicit, + GrantType.refresh_token, + GrantType.password, + GrantType.client_credentials, + "phone_code" // 重要代码:自定义授权模式 手机号验证码登录 + ) + ; + } + return null; + } + // 其它代码 ... +} +``` + +完工,开始测试。 + + +### 3、测试步骤 + +#### 1、先发送验证码 + +``` url +http://sa-oauth-server.com:8000/oauth2/sendPhoneCode?phone=13144556677 +``` + +#### 2、请求 token + +注意 `grant_type` 要填写我们自定义的 `phone_code`,code 的具体值可以在后端的控制台上看到 + +``` url +http://sa-oauth-server.com:8000/oauth2/token + ?grant_type=phone_code + &client_id=1001 + &client_secret=aaaa-bbbb-cccc-dddd-eeee + &scope=openid + &phone=13144556677 + &code={value} +``` + +返回结果参考如下: + +``` js +{ + "code": 200, + "msg": "ok", + "data": null, + "token_type": "bearer", + "access_token": "pfxRz6KVacwvKNu4IHmDsCJs33kvvARs2z1lTch7stog8nRt6rfVLowtAZ0E", + "refresh_token": "qcFD6Wo2qZidofXQtWF5oK5ML6ljHKufQ5SbouBxzGnHhnMjUG4VV0iXZhdE", + "expires_in": 7199, + "refresh_expires_in": 2591999, + "client_id": "1001", + "scope": "openid", + "openid": "ded91dc189a437dd1bac2274be167d50" +} +``` + + + + + + + + + + diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/SaOAuth2Manager.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/SaOAuth2Manager.java index 0d9d1790..8521d353 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/SaOAuth2Manager.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/SaOAuth2Manager.java @@ -26,6 +26,9 @@ import cn.dev33.satoken.oauth2.data.loader.SaOAuth2DataLoader; import cn.dev33.satoken.oauth2.data.loader.SaOAuth2DataLoaderDefaultImpl; import cn.dev33.satoken.oauth2.data.resolver.SaOAuth2DataResolver; import cn.dev33.satoken.oauth2.data.resolver.SaOAuth2DataResolverDefaultImpl; +import cn.dev33.satoken.oauth2.template.SaOAuth2Template; +import cn.dev33.satoken.stp.StpLogic; +import cn.dev33.satoken.stp.StpUtil; /** * Sa-Token-OAuth2 模块 总控类 @@ -144,4 +147,40 @@ public class SaOAuth2Manager { SaOAuth2Manager.dao = dao; } + /** + * OAuth2 模板方法 Bean + */ + private static volatile SaOAuth2Template template; + public static SaOAuth2Template getTemplate() { + if (template == null) { + synchronized (SaOAuth2Manager.class) { + if (template == null) { + setTemplate(new SaOAuth2Template()); + } + } + } + return template; + } + public static void setTemplate(SaOAuth2Template template) { + SaOAuth2Manager.template = template; + } + + /** + * OAuth2 StpLogic + */ + private static volatile StpLogic stpLogic; + public static StpLogic getStpLogic() { + if (stpLogic == null) { + synchronized (SaOAuth2Manager.class) { + if (stpLogic == null) { + setStpLogic(StpUtil.stpLogic); + } + } + } + return stpLogic; + } + public static void setStpLogic(StpLogic stpLogic) { + SaOAuth2Manager.stpLogic = stpLogic; + } + } diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/consts/GrantType.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/consts/GrantType.java new file mode 100644 index 00000000..d1238d43 --- /dev/null +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/consts/GrantType.java @@ -0,0 +1,12 @@ +package cn.dev33.satoken.oauth2.consts; + +/** + * 所有授权类型 + */ +public final class GrantType { + public static String authorization_code = "authorization_code"; + public static String refresh_token = "refresh_token"; + public static String password = "password"; + public static String client_credentials = "client_credentials"; + public static String implicit = "implicit"; +} \ No newline at end of file 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 1ecd5425..d2ab4bb8 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 @@ -68,17 +68,6 @@ public class SaOAuth2Consts { public static String token = "token"; } - /** - * 所有授权类型 - */ - public static final class GrantType { - public static String authorization_code = "authorization_code"; - public static String refresh_token = "refresh_token"; - public static String password = "password"; - public static String client_credentials = "client_credentials"; - public static String implicit = "implicit"; - } - /** * 所有 token 类型 */ diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerate.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerate.java index a4a1670b..6fbee6a3 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerate.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerate.java @@ -35,21 +35,21 @@ public interface SaOAuth2DataGenerate { * @param ra 请求参数Model * @return 授权码Model */ - public CodeModel generateCode(RequestAuthModel ra); + CodeModel generateCode(RequestAuthModel ra); /** * 构建Model:Access-Token * @param code 授权码Model * @return AccessToken Model */ - public AccessTokenModel generateAccessToken(String code); + AccessTokenModel generateAccessToken(String code); /** * 刷新Model:根据 Refresh-Token 生成一个新的 Access-Token * @param refreshToken Refresh-Token值 * @return 新的 Access-Token */ - public AccessTokenModel refreshAccessToken(String refreshToken); + AccessTokenModel refreshAccessToken(String refreshToken); /** * 构建Model:Access-Token (根据RequestAuthModel构建,用于隐藏式 and 密码式) @@ -57,7 +57,7 @@ public interface SaOAuth2DataGenerate { * @param isCreateRt 是否生成对应的Refresh-Token * @return Access-Token Model */ - public AccessTokenModel generateAccessToken(RequestAuthModel ra, boolean isCreateRt); + AccessTokenModel generateAccessToken(RequestAuthModel ra, boolean isCreateRt); /** * 构建Model:Client-Token @@ -65,7 +65,7 @@ public interface SaOAuth2DataGenerate { * @param scopes 授权范围 * @return Client-Token Model */ - public ClientTokenModel generateClientToken(String clientId, List scopes); + ClientTokenModel generateClientToken(String clientId, List scopes); /** * 构建URL:下放Code URL (Authorization Code 授权码) @@ -74,7 +74,7 @@ public interface SaOAuth2DataGenerate { * @param state state参数 * @return 构建完毕的URL */ - public String buildRedirectUri(String redirectUri, String code, String state); + String buildRedirectUri(String redirectUri, String code, String state); /** * 构建URL:下放Access-Token URL (implicit 隐藏式) @@ -83,14 +83,12 @@ public interface SaOAuth2DataGenerate { * @param state state参数 * @return 构建完毕的URL */ - public String buildImplicitRedirectUri(String redirectUri, String token, String state); + String buildImplicitRedirectUri(String redirectUri, String token, String state); /** * 回收 Access-Token * @param accessToken Access-Token值 */ - public void revokeAccessToken(String accessToken); - - + void revokeAccessToken(String accessToken); } 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 248c1aef..f84ce3d2 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 @@ -52,25 +52,11 @@ public class SaClientModel implements Serializable { * 应用允许授权的所有URL */ public List allowUrls; - - /** 此 Client 是否打开模式:授权码(Authorization Code) */ - public Boolean enableCode = false; - - /** 此 Client 是否打开模式:隐藏式(Implicit) */ - public Boolean enableImplicit = false; - - /** 此 Client 是否打开模式:密码式(Password) */ - public Boolean enablePassword = false; - /** 此 Client 是否打开模式:凭证式(Client Credentials) */ - public Boolean enableClient = false; - -// /** -// * 是否自动判断此 Client 开放的授权模式 -// *
此值为true时:四种模式(isCode、isImplicit、isPassword、isClient)是否生效,依靠全局设置 -// *
此值为false时:四种模式(isCode、isImplicit、isPassword、isClient)是否生效,依靠局部配置+全局配置 -// */ -// public Boolean isAutoMode = true; + /** + * 应用允许的所有 grant_type + */ + public List allowGrantTypes = new ArrayList<>(); /** 单独配置此Client:是否在每次 Refresh-Token 刷新 Access-Token 时,产生一个新的 Refresh-Token [默认取全局配置] */ public Boolean isNewRefresh; @@ -169,86 +155,22 @@ public class SaClientModel implements Serializable { } /** - * @return 此 Client 是否打开模式:授权码(Authorization Code) - */ - public Boolean getEnableCode() { - return enableCode; - } - - /** - * @param enableCode 此 Client 是否打开模式:授权码(Authorization Code) - * @return 对象自身 - */ - public SaClientModel setEnableCode(Boolean enableCode) { - this.enableCode = enableCode; - return this; - } - - /** - * @return 此 Client 是否打开模式:隐藏式(Implicit) - */ - public Boolean getEnableImplicit() { - return enableImplicit; - } - - /** - * @param enableImplicit 此 Client 是否打开模式:隐藏式(Implicit) - * @return 对象自身 - */ - public SaClientModel setEnableImplicit(Boolean enableImplicit) { - this.enableImplicit = enableImplicit; - return this; - } - - /** - * @return 此 Client 是否打开模式:密码式(Password) - */ - public Boolean getEnablePassword() { - return enablePassword; - } - - /** - * @param enablePassword 此 Client 是否打开模式:密码式(Password) - * @return 对象自身 - */ - public SaClientModel setEnablePassword(Boolean enablePassword) { - this.enablePassword = enablePassword; - return this; - } - - /** - * @return 此 Client 是否打开模式:凭证式(Client Credentials) + * @return 应用允许的所有 grant_type */ - public Boolean getEnableClient() { - return enableClient; + public List getAllowGrantTypes() { + return allowGrantTypes; } - + /** - * @param enableClient 此 Client 是否打开模式:凭证式(Client Credentials) - * @return 对象自身 + * 应用允许的所有 grant_type + * @param allowGrantTypes / + * @return / */ - public SaClientModel setEnableClient(Boolean enableClient) { - this.enableClient = enableClient; + public SaClientModel setAllowGrantTypes(List allowGrantTypes) { + this.allowGrantTypes = allowGrantTypes; return this; } -// -// /** -// * @return 是否自动判断此 Client 开放的授权模式 -// */ -// public Boolean getIsAutoMode() { -// return isAutoMode; -// } -// -// /** -// * @param isAutoMode 是否自动判断此 Client 开放的授权模式 -// * @return 对象自身 -// */ -// public SaClientModel setIsAutoMode(Boolean isAutoMode) { -// this.isAutoMode = isAutoMode; -// return this; -// } -// - + /** * @return 此Client:是否在每次 Refresh-Token 刷新 Access-Token 时,产生一个新的 Refresh-Token [默认取全局配置] */ @@ -338,11 +260,7 @@ public class SaClientModel implements Serializable { ", clientSecret='" + clientSecret + '\'' + ", contractScopes=" + contractScopes + ", allowUrls=" + allowUrls + - ", isCode=" + enableCode + - ", isImplicit=" + enableImplicit + - ", isPassword=" + enablePassword + - ", isClient=" + enableClient + -// ", isAutoMode=" + isAutoMode + + ", allowGrantTypes=" + allowGrantTypes + ", isNewRefresh=" + isNewRefresh + ", accessTokenTimeout=" + accessTokenTimeout + ", refreshTokenTimeout=" + refreshTokenTimeout + @@ -367,7 +285,7 @@ public class SaClientModel implements Serializable { } /** - * @param urls 添加应用签约的所有权限 + * @param urls 添加应用允许授权的所有URL * @return 对象自身 */ public SaClientModel addAllowUrls(String... urls) { @@ -378,5 +296,17 @@ public class SaClientModel implements Serializable { return this; } + /** + * @param grantTypes 应用允许的所有 grant_type + * @return 对象自身 + */ + public SaClientModel addAllowGrantTypes(String... grantTypes) { + if(this.allowGrantTypes == null) { + this.allowGrantTypes = new ArrayList<>(); + } + this.allowGrantTypes.addAll(Arrays.asList(grantTypes)); + return this; + } + } diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/function/strategy/SaOAuth2GrantTypeAuthFunction.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/function/strategy/SaOAuth2GrantTypeAuthFunction.java new file mode 100644 index 00000000..caaf5ab9 --- /dev/null +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/function/strategy/SaOAuth2GrantTypeAuthFunction.java @@ -0,0 +1,35 @@ +/* + * 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.function.strategy; + +import cn.dev33.satoken.context.model.SaRequest; +import cn.dev33.satoken.oauth2.data.model.AccessTokenModel; + +import java.util.function.Function; + +/** + * 函数式接口:GrantType 认证 + * + *

参数:SaRequest、grant_type

+ *

返回:处理结果

+ * + * @author click33 + * @since 1.39.0 + */ +@FunctionalInterface +public interface SaOAuth2GrantTypeAuthFunction extends Function { + +} \ No newline at end of file diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/granttype/handler/AuthorizationCodeGrantTypeHandler.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/granttype/handler/AuthorizationCodeGrantTypeHandler.java new file mode 100644 index 00000000..1f80841b --- /dev/null +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/granttype/handler/AuthorizationCodeGrantTypeHandler.java @@ -0,0 +1,57 @@ +/* + * 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.granttype.handler; + +import cn.dev33.satoken.context.model.SaRequest; +import cn.dev33.satoken.oauth2.SaOAuth2Manager; +import cn.dev33.satoken.oauth2.consts.SaOAuth2Consts; +import cn.dev33.satoken.oauth2.consts.GrantType; +import cn.dev33.satoken.oauth2.data.model.AccessTokenModel; +import cn.dev33.satoken.oauth2.data.model.request.ClientIdAndSecretModel; + +import java.util.List; + +/** + * authorization_code grant_type 处理器 + * + * @author click33 + * @since 1.39.0 + */ +public class AuthorizationCodeGrantTypeHandler implements SaOAuth2GrantTypeHandlerInterface { + + @Override + public String getHandlerGrantType() { + return GrantType.authorization_code; + } + + @Override + public AccessTokenModel getAccessTokenModel(SaRequest req, String clientId, List scopes) { + // 获取参数 + ClientIdAndSecretModel clientIdAndSecret = SaOAuth2Manager.getDataResolver().readClientIdAndSecret(req); +// String clientId = clientIdAndSecret.clientId; + String clientSecret = clientIdAndSecret.clientSecret; + String code = req.getParamNotNull(SaOAuth2Consts.Param.code); + String redirectUri = req.getParam(SaOAuth2Consts.Param.redirect_uri); + + // 校验参数 + SaOAuth2Manager.getTemplate().checkGainTokenParam(code, clientId, clientSecret, redirectUri); + + // 构建 Access-Token、返回 + AccessTokenModel accessTokenModel = SaOAuth2Manager.getDataGenerate().generateAccessToken(code); + return accessTokenModel; + } + +} \ No newline at end of file diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/granttype/handler/PasswordGrantTypeHandler.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/granttype/handler/PasswordGrantTypeHandler.java new file mode 100644 index 00000000..89a89df2 --- /dev/null +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/granttype/handler/PasswordGrantTypeHandler.java @@ -0,0 +1,76 @@ +/* + * 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.granttype.handler; + +import cn.dev33.satoken.context.model.SaRequest; +import cn.dev33.satoken.oauth2.SaOAuth2Manager; +import cn.dev33.satoken.oauth2.consts.GrantType; +import cn.dev33.satoken.oauth2.consts.SaOAuth2Consts; +import cn.dev33.satoken.oauth2.data.model.AccessTokenModel; +import cn.dev33.satoken.oauth2.data.model.request.RequestAuthModel; +import cn.dev33.satoken.oauth2.exception.SaOAuth2Exception; +import cn.dev33.satoken.stp.StpUtil; + +import java.util.List; + +/** + * password grant_type 处理器 + * + * @author click33 + * @since 1.39.0 + */ +public class PasswordGrantTypeHandler implements SaOAuth2GrantTypeHandlerInterface { + + @Override + public String getHandlerGrantType() { + return GrantType.password; + } + + @Override + public AccessTokenModel getAccessTokenModel(SaRequest req, String clientId, List scopes) { + + // 1、获取请求参数 + String username = req.getParamNotNull(SaOAuth2Consts.Param.username); + String password = req.getParamNotNull(SaOAuth2Consts.Param.password); + + // 3、调用API 开始登录,如果没能成功登录,则直接退出 + loginByUsernamePassword(username, password); + Object loginId = StpUtil.getLoginIdDefaultNull(); + if(loginId == null) { + throw new SaOAuth2Exception("登录失败"); + } + + // 4、构建 ra 对象 + RequestAuthModel ra = new RequestAuthModel(); + ra.clientId = clientId; + ra.loginId = loginId; + ra.scopes = scopes; + + // 5、生成 Access-Token + AccessTokenModel at = SaOAuth2Manager.getDataGenerate().generateAccessToken(ra, true); + return at; + } + + /** + * 根据 username、password 进行登录,如果登录失败请直接抛出异常 + * @param username / + * @param password / + */ + public void loginByUsernamePassword(String username, String password) { + SaOAuth2Manager.getConfig().doLoginHandle.apply(username, password); + } + +} \ No newline at end of file diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/granttype/handler/RefreshTokenGrantTypeHandler.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/granttype/handler/RefreshTokenGrantTypeHandler.java new file mode 100644 index 00000000..b526f7bf --- /dev/null +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/granttype/handler/RefreshTokenGrantTypeHandler.java @@ -0,0 +1,59 @@ +/* + * 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.granttype.handler; + +import cn.dev33.satoken.context.model.SaRequest; +import cn.dev33.satoken.oauth2.SaOAuth2Manager; +import cn.dev33.satoken.oauth2.consts.GrantType; +import cn.dev33.satoken.oauth2.consts.SaOAuth2Consts; +import cn.dev33.satoken.oauth2.data.model.AccessTokenModel; +import cn.dev33.satoken.oauth2.data.model.RefreshTokenModel; +import cn.dev33.satoken.oauth2.error.SaOAuth2ErrorCode; +import cn.dev33.satoken.oauth2.exception.SaOAuth2Exception; + +import java.util.List; + +/** + * refresh_token grant_type 处理器 + * + * @author click33 + * @since 1.39.0 + */ +public class RefreshTokenGrantTypeHandler implements SaOAuth2GrantTypeHandlerInterface { + + @Override + public String getHandlerGrantType() { + return GrantType.refresh_token; + } + + @Override + public AccessTokenModel getAccessTokenModel(SaRequest req, String clientId, List scopes) { + // 获取参数 + String refreshToken = req.getParamNotNull(SaOAuth2Consts.Param.refresh_token); + + // 校验:Refresh-Token 是否存在 + RefreshTokenModel rt = SaOAuth2Manager.getDao().getRefreshToken(refreshToken); + SaOAuth2Exception.throwBy(rt == null, "无效refresh_token: " + refreshToken, SaOAuth2ErrorCode.CODE_30121); + + // 校验:Refresh-Token 代表的 ClientId 与提供的 ClientId 是否一致 + SaOAuth2Exception.throwBy( ! rt.clientId.equals(clientId), "无效client_id: " + clientId, SaOAuth2ErrorCode.CODE_30122); + + // 获取新 Access-Token + AccessTokenModel accessTokenModel = SaOAuth2Manager.getDataGenerate().refreshAccessToken(refreshToken); + return accessTokenModel; + } + +} \ No newline at end of file diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/granttype/handler/SaOAuth2GrantTypeHandlerInterface.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/granttype/handler/SaOAuth2GrantTypeHandlerInterface.java new file mode 100644 index 00000000..c07868e1 --- /dev/null +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/granttype/handler/SaOAuth2GrantTypeHandlerInterface.java @@ -0,0 +1,46 @@ +/* + * 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.granttype.handler; + +import cn.dev33.satoken.context.model.SaRequest; +import cn.dev33.satoken.oauth2.data.model.AccessTokenModel; + +import java.util.List; + +/** + * 所有 OAuth2 GrantType 处理器的父接口 + * + * @author click33 + * @since 1.39.0 + */ +public interface SaOAuth2GrantTypeHandlerInterface { + + /** + * 获取所要处理的 GrantType + * + * @return / + */ + String getHandlerGrantType(); + + /** + * 获取 AccessTokenModel 对象 + * + * @param req / + * @return / + */ + AccessTokenModel getAccessTokenModel(SaRequest req, String clientId, List scopes); + +} \ No newline at end of file diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/processor/SaOAuth2ServerProcessor.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/processor/SaOAuth2ServerProcessor.java index e5d3af01..feaa02de 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/processor/SaOAuth2ServerProcessor.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/processor/SaOAuth2ServerProcessor.java @@ -20,9 +20,9 @@ import cn.dev33.satoken.context.model.SaRequest; import cn.dev33.satoken.context.model.SaResponse; import cn.dev33.satoken.oauth2.SaOAuth2Manager; import cn.dev33.satoken.oauth2.config.SaOAuth2Config; +import cn.dev33.satoken.oauth2.consts.GrantType; import cn.dev33.satoken.oauth2.consts.SaOAuth2Consts; import cn.dev33.satoken.oauth2.consts.SaOAuth2Consts.Api; -import cn.dev33.satoken.oauth2.consts.SaOAuth2Consts.GrantType; import cn.dev33.satoken.oauth2.consts.SaOAuth2Consts.Param; import cn.dev33.satoken.oauth2.consts.SaOAuth2Consts.ResponseType; import cn.dev33.satoken.oauth2.data.generate.SaOAuth2DataGenerate; @@ -34,10 +34,9 @@ import cn.dev33.satoken.oauth2.data.model.request.ClientIdAndSecretModel; import cn.dev33.satoken.oauth2.data.model.request.RequestAuthModel; import cn.dev33.satoken.oauth2.error.SaOAuth2ErrorCode; import cn.dev33.satoken.oauth2.exception.SaOAuth2Exception; +import cn.dev33.satoken.oauth2.strategy.SaOAuth2Strategy; import cn.dev33.satoken.oauth2.template.SaOAuth2Template; import cn.dev33.satoken.router.SaHttpMethod; -import cn.dev33.satoken.stp.StpLogic; -import cn.dev33.satoken.stp.StpUtil; import cn.dev33.satoken.util.SaResult; import java.util.List; @@ -55,11 +54,6 @@ public class SaOAuth2ServerProcessor { */ public static SaOAuth2ServerProcessor instance = new SaOAuth2ServerProcessor(); - /** - * 底层 SaOAuth2Template 对象 - */ - public SaOAuth2Template oauth2Template = new SaOAuth2Template(); - /** * 处理 Server 端请求, 路由分发 * @return 处理结果 @@ -68,7 +62,6 @@ public class SaOAuth2ServerProcessor { // 获取变量 SaRequest req = SaHolder.getRequest(); - SaOAuth2Config cfg = SaOAuth2Manager.getConfig(); // ------------------ 路由分发 ------------------ @@ -79,7 +72,7 @@ public class SaOAuth2ServerProcessor { // Code 换 Access-Token || 模式三:密码式 if(req.isPath(Api.token)) { - return tokenOrPassword(); + return token(); } // Refresh-Token 刷新 Access-Token @@ -122,18 +115,19 @@ public class SaOAuth2ServerProcessor { SaResponse res = SaHolder.getResponse(); SaOAuth2Config cfg = SaOAuth2Manager.getConfig(); SaOAuth2DataGenerate dataGenerate = SaOAuth2Manager.getDataGenerate(); + SaOAuth2Template oauth2Template = SaOAuth2Manager.getTemplate(); String responseType = req.getParamNotNull(Param.response_type); // 1、先判断是否开启了指定的授权模式 checkAuthorizeResponseType(responseType, req, cfg); // 2、如果尚未登录, 则先去登录 - if( ! getStpLogic().isLogin()) { + if( ! SaOAuth2Manager.getStpLogic().isLogin()) { return cfg.notLoginView.get(); } // 3、构建请求 Model - RequestAuthModel ra = SaOAuth2Manager.getDataResolver().readRequestAuthModel(req, getStpLogic().getLoginId()); + RequestAuthModel ra = SaOAuth2Manager.getDataResolver().readRequestAuthModel(req, SaOAuth2Manager.getStpLogic().getLoginId()); // 4、校验:重定向域名是否合法 oauth2Template.checkRightUrl(ra.clientId, ra.redirectUri); @@ -167,55 +161,11 @@ public class SaOAuth2ServerProcessor { } /** - * Code 换 Access-Token || 模式三:密码式 - * @return 处理结果 - */ - public Object tokenOrPassword() { - - String grantType = SaHolder.getRequest().getParamNotNull(Param.grant_type); - - // Code 换 Access-Token - if(grantType.equals(GrantType.authorization_code)) { - return token(); - } - - // 模式三:密码式 - if(grantType.equals(GrantType.password)) { - SaOAuth2Config cfg = SaOAuth2Manager.getConfig(); - if(!cfg.enablePassword) { - throwErrorSystemNotEnableModel(); - } - if(!currClientModel().enablePassword) { - throwErrorClientNotEnableModel(); - } - return password(); - } - - throw new SaOAuth2Exception("无效 grant_type:" + grantType); - } - - /** - * Code授权码 获取 Access-Token + * Code 换 Access-Token / 模式三:密码式 / 自定义 grant_type * @return 处理结果 */ public Object token() { - // 获取变量 - SaRequest req = SaHolder.getRequest(); - - // 获取参数 - ClientIdAndSecretModel clientIdAndSecret = SaOAuth2Manager.getDataResolver().readClientIdAndSecret(req); - String clientId = clientIdAndSecret.clientId; - String clientSecret = clientIdAndSecret.clientSecret; - String code = req.getParamNotNull(Param.code); - String redirectUri = req.getParam(Param.redirect_uri); - - // 校验参数 - oauth2Template.checkGainTokenParam(code, clientId, clientSecret, redirectUri); - - // 构建 Access-Token - AccessTokenModel accessTokenModel = SaOAuth2Manager.getDataGenerate().generateAccessToken(code); - - // 返回 + AccessTokenModel accessTokenModel = SaOAuth2Strategy.instance.grantTypeAuth.apply(SaHolder.getRequest()); return SaOAuth2Manager.getDataResolver().buildTokenReturnValue(accessTokenModel); } @@ -224,27 +174,10 @@ public class SaOAuth2ServerProcessor { * @return 处理结果 */ public Object refresh() { - // 获取变量 SaRequest req = SaHolder.getRequest(); String grantType = req.getParamNotNull(Param.grant_type); - if(!grantType.equals(GrantType.refresh_token)) { - throw new SaOAuth2Exception("无效 grant_type:" + grantType).setCode(SaOAuth2ErrorCode.CODE_30126); - } - - // 获取参数 - - ClientIdAndSecretModel clientIdAndSecret = SaOAuth2Manager.getDataResolver().readClientIdAndSecret(req); - String clientId = clientIdAndSecret.clientId; - String clientSecret = clientIdAndSecret.clientSecret; - String refreshToken = req.getParamNotNull(Param.refresh_token); - - // 校验参数 - oauth2Template.checkRefreshTokenParam(clientId, clientSecret, refreshToken); - - // 获取新 Access-Token - AccessTokenModel accessTokenModel = SaOAuth2Manager.getDataGenerate().refreshAccessToken(refreshToken); - - // 返回 + SaOAuth2Exception.throwBy(!grantType.equals(GrantType.refresh_token), "无效 grant_type:" + grantType, SaOAuth2ErrorCode.CODE_30126); + AccessTokenModel accessTokenModel = SaOAuth2Strategy.instance.grantTypeAuth.apply(req); return SaOAuth2Manager.getDataResolver().buildRefreshTokenReturnValue(accessTokenModel); } @@ -254,6 +187,7 @@ public class SaOAuth2ServerProcessor { */ public Object revoke() { // 获取变量 + SaOAuth2Template oauth2Template = SaOAuth2Manager.getTemplate(); SaRequest req = SaHolder.getRequest(); // 获取参数 @@ -297,10 +231,11 @@ public class SaOAuth2ServerProcessor { // 获取变量 SaRequest req = SaHolder.getRequest(); String clientId = req.getParamNotNull(Param.client_id); - Object loginId = getStpLogic().getLoginId(); + Object loginId = SaOAuth2Manager.getStpLogic().getLoginId(); String scope = req.getParamNotNull(Param.scope); List scopes = SaOAuth2Manager.getDataConverter().convertScopeStringToList(scope); SaOAuth2DataGenerate dataGenerate = SaOAuth2Manager.getDataGenerate(); + SaOAuth2Template oauth2Template = SaOAuth2Manager.getTemplate(); // 此请求只允许 POST 方式 if(!req.isMethod(SaHttpMethod.POST)) { @@ -343,49 +278,6 @@ public class SaOAuth2ServerProcessor { throw new SaOAuth2Exception("无效response_type: " + ra.responseType).setCode(SaOAuth2ErrorCode.CODE_30125); } - /** - * 模式三:密码式 - * @return 处理结果 - */ - public Object password() { - // 获取变量 - SaRequest req = SaHolder.getRequest(); - SaOAuth2Config cfg = SaOAuth2Manager.getConfig(); - - // 1、获取请求参数 - String username = req.getParamNotNull(Param.username); - String password = req.getParamNotNull(Param.password); - ClientIdAndSecretModel clientIdAndSecret = SaOAuth2Manager.getDataResolver().readClientIdAndSecret(req); - String clientId = clientIdAndSecret.clientId; - String clientSecret = clientIdAndSecret.clientSecret; - String scope = req.getParam(Param.scope, ""); - List scopes = SaOAuth2Manager.getDataConverter().convertScopeStringToList(scope); - - // 2、校验 ClientScope 和 scope - oauth2Template.checkClientSecretAndScope(clientId, clientSecret, scopes); - - // 3、防止因前端误传token造成逻辑干扰 - // SaHolder.getStorage().set(getStpLogic().stpLogic.splicingKeyJustCreatedSave(), "no-token"); - - // 3、调用API 开始登录,如果没能成功登录,则直接退出 - Object retObj = cfg.doLoginHandle.apply(username, password); - if( ! getStpLogic().isLogin()) { - return retObj; - } - - // 4、构建 ra对象 - RequestAuthModel ra = new RequestAuthModel(); - ra.clientId = clientId; - ra.loginId = getStpLogic().getLoginId(); - ra.scopes = scopes; - - // 5、生成 Access-Token - AccessTokenModel at = SaOAuth2Manager.getDataGenerate().generateAccessToken(ra, true); - - // 6、返回 Access-Token - return SaOAuth2Manager.getDataResolver().buildPasswordReturnValue(at); - } - /** * 模式四:凭证式 * @return 处理结果 @@ -394,6 +286,7 @@ public class SaOAuth2ServerProcessor { // 获取变量 SaRequest req = SaHolder.getRequest(); SaOAuth2Config cfg = SaOAuth2Manager.getConfig(); + SaOAuth2Template oauth2Template = SaOAuth2Manager.getTemplate(); String grantType = req.getParamNotNull(Param.grant_type); if(!grantType.equals(GrantType.client_credentials)) { @@ -402,7 +295,7 @@ public class SaOAuth2ServerProcessor { if(!cfg.enableClient) { throwErrorSystemNotEnableModel(); } - if(!currClientModel().enableClient) { + if(!currClientModel().getAllowGrantTypes().contains(GrantType.client_credentials)) { throwErrorClientNotEnableModel(); } @@ -434,6 +327,7 @@ public class SaOAuth2ServerProcessor { * @return / */ public SaClientModel currClientModel() { + SaOAuth2Template oauth2Template = SaOAuth2Manager.getTemplate(); ClientIdAndSecretModel clientIdAndSecret = SaOAuth2Manager.getDataResolver().readClientIdAndSecret(SaHolder.getRequest()); return oauth2Template.checkClientModel(clientIdAndSecret.clientId); } @@ -447,7 +341,7 @@ public class SaOAuth2ServerProcessor { if(!cfg.enableCode) { throwErrorSystemNotEnableModel(); } - if(!currClientModel().enableCode) { + if(!currClientModel().getAllowGrantTypes().contains(GrantType.authorization_code)) { throwErrorClientNotEnableModel(); } } @@ -456,7 +350,7 @@ public class SaOAuth2ServerProcessor { if(!cfg.enableImplicit) { throwErrorSystemNotEnableModel(); } - if(!currClientModel().enableImplicit) { + if(!currClientModel().getAllowGrantTypes().contains(GrantType.implicit)) { throwErrorClientNotEnableModel(); } } @@ -466,15 +360,6 @@ public class SaOAuth2ServerProcessor { } } - /** - * 获取底层使用的会话对象 - * - * @return / - */ - public StpLogic getStpLogic() { - return StpUtil.stpLogic; - } - /** * 系统未开放此授权模式时抛出异常 */ 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 f1dc0187..70affdc3 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 @@ -16,8 +16,18 @@ package cn.dev33.satoken.oauth2.strategy; import cn.dev33.satoken.SaManager; +import cn.dev33.satoken.oauth2.SaOAuth2Manager; +import cn.dev33.satoken.oauth2.config.SaOAuth2Config; +import cn.dev33.satoken.oauth2.consts.GrantType; import cn.dev33.satoken.oauth2.consts.SaOAuth2Consts; +import cn.dev33.satoken.oauth2.data.model.loader.SaClientModel; +import cn.dev33.satoken.oauth2.data.model.request.ClientIdAndSecretModel; +import cn.dev33.satoken.oauth2.exception.SaOAuth2Exception; import cn.dev33.satoken.oauth2.function.strategy.*; +import cn.dev33.satoken.oauth2.granttype.handler.AuthorizationCodeGrantTypeHandler; +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; @@ -26,6 +36,7 @@ import cn.dev33.satoken.oauth2.scope.handler.UserIdScopeHandler; import cn.dev33.satoken.util.SaFoxUtil; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; /** @@ -38,6 +49,7 @@ public final class SaOAuth2Strategy { private SaOAuth2Strategy() { registerDefaultScopeHandler(); + registerDefaultGrantTypeHandler(); } /** @@ -78,9 +90,6 @@ public final class SaOAuth2Strategy { scopeHandlerMap.remove(scope); } - - // ----------------------- 所有策略 - /** * 根据 scope 信息对一个 AccessTokenModel 进行加工处理 */ @@ -117,6 +126,75 @@ public final class SaOAuth2Strategy { } }; + // grant_type 处理器 + + /** + * grant_type 处理器集合 + */ + public Map grantTypeHandlerMap = new LinkedHashMap<>(); + + /** + * 注册所有默认的权限处理器 + */ + public void registerDefaultGrantTypeHandler() { + grantTypeHandlerMap.put(GrantType.authorization_code, new AuthorizationCodeGrantTypeHandler()); + grantTypeHandlerMap.put(GrantType.password, new PasswordGrantTypeHandler()); + grantTypeHandlerMap.put(GrantType.refresh_token, new RefreshTokenGrantTypeHandler()); + } + + /** + * 注册一个权限处理器 + */ + public void registerGrantTypeHandler(SaOAuth2GrantTypeHandlerInterface handler) { + grantTypeHandlerMap.put(handler.getHandlerGrantType(), handler); + // TODO 优化日志输出 + SaManager.getLog().info("新增GrantType处理器:" + handler.getHandlerGrantType()); + // SaTokenEventCenter.doRegisterAnnotationHandler(handler); + } + + /** + * 移除一个权限处理器 + */ + public void removeGrantTypeHandler(String scope) { + scopeHandlerMap.remove(scope); + } + + /** + * 根据 scope 信息对一个 AccessTokenModel 进行加工处理 + */ + public SaOAuth2GrantTypeAuthFunction grantTypeAuth = (req) -> { + String grantType = req.getParamNotNull(SaOAuth2Consts.Param.grant_type); + SaOAuth2GrantTypeHandlerInterface grantTypeHandler = grantTypeHandlerMap.get(grantType); + if(grantTypeHandler == null) { + throw new RuntimeException("无效 grant_type: " + grantType); + } + + // 看看全局是否开启了此 grantType + SaOAuth2Config config = SaOAuth2Manager.getConfig(); + if(grantType.equals(GrantType.authorization_code) && !config.getEnableCode() ) { + throw new SaOAuth2Exception("系统未开放的 grant_type: " + grantType); + } + if(grantType.equals(GrantType.password) && !config.getEnablePassword() ) { + throw new SaOAuth2Exception("系统未开放的 grant_type: " + grantType); + } + + // 校验 clientSecret 和 scope + ClientIdAndSecretModel clientIdAndSecretModel = SaOAuth2Manager.getDataResolver().readClientIdAndSecret(req); + List scopes = SaOAuth2Manager.getDataConverter().convertScopeStringToList(req.getParam(SaOAuth2Consts.Param.scope)); + SaClientModel clientModel = SaOAuth2Manager.getTemplate().checkClientSecretAndScope(clientIdAndSecretModel.getClientId(), clientIdAndSecretModel.getClientSecret(), scopes); + + // 检测应用是否开启此 grantType + if(!clientModel.getAllowGrantTypes().contains(grantType)) { + throw new SaOAuth2Exception("应用未开放的 grant_type: " + grantType); + } + + // 调用 处理器 + return grantTypeHandler.getAccessTokenModel(req, clientIdAndSecretModel.getClientId(), scopes); + }; + + + // ----------------------- 所有策略 + /** * 创建一个 code value */ diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Util.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Util.java index 38a6464a..1e2fe37c 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Util.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Util.java @@ -41,7 +41,7 @@ public class SaOAuth2Util { * @return ClientModel */ public static SaClientModel checkClientModel(String clientId) { - return SaOAuth2ServerProcessor.instance.oauth2Template.checkClientModel(clientId); + return SaOAuth2Manager.getTemplate().checkClientModel(clientId); } /** @@ -50,7 +50,7 @@ public class SaOAuth2Util { * @return . */ public static AccessTokenModel checkAccessToken(String accessToken) { - return SaOAuth2ServerProcessor.instance.oauth2Template.checkAccessToken(accessToken); + return SaOAuth2Manager.getTemplate().checkAccessToken(accessToken); } /** @@ -59,7 +59,7 @@ public class SaOAuth2Util { * @return . */ public static ClientTokenModel checkClientToken(String clientToken) { - return SaOAuth2ServerProcessor.instance.oauth2Template.checkClientToken(clientToken); + return SaOAuth2Manager.getTemplate().checkClientToken(clientToken); } /** @@ -68,7 +68,7 @@ public class SaOAuth2Util { * @return LoginId */ public static Object getLoginIdByAccessToken(String accessToken) { - return SaOAuth2ServerProcessor.instance.oauth2Template.getLoginIdByAccessToken(accessToken); + return SaOAuth2Manager.getTemplate().getLoginIdByAccessToken(accessToken); } /** @@ -77,7 +77,7 @@ public class SaOAuth2Util { * @param scopes 需要校验的权限列表 */ public static void checkScope(String accessToken, String... scopes) { - SaOAuth2ServerProcessor.instance.oauth2Template.checkScope(accessToken, scopes); + SaOAuth2Manager.getTemplate().checkScope(accessToken, scopes); } /** @@ -86,7 +86,7 @@ public class SaOAuth2Util { * @param scopes 需要校验的权限列表 */ public static void checkClientTokenScope(String clientToken, String... scopes) { - SaOAuth2ServerProcessor.instance.oauth2Template.checkClientTokenScope(clientToken, scopes); + SaOAuth2Manager.getTemplate().checkClientTokenScope(clientToken, scopes); } @@ -100,7 +100,7 @@ public class SaOAuth2Util { * @return 是否已经授权 */ public static boolean isGrant(Object loginId, String clientId, List scopes) { - return SaOAuth2ServerProcessor.instance.oauth2Template.isGrant(loginId, clientId, scopes); + return SaOAuth2Manager.getTemplate().isGrant(loginId, clientId, scopes); } /** @@ -109,7 +109,7 @@ public class SaOAuth2Util { * @param scopes 权限(多个用逗号隔开) */ public static void checkContract(String clientId, List scopes) { - SaOAuth2ServerProcessor.instance.oauth2Template.checkContract(clientId, scopes); + SaOAuth2Manager.getTemplate().checkContract(clientId, scopes); } /** @@ -118,7 +118,7 @@ public class SaOAuth2Util { * @param url 指定url */ public static void checkRightUrl(String clientId, String url) { - SaOAuth2ServerProcessor.instance.oauth2Template.checkRightUrl(clientId, url); + SaOAuth2Manager.getTemplate().checkRightUrl(clientId, url); } /** @@ -128,7 +128,7 @@ public class SaOAuth2Util { * @return SaClientModel对象 */ public static SaClientModel checkClientSecret(String clientId, String clientSecret) { - return SaOAuth2ServerProcessor.instance.oauth2Template.checkClientSecret(clientId, clientSecret); + return SaOAuth2Manager.getTemplate().checkClientSecret(clientId, clientSecret); } /** @@ -139,7 +139,7 @@ public class SaOAuth2Util { * @return SaClientModel对象 */ public static SaClientModel checkClientSecretAndScope(String clientId, String clientSecret, List scopes) { - return SaOAuth2ServerProcessor.instance.oauth2Template.checkClientSecretAndScope(clientId, clientSecret, scopes); + return SaOAuth2Manager.getTemplate().checkClientSecretAndScope(clientId, clientSecret, scopes); } /** @@ -151,7 +151,7 @@ public class SaOAuth2Util { * @return CodeModel对象 */ public static CodeModel checkGainTokenParam(String code, String clientId, String clientSecret, String redirectUri) { - return SaOAuth2ServerProcessor.instance.oauth2Template.checkGainTokenParam(code, clientId, clientSecret, redirectUri); + return SaOAuth2Manager.getTemplate().checkGainTokenParam(code, clientId, clientSecret, redirectUri); } /** @@ -162,7 +162,7 @@ public class SaOAuth2Util { * @return CodeModel对象 */ public static RefreshTokenModel checkRefreshTokenParam(String clientId, String clientSecret, String refreshToken) { - return SaOAuth2ServerProcessor.instance.oauth2Template.checkRefreshTokenParam(clientId, clientSecret, refreshToken); + return SaOAuth2Manager.getTemplate().checkRefreshTokenParam(clientId, clientSecret, refreshToken); } /** @@ -173,7 +173,7 @@ public class SaOAuth2Util { * @return SaClientModel对象 */ public static AccessTokenModel checkAccessTokenParam(String clientId, String clientSecret, String accessToken) { - return SaOAuth2ServerProcessor.instance.oauth2Template.checkAccessTokenParam(clientId, clientSecret, accessToken); + return SaOAuth2Manager.getTemplate().checkAccessTokenParam(clientId, clientSecret, accessToken); } // ------------------- save 数据 @@ -185,7 +185,7 @@ public class SaOAuth2Util { * @param scopes 权限列表 */ public static void saveGrantScope(String clientId, Object loginId, List scopes) { - SaOAuth2ServerProcessor.instance.oauth2Template.saveGrantScope(clientId, loginId, scopes); + SaOAuth2Manager.getTemplate().saveGrantScope(clientId, loginId, scopes); } diff --git a/sa-token-starter/sa-token-spring-boot-autoconfig/src/main/java/cn/dev33/satoken/spring/oauth2/SaOAuth2BeanInject.java b/sa-token-starter/sa-token-spring-boot-autoconfig/src/main/java/cn/dev33/satoken/spring/oauth2/SaOAuth2BeanInject.java index 3ff3dda9..8511384b 100644 --- a/sa-token-starter/sa-token-spring-boot-autoconfig/src/main/java/cn/dev33/satoken/spring/oauth2/SaOAuth2BeanInject.java +++ b/sa-token-starter/sa-token-spring-boot-autoconfig/src/main/java/cn/dev33/satoken/spring/oauth2/SaOAuth2BeanInject.java @@ -22,6 +22,7 @@ import cn.dev33.satoken.oauth2.data.convert.SaOAuth2DataConverter; import cn.dev33.satoken.oauth2.data.generate.SaOAuth2DataGenerate; import cn.dev33.satoken.oauth2.data.loader.SaOAuth2DataLoader; import cn.dev33.satoken.oauth2.data.resolver.SaOAuth2DataResolver; +import cn.dev33.satoken.oauth2.granttype.handler.SaOAuth2GrantTypeHandlerInterface; import cn.dev33.satoken.oauth2.processor.SaOAuth2ServerProcessor; import cn.dev33.satoken.oauth2.scope.handler.SaOAuth2ScopeHandlerInterface; import cn.dev33.satoken.oauth2.strategy.SaOAuth2Strategy; @@ -62,7 +63,17 @@ public class SaOAuth2BeanInject { */ @Autowired(required = false) public void setSaOAuth2Template(SaOAuth2Template saOAuth2Template) { - SaOAuth2ServerProcessor.instance.oauth2Template = saOAuth2Template; + SaOAuth2Manager.setTemplate(saOAuth2Template); + } + + /** + * 注入 OAuth2 请求处理器 + * + * @param serverProcessor 请求处理器 + */ + @Autowired(required = false) + public void setSaOAuth2Template(SaOAuth2ServerProcessor serverProcessor) { + SaOAuth2ServerProcessor.instance = serverProcessor; } /** @@ -127,4 +138,16 @@ public class SaOAuth2BeanInject { } } + /** + * 注入自定义 grant_type 处理器 + * + * @param handlerList 自定义 grant_type 处理器集合 + */ + @Autowired(required = false) + public void setSaOAuth2GrantTypeHandlerInterface(List handlerList) { + for (SaOAuth2GrantTypeHandlerInterface handler : handlerList) { + SaOAuth2Strategy.instance.registerGrantTypeHandler(handler); + } + } + } -- Gitee From 419ca3797cda80d28e59f10b07dbb75dc2d76936 Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Fri, 23 Aug 2024 16:18:24 +0800 Subject: [PATCH 144/172] =?UTF-8?q?=E7=BB=86=E8=8A=82=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E3=80=81=E6=96=87=E6=A1=A3=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/cn/dev33/satoken/stp/StpLogic.java | 5 +- .../com/pj/SaOAuth2ServerApplication.java | 2 +- .../com/pj/oauth2/SaOAuth2DataLoaderImpl.java | 2 +- .../pj/oauth2/SaOAuth2ServerController.java | 15 ++-- .../custom/PhoneCodeGrantTypeHandler.java | 3 +- .../src/main/resources/application.yml | 8 +- sa-token-doc/_sidebar.md | 2 +- sa-token-doc/oauth2/oauth2-check-domain.md | 18 +++-- sa-token-doc/oauth2/oauth2-custom-api.md | 6 +- .../oauth2/oauth2-custom-grant_type.md | 5 +- sa-token-doc/oauth2/oauth2-custom-login.md | 8 +- ...cope-handler.md => oauth2-custom-scope.md} | 0 sa-token-doc/oauth2/oauth2-interworking.md | 41 +++++----- sa-token-doc/oauth2/oauth2-server.md | 67 +++++++++------ sa-token-doc/up/integ-redis.md | 2 +- sa-token-doc/use/config.md | 81 +++++++++---------- .../dev33/satoken/oauth2/SaOAuth2Manager.java | 10 +-- ...2Config.java => SaOAuth2ServerConfig.java} | 58 ++++++------- .../SaOAuth2DataConverterDefaultImpl.java | 6 +- .../data/model/loader/SaClientModel.java | 36 ++++----- .../AuthorizationCodeGrantTypeHandler.java | 2 +- .../handler/PasswordGrantTypeHandler.java | 2 +- .../handler/RefreshTokenGrantTypeHandler.java | 2 +- .../SaOAuth2GrantTypeHandlerInterface.java | 2 +- .../processor/SaOAuth2ServerProcessor.java | 14 ++-- .../oauth2/strategy/SaOAuth2Strategy.java | 8 +- .../oauth2/template/SaOAuth2Template.java | 4 +- .../solon/oauth2/SaOAuth2AutoConfigure.java | 6 +- .../spring/oauth2/SaOAuth2BeanInject.java | 4 +- .../spring/oauth2/SaOAuth2BeanRegister.java | 8 +- 30 files changed, 227 insertions(+), 200 deletions(-) rename sa-token-doc/oauth2/{oauth2-custom-scope-handler.md => oauth2-custom-scope.md} (100%) rename sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/config/{SaOAuth2Config.java => SaOAuth2ServerConfig.java} (79%) diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpLogic.java b/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpLogic.java index 1c071119..0e5050b5 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpLogic.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpLogic.java @@ -36,6 +36,7 @@ import cn.dev33.satoken.util.SaFoxUtil; import cn.dev33.satoken.util.SaTokenConsts; import cn.dev33.satoken.util.SaValue2Box; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; @@ -2114,7 +2115,7 @@ public class StpLogic { // 如果该账号的 Account-Session 为 null,说明此账号尚没有客户端在登录,此时返回空集合 SaSession session = getSessionByLoginId(loginId, false); if(session == null) { - return Collections.emptyList(); + return new ArrayList<>(); } // 按照设备类型进行筛选 @@ -2132,7 +2133,7 @@ public class StpLogic { // 如果该账号的 Account-Session 为 null,说明此账号尚没有客户端在登录,此时返回空集合 SaSession session = getSessionByLoginId(loginId, false); if(session == null) { - return Collections.emptyList(); + return new ArrayList<>(); } // 按照设备类型进行筛选 diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/SaOAuth2ServerApplication.java b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/SaOAuth2ServerApplication.java index 4cf9200b..8666f2c3 100644 --- a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/SaOAuth2ServerApplication.java +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/SaOAuth2ServerApplication.java @@ -13,7 +13,7 @@ public class SaOAuth2ServerApplication { public static void main(String[] args) { SpringApplication.run(SaOAuth2ServerApplication.class, args); - System.out.println("\nSa-Token-OAuth Server端启动成功,配置如下:"); + System.out.println("\nSa-Token-OAuth2 Server端启动成功,配置如下:"); System.out.println(SaOAuth2Manager.getConfig()); } diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2DataLoaderImpl.java b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2DataLoaderImpl.java index fbcc2863..fd8216fc 100644 --- a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2DataLoaderImpl.java +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2DataLoaderImpl.java @@ -21,7 +21,7 @@ public class SaOAuth2DataLoaderImpl implements SaOAuth2DataLoader { return new SaClientModel() .setClientId("1001") // client id .setClientSecret("aaaa-bbbb-cccc-dddd-eeee") // client 秘钥 - .addAllowUrls("*") // 所有允许授权的 url + .addAllowRedirectUris("*") // 所有允许授权的 url .addContractScopes("openid", "userid", "userinfo") // 所有签约的权限 .addAllowGrantTypes( // 所有允许的授权模式 GrantType.authorization_code, // 授权码式 diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java index 37261fd0..bc473a8a 100644 --- a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java @@ -1,7 +1,7 @@ package com.pj.oauth2; import cn.dev33.satoken.context.SaHolder; -import cn.dev33.satoken.oauth2.config.SaOAuth2Config; +import cn.dev33.satoken.oauth2.config.SaOAuth2ServerConfig; import cn.dev33.satoken.oauth2.processor.SaOAuth2ServerProcessor; import cn.dev33.satoken.oauth2.template.SaOAuth2Util; import cn.dev33.satoken.stp.StpUtil; @@ -24,23 +24,23 @@ import java.util.Map; @RestController public class SaOAuth2ServerController { - // OAuth2-Server 端:处理所有OAuth相关请求 + // OAuth2-Server 端:处理所有 OAuth2 相关请求 @RequestMapping("/oauth2/*") public Object request() { System.out.println("------- 进入请求: " + SaHolder.getRequest().getUrl()); return SaOAuth2ServerProcessor.instance.dister(); } - // Sa-OAuth2 定制化配置 + // Sa-Token OAuth2 定制化配置 @Autowired - public void configOAuth2Server(SaOAuth2Config cfg) { + public void configOAuth2Server(SaOAuth2ServerConfig oauth2Server) { // 未登录的视图 - cfg.notLoginView = ()->{ + oauth2Server.notLoginView = ()->{ return new ModelAndView("login.html"); }; // 登录处理函数 - cfg.doLoginHandle = (name, pwd) -> { + oauth2Server.doLoginHandle = (name, pwd) -> { if("sa".equals(name) && "123456".equals(pwd)) { StpUtil.login(10001); return SaResult.ok(); @@ -49,12 +49,13 @@ public class SaOAuth2ServerController { }; // 授权确认视图 - cfg.confirmView = (clientId, scopes)->{ + oauth2Server.confirmView = (clientId, scopes)->{ Map map = new HashMap<>(); map.put("clientId", clientId); map.put("scope", scopes); return new ModelAndView("confirm.html", map); }; + } diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/custom/PhoneCodeGrantTypeHandler.java b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/custom/PhoneCodeGrantTypeHandler.java index 9bad7d29..6afb64a7 100644 --- a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/custom/PhoneCodeGrantTypeHandler.java +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/custom/PhoneCodeGrantTypeHandler.java @@ -26,8 +26,9 @@ public class PhoneCodeGrantTypeHandler implements SaOAuth2GrantTypeHandlerInterf } @Override - public AccessTokenModel getAccessTokenModel(SaRequest req, String clientId, List scopes) { + public AccessTokenModel getAccessToken(SaRequest req, String clientId, List scopes) { + // 获取前端提交的参数 String phone = req.getParamNotNull("phone"); String code = req.getParamNotNull("code"); String realCode = SaManager.getSaTokenDao().get("phone_code:" + phone); diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/resources/application.yml b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/resources/application.yml index 0cd51d5c..eada44d9 100644 --- a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/resources/application.yml +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/resources/application.yml @@ -4,19 +4,19 @@ server: # sa-token配置 sa-token: # token名称 (同时也是 Cookie 名称) - token-name: satoken-oauth2-server + token-name: sa-token-oauth2-server # 是否打印操作日志 is-log: true # OAuth2.0 配置 - oauth2: + oauth2-server: # 是否全局开启授权码模式 - enable-code: true + enable-authorization-code: true # 是否全局开启 Implicit 模式 enable-implicit: true # 是否全局开启密码模式 enable-password: true # 是否全局开启客户端模式 - enable-client: true + enable-client-credentials: true # 定义哪些 scope 是高级权限,多个用逗号隔开 # higher-scope: openid,userid # 定义哪些 scope 是低级权限,多个用逗号隔开 diff --git a/sa-token-doc/_sidebar.md b/sa-token-doc/_sidebar.md index f87b8192..3a7c4b0f 100644 --- a/sa-token-doc/_sidebar.md +++ b/sa-token-doc/_sidebar.md @@ -58,7 +58,7 @@ - [配置 client 域名校验 ](/oauth2/oauth2-check-domain) - [定制化登录页面与授权页面](/oauth2/oauth2-custom-login) - [自定义 API 路由 ](/oauth2/oauth2-custom-api) - - [自定义 Scope 权限以处理器](/oauth2/oauth2-custom-scope-handler) + - [自定义 Scope 权限以处理器](/oauth2/oauth2-custom-scope) - [为 Scope 划分等级](/oauth2/oauth2-scope-level) - [自定义 grant_type](/oauth2/oauth2-custom-grant_type) - [OAuth2-与登录会话实现数据互通](/oauth2/oauth2-interworking) diff --git a/sa-token-doc/oauth2/oauth2-check-domain.md b/sa-token-doc/oauth2/oauth2-check-domain.md index f1cca9e3..a6e8faf6 100644 --- a/sa-token-doc/oauth2/oauth2-check-domain.md +++ b/sa-token-doc/oauth2/oauth2-check-domain.md @@ -14,7 +14,7 @@ public class SaOAuth2DataLoaderImpl implements SaOAuth2DataLoader { if("1001".equals(clientId)) { return new SaClientModel() // ... - .addAllowUrls("*") // 所有允许授权的 url + .addAllowRedirectUris("*") // 所有允许授权的 url // ... } return null; @@ -23,7 +23,7 @@ public class SaOAuth2DataLoaderImpl implements SaOAuth2DataLoader { } ``` -配置项 `AllowUrls` 意为配置此 `Client` 端所有允许的授权地址,不在此配置项中的 URL 将无法下发 `code` 授权码。 +配置项 `AllowRedirectUris` 意为配置此 `Client` 端所有允许的授权地址,不在此配置项中的 URL 将无法下发 `code` 授权码。 为了方便测试,上述代码将其配置为`*`,但是,在生产环境中,此配置项绝对不能配置为 * ,否则会有被 `code` 劫持的风险。 @@ -40,7 +40,7 @@ public class SaOAuth2DataLoaderImpl implements SaOAuth2DataLoader { ### 2、防范方法 -造成此漏洞的直接原因就是我们对此 client 配置了过于宽泛的 `AllowUrls` 允许授权地址,防范的方法也很简单,就是缩小 `AllowUrls` 授权范围。 +造成此漏洞的直接原因就是我们对此 client 配置了过于宽泛的 `AllowRedirectUris` 允许授权地址,防范的方法也很简单,就是缩小 `AllowRedirectUris` 授权范围。 我们将其配置为一个具体的URL: @@ -53,7 +53,7 @@ public class SaOAuth2DataLoaderImpl implements SaOAuth2DataLoader { if("1001".equals(clientId)) { return new SaClientModel() // ... - .addAllowUrls("http://sa-oauth-client.com:8002/") // 所有允许授权的 url + .addAllowRedirectUris("http://sa-oauth-client.com:8002/") // 所有允许授权的 url // ... } return null; @@ -66,7 +66,7 @@ public class SaOAuth2DataLoaderImpl implements SaOAuth2DataLoader { ![oauth2-feifa-rf](https://oss.dev33.cn/sa-token/doc/oauth2-new/oauth2-feifa-rf.png 's-w-sh') -域名没有通过校验,拒绝授权! +URL 没有通过校验,拒绝授权! ### 3、配置安全性参考表 @@ -81,10 +81,18 @@ public class SaOAuth2DataLoaderImpl implements SaOAuth2DataLoader { ### 4、其它规则 1、AllowUrls 配置的地址不允许出现 `@` 字符。 + +- 反例:`http://user@sa-token.cc` +- 反例:`http://sa-oauth-client.com@sa-token.cc` + *详见源码:[SaOAuth2Template.java](https://gitee.com/dromara/sa-token/blob/master/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java) `checkRightUrl` 方法。* 2、AllowUrls 配置的地址 `*` 通配符只允许出现在字符串末尾,不允许出现在字符串中间位置。 + +- 反例:`http*://sa-oauth-client.com/` +- 反例:`http://*.sa-oauth-client.com/` + *详见源码: [SaOAuth2Template.java](https://gitee.com/dromara/sa-token/blob/master/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java) `checkAllowUrlListStaticMethod` 方法。* diff --git a/sa-token-doc/oauth2/oauth2-custom-api.md b/sa-token-doc/oauth2/oauth2-custom-api.md index a398c37d..1802a4f1 100644 --- a/sa-token-doc/oauth2/oauth2-custom-api.md +++ b/sa-token-doc/oauth2/oauth2-custom-api.md @@ -12,7 +12,7 @@ @RestController public class SaOAuth2ServerController { - // OAuth2-Server 端:处理所有 OAuth 相关请求 + // OAuth2-Server 端:处理所有 OAuth2 相关请求 @RequestMapping("/oauth2/*") public Object request() { return SaOAuth2ServerProcessor.instance.dister(); @@ -33,7 +33,7 @@ public class SaOAuth2ServerController { ``` java // 配置 OAuth2 相关参数 @Autowired -private void configOAuth2Server(SaOAuth2Config cfg) { +private void configOAuth2Server(SaOAuth2ServerConfig oauth2Server) { // 自定义API地址 SaOAuth2Consts.Api.authorize = "/oauth2/authorize2"; // ... @@ -76,7 +76,7 @@ public class SaOAuth2ServerController { // Code 换 Access-Token || 模式三:密码式 @RequestMapping("/oauth2/token") public Object token() { - return SaOAuth2ServerProcessor.instance.tokenOrPassword(); + return SaOAuth2ServerProcessor.instance.token(); } // Refresh-Token 刷新 Access-Token diff --git a/sa-token-doc/oauth2/oauth2-custom-grant_type.md b/sa-token-doc/oauth2/oauth2-custom-grant_type.md index 071fab88..4714b329 100644 --- a/sa-token-doc/oauth2/oauth2-custom-grant_type.md +++ b/sa-token-doc/oauth2/oauth2-custom-grant_type.md @@ -58,8 +58,9 @@ public class PhoneCodeGrantTypeHandler implements SaOAuth2GrantTypeHandlerInterf } @Override - public AccessTokenModel getAccessTokenModel(SaRequest req, String clientId, List scopes) { + public AccessTokenModel getAccessToken(SaRequest req, String clientId, List scopes) { + // 获取前端提交的参数 String phone = req.getParamNotNull("phone"); String code = req.getParamNotNull("code"); String realCode = SaManager.getSaTokenDao().get("phone_code:" + phone); @@ -102,7 +103,7 @@ public class SaOAuth2DataLoaderImpl implements SaOAuth2DataLoader { return new SaClientModel() .setClientId("1001") .setClientSecret("aaaa-bbbb-cccc-dddd-eeee") - .addAllowUrls("*") + .addAllowRedirectUris("*") // 所有允许授权的 url .addContractScopes("openid", "userid", "userinfo") .addAllowGrantTypes( GrantType.authorization_code, diff --git a/sa-token-doc/oauth2/oauth2-custom-login.md b/sa-token-doc/oauth2/oauth2-custom-login.md index 25cb0bb1..2a23e7a6 100644 --- a/sa-token-doc/oauth2/oauth2-custom-login.md +++ b/sa-token-doc/oauth2/oauth2-custom-login.md @@ -10,9 +10,9 @@ ``` java @Autowired -public void configOAuth2Server(SaOAuth2Config cfg) { +public void configOAuth2Server(SaOAuth2ServerConfig oauth2Server) { // 配置:未登录时返回的View - cfg.notLoginView = ()->{ + oauth2Server.notLoginView = ()->{ return new ModelAndView("xxx.html"); }; } @@ -64,9 +64,9 @@ public SaResult ss(String name, String pwd) { ``` java @Autowired -public void configOAuth2Server(SaOAuth2Config cfg) { +public void configOAuth2Server(SaOAuth2ServerConfig oauth2Server) { // 配置:授权确认视图 - cfg.confirmView = (clientId, scopes)->{ + oauth2Server.confirmView = (clientId, scopes)->{ Map map = new HashMap<>(); map.put("clientId", clientId); map.put("scope", scopes); diff --git a/sa-token-doc/oauth2/oauth2-custom-scope-handler.md b/sa-token-doc/oauth2/oauth2-custom-scope.md similarity index 100% rename from sa-token-doc/oauth2/oauth2-custom-scope-handler.md rename to sa-token-doc/oauth2/oauth2-custom-scope.md diff --git a/sa-token-doc/oauth2/oauth2-interworking.md b/sa-token-doc/oauth2/oauth2-interworking.md index 002ec431..77c0b33c 100644 --- a/sa-token-doc/oauth2/oauth2-interworking.md +++ b/sa-token-doc/oauth2/oauth2-interworking.md @@ -1,4 +1,4 @@ -# Sa-Token-OAuth2 与登录会话实现数据互通 +# OAuth2 与登录会话实现数据互通 --- @@ -16,29 +16,35 @@ ### OAuth2-Server 端数据互通 -很简单,你只需要在 `SaOAuth2TemplateImpl` 实现类中继续重写 Access-Token 的生成策略: +很简单,你只需要在 `configOAuth2Server` 中重写 Access-Token 的生成策略: ``` java -@Component -public class SaOAuth2TemplateImpl extends SaOAuth2Template { - - // ... 其它代码 - - // 重写 Access-Token 生成策略:复用登录会话的Token - @Override - public String randomAccessToken(String clientId, Object loginId, String scope) { - String tokenValue = StpUtil.createLoginSession(loginId); - return tokenValue; - } +// Sa-Token OAuth2 定制化配置 +@Autowired +public void configOAuth2Server(SaOAuth2ServerConfig oauth2Server) { + // 其它配置 ... + // 重写 AccessToken 创建策略,返回会话令牌 + SaOAuth2Strategy.instance.createAccessToken = (clientId, loginId, scopes) -> { + System.out.println("----返回会话令牌"); + return StpUtil.createLoginSession(loginId); + }; + } ``` 重启项目,然后在 OAuth2 模块授权登录,现在生成的 `access_token` ,可以用来访问 `satoken` 的会话接口了。 +> [!WARNING| label:注意点] +> 数据互通,让前端与后端的交互更加方便,一个 token 即可访问所有接口,但也一定程度上失去了OAuth2的 “不同 Client 不同权限” 的设计意义, +> 同时也默认每个 Client 都拥有了账号的会话权限(access_token 与 satoken 为同一个)。 +> +> 应该根据自己的架构合理分析是否应该整合数据互通。 + + ### OAuth2-Client 数据互通 -除了Server端,Client端也可以打通 `access_token` 与 `satoken` 会话。做法是在 Client 端拿到 `access_token` 后进行登录时,使用 SaLoginModel 预定登录生成的 Token 值 +除了Server端,Client端也可以打通 `access_token` 与 `satoken` 会话。做法是在 Client 端拿到 `access_token` 后进行登录时,使用 `SaLoginModel` 预定登录生成的 Token 值 ``` java // 1. 获取到access_token @@ -51,13 +57,6 @@ StpUtil.login(uid, SaLoginConfig.setToken(access_token)); ``` -> [!WARNING| label:注意点] -> 数据互通,让前端与后端的交互更加方便,一个 token 即可访问所有接口,但也一定程度上失去了OAuth2的 “不同 Client 不同权限” 的设计意义, -> 同时也默认每个 Client 都拥有了账号的会话权限(access_token 与 satoken 为同一个)。 -> -> 应该根据自己的架构合理分析是否应该整合数据互通。 - - diff --git a/sa-token-doc/oauth2/oauth2-server.md b/sa-token-doc/oauth2/oauth2-server.md index de684040..ab385de2 100644 --- a/sa-token-doc/oauth2/oauth2-server.md +++ b/sa-token-doc/oauth2/oauth2-server.md @@ -23,23 +23,39 @@ ${sa.top.version} - + cn.dev33 sa-token-oauth2 ${sa.top.version} + + + + cn.dev33 + sa-token-redis-jackson + ${sa-token.version} + + + org.apache.commons + commons-pool2 + ``` ``` gradle // Sa-Token 权限认证,在线文档:https://sa-token.cc implementation 'cn.dev33:sa-token-spring-boot-starter:${sa.top.version}' -// Sa-Token-OAuth2.0 模块 +// Sa-Token OAuth2.0 模块 implementation 'cn.dev33:sa-token-oauth2:${sa.top.version}' + +// Sa-Token 整合 Redis (可选) +implementation 'cn.dev33:sa-token-redis-jackson:${sa.top.version}' +implementation 'org.apache.commons:commons-pool2' ``` +注:Redis 相关依赖是非必须的,如果集成了 redis,可以让你更细致的观察到 sa-token-oauth2 的底层数据格式。 ### 3、开放服务 @@ -60,9 +76,16 @@ public class SaOAuth2DataLoaderImpl implements SaOAuth2DataLoader { return new SaClientModel() .setClientId("1001") // client id .setClientSecret("aaaa-bbbb-cccc-dddd-eeee") // client 秘钥 - .addAllowUrls("*") // 所有允许授权的 url + .addAllowRedirectUris("*") // 所有允许授权的 url .addContractScopes("openid", "userid", "userinfo") // 所有签约的权限 - .setIsAutoMode(true); // 是否自动判断开放的授权模式 + .addAllowGrantTypes( // 所有允许的授权模式 + GrantType.authorization_code, // 授权码式 + GrantType.implicit, // 隐式式 + GrantType.refresh_token, // 刷新令牌 + GrantType.password, // 密码式 + GrantType.client_credentials // 客户端模式 + ) + ; } return null; } @@ -83,24 +106,24 @@ public class SaOAuth2DataLoaderImpl implements SaOAuth2DataLoader { 2、新建`SaOAuth2ServerController` ``` java /** - * Sa-OAuth2 Server端 控制器 + * Sa-Token OAuth2 Server端 控制器 */ @RestController public class SaOAuth2ServerController { - // 处理所有OAuth相关请求 + // OAuth2-Server 端:处理所有 OAuth2 相关请求 @RequestMapping("/oauth2/*") public Object request() { System.out.println("------- 进入请求: " + SaHolder.getRequest().getUrl()); - return SaOAuth2Handle.serverRequest(); + return SaOAuth2ServerProcessor.instance.dister(); } - - // Sa-OAuth2 定制化配置 + + // Sa-Token OAuth2 定制化配置 @Autowired - public void setSaOAuth2Config(SaOAuth2Config cfg) { + public void setSaOAuth2Config(SaOAuth2Config oauth2Server) { // 配置:未登录时返回的View - cfg.notLoginView = () -> { + oauth2Server.notLoginView = () -> { String msg = "当前会话在OAuth-Server端尚未登录,请先访问" + " doLogin登录 " + "进行登录之后,刷新页面开始授权"; @@ -108,7 +131,7 @@ public class SaOAuth2ServerController { }; // 配置:登录处理函数 - cfg.doLoginHandle = (name, pwd) -> { + oauth2Server.doLoginHandle = (name, pwd) -> { if("sa".equals(name) && "123456".equals(pwd)) { StpUtil.login(10001); return SaResult.ok(); @@ -117,7 +140,7 @@ public class SaOAuth2ServerController { }; // 配置:确认授权时返回的 view - cfg.confirmView = (clientId, scopes) -> { + oauth2Server.confirmView = (clientId, scopes) -> { String scopeStr = SaFoxUtil.convertListToString(scopes); String yesCode = "fetch('/oauth2/doConfirm?client_id=" + clientId + "&scope=" + scopeStr + "', {method: 'POST'})" + @@ -132,16 +155,9 @@ public class SaOAuth2ServerController { }; } - // 全局异常拦截 - @ExceptionHandler - public SaResult handlerException(Exception e) { - e.printStackTrace(); - return SaResult.error(e.getMessage()); - } - } ``` -注意:在`setDoLoginHandle`函数里如果要获取name, pwd以外的参数,可通过`SaHolder.getRequest().getParam("xxx")`来获取 +注意:在 `doLoginHandle` 函数里如果要获取 name, pwd 以外的参数,可通过 `SaHolder.getRequest().getParam("xxx")` 来获取。 3、全局异常处理 ``` java @@ -164,7 +180,8 @@ public class GlobalExceptionHandler { public class SaOAuth2ServerApplication { public static void main(String[] args) { SpringApplication.run(SaOAuth2ServerApplication.class, args); - System.out.println("\nSa-Token-OAuth Server 端启动成功"); + System.out.println("\nSa-Token-OAuth2 Server 端启动成功"); + System.out.println(SaOAuth2Manager.getConfig()); } } ``` @@ -173,7 +190,7 @@ public class SaOAuth2ServerApplication { ### 4、访问测试 -1、由于暂未搭建Client端,我们可以使用Sa-Token官网作为重定向URL进行测试: +1、由于暂未搭建Client端,我们可以使用 Sa-Token 官网作为重定向URL进行测试: ``` url http://sa-oauth-server.com:8000/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=https://sa-token.cc&scope=openid ``` @@ -185,7 +202,7 @@ http://sa-oauth-server.com:8000/oauth2/authorize?response_type=code&client_id=10 3、点击doLogin进行登录之后刷新页面,会提示我们确认授权 ![sa-oauth2-server-scope](https://oss.dev33.cn/sa-token/doc/oauth2-new/sa-oauth2-server-scope.png 's-w-sh') -4、点击确认授权之后刷新页面,我们会被重定向至 redirect_uri 页面,并携带了code参数 +4、点击同意授权之后,我们会被重定向至 redirect_uri 页面,并携带了code参数 ![sa-oauth2-server-code](https://oss.dev33.cn/sa-token/doc/oauth2-new/sa-oauth2-server-code.png 's-w-sh') @@ -194,7 +211,7 @@ http://sa-oauth-server.com:8000/oauth2/authorize?response_type=code&client_id=10 http://sa-oauth-server.com:8000/oauth2/token?grant_type=authorization_code&client_id=1001&client_secret=aaaa-bbbb-cccc-dddd-eeee&code={code} ``` -将得到 `Access-Token`、`Refresh-Token`、`openid`等授权信息 +将得到 `Access-Token`、`Refresh-Token`、`openid`等授权信息: ``` js { diff --git a/sa-token-doc/up/integ-redis.md b/sa-token-doc/up/integ-redis.md index b01ec4de..e0c18701 100644 --- a/sa-token-doc/up/integ-redis.md +++ b/sa-token-doc/up/integ-redis.md @@ -73,7 +73,7 @@ implementation 'cn.dev33:sa-token-redis-jackson:${sa.top.version}' ``` ``` gradle -// Sa-Token 整合 Redis (使用 jackson 序列化方式) +// 提供Redis连接池 implementation 'org.apache.commons:commons-pool2' ``` diff --git a/sa-token-doc/use/config.md b/sa-token-doc/use/config.md index 89a97a97..bc314b06 100644 --- a/sa-token-doc/use/config.md +++ b/sa-token-doc/use/config.md @@ -294,18 +294,21 @@ sa-token.sso-client.is-slo=true ### 4、OAuth2.0相关配置 -| 参数名称 | 类型 | 默认值 | 说明 | -| :-------- | :-------- | :-------- | :-------- | -| isCode | Boolean | true | 是否打开模式:授权码(`Authorization Code`) | -| isImplicit | Boolean | true | 是否打开模式:隐藏式(`Implicit`) | -| isPassword | Boolean | true | 是否打开模式:密码式(`Password`) | -| isClient | Boolean | true | 是否打开模式:凭证式(`Client Credentials`) | -| isNewRefresh | Boolean | false | 是否在每次 `Refresh-Token` 刷新 `Access-Token` 时,产生一个新的 Refresh-Token | -| codeTimeout | long | 300 | Code授权码 保存的时间(单位:秒) 默认五分钟 | -| accessTokenTimeout | long | 7200 | `Access-Token` 保存的时间(单位:秒)默认两个小时 | -| refreshTokenTimeout | long | 2592000 | `Refresh-Token` 保存的时间(单位:秒) 默认30 天 | -| clientTokenTimeout | long | 7200 | `Client-Token` 保存的时间(单位:秒) 默认两个小时 | -| pastClientTokenTimeout | long | 7200 | `Past-Client-Token` 保存的时间(单位:秒) ,默认为-1,代表延续 `Client-Token` 的有效时间 | +| 参数名称 | 类型 | 默认值 | 说明 | +| :-------- | :-------- | :-------- | :-------- | +| enableAuthorizationCode | Boolean | true | 是否打开模式:授权码(`Authorization Code`) | +| enableImplicit | Boolean | true | 是否打开模式:隐藏式(`Implicit`) | +| enablePassword | Boolean | true | 是否打开模式:密码式(`Password`) | +| enableClientCredentials | Boolean | true | 是否打开模式:凭证式(`Client Credentials`) | +| isNewRefresh | Boolean | false | 是否在每次 `Refresh-Token` 刷新 `Access-Token` 时,产生一个新的 `Refresh-Token` | +| codeTimeout | long | 300 | Code授权码 保存的时间(单位:秒) 默认五分钟 | +| accessTokenTimeout | long | 7200 | `Access-Token` 保存的时间(单位:秒)默认两个小时 | +| refreshTokenTimeout | long | 2592000 | `Refresh-Token` 保存的时间(单位:秒) 默认30 天 | +| clientTokenTimeout | long | 7200 | `Client-Token` 保存的时间(单位:秒) 默认两个小时 | +| pastClientTokenTimeout | long | 7200 | `Past-Client-Token` 保存的时间(单位:秒) ,默认为-1,代表延续 `Client-Token` 的有效时间 | +| openidDigestPrefix | String | openid_default_digest_prefix | 默认 openid 生成算法中使用的摘要前缀 | +| higherScope | String | | 指定高级权限,多个用逗号隔开 | +| lowerScope | String | | 指定低级权限,多个用逗号隔开 | 配置示例: @@ -313,44 +316,40 @@ sa-token.sso-client.is-slo=true ``` yaml # Sa-Token 配置 sa-token: - token-name: satoken-server + token-name: sa-token-oauth2-server # OAuth2.0 配置 - oauth2: - is-code: true - is-implicit: true - is-password: true - is-client: true + oauth2-server: + enable-authorization-code: true + enable-implicit: true + enable-password: true + enable-client-credentials: true ``` ``` properties # Sa-Token 配置 -sa-token.token-name=satoken-server +sa-token.token-name=sa-token-oauth2-server # OAuth2.0 配置 -sa-token.oauth2.is-code=true -sa-token.oauth2.is-implicit=true -sa-token.oauth2.is-password=true -sa-token.oauth2.is-client=true +sa-token.oauth2-server.enable-authorization-code=true +sa-token.oauth2-server.enable-implicit=true +sa-token.oauth2-server.enable-password=true +sa-token.oauth2-server.enable-client-credentials=true ``` ##### SaClientModel属性定义 -| 参数名称 | 类型 | 默认值 | 说明 | -| :-------- | :-------- | :-------- | :-------- | -| clientId | String | null | 应用id,应该全局唯一 | -| clientSecret | String | null | 应用秘钥 | -| contractScope | String | null | 应用签约的所有权限, 多个用逗号隔开 | -| allowUrl | String | null | 应用允许授权的所有URL, 多个用逗号隔开 (可以使用 `*` 号通配符) | -| isCode | Boolean | false | 单独配置此 Client 是否打开模式:授权码(`Authorization Code`) | -| isImplicit | Boolean | false | 单独配置此 Client 是否打开模式:隐藏式(`Implicit`) | -| isPassword | Boolean | false | 单独配置此 Client 是否打开模式:密码式(`Password`) | -| isClient | Boolean | false | 单独配置此 Client 是否打开模式:凭证式(`Client Credentials`) | -| isAutoMode | Boolean | true | 是否自动判断此 Client 开放的授权模式。 参考:[详解](/use/config?id=配置项详解:isAutoMode) | -| isNewRefresh | Boolean | 取全局配置 | 单独配置此Client:是否在每次 `Refresh-Token` 刷新 `Access-Token` 时,产生一个新的 Refresh-Token [ 默认取全局配置 ] | -| accessTokenTimeout | long | 取全局配置 | 单独配置此Client:`Access-Token` 保存的时间(单位:秒) [默认取全局配置] | -| refreshTokenTimeout | long | 取全局配置 | 单独配置此Client:`Refresh-Token` 保存的时间(单位:秒) [默认取全局配置] | -| clientTokenTimeout | long | 取全局配置 | 单独配置此Client:`Client-Token` 保存的时间(单位:秒) [默认取全局配置] | -| pastClientTokenTimeout | long | 取全局配置 | 单独配置此Client:`Past-Client-Token` 保存的时间(单位:秒) [默认取全局配置] | +| 参数名称 | 类型 | 默认值 | 说明 | +| :-------- | :-------- | :-------- | :-------- | +| clientId | String | null | 应用id,应该全局唯一 | +| clientSecret | String | null | 应用秘钥 | +| contractScopes | List | null | 应用签约的所有权限 | +| allowUrls | List | null | 应用允许授权的所有URL(可以使用 `*` 号通配符) | +| allowGrantTypes | List | new ArrayList<>() | 应用允许的所有 `grant_type` | +| isNewRefresh | Boolean | 取全局配置 | 单独配置此Client:是否在每次 `Refresh-Token` 刷新 `Access-Token` 时,产生一个新的 Refresh-Token [ 默认取全局配置 ] | +| accessTokenTimeout | long | 取全局配置 | 单独配置此Client:`Access-Token` 保存的时间(单位:秒) [默认取全局配置] | +| refreshTokenTimeout | long | 取全局配置 | 单独配置此Client:`Refresh-Token` 保存的时间(单位:秒) [默认取全局配置] | +| clientTokenTimeout | long | 取全局配置 | 单独配置此Client:`Client-Token` 保存的时间(单位:秒) [默认取全局配置] | +| pastClientTokenTimeout | long | 取全局配置 | 单独配置此Client:`Past-Client-Token` 保存的时间(单位:秒) [默认取全局配置] | @@ -392,14 +391,14 @@ sa-token.oauth2.is-client=true 但是 —— 有的场景下我们又确实需要在登录之前就使用 Token-Session 对象,这时候就把配置项 `tokenSessionCheckLogin` 值改为 `false` 即可。 - + #### 配置项详解:isHttp diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/SaOAuth2Manager.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/SaOAuth2Manager.java index 8521d353..8ba535b8 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/SaOAuth2Manager.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/SaOAuth2Manager.java @@ -15,7 +15,7 @@ */ package cn.dev33.satoken.oauth2; -import cn.dev33.satoken.oauth2.config.SaOAuth2Config; +import cn.dev33.satoken.oauth2.config.SaOAuth2ServerConfig; import cn.dev33.satoken.oauth2.dao.SaOAuth2Dao; import cn.dev33.satoken.oauth2.dao.SaOAuth2DaoDefaultImpl; import cn.dev33.satoken.oauth2.data.convert.SaOAuth2DataConverter; @@ -41,19 +41,19 @@ public class SaOAuth2Manager { /** * OAuth2 配置 Bean */ - private static volatile SaOAuth2Config config; - public static SaOAuth2Config getConfig() { + private static volatile SaOAuth2ServerConfig config; + public static SaOAuth2ServerConfig getConfig() { if (config == null) { // 初始化默认值 synchronized (SaOAuth2Manager.class) { if (config == null) { - setConfig(new SaOAuth2Config()); + setConfig(new SaOAuth2ServerConfig()); } } } return config; } - public static void setConfig(SaOAuth2Config config) { + public static void setConfig(SaOAuth2ServerConfig config) { SaOAuth2Manager.config = config; } diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/config/SaOAuth2Config.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/config/SaOAuth2ServerConfig.java similarity index 79% rename from sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/config/SaOAuth2Config.java rename to sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/config/SaOAuth2ServerConfig.java index 0bb39073..1ae2e05f 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/config/SaOAuth2Config.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/config/SaOAuth2ServerConfig.java @@ -24,17 +24,17 @@ import cn.dev33.satoken.util.SaResult; import java.io.Serializable; /** - * Sa-Token-OAuth2 配置类 Model + * Sa-Token OAuth2 Server 端 配置类 Model * * @author click33 * @since 1.19.0 */ -public class SaOAuth2Config implements Serializable { +public class SaOAuth2ServerConfig implements Serializable { private static final long serialVersionUID = -6541180061782004705L; /** 是否打开模式:授权码(Authorization Code) */ - public Boolean enableCode = true; + public Boolean enableAuthorizationCode = true; /** 是否打开模式:隐藏式(Implicit) */ public Boolean enableImplicit = true; @@ -43,7 +43,7 @@ public class SaOAuth2Config implements Serializable { public Boolean enablePassword = true; /** 是否打开模式:凭证式(Client Credentials) */ - public Boolean enableClient = true; + public Boolean enableClientCredentials = true; /** 是否在每次 Refresh-Token 刷新 Access-Token 时,产生一个新的 Refresh-Token */ public Boolean isNewRefresh = false; @@ -75,16 +75,16 @@ public class SaOAuth2Config implements Serializable { /** * @return enableCode */ - public Boolean getEnableCode() { - return enableCode; + public Boolean getEnableAuthorizationCode() { + return enableAuthorizationCode; } /** - * @param enableCode 要设置的 enableCode + * @param enableAuthorizationCode 要设置的 enableAuthorizationCode * @return / */ - public SaOAuth2Config setEnableCode(Boolean enableCode) { - this.enableCode = enableCode; + public SaOAuth2ServerConfig setEnableAuthorizationCode(Boolean enableAuthorizationCode) { + this.enableAuthorizationCode = enableAuthorizationCode; return this; } @@ -99,7 +99,7 @@ public class SaOAuth2Config implements Serializable { * @param enableImplicit 要设置的 enableImplicit * @return / */ - public SaOAuth2Config setEnableImplicit(Boolean enableImplicit) { + public SaOAuth2ServerConfig setEnableImplicit(Boolean enableImplicit) { this.enableImplicit = enableImplicit; return this; } @@ -114,24 +114,24 @@ public class SaOAuth2Config implements Serializable { /** * @param enablePassword 要设置的 enablePassword */ - public SaOAuth2Config setEnablePassword(Boolean enablePassword) { + public SaOAuth2ServerConfig setEnablePassword(Boolean enablePassword) { this.enablePassword = enablePassword; return this; } /** - * @return enableClient + * @return enableClientCredentials */ - public Boolean getEnableClient() { - return enableClient; + public Boolean getEnableClientCredentials() { + return enableClientCredentials; } /** - * @param enableClient 要设置的 enableClient + * @param enableClientCredentials 要设置的 enableClientCredentials * @return / */ - public SaOAuth2Config setEnableClient(Boolean enableClient) { - this.enableClient = enableClient; + public SaOAuth2ServerConfig setEnableClientCredentials(Boolean enableClientCredentials) { + this.enableClientCredentials = enableClientCredentials; return this; } @@ -146,7 +146,7 @@ public class SaOAuth2Config implements Serializable { * @param isNewRefresh 要设置的 isNewRefresh * @return / */ - public SaOAuth2Config setIsNewRefresh(Boolean isNewRefresh) { + public SaOAuth2ServerConfig setIsNewRefresh(Boolean isNewRefresh) { this.isNewRefresh = isNewRefresh; return this; } @@ -162,7 +162,7 @@ public class SaOAuth2Config implements Serializable { * @param codeTimeout 要设置的 codeTimeout * @return 对象自身 */ - public SaOAuth2Config setCodeTimeout(long codeTimeout) { + public SaOAuth2ServerConfig setCodeTimeout(long codeTimeout) { this.codeTimeout = codeTimeout; return this; } @@ -178,7 +178,7 @@ public class SaOAuth2Config implements Serializable { * @param accessTokenTimeout 要设置的 accessTokenTimeout * @return 对象自身 */ - public SaOAuth2Config setAccessTokenTimeout(long accessTokenTimeout) { + public SaOAuth2ServerConfig setAccessTokenTimeout(long accessTokenTimeout) { this.accessTokenTimeout = accessTokenTimeout; return this; } @@ -194,7 +194,7 @@ public class SaOAuth2Config implements Serializable { * @param refreshTokenTimeout 要设置的 refreshTokenTimeout * @return 对象自身 */ - public SaOAuth2Config setRefreshTokenTimeout(long refreshTokenTimeout) { + public SaOAuth2ServerConfig setRefreshTokenTimeout(long refreshTokenTimeout) { this.refreshTokenTimeout = refreshTokenTimeout; return this; } @@ -210,7 +210,7 @@ public class SaOAuth2Config implements Serializable { * @param clientTokenTimeout 要设置的 clientTokenTimeout * @return 对象自身 */ - public SaOAuth2Config setClientTokenTimeout(long clientTokenTimeout) { + public SaOAuth2ServerConfig setClientTokenTimeout(long clientTokenTimeout) { this.clientTokenTimeout = clientTokenTimeout; return this; } @@ -226,7 +226,7 @@ public class SaOAuth2Config implements Serializable { * @param pastClientTokenTimeout 要设置的 pastClientTokenTimeout * @return 对象自身 */ - public SaOAuth2Config setPastClientTokenTimeout(long pastClientTokenTimeout) { + public SaOAuth2ServerConfig setPastClientTokenTimeout(long pastClientTokenTimeout) { this.pastClientTokenTimeout = pastClientTokenTimeout; return this; } @@ -242,7 +242,7 @@ public class SaOAuth2Config implements Serializable { * @param openidDigestPrefix 要设置的 openidDigestPrefix * @return 对象自身 */ - public SaOAuth2Config setOpenidDigestPrefix(String openidDigestPrefix) { + public SaOAuth2ServerConfig setOpenidDigestPrefix(String openidDigestPrefix) { this.openidDigestPrefix = openidDigestPrefix; return this; } @@ -262,7 +262,7 @@ public class SaOAuth2Config implements Serializable { * @param higherScope 指定高级权限,多个用逗号隔开 * @return / */ - public SaOAuth2Config setHigherScope(String higherScope) { + public SaOAuth2ServerConfig setHigherScope(String higherScope) { this.higherScope = higherScope; return this; } @@ -282,7 +282,7 @@ public class SaOAuth2Config implements Serializable { * @param lowerScope 指定低级权限,多个用逗号隔开 * @return / */ - public SaOAuth2Config setLowerScope(String lowerScope) { + public SaOAuth2ServerConfig setLowerScope(String lowerScope) { this.lowerScope = lowerScope; return this; } @@ -307,11 +307,11 @@ public class SaOAuth2Config implements Serializable { @Override public String toString() { - return "SaOAuth2Config{" + - "enableCode=" + enableCode + + return "SaOAuth2ServerConfig{" + + "enableAuthorizationCode=" + enableAuthorizationCode + ", enableImplicit=" + enableImplicit + ", enablePassword=" + enablePassword + - ", enableClient=" + enableClient + + ", enableClientCredentials=" + enableClientCredentials + ", isNewRefresh=" + isNewRefresh + ", codeTimeout=" + codeTimeout + ", accessTokenTimeout=" + accessTokenTimeout + diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/convert/SaOAuth2DataConverterDefaultImpl.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/convert/SaOAuth2DataConverterDefaultImpl.java index 5212fc43..1c8865f5 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/convert/SaOAuth2DataConverterDefaultImpl.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/convert/SaOAuth2DataConverterDefaultImpl.java @@ -23,7 +23,7 @@ import cn.dev33.satoken.oauth2.data.model.loader.SaClientModel; import cn.dev33.satoken.oauth2.strategy.SaOAuth2Strategy; import cn.dev33.satoken.util.SaFoxUtil; -import java.util.Collections; +import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; @@ -41,7 +41,7 @@ public class SaOAuth2DataConverterDefaultImpl implements SaOAuth2DataConverter { @Override public List convertScopeStringToList(String scopeString) { if(SaFoxUtil.isEmpty(scopeString)) { - return Collections.emptyList(); + return new ArrayList<>(); } // 兼容以下三种分隔符:空格、逗号、%20 scopeString = scopeString.replaceAll(" ", ","); @@ -63,7 +63,7 @@ public class SaOAuth2DataConverterDefaultImpl implements SaOAuth2DataConverter { @Override public List convertAllowUrlStringToList(String allowUrl) { if(SaFoxUtil.isEmpty(allowUrl)) { - return Collections.emptyList(); + return new ArrayList<>(); } return SaFoxUtil.convertStringToList(allowUrl); } 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 f84ce3d2..e6ae542d 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 @@ -16,7 +16,7 @@ package cn.dev33.satoken.oauth2.data.model.loader; import cn.dev33.satoken.oauth2.SaOAuth2Manager; -import cn.dev33.satoken.oauth2.config.SaOAuth2Config; +import cn.dev33.satoken.oauth2.config.SaOAuth2ServerConfig; import java.io.Serializable; import java.util.ArrayList; @@ -49,9 +49,9 @@ public class SaClientModel implements Serializable { public List contractScopes; /** - * 应用允许授权的所有URL + * 应用允许授权的所有 redirect_uri */ - public List allowUrls; + public List allowRedirectUris; /** * 应用允许的所有 grant_type @@ -75,19 +75,19 @@ public class SaClientModel implements Serializable { public SaClientModel() { - SaOAuth2Config config = SaOAuth2Manager.getConfig(); + SaOAuth2ServerConfig config = SaOAuth2Manager.getConfig(); this.isNewRefresh = config.getIsNewRefresh(); this.accessTokenTimeout = config.getAccessTokenTimeout(); this.refreshTokenTimeout = config.getRefreshTokenTimeout(); this.clientTokenTimeout = config.getClientTokenTimeout(); this.pastClientTokenTimeout = config.getPastClientTokenTimeout(); } - public SaClientModel(String clientId, String clientSecret, List contractScopes, List allowUrls) { + public SaClientModel(String clientId, String clientSecret, List contractScopes, List allowRedirectUris) { super(); this.clientId = clientId; this.clientSecret = clientSecret; this.contractScopes = contractScopes; - this.allowUrls = allowUrls; + this.allowRedirectUris = allowRedirectUris; } /** @@ -139,18 +139,18 @@ public class SaClientModel implements Serializable { } /** - * @return 应用允许授权的所有URL + * @return 应用允许授权的所有 redirect_uri */ - public List getAllowUrls() { - return allowUrls; + public List getAllowRedirectUris() { + return allowRedirectUris; } /** - * @param allowUrls 应用允许授权的所有URL + * @param allowRedirectUris 应用允许授权的所有 redirect_uri * @return 对象自身 */ - public SaClientModel setAllowUrls(List allowUrls) { - this.allowUrls = allowUrls; + public SaClientModel setAllowRedirectUris(List allowRedirectUris) { + this.allowRedirectUris = allowRedirectUris; return this; } @@ -259,7 +259,7 @@ public class SaClientModel implements Serializable { "clientId='" + clientId + '\'' + ", clientSecret='" + clientSecret + '\'' + ", contractScopes=" + contractScopes + - ", allowUrls=" + allowUrls + + ", allowRedirectUris=" + allowRedirectUris + ", allowGrantTypes=" + allowGrantTypes + ", isNewRefresh=" + isNewRefresh + ", accessTokenTimeout=" + accessTokenTimeout + @@ -285,14 +285,14 @@ public class SaClientModel implements Serializable { } /** - * @param urls 添加应用允许授权的所有URL + * @param redirectUris 添加应用允许授权的所有 redirect_uri * @return 对象自身 */ - public SaClientModel addAllowUrls(String... urls) { - if(this.allowUrls == null) { - this.allowUrls = new ArrayList<>(); + public SaClientModel addAllowRedirectUris(String... redirectUris) { + if(this.allowRedirectUris == null) { + this.allowRedirectUris = new ArrayList<>(); } - this.allowUrls.addAll(Arrays.asList(urls)); + this.allowRedirectUris.addAll(Arrays.asList(redirectUris)); return this; } diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/granttype/handler/AuthorizationCodeGrantTypeHandler.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/granttype/handler/AuthorizationCodeGrantTypeHandler.java index 1f80841b..00de1e0c 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/granttype/handler/AuthorizationCodeGrantTypeHandler.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/granttype/handler/AuthorizationCodeGrantTypeHandler.java @@ -38,7 +38,7 @@ public class AuthorizationCodeGrantTypeHandler implements SaOAuth2GrantTypeHandl } @Override - public AccessTokenModel getAccessTokenModel(SaRequest req, String clientId, List scopes) { + public AccessTokenModel getAccessToken(SaRequest req, String clientId, List scopes) { // 获取参数 ClientIdAndSecretModel clientIdAndSecret = SaOAuth2Manager.getDataResolver().readClientIdAndSecret(req); // String clientId = clientIdAndSecret.clientId; diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/granttype/handler/PasswordGrantTypeHandler.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/granttype/handler/PasswordGrantTypeHandler.java index 89a89df2..1609e355 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/granttype/handler/PasswordGrantTypeHandler.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/granttype/handler/PasswordGrantTypeHandler.java @@ -40,7 +40,7 @@ public class PasswordGrantTypeHandler implements SaOAuth2GrantTypeHandlerInterfa } @Override - public AccessTokenModel getAccessTokenModel(SaRequest req, String clientId, List scopes) { + public AccessTokenModel getAccessToken(SaRequest req, String clientId, List scopes) { // 1、获取请求参数 String username = req.getParamNotNull(SaOAuth2Consts.Param.username); diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/granttype/handler/RefreshTokenGrantTypeHandler.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/granttype/handler/RefreshTokenGrantTypeHandler.java index b526f7bf..238e35c1 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/granttype/handler/RefreshTokenGrantTypeHandler.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/granttype/handler/RefreshTokenGrantTypeHandler.java @@ -40,7 +40,7 @@ public class RefreshTokenGrantTypeHandler implements SaOAuth2GrantTypeHandlerInt } @Override - public AccessTokenModel getAccessTokenModel(SaRequest req, String clientId, List scopes) { + public AccessTokenModel getAccessToken(SaRequest req, String clientId, List scopes) { // 获取参数 String refreshToken = req.getParamNotNull(SaOAuth2Consts.Param.refresh_token); diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/granttype/handler/SaOAuth2GrantTypeHandlerInterface.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/granttype/handler/SaOAuth2GrantTypeHandlerInterface.java index c07868e1..48d47a19 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/granttype/handler/SaOAuth2GrantTypeHandlerInterface.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/granttype/handler/SaOAuth2GrantTypeHandlerInterface.java @@ -41,6 +41,6 @@ public interface SaOAuth2GrantTypeHandlerInterface { * @param req / * @return / */ - AccessTokenModel getAccessTokenModel(SaRequest req, String clientId, List scopes); + AccessTokenModel getAccessToken(SaRequest req, String clientId, List scopes); } \ No newline at end of file diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/processor/SaOAuth2ServerProcessor.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/processor/SaOAuth2ServerProcessor.java index feaa02de..ed8cc38b 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/processor/SaOAuth2ServerProcessor.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/processor/SaOAuth2ServerProcessor.java @@ -19,7 +19,7 @@ import cn.dev33.satoken.context.SaHolder; import cn.dev33.satoken.context.model.SaRequest; import cn.dev33.satoken.context.model.SaResponse; import cn.dev33.satoken.oauth2.SaOAuth2Manager; -import cn.dev33.satoken.oauth2.config.SaOAuth2Config; +import cn.dev33.satoken.oauth2.config.SaOAuth2ServerConfig; import cn.dev33.satoken.oauth2.consts.GrantType; import cn.dev33.satoken.oauth2.consts.SaOAuth2Consts; import cn.dev33.satoken.oauth2.consts.SaOAuth2Consts.Api; @@ -113,7 +113,7 @@ public class SaOAuth2ServerProcessor { // 获取变量 SaRequest req = SaHolder.getRequest(); SaResponse res = SaHolder.getResponse(); - SaOAuth2Config cfg = SaOAuth2Manager.getConfig(); + SaOAuth2ServerConfig cfg = SaOAuth2Manager.getConfig(); SaOAuth2DataGenerate dataGenerate = SaOAuth2Manager.getDataGenerate(); SaOAuth2Template oauth2Template = SaOAuth2Manager.getTemplate(); String responseType = req.getParamNotNull(Param.response_type); @@ -218,7 +218,7 @@ public class SaOAuth2ServerProcessor { public Object doLogin() { // 获取变量 SaRequest req = SaHolder.getRequest(); - SaOAuth2Config cfg = SaOAuth2Manager.getConfig(); + SaOAuth2ServerConfig cfg = SaOAuth2Manager.getConfig(); return cfg.doLoginHandle.apply(req.getParam(Param.name), req.getParam(Param.pwd)); } @@ -285,14 +285,14 @@ public class SaOAuth2ServerProcessor { public Object clientToken() { // 获取变量 SaRequest req = SaHolder.getRequest(); - SaOAuth2Config cfg = SaOAuth2Manager.getConfig(); + SaOAuth2ServerConfig cfg = SaOAuth2Manager.getConfig(); SaOAuth2Template oauth2Template = SaOAuth2Manager.getTemplate(); String grantType = req.getParamNotNull(Param.grant_type); if(!grantType.equals(GrantType.client_credentials)) { throw new SaOAuth2Exception("无效 grant_type:" + grantType).setCode(SaOAuth2ErrorCode.CODE_30126); } - if(!cfg.enableClient) { + if(!cfg.enableClientCredentials) { throwErrorSystemNotEnableModel(); } if(!currClientModel().getAllowGrantTypes().contains(GrantType.client_credentials)) { @@ -335,10 +335,10 @@ public class SaOAuth2ServerProcessor { /** * 校验 authorize 路由的 ResponseType 参数 */ - public void checkAuthorizeResponseType(String responseType, SaRequest req, SaOAuth2Config cfg) { + public void checkAuthorizeResponseType(String responseType, SaRequest req, SaOAuth2ServerConfig cfg) { // 模式一:Code授权码 if(responseType.equals(ResponseType.code)) { - if(!cfg.enableCode) { + if(!cfg.enableAuthorizationCode) { throwErrorSystemNotEnableModel(); } if(!currClientModel().getAllowGrantTypes().contains(GrantType.authorization_code)) { 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 70affdc3..dbfda655 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 @@ -17,7 +17,7 @@ package cn.dev33.satoken.oauth2.strategy; import cn.dev33.satoken.SaManager; import cn.dev33.satoken.oauth2.SaOAuth2Manager; -import cn.dev33.satoken.oauth2.config.SaOAuth2Config; +import cn.dev33.satoken.oauth2.config.SaOAuth2ServerConfig; import cn.dev33.satoken.oauth2.consts.GrantType; import cn.dev33.satoken.oauth2.consts.SaOAuth2Consts; import cn.dev33.satoken.oauth2.data.model.loader.SaClientModel; @@ -170,8 +170,8 @@ public final class SaOAuth2Strategy { } // 看看全局是否开启了此 grantType - SaOAuth2Config config = SaOAuth2Manager.getConfig(); - if(grantType.equals(GrantType.authorization_code) && !config.getEnableCode() ) { + SaOAuth2ServerConfig config = SaOAuth2Manager.getConfig(); + if(grantType.equals(GrantType.authorization_code) && !config.getEnableAuthorizationCode() ) { throw new SaOAuth2Exception("系统未开放的 grant_type: " + grantType); } if(grantType.equals(GrantType.password) && !config.getEnablePassword() ) { @@ -189,7 +189,7 @@ public final class SaOAuth2Strategy { } // 调用 处理器 - return grantTypeHandler.getAccessTokenModel(req, clientIdAndSecretModel.getClientId(), scopes); + return grantTypeHandler.getAccessToken(req, clientIdAndSecretModel.getClientId(), scopes); }; diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java index 3062f525..cbfeedbb 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java @@ -224,8 +224,8 @@ public class SaOAuth2Template { // 4、是否在[允许地址列表]之中 SaClientModel clientModel = checkClientModel(clientId); - checkAllowUrlList(clientModel.allowUrls); - if( ! SaStrategy.instance.hasElement.apply(clientModel.allowUrls, url)) { + checkAllowUrlList(clientModel.allowRedirectUris); + if( ! SaStrategy.instance.hasElement.apply(clientModel.allowRedirectUris, url)) { throw new SaOAuth2Exception("非法 redirect_url: " + url).setCode(SaOAuth2ErrorCode.CODE_30114); } } diff --git a/sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/oauth2/SaOAuth2AutoConfigure.java b/sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/oauth2/SaOAuth2AutoConfigure.java index 5c7626c5..dab18067 100644 --- a/sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/oauth2/SaOAuth2AutoConfigure.java +++ b/sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/oauth2/SaOAuth2AutoConfigure.java @@ -16,7 +16,7 @@ package cn.dev33.satoken.solon.oauth2; import cn.dev33.satoken.oauth2.SaOAuth2Manager; -import cn.dev33.satoken.oauth2.config.SaOAuth2Config; +import cn.dev33.satoken.oauth2.config.SaOAuth2ServerConfig; import cn.dev33.satoken.oauth2.template.SaOAuth2Template; import cn.dev33.satoken.oauth2.template.SaOAuth2Util; import org.noear.solon.annotation.Bean; @@ -39,7 +39,7 @@ public class SaOAuth2AutoConfigure { SaOAuth2Util.saOAuth2Template = bean; }); - appContext.subBeansOfType(SaOAuth2Config.class, bean -> { + appContext.subBeansOfType(SaOAuth2ServerConfig.class, bean -> { SaOAuth2Manager.setConfig(bean); }); } @@ -48,7 +48,7 @@ public class SaOAuth2AutoConfigure { * 获取 OAuth2配置Bean */ @Bean - public SaOAuth2Config getConfig(@Inject(value = "${sa-token.oauth2}", required = false) SaOAuth2Config oAuth2Config) { + public SaOAuth2ServerConfig getConfig(@Inject(value = "${sa-token.oauth2}", required = false) SaOAuth2ServerConfig oAuth2Config) { return oAuth2Config; } } \ No newline at end of file diff --git a/sa-token-starter/sa-token-spring-boot-autoconfig/src/main/java/cn/dev33/satoken/spring/oauth2/SaOAuth2BeanInject.java b/sa-token-starter/sa-token-spring-boot-autoconfig/src/main/java/cn/dev33/satoken/spring/oauth2/SaOAuth2BeanInject.java index 8511384b..092d7956 100644 --- a/sa-token-starter/sa-token-spring-boot-autoconfig/src/main/java/cn/dev33/satoken/spring/oauth2/SaOAuth2BeanInject.java +++ b/sa-token-starter/sa-token-spring-boot-autoconfig/src/main/java/cn/dev33/satoken/spring/oauth2/SaOAuth2BeanInject.java @@ -16,7 +16,7 @@ package cn.dev33.satoken.spring.oauth2; import cn.dev33.satoken.oauth2.SaOAuth2Manager; -import cn.dev33.satoken.oauth2.config.SaOAuth2Config; +import cn.dev33.satoken.oauth2.config.SaOAuth2ServerConfig; import cn.dev33.satoken.oauth2.dao.SaOAuth2Dao; import cn.dev33.satoken.oauth2.data.convert.SaOAuth2DataConverter; import cn.dev33.satoken.oauth2.data.generate.SaOAuth2DataGenerate; @@ -52,7 +52,7 @@ public class SaOAuth2BeanInject { * @param saOAuth2Config 配置对象 */ @Autowired(required = false) - public void setSaOAuth2Config(SaOAuth2Config saOAuth2Config) { + public void setSaOAuth2Config(SaOAuth2ServerConfig saOAuth2Config) { SaOAuth2Manager.setConfig(saOAuth2Config); } diff --git a/sa-token-starter/sa-token-spring-boot-autoconfig/src/main/java/cn/dev33/satoken/spring/oauth2/SaOAuth2BeanRegister.java b/sa-token-starter/sa-token-spring-boot-autoconfig/src/main/java/cn/dev33/satoken/spring/oauth2/SaOAuth2BeanRegister.java index 2a4a5ed7..788f97af 100644 --- a/sa-token-starter/sa-token-spring-boot-autoconfig/src/main/java/cn/dev33/satoken/spring/oauth2/SaOAuth2BeanRegister.java +++ b/sa-token-starter/sa-token-spring-boot-autoconfig/src/main/java/cn/dev33/satoken/spring/oauth2/SaOAuth2BeanRegister.java @@ -16,7 +16,7 @@ package cn.dev33.satoken.spring.oauth2; import cn.dev33.satoken.oauth2.SaOAuth2Manager; -import cn.dev33.satoken.oauth2.config.SaOAuth2Config; +import cn.dev33.satoken.oauth2.config.SaOAuth2ServerConfig; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; @@ -36,9 +36,9 @@ public class SaOAuth2BeanRegister { * @return 配置对象 */ @Bean - @ConfigurationProperties(prefix = "sa-token.oauth2") - public SaOAuth2Config getSaOAuth2Config() { - return new SaOAuth2Config(); + @ConfigurationProperties(prefix = "sa-token.oauth2-server") + public SaOAuth2ServerConfig getSaOAuth2Config() { + return new SaOAuth2ServerConfig(); } } -- Gitee From a7a3e8c14f8f333f2dc9e8188e6e5a19c35649ef Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Sat, 24 Aug 2024 00:20:17 +0800 Subject: [PATCH 145/172] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20OIDC=20=E5=8D=8F?= =?UTF-8?q?=E8=AE=AE=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/templates/index.html | 4 +- .../sa-token-demo-oauth2-server/pom.xml | 7 + .../com/pj/SaOAuth2ServerApplication.java | 6 +- .../com/pj/oauth2/SaOAuth2DataLoaderImpl.java | 2 +- .../oauth2/custom/CustomOidcScopeHandler.java | 32 ++++ .../src/main/resources/application.yml | 2 + sa-token-doc/_sidebar.md | 1 + sa-token-doc/oauth2/oauth2-oidc.md | 154 ++++++++++++++++++ .../cn/dev33/satoken/jwt/SaJwtTemplate.java | 20 +++ .../java/cn/dev33/satoken/jwt/SaJwtUtil.java | 17 +- sa-token-plugin/sa-token-oauth2/pom.xml | 6 + .../dev33/satoken/oauth2/SaOAuth2Manager.java | 16 +- .../oauth2/config/SaOAuth2OidcConfig.java | 62 +++++++ .../oauth2/config/SaOAuth2ServerConfig.java | 26 +++ .../dev33/satoken/oauth2/dao/SaOAuth2Dao.java | 6 +- .../data/loader/SaOAuth2DataLoader.java | 2 +- .../data/model/loader/SaClientModel.java | 2 +- .../oauth2/data/model/oidc/IdTokenModel.java | 90 ++++++++++ .../oauth2/exception/SaOAuth2Exception.java | 10 +- .../handler/PasswordGrantTypeHandler.java | 2 +- .../processor/SaOAuth2ServerProcessor.java | 6 +- .../scope/handler/OidcScopeHandler.java | 122 +++++++++++++- .../oauth2/strategy/SaOAuth2Strategy.java | 2 +- .../oauth2/template/SaOAuth2Template.java | 4 +- .../solon/oauth2/SaOAuth2AutoConfigure.java | 7 +- .../spring/oauth2/SaOAuth2BeanInject.java | 2 +- 26 files changed, 576 insertions(+), 34 deletions(-) create mode 100644 sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/custom/CustomOidcScopeHandler.java create mode 100644 sa-token-doc/oauth2/oauth2-oidc.md create mode 100644 sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/config/SaOAuth2OidcConfig.java create mode 100644 sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/model/oidc/IdTokenModel.java diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-client/src/main/resources/templates/index.html b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-client/src/main/resources/templates/index.html index 79c33db3..adc3bd0a 100644 --- a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-client/src/main/resources/templates/index.html +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-client/src/main/resources/templates/index.html @@ -48,11 +48,11 @@ 当请求链接不包含 scope 权限,或请求的 scope 近期已授权时,将无需用户手动确认,做到静默授权 http://sa-oauth-server.com:8000/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/ - + 当请求链接包含具体的 scope 权限时,将需要用户手动确认,此时 OAuth-Server 会返回更多的数据 - http://sa-oauth-server.com:8000/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/&scope=openid,userid,userinfo + http://sa-oauth-server.com:8000/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/&scope=openid,userid,userinfo,oidc 我们可以拿着 Refresh-Token 去刷新我们的 Access-Token,每次刷新后旧Token将作废 diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/pom.xml b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/pom.xml index a9599fba..db0b6c7d 100644 --- a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/pom.xml +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/pom.xml @@ -59,6 +59,13 @@ spring-boot-starter-thymeleaf + + + cn.dev33 + sa-token-jwt + ${sa-token.version} + + diff --git a/sa-token-doc/oauth2/oauth2-oidc.md b/sa-token-doc/oauth2/oauth2-oidc.md new file mode 100644 index 00000000..754822ef --- /dev/null +++ b/sa-token-doc/oauth2/oauth2-oidc.md @@ -0,0 +1,154 @@ +# OAuth2 开启 OIDC 协议 (OpenID Connect) + +--- + +### 1、开启步骤 + +1、引入 `sa-token-jwt` 依赖,用来签发 `id_token` + + + +``` xml + + + cn.dev33 + sa-token-jwt + ${sa.top.version} + +``` + +``` gradle +// sa-token-jwt 签发 OIDC id_token 令牌 +implementation 'cn.dev33:sa-token-jwt:${sa.top.version}' +``` + + + +2、在 `SaOAuth2DataLoader` 实现类中,返回的 `SaClientModel` 中添加 `oidc` 的签约权限。 + +``` java +@Component +public class SaOAuth2DataLoaderImpl implements SaOAuth2DataLoader { + @Override + public SaClientModel getClientModel(String clientId) { + // 此为模拟数据,真实环境需要从数据库查询 + if("1001".equals(clientId)) { + return new SaClientModel() + // .... + .addContractScopes("openid", "userid", "userinfo", "oidc") // 此处添加上签约权限:oidc + .addAllowGrantTypes( + // ... + ) + ; + } + return null; + } + // 其它代码 ... +} +``` + + +### 2、测试 + +1、在 OAuth2-Client 端申请授权码时,添加上 `oidc` 权限: +``` url +http://sa-oauth-server.com:8000/oauth2/authorize + ?response_type=code + &client_id=1001 + &redirect_uri=http://sa-oauth-client.com:8002/ + &scope=oidc +``` + +2、得到授权码后,然后拿着 `code` 换 `access_token` +``` url +http://sa-oauth-server.com:8000/oauth2/token + ?grant_type=authorization_code + &client_id=1001 + &client_secret=aaaa-bbbb-cccc-dddd-eeee + &code=${code} +``` + +3、返回的结果中将包含 `id_token` 字段: +``` js +{ + "code": 200, + "msg": "ok", + "data": null, + "token_type": "bearer", + "access_token": "WdpjZdGlXdOzsAcr7gqPwmLVInHrhpznQa2pDOVqZmLXQynBflkcWqE6f5o2", + "refresh_token": "hKHwBm3eH6iqSHlXRGWQaziV8OoyHvzmUb97lKEEZnZJLt3NunBFx7rVZWbT", + "expires_in": 7199, + "refresh_expires_in": 2591999, + "client_id": "1001", + "scope": "oidc", + "id_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vc2Etb2F1dGgtc2VydmVyLmNvbTo4MDAwIiwic3ViIjoiMTAwMDEiLCJhdWQiOiIxMDAxIiwiZXhwIjoxNzI0NDI1OTg5LCJpYXQiOjE3MjQ0MjUzODksImF1dGhfdGltZSI6MTcyNDQwMDUyNiwibm9uY2UiOiJLTHlNR08zZ1R0YVdhMEFRcHF0RUNpTk9SWkY1QkhvRCIsImF6cCI6IjEwMDEifQ.gP3UYMexaQ9v0huKUuqhV9-dPxPpaEuFPIlPb2UZaOI" +} +``` + +4、解析 `id_token` 将得到以下载荷 +``` js +{ + "iss": "http://sa-oauth-server.com:8000", // 签发人 + "sub": "10001", // userId + "aud": "1001", // clientId + "exp": 1724425989, // 令牌到期时间,10位时间戳 + "iat": 1724425389, // 签发此令牌的时间,10位时间戳 + "auth_time": 1724400526, // 用户认证时间,10位时间戳 + "nonce": "KLyMGO3gTtaWa0AQpqtECiNORZF5BHoD", // 随机数,防止重放攻击 + "azp": "1001" // clientId +} +``` + +如果默认携带的载荷无法满足你的业务需求,你还可以自定义追加扩展字段,让 `id_token` 返回更多信息 + + +### 3、扩展 id_token 载荷 + +新建 `CustomOidcScopeHandler` 集成 `OidcScopeHandler`,扩展 OIDC 权限处理器,返回更多字段: +``` java +/** + * 扩展 OIDC 权限处理器,返回更多字段 + */ +@Component +public class CustomOidcScopeHandler extends OidcScopeHandler { + + @Override + public IdTokenModel workExtraData(IdTokenModel idToken) { + Object userId = idToken.sub; + System.out.println("----- 为 idToken 追加扩展字段 ----- "); + + idToken.extraData.put("uid", userId); // 用户id + idToken.extraData.put("nickname", "lin_xiao_lin"); // 昵称 + idToken.extraData.put("picture", "https://sa-token.cc/logo.png"); // 头像 + idToken.extraData.put("email", "456456@xx.com"); // 邮箱 + idToken.extraData.put("phone_number", "13144556677"); // 手机号 + // 更多字段 ... + // 可参考:https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims + + return idToken; + } + +} +``` + +重启项目,再次请求授权,返回的 `id_token` 载荷将包含更多字段: + +``` js +{ + "iss": "http://sa-oauth-server.com:8000", + "sub": "10001", + "aud": "1001", + "exp": 1724430149, + "iat": 1724429549, + "auth_time": 1724400526, + "nonce": "SBRLOcfeo9FFmLTB8OINvuulam5FMOre", + "azp": "1001", + "uid": "10001", + "nickname": "lin_xiao_lin", + "picture": "https://sa-token.cc/logo.png", + "email": "456456@xx.com", + "phone_number": "13144556677" +} +``` + + diff --git a/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/SaJwtTemplate.java b/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/SaJwtTemplate.java index 90ad98ef..1f828409 100644 --- a/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/SaJwtTemplate.java +++ b/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/SaJwtTemplate.java @@ -309,4 +309,24 @@ public class SaJwtTemplate { return (effTime - System.currentTimeMillis()) / 1000; } + + + // -------------- 其它方法 + + /** + * 创建 jwt (Map 参数方式) + * + * @param map 扩展数据 + * @param keyt 秘钥 + * @return jwt-token + */ + public String createToken(Map map, String keyt) { + // 创建 + JWT jwt = JWT.create().addPayloads(map); + + // 返回 + return generateToken(jwt, keyt); + } + + } diff --git a/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/SaJwtUtil.java b/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/SaJwtUtil.java index 3661b614..42c4b541 100644 --- a/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/SaJwtUtil.java +++ b/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/SaJwtUtil.java @@ -15,11 +15,11 @@ */ package cn.dev33.satoken.jwt; -import java.util.Map; - import cn.hutool.json.JSONObject; import cn.hutool.jwt.JWT; +import java.util.Map; + /** * jwt 操作工具类封装 * @@ -195,4 +195,17 @@ public class SaJwtUtil { return saJwtTemplate.getTimeout(token, loginType, keyt); } + + // -------------- 其它方法 + + /** + * 创建 jwt (Map 参数方式) + * + * @param map 扩展数据 + * @param keyt 秘钥 + * @return jwt-token + */ + public static String createToken(Map map, String keyt) { + return saJwtTemplate.createToken(map, keyt); + } } diff --git a/sa-token-plugin/sa-token-oauth2/pom.xml b/sa-token-plugin/sa-token-oauth2/pom.xml index f8717b99..f3f5bee9 100644 --- a/sa-token-plugin/sa-token-oauth2/pom.xml +++ b/sa-token-plugin/sa-token-oauth2/pom.xml @@ -22,6 +22,12 @@ cn.dev33 sa-token-core + + + cn.dev33 + sa-token-jwt + true + diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/SaOAuth2Manager.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/SaOAuth2Manager.java index 8ba535b8..270da526 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/SaOAuth2Manager.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/SaOAuth2Manager.java @@ -41,20 +41,20 @@ public class SaOAuth2Manager { /** * OAuth2 配置 Bean */ - private static volatile SaOAuth2ServerConfig config; - public static SaOAuth2ServerConfig getConfig() { - if (config == null) { + private static volatile SaOAuth2ServerConfig serverConfig; + public static SaOAuth2ServerConfig getServerConfig() { + if (serverConfig == null) { // 初始化默认值 synchronized (SaOAuth2Manager.class) { - if (config == null) { - setConfig(new SaOAuth2ServerConfig()); + if (serverConfig == null) { + setServerConfig(new SaOAuth2ServerConfig()); } } } - return config; + return serverConfig; } - public static void setConfig(SaOAuth2ServerConfig config) { - SaOAuth2Manager.config = config; + public static void setServerConfig(SaOAuth2ServerConfig serverConfig) { + SaOAuth2Manager.serverConfig = serverConfig; } /** diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/config/SaOAuth2OidcConfig.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/config/SaOAuth2OidcConfig.java new file mode 100644 index 00000000..2eb817d4 --- /dev/null +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/config/SaOAuth2OidcConfig.java @@ -0,0 +1,62 @@ +/* + * 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.config; + +import java.io.Serializable; + +/** + * Sa-Token OAuth2 Server 端 Oidc 配置类 Model + * + * @author click33 + * @since 1.39.0 + */ +public class SaOAuth2OidcConfig implements Serializable { + + private static final long serialVersionUID = -6541180061782004705L; + + /** iss 值,如不配置则自动计算 */ + public String iss; + + /** idToken 有效期(单位秒) 默认十分钟 */ + public long idTokenTimeout = 60 * 10; + + public String getIss() { + return iss; + } + + public SaOAuth2OidcConfig setIss(String iss) { + this.iss = iss; + return this; + } + + public long getIdTokenTimeout() { + return idTokenTimeout; + } + + public SaOAuth2OidcConfig setIdTokenTimeout(long idTokenTimeout) { + this.idTokenTimeout = idTokenTimeout; + return this; + } + + @Override + public String toString() { + return "SaOAuth2OidcConfig{" + + "iss='" + iss + '\'' + + ", idTokenTimeout=" + idTokenTimeout + + '}'; + } + +} 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 1ae2e05f..dbb2e74f 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 @@ -72,6 +72,11 @@ public class SaOAuth2ServerConfig implements Serializable { /** 指定低级权限,多个用逗号隔开 */ public String lowerScope; + /** + * oidc 相关配置 + */ + SaOAuth2OidcConfig oidc = new SaOAuth2OidcConfig(); + /** * @return enableCode */ @@ -287,6 +292,26 @@ public class SaOAuth2ServerConfig implements Serializable { return this; } + /** + * 获取 oidc 相关配置 + * + * @return oidc 相关配置 + */ + public SaOAuth2OidcConfig getOidc() { + return this.oidc; + } + + /** + * 设置 oidc 相关配置 + * + * @param oidc / + * @return / + */ + public SaOAuth2ServerConfig setOidc(SaOAuth2OidcConfig oidc) { + this.oidc = oidc; + return this; + } + // -------------------- SaOAuth2Handle 所有回调函数 -------------------- @@ -321,6 +346,7 @@ public class SaOAuth2ServerConfig implements Serializable { ", openidDigestPrefix='" + openidDigestPrefix + ", higherScope='" + higherScope + ", lowerScope='" + lowerScope + + ", oidc='" + oidc + '}'; } } diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/dao/SaOAuth2Dao.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/dao/SaOAuth2Dao.java index 209f8a55..801e22cf 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/dao/SaOAuth2Dao.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/dao/SaOAuth2Dao.java @@ -45,7 +45,7 @@ public interface SaOAuth2Dao { if(c == null) { return; } - getSaTokenDao().setObject(splicingCodeSaveKey(c.code), c, SaOAuth2Manager.getConfig().getCodeTimeout()); + getSaTokenDao().setObject(splicingCodeSaveKey(c.code), c, SaOAuth2Manager.getServerConfig().getCodeTimeout()); } /** @@ -56,7 +56,7 @@ public interface SaOAuth2Dao { if(c == null) { return; } - getSaTokenDao().set(splicingCodeIndexKey(c.clientId, c.loginId), c.code, SaOAuth2Manager.getConfig().getCodeTimeout()); + getSaTokenDao().set(splicingCodeIndexKey(c.clientId, c.loginId), c.code, SaOAuth2Manager.getServerConfig().getCodeTimeout()); } /** @@ -151,7 +151,7 @@ public interface SaOAuth2Dao { default void saveGrantScope(String clientId, Object loginId, List scopes) { if( ! SaFoxUtil.isEmpty(scopes)) { // TODO ttl 计算规则优化 - long ttl = SaOAuth2Manager.getConfig().getAccessTokenTimeout(); + long ttl = SaOAuth2Manager.getServerConfig().getAccessTokenTimeout(); // long ttl = checkClientModel(clientId).getAccessTokenTimeout(); String value = SaOAuth2Manager.getDataConverter().convertScopeListToString(scopes); getSaTokenDao().set(splicingGrantScopeKey(clientId, loginId), value, ttl); 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 df6dece3..c8f93ef6 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 @@ -60,7 +60,7 @@ public interface SaOAuth2DataLoader { * @return 此账号在此Client下的openid */ default String getOpenid(String clientId, Object loginId) { - return SaSecureUtil.md5(SaOAuth2Manager.getConfig().getOpenidDigestPrefix() + "_" + clientId + "_" + loginId); + return SaSecureUtil.md5(SaOAuth2Manager.getServerConfig().getOpenidDigestPrefix() + "_" + clientId + "_" + 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 e6ae542d..46e64344 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 @@ -75,7 +75,7 @@ public class SaClientModel implements Serializable { public SaClientModel() { - SaOAuth2ServerConfig config = SaOAuth2Manager.getConfig(); + SaOAuth2ServerConfig config = SaOAuth2Manager.getServerConfig(); this.isNewRefresh = config.getIsNewRefresh(); this.accessTokenTimeout = config.getAccessTokenTimeout(); this.refreshTokenTimeout = config.getRefreshTokenTimeout(); diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/model/oidc/IdTokenModel.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/model/oidc/IdTokenModel.java new file mode 100644 index 00000000..7e9783e0 --- /dev/null +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/model/oidc/IdTokenModel.java @@ -0,0 +1,90 @@ +/* + * 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.data.model.oidc; + +import java.io.Serializable; +import java.util.Map; + +/** + * OIDC IdToken Model + * + *
参考: + *
IDToken + *
StandardClaims + * + * @author click33 + * @since 1.23.0 + */ +public class IdTokenModel implements Serializable { + + private static final long serialVersionUID = -6541180061782004705L; + + /** + * 必填:发行者标识符,例如:https://server.example.com + */ + public String iss; + + /** + * 必填:用户标识符,用户id,例如:10001 + */ + public Object sub; + + /** + * 必填:客户端标识符,clientId,例如:s6BhdRkqt3 + */ + public String aud; + + /** + * 必填:令牌到期时间,10位时间戳,例如:1723341795 + */ + public long exp; + + /** + * 必填:签发此令牌的时间,10位时间戳,例如:1723339995 + */ + public long iat; + + /** + * 用户认证时间,10位时间戳,例如:1723339988 + */ + public long authTime; + + /** + * 随机数,客户端提供,防止重放攻击,例如:e9a3f4d9 + */ + public String nonce; + + /** + * 身份验证上下文类引用 + */ + public String acr; + + /** + * 身份验证方法参考 + */ + public String amr; + + /** + * 授权方 - 签发 ID 令牌的一方,如果存在,它必须包含此方的 OAuth 2.0 客户端 ID。 + */ + public String azp; + + /** + * 扩展数据 + */ + public Map extraData; + +} diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2Exception.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2Exception.java index 62803e27..c1b96496 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2Exception.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2Exception.java @@ -29,7 +29,15 @@ public class SaOAuth2Exception extends SaTokenException { * 序列化版本号 */ private static final long serialVersionUID = 6806129545290130114L; - + + /** + * 一个异常:代表OAuth2认证流程错误 + * @param cause 根异常原因 + */ + public SaOAuth2Exception(Throwable cause) { + super(cause); + } + /** * 一个异常:代表OAuth2认证流程错误 * @param message 异常描述 diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/granttype/handler/PasswordGrantTypeHandler.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/granttype/handler/PasswordGrantTypeHandler.java index 1609e355..e5615e83 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/granttype/handler/PasswordGrantTypeHandler.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/granttype/handler/PasswordGrantTypeHandler.java @@ -70,7 +70,7 @@ public class PasswordGrantTypeHandler implements SaOAuth2GrantTypeHandlerInterfa * @param password / */ public void loginByUsernamePassword(String username, String password) { - SaOAuth2Manager.getConfig().doLoginHandle.apply(username, password); + SaOAuth2Manager.getServerConfig().doLoginHandle.apply(username, password); } } \ No newline at end of file diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/processor/SaOAuth2ServerProcessor.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/processor/SaOAuth2ServerProcessor.java index ed8cc38b..ce05abc9 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/processor/SaOAuth2ServerProcessor.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/processor/SaOAuth2ServerProcessor.java @@ -113,7 +113,7 @@ public class SaOAuth2ServerProcessor { // 获取变量 SaRequest req = SaHolder.getRequest(); SaResponse res = SaHolder.getResponse(); - SaOAuth2ServerConfig cfg = SaOAuth2Manager.getConfig(); + SaOAuth2ServerConfig cfg = SaOAuth2Manager.getServerConfig(); SaOAuth2DataGenerate dataGenerate = SaOAuth2Manager.getDataGenerate(); SaOAuth2Template oauth2Template = SaOAuth2Manager.getTemplate(); String responseType = req.getParamNotNull(Param.response_type); @@ -218,7 +218,7 @@ public class SaOAuth2ServerProcessor { public Object doLogin() { // 获取变量 SaRequest req = SaHolder.getRequest(); - SaOAuth2ServerConfig cfg = SaOAuth2Manager.getConfig(); + SaOAuth2ServerConfig cfg = SaOAuth2Manager.getServerConfig(); return cfg.doLoginHandle.apply(req.getParam(Param.name), req.getParam(Param.pwd)); } @@ -285,7 +285,7 @@ public class SaOAuth2ServerProcessor { public Object clientToken() { // 获取变量 SaRequest req = SaHolder.getRequest(); - SaOAuth2ServerConfig cfg = SaOAuth2Manager.getConfig(); + SaOAuth2ServerConfig cfg = SaOAuth2Manager.getServerConfig(); SaOAuth2Template oauth2Template = SaOAuth2Manager.getTemplate(); String grantType = req.getParamNotNull(Param.grant_type); diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/scope/handler/OidcScopeHandler.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/scope/handler/OidcScopeHandler.java index 7adb42d6..97c70d1d 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/scope/handler/OidcScopeHandler.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/scope/handler/OidcScopeHandler.java @@ -15,9 +15,25 @@ */ package cn.dev33.satoken.oauth2.scope.handler; +import cn.dev33.satoken.SaManager; +import cn.dev33.satoken.context.SaHolder; +import cn.dev33.satoken.context.model.SaRequest; +import cn.dev33.satoken.jwt.SaJwtUtil; +import cn.dev33.satoken.jwt.error.SaJwtErrorCode; +import cn.dev33.satoken.jwt.exception.SaJwtException; +import cn.dev33.satoken.oauth2.SaOAuth2Manager; import cn.dev33.satoken.oauth2.data.model.AccessTokenModel; import cn.dev33.satoken.oauth2.data.model.ClientTokenModel; +import cn.dev33.satoken.oauth2.data.model.oidc.IdTokenModel; +import cn.dev33.satoken.oauth2.data.model.request.ClientIdAndSecretModel; +import cn.dev33.satoken.oauth2.exception.SaOAuth2Exception; import cn.dev33.satoken.oauth2.scope.CommonScope; +import cn.dev33.satoken.util.SaFoxUtil; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.LinkedHashMap; +import java.util.Map; /** * id_token 权限处理器:在 AccessToken 扩展参数中追加 id_token 字段 @@ -33,7 +49,31 @@ public class OidcScopeHandler implements SaOAuth2ScopeHandlerInterface { @Override public void workAccessToken(AccessTokenModel at) { - // TODO 追加参数 id_token + SaRequest req = SaHolder.getRequest(); + ClientIdAndSecretModel client = SaOAuth2Manager.getDataResolver().readClientIdAndSecret(req); + + // 基础参数 + IdTokenModel idToken = new IdTokenModel(); + idToken.iss = getIss(); + idToken.sub = at.loginId; + idToken.aud = client.clientId; + idToken.iat = System.currentTimeMillis() / 1000; + idToken.exp = idToken.iat + SaOAuth2Manager.getServerConfig().getOidc().getIdTokenTimeout(); + idToken.authTime = SaOAuth2Manager.getStpLogic().getSessionByLoginId(at.loginId).getCreateTime() / 1000; + idToken.nonce = getNonce(); + idToken.acr = null; + idToken.amr = null; + idToken.azp = client.clientId; + + // 额外参数 + idToken.extraData = new LinkedHashMap<>(); + idToken = workExtraData(idToken); + + // 构建 jwtIdToken + String jwtIdToken = generateJwtIdToken(idToken); + + // 放入 AccessTokenModel + at.extraData.put("id_token", jwtIdToken); } @Override @@ -41,4 +81,84 @@ public class OidcScopeHandler implements SaOAuth2ScopeHandlerInterface { } + /** + * 获取 iss + * @return / + */ + public String getIss() { + String urlString = SaHolder.getRequest().getUrl(); + try { + URL url = new URL(urlString); + String iss = url.getProtocol() + "://" + url.getHost(); + if(url.getPort() != -1) { + iss += ":" + url.getPort(); + } + return iss; + } catch (MalformedURLException e) { + throw new SaOAuth2Exception(e); + } + } + + /** + * 获取 nonce + * @return / + */ + public String getNonce() { + String nonce = SaHolder.getRequest().getParam("nonce"); + if(SaFoxUtil.isEmpty(nonce)) { + nonce = SaFoxUtil.getRandomString(32); + } + SaManager.getSaSignTemplate().checkNonce(nonce); + return nonce; + } + + /** + * 加工 IdTokenModel + * @return / + */ + public IdTokenModel workExtraData(IdTokenModel idToken) { + // + return idToken; + } + + /** + * 将 IdTokenModel 转化为 Map 数据 + * @return / + */ + public Map convertIdTokenToMap(IdTokenModel idToken) { + // 基础参数 + Map map = new LinkedHashMap<>(); + map.put("iss", idToken.iss); + map.put("sub", idToken.sub); + map.put("aud", idToken.aud); + map.put("exp", idToken.exp); + map.put("iat", idToken.iat); + map.put("auth_time", idToken.authTime); + map.put("nonce", idToken.nonce); + map.put("acr", idToken.acr); + map.put("amr", idToken.amr); + map.put("azp", idToken.azp); + + // 移除 null 值 + idToken.extraData.entrySet().removeIf(entry -> entry.getValue() == null); + + // 扩展参数 + map.putAll(idToken.extraData); + + // 返回 + return map; + } + + /** + * 生成 jwt 格式的 id_token + * @param idToken / + * @return / + */ + public String generateJwtIdToken(IdTokenModel idToken) { + Map dataMap = convertIdTokenToMap(idToken); + String keyt = SaOAuth2Manager.getStpLogic().getConfigOrGlobal().getJwtSecretKey(); + SaJwtException.throwByNull(keyt, "请配置jwt秘钥", SaJwtErrorCode.CODE_30205); + return SaJwtUtil.createToken(dataMap, keyt); + } + } \ 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 dbfda655..939b9ff9 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 @@ -170,7 +170,7 @@ public final class SaOAuth2Strategy { } // 看看全局是否开启了此 grantType - SaOAuth2ServerConfig config = SaOAuth2Manager.getConfig(); + SaOAuth2ServerConfig config = SaOAuth2Manager.getServerConfig(); if(grantType.equals(GrantType.authorization_code) && !config.getEnableAuthorizationCode() ) { throw new SaOAuth2Exception("系统未开放的 grant_type: " + grantType); } diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java index cbfeedbb..ea5d33ca 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java @@ -401,7 +401,7 @@ public class SaOAuth2Template { * @return / */ public List getHigherScopeList() { - String higherScope = SaOAuth2Manager.getConfig().getHigherScope(); + String higherScope = SaOAuth2Manager.getServerConfig().getHigherScope(); return SaOAuth2Manager.getDataConverter().convertScopeStringToList(higherScope); } @@ -410,7 +410,7 @@ public class SaOAuth2Template { * @return / */ public List getLowerScopeList() { - String lowerScope = SaOAuth2Manager.getConfig().getLowerScope(); + String lowerScope = SaOAuth2Manager.getServerConfig().getLowerScope(); return SaOAuth2Manager.getDataConverter().convertScopeStringToList(lowerScope); } diff --git a/sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/oauth2/SaOAuth2AutoConfigure.java b/sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/oauth2/SaOAuth2AutoConfigure.java index dab18067..d0d7ee45 100644 --- a/sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/oauth2/SaOAuth2AutoConfigure.java +++ b/sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/oauth2/SaOAuth2AutoConfigure.java @@ -18,7 +18,6 @@ package cn.dev33.satoken.solon.oauth2; import cn.dev33.satoken.oauth2.SaOAuth2Manager; import cn.dev33.satoken.oauth2.config.SaOAuth2ServerConfig; import cn.dev33.satoken.oauth2.template.SaOAuth2Template; -import cn.dev33.satoken.oauth2.template.SaOAuth2Util; import org.noear.solon.annotation.Bean; import org.noear.solon.annotation.Condition; import org.noear.solon.annotation.Configuration; @@ -36,11 +35,11 @@ public class SaOAuth2AutoConfigure { @Bean public void init(AppContext appContext) throws Throwable { appContext.subBeansOfType(SaOAuth2Template.class, bean -> { - SaOAuth2Util.saOAuth2Template = bean; + SaOAuth2Manager.setTemplate(bean); }); appContext.subBeansOfType(SaOAuth2ServerConfig.class, bean -> { - SaOAuth2Manager.setConfig(bean); + SaOAuth2Manager.setServerConfig(bean); }); } @@ -48,7 +47,7 @@ public class SaOAuth2AutoConfigure { * 获取 OAuth2配置Bean */ @Bean - public SaOAuth2ServerConfig getConfig(@Inject(value = "${sa-token.oauth2}", required = false) SaOAuth2ServerConfig oAuth2Config) { + public SaOAuth2ServerConfig getConfig(@Inject(value = "${sa-token.oauth2-server}", required = false) SaOAuth2ServerConfig oAuth2Config) { return oAuth2Config; } } \ No newline at end of file diff --git a/sa-token-starter/sa-token-spring-boot-autoconfig/src/main/java/cn/dev33/satoken/spring/oauth2/SaOAuth2BeanInject.java b/sa-token-starter/sa-token-spring-boot-autoconfig/src/main/java/cn/dev33/satoken/spring/oauth2/SaOAuth2BeanInject.java index 092d7956..03748859 100644 --- a/sa-token-starter/sa-token-spring-boot-autoconfig/src/main/java/cn/dev33/satoken/spring/oauth2/SaOAuth2BeanInject.java +++ b/sa-token-starter/sa-token-spring-boot-autoconfig/src/main/java/cn/dev33/satoken/spring/oauth2/SaOAuth2BeanInject.java @@ -53,7 +53,7 @@ public class SaOAuth2BeanInject { */ @Autowired(required = false) public void setSaOAuth2Config(SaOAuth2ServerConfig saOAuth2Config) { - SaOAuth2Manager.setConfig(saOAuth2Config); + SaOAuth2Manager.setServerConfig(saOAuth2Config); } /** -- Gitee From 2d13e908b1016890817f802bc71de2a96ded6287 Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Sat, 24 Aug 2024 03:46:03 +0800 Subject: [PATCH 146/172] =?UTF-8?q?access=5Ftoken=20=E8=AF=BB=E5=8F=96?= =?UTF-8?q?=E5=85=BC=E5=AE=B9=20Bearer=20Token=20=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/pj/SaOAuth2ServerApplication.java | 4 +-- .../pj/oauth2/SaOAuth2ServerController.java | 5 +-- sa-token-doc/oauth2/readme.md | 5 +-- .../satoken/oauth2/consts/SaOAuth2Consts.java | 1 + .../data/resolver/SaOAuth2DataResolver.java | 21 ++++++------- .../SaOAuth2DataResolverDefaultImpl.java | 31 +++++++++++++++++-- 6 files changed, 46 insertions(+), 21 deletions(-) diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/SaOAuth2ServerApplication.java b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/SaOAuth2ServerApplication.java index d8c7a741..33cadea4 100644 --- a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/SaOAuth2ServerApplication.java +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/SaOAuth2ServerApplication.java @@ -4,8 +4,6 @@ import cn.dev33.satoken.oauth2.SaOAuth2Manager; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import java.net.MalformedURLException; - /** * 启动:Sa-OAuth2 Server端 * @author click33 @@ -13,7 +11,7 @@ import java.net.MalformedURLException; @SpringBootApplication public class SaOAuth2ServerApplication { - public static void main(String[] args) throws MalformedURLException { + public static void main(String[] args) { SpringApplication.run(SaOAuth2ServerApplication.class, args); System.out.println("\nSa-Token-OAuth2 Server端启动成功,配置如下:"); System.out.println(SaOAuth2Manager.getServerConfig()); diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java index bc473a8a..929bdd4f 100644 --- a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java @@ -1,6 +1,7 @@ package com.pj.oauth2; import cn.dev33.satoken.context.SaHolder; +import cn.dev33.satoken.oauth2.SaOAuth2Manager; import cn.dev33.satoken.oauth2.config.SaOAuth2ServerConfig; import cn.dev33.satoken.oauth2.processor.SaOAuth2ServerProcessor; import cn.dev33.satoken.oauth2.template.SaOAuth2Util; @@ -8,7 +9,6 @@ import cn.dev33.satoken.stp.StpUtil; import cn.dev33.satoken.util.SaResult; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.ModelAndView; @@ -63,8 +63,9 @@ public class SaOAuth2ServerController { // 获取 userinfo 信息:昵称、头像、性别等等 @RequestMapping("/oauth2/userinfo") - public SaResult userinfo(@RequestParam("access_token") String accessToken) { + public SaResult userinfo() { // 获取 Access-Token 对应的账号id + String accessToken = SaOAuth2Manager.getDataResolver().readAccessToken(SaHolder.getRequest()); Object loginId = SaOAuth2Util.getLoginIdByAccessToken(accessToken); System.out.println("-------- 此Access-Token对应的账号id: " + loginId); diff --git a/sa-token-doc/oauth2/readme.md b/sa-token-doc/oauth2/readme.md index 5776fb42..9a34cb52 100644 --- a/sa-token-doc/oauth2/readme.md +++ b/sa-token-doc/oauth2/readme.md @@ -40,8 +40,9 @@ 2. oauth2-client 第三方公司端 1. 第三方公司登录 oauth-server 数据前台申请端,申请注册应用,拿到 `clientId`、`clientSecret` 等数据。 - 2. 在自己系统通过 `clientId`、`clientSecret` 等参数对接 oauth2-server 授权端,拿到 `access_token`。 - 3. 通过 `access_token` 调用 oauth2-server 资源端接口,拿到对应资源数据。 + 2. 根据自己的业务选择对应的 scope 申请签约,等待平台端审核通过。 + 3. 在自己系统通过 `clientId`、`clientSecret` 等参数对接 oauth2-server 授权端,拿到 `access_token`。 + 4. 通过 `access_token` 调用 oauth2-server 资源端接口,拿到对应资源数据。 3. 用户端操作 1. 打开第三方公司开发的网站或APP等程序。 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 d2ab4bb8..bdb8c650 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 @@ -58,6 +58,7 @@ public class SaOAuth2Consts { public static String name = "name"; public static String pwd = "pwd"; public static String build_redirect_uri = "build_redirect_uri"; + public static String Authorization = "Authorization"; } /** diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/resolver/SaOAuth2DataResolver.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/resolver/SaOAuth2DataResolver.java index 4dd957b9..5ea2b7dd 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/resolver/SaOAuth2DataResolver.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/resolver/SaOAuth2DataResolver.java @@ -18,8 +18,8 @@ package cn.dev33.satoken.oauth2.data.resolver; import cn.dev33.satoken.context.model.SaRequest; import cn.dev33.satoken.oauth2.data.model.AccessTokenModel; import cn.dev33.satoken.oauth2.data.model.ClientTokenModel; -import cn.dev33.satoken.oauth2.data.model.request.RequestAuthModel; import cn.dev33.satoken.oauth2.data.model.request.ClientIdAndSecretModel; +import cn.dev33.satoken.oauth2.data.model.request.RequestAuthModel; import cn.dev33.satoken.util.SaResult; import java.util.Map; @@ -42,6 +42,14 @@ public interface SaOAuth2DataResolver { */ ClientIdAndSecretModel readClientIdAndSecret(SaRequest request); + /** + * 数据读取:从请求对象中读取 AccessToken + * + * @param request / + * @return / + */ + String readAccessToken(SaRequest request); + /** * 数据读取:从请求对象中构建 RequestAuthModel * @param req SaRequest对象 @@ -75,21 +83,10 @@ public interface SaOAuth2DataResolver { return SaResult.ok(); } - /** - * 构建返回值: password 模式认证 获取 token - * @param at token信息 - * @return / - */ - default Map buildPasswordReturnValue(AccessTokenModel at) { - return buildTokenReturnValue(at); - } - /** * 构建返回值: 凭证式 模式认证 获取 token * @param ct token信息 */ Map buildClientTokenReturnValue(ClientTokenModel ct); - - } diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/resolver/SaOAuth2DataResolverDefaultImpl.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/resolver/SaOAuth2DataResolverDefaultImpl.java index 339ac51a..7ccc9fe2 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/resolver/SaOAuth2DataResolverDefaultImpl.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/resolver/SaOAuth2DataResolverDefaultImpl.java @@ -22,8 +22,8 @@ import cn.dev33.satoken.oauth2.consts.SaOAuth2Consts; import cn.dev33.satoken.oauth2.consts.SaOAuth2Consts.TokenType; import cn.dev33.satoken.oauth2.data.model.AccessTokenModel; import cn.dev33.satoken.oauth2.data.model.ClientTokenModel; -import cn.dev33.satoken.oauth2.data.model.request.RequestAuthModel; import cn.dev33.satoken.oauth2.data.model.request.ClientIdAndSecretModel; +import cn.dev33.satoken.oauth2.data.model.request.RequestAuthModel; import cn.dev33.satoken.oauth2.exception.SaOAuth2Exception; import cn.dev33.satoken.util.SaFoxUtil; import cn.dev33.satoken.util.SaResult; @@ -56,7 +56,7 @@ public class SaOAuth2DataResolverDefaultImpl implements SaOAuth2DataResolver { return new ClientIdAndSecretModel(clientId, clientSecret); } - // 如果请求参数中没有提供 client_id 参数,则尝试从 base auth 中获取 + // 如果请求参数中没有提供 client_id 参数,则尝试从 Authorization 中获取 String authorizationValue = SaHttpBasicUtil.getAuthorizationValue(); if(SaFoxUtil.isNotEmpty(authorizationValue)) { String[] arr = authorizationValue.split(":"); @@ -71,6 +71,33 @@ public class SaOAuth2DataResolverDefaultImpl implements SaOAuth2DataResolver { throw new SaOAuth2Exception("请提供 client 信息"); } + /** + * 数据读取:从请求对象中读取 AccessToken + */ + @Override + public String readAccessToken(SaRequest request) { + // 优先从请求参数中获取 + String accessToken = request.getParam(SaOAuth2Consts.Param.access_token); + if(SaFoxUtil.isNotEmpty(accessToken)) { + return accessToken; + } + + // 如果请求参数中没有提供 access_token 参数,则尝试从 Authorization 中获取 + String authorizationValue = request.getHeader(SaOAuth2Consts.Param.Authorization); + if(SaFoxUtil.isEmpty(authorizationValue)) { + return null; + } + + // 判断前缀,裁剪 + String prefix = TokenType.Bearer + " "; + if(authorizationValue.startsWith(prefix)) { + return authorizationValue.substring(prefix.length()); + } + + // 前缀不符合,返回 null + return null; + } + /** * 数据读取:从请求对象中构建 RequestAuthModel */ -- Gitee From 06b06cdb5e5411fd65a93b1fd6002b0d79a5d74b Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Sat, 24 Aug 2024 04:14:12 +0800 Subject: [PATCH 147/172] =?UTF-8?q?TokenType=20=E6=8C=87=E5=AE=9A=E6=96=B9?= =?UTF-8?q?=E5=BC=8F=E7=BB=86=E8=8A=82=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sa-token-doc/oauth2/oauth2-apidoc.md | 5 ++++ sa-token-doc/oauth2/oauth2-custom-scope.md | 3 +- .../SaOAuth2DataConverterDefaultImpl.java | 3 ++ .../SaOAuth2DataGenerateDefaultImpl.java | 2 ++ .../oauth2/data/model/AccessTokenModel.java | 28 +++++++++++++++++-- .../oauth2/data/model/ClientTokenModel.java | 19 +++++++++++-- .../SaOAuth2DataResolverDefaultImpl.java | 3 +- .../processor/SaOAuth2ServerProcessor.java | 3 +- 8 files changed, 57 insertions(+), 9 deletions(-) diff --git a/sa-token-doc/oauth2/oauth2-apidoc.md b/sa-token-doc/oauth2/oauth2-apidoc.md index a1b7b10f..6699de53 100644 --- a/sa-token-doc/oauth2/oauth2-apidoc.md +++ b/sa-token-doc/oauth2/oauth2-apidoc.md @@ -233,6 +233,11 @@ http://{host}:{port}/oauth2/userinfo?access_token={access_token} } ``` +除了直接在 url 中以 query 参数方式提交 `access_token`,你也可以在 `Authorization` 请求头以 `Bearer Token` 方式提交: +``` js +header['Authorization'] = 'Bearer access_token'; +``` + ## 2、模式二:隐藏式(Implicit) diff --git a/sa-token-doc/oauth2/oauth2-custom-scope.md b/sa-token-doc/oauth2/oauth2-custom-scope.md index 58207223..a857913d 100644 --- a/sa-token-doc/oauth2/oauth2-custom-scope.md +++ b/sa-token-doc/oauth2/oauth2-custom-scope.md @@ -20,8 +20,9 @@ sa-token-oauth2 提供两种模式,让 access_token 可以得到更多信息 ``` java // 获取 userinfo 信息:昵称、头像、性别等等 @RequestMapping("/oauth2/userinfo") -public SaResult userinfo(@RequestParam("access_token") String accessToken) { +public SaResult userinfo() { // 获取 Access-Token 对应的账号id + String accessToken = SaOAuth2Manager.getDataResolver().readAccessToken(SaHolder.getRequest()); Object loginId = SaOAuth2Util.getLoginIdByAccessToken(accessToken); System.out.println("-------- 此Access-Token对应的账号id: " + loginId); diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/convert/SaOAuth2DataConverterDefaultImpl.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/convert/SaOAuth2DataConverterDefaultImpl.java index 1c8865f5..03073085 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/convert/SaOAuth2DataConverterDefaultImpl.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/convert/SaOAuth2DataConverterDefaultImpl.java @@ -16,6 +16,7 @@ package cn.dev33.satoken.oauth2.data.convert; import cn.dev33.satoken.oauth2.SaOAuth2Manager; +import cn.dev33.satoken.oauth2.consts.SaOAuth2Consts; import cn.dev33.satoken.oauth2.data.model.AccessTokenModel; import cn.dev33.satoken.oauth2.data.model.CodeModel; import cn.dev33.satoken.oauth2.data.model.RefreshTokenModel; @@ -78,6 +79,7 @@ public class SaOAuth2DataConverterDefaultImpl implements SaOAuth2DataConverter { at.clientId = cm.clientId; at.loginId = cm.loginId; at.scopes = cm.scopes; + at.tokenType = SaOAuth2Consts.TokenType.bearer; SaClientModel clientModel = SaOAuth2Manager.getDataLoader().getClientModelNotNull(cm.clientId); at.expiresTime = System.currentTimeMillis() + (clientModel.getAccessTokenTimeout() * 1000); at.extraData = new LinkedHashMap<>(); @@ -118,6 +120,7 @@ public class SaOAuth2DataConverterDefaultImpl implements SaOAuth2DataConverter { at.clientId = rt.clientId; at.loginId = rt.loginId; at.scopes = rt.scopes; + at.tokenType = SaOAuth2Consts.TokenType.bearer; at.extraData = new LinkedHashMap<>(rt.extraData); SaClientModel clientModel = SaOAuth2Manager.getDataLoader().getClientModelNotNull(rt.clientId); at.expiresTime = System.currentTimeMillis() + (clientModel.getAccessTokenTimeout() * 1000); diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerateDefaultImpl.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerateDefaultImpl.java index 1ff51e79..0090a2db 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerateDefaultImpl.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerateDefaultImpl.java @@ -166,6 +166,7 @@ public class SaOAuth2DataGenerateDefaultImpl implements SaOAuth2DataGenerate { // 2、生成 新Access-Token String newAtValue = SaOAuth2Strategy.instance.createAccessToken.execute(ra.clientId, ra.loginId, ra.scopes); AccessTokenModel at = new AccessTokenModel(newAtValue, ra.clientId, ra.loginId, ra.scopes); + at.tokenType = SaOAuth2Consts.TokenType.bearer; // 3、根据权限构建额外参数 at.extraData = new LinkedHashMap<>(); @@ -220,6 +221,7 @@ public class SaOAuth2DataGenerateDefaultImpl implements SaOAuth2DataGenerate { // 3、生成新 Client-Token String clientTokenValue = SaOAuth2Strategy.instance.createClientToken.execute(clientId, scopes); ClientTokenModel ct = new ClientTokenModel(clientTokenValue, clientId, scopes); + ct.tokenType = SaOAuth2Consts.TokenType.bearer; ct.expiresTime = System.currentTimeMillis() + (cm.getClientTokenTimeout() * 1000); ct.extraData = new LinkedHashMap<>(); SaOAuth2Strategy.instance.workClientTokenByScope.accept(ct); diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/model/AccessTokenModel.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/model/AccessTokenModel.java index 83325e81..21935d83 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/model/AccessTokenModel.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/model/AccessTokenModel.java @@ -64,6 +64,11 @@ public class AccessTokenModel implements Serializable { */ public List scopes; + /** + * Token 类型 + */ + public String tokenType; + /** * 扩展数据 */ @@ -152,6 +157,15 @@ public class AccessTokenModel implements Serializable { return this; } + public String getTokenType() { + return tokenType; + } + + public AccessTokenModel setTokenType(String tokenType) { + this.tokenType = tokenType; + return this; + } + public Map getExtraData() { return extraData; } @@ -163,9 +177,17 @@ public class AccessTokenModel implements Serializable { @Override public String toString() { - return "AccessTokenModel [accessToken=" + accessToken + ", refreshToken=" + refreshToken - + ", accessTokenTimeout=" + expiresTime + ", refreshTokenTimeout=" + refreshExpiresTime - + ", clientId=" + clientId + ", scopes=" + scopes + ", extraData=" + extraData + "]"; + return "AccessTokenModel{" + + "accessToken='" + accessToken + '\'' + + ", refreshToken='" + refreshToken + '\'' + + ", expiresTime=" + expiresTime + + ", refreshExpiresTime=" + refreshExpiresTime + + ", clientId='" + clientId + '\'' + + ", loginId=" + loginId + + ", scopes=" + scopes + + ", tokenType='" + tokenType + '\'' + + ", extraData=" + extraData + + '}'; } // 追加只读属性 diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/model/ClientTokenModel.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/model/ClientTokenModel.java index 1d9865eb..d31bb1c9 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/model/ClientTokenModel.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/model/ClientTokenModel.java @@ -49,6 +49,11 @@ public class ClientTokenModel implements Serializable { */ public List scopes; + /** + * Token 类型 + */ + public String tokenType; + /** * 扩展数据 */ @@ -91,6 +96,15 @@ public class ClientTokenModel implements Serializable { return this; } + public String getTokenType() { + return tokenType; + } + + public ClientTokenModel setTokenType(String tokenType) { + this.tokenType = tokenType; + return this; + } + public Map getExtraData() { return extraData; } @@ -118,10 +132,11 @@ public class ClientTokenModel implements Serializable { @Override public String toString() { return "ClientTokenModel{" + - "clientToken='" + clientToken + '\'' + + "clientToken='" + clientToken + ", expiresTime=" + expiresTime + - ", clientId='" + clientId + '\'' + + ", clientId='" + clientId + ", scopes=" + scopes + + ", tokenType=" + tokenType + ", extraData=" + extraData + '}'; } diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/resolver/SaOAuth2DataResolverDefaultImpl.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/resolver/SaOAuth2DataResolverDefaultImpl.java index 7ccc9fe2..5693d9aa 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/resolver/SaOAuth2DataResolverDefaultImpl.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/resolver/SaOAuth2DataResolverDefaultImpl.java @@ -122,7 +122,7 @@ public class SaOAuth2DataResolverDefaultImpl implements SaOAuth2DataResolver { @Override public Map buildTokenReturnValue(AccessTokenModel at) { Map map = new LinkedHashMap<>(); - map.put("token_type", TokenType.bearer); + map.put("token_type", at.tokenType); map.put("access_token", at.accessToken); map.put("refresh_token", at.refreshToken); map.put("expires_in", at.getExpiresIn()); @@ -139,6 +139,7 @@ public class SaOAuth2DataResolverDefaultImpl implements SaOAuth2DataResolver { @Override public Map buildClientTokenReturnValue(ClientTokenModel ct) { Map map = new LinkedHashMap<>(); + map.put("token_type", ct.tokenType); map.put("client_token", ct.clientToken); // map.put("access_token", ct.clientToken); // 兼容 OAuth2 协议 map.put("expires_in", ct.getExpiresIn()); diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/processor/SaOAuth2ServerProcessor.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/processor/SaOAuth2ServerProcessor.java index ce05abc9..552c46b4 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/processor/SaOAuth2ServerProcessor.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/processor/SaOAuth2ServerProcessor.java @@ -303,8 +303,7 @@ public class SaOAuth2ServerProcessor { ClientIdAndSecretModel clientIdAndSecret = SaOAuth2Manager.getDataResolver().readClientIdAndSecret(req); String clientId = clientIdAndSecret.clientId; String clientSecret = clientIdAndSecret.clientSecret; - String scope = req.getParam(Param.scope, ""); - List scopes = SaOAuth2Manager.getDataConverter().convertScopeStringToList(scope); + List scopes = SaOAuth2Manager.getDataConverter().convertScopeStringToList(req.getParam(Param.scope)); //校验 ClientScope oauth2Template.checkContract(clientId, scopes); -- Gitee From 760805f78cdd65477039997314eeb0c64d08b9f8 Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Sat, 24 Aug 2024 04:27:29 +0800 Subject: [PATCH 148/172] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20mode4ReturnAccessT?= =?UTF-8?q?oken=20=E9=85=8D=E7=BD=AE=EF=BC=8C=E6=8C=87=E5=AE=9A=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F4=E6=98=AF=E5=90=A6=E8=BF=94=E5=9B=9E=20AccessToken=20?= =?UTF-8?q?=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sa-token-doc/use/config.md | 27 +++++++++++++++++++ .../oauth2/config/SaOAuth2ServerConfig.java | 19 +++++++++++++ .../SaOAuth2DataResolverDefaultImpl.java | 5 +++- 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/sa-token-doc/use/config.md b/sa-token-doc/use/config.md index bc314b06..210175e7 100644 --- a/sa-token-doc/use/config.md +++ b/sa-token-doc/use/config.md @@ -309,6 +309,8 @@ sa-token.sso-client.is-slo=true | openidDigestPrefix | String | openid_default_digest_prefix | 默认 openid 生成算法中使用的摘要前缀 | | higherScope | String | | 指定高级权限,多个用逗号隔开 | | lowerScope | String | | 指定低级权限,多个用逗号隔开 | +| mode4ReturnAccessToken | Boolean | false | 模式4是否返回 AccessToken 字段,用于兼容OAuth2标准协议 | +| oidc | SaOAuth2OidcConfig | new SaOAuth2OidcConfig() | OIDC 相关配置 | 配置示例: @@ -337,6 +339,31 @@ sa-token.oauth2-server.enable-client-credentials=true +##### OIDC 相关配置 +| 参数名称 | 类型 | 默认值 | 说明 | +| :-------- | :-------- | :-------- | :-------- | +| iss | String | | iss 值,如不配置则自动计算 | +| idTokenTimeout | long | 600 | idToken 有效期(单位秒) 默认十分钟 | + + + +``` yaml +# Sa-Token 配置 +sa-token: + oauth2-server: + oidc: + iss: xxx + idTokenTimeout: 600 +``` + +``` properties +sa-token.oauth2-server.oidc.iss=xxx +sa-token.oauth2-server.oidc.idTokenTimeout=600 +``` + + + + ##### SaClientModel属性定义 | 参数名称 | 类型 | 默认值 | 说明 | | :-------- | :-------- | :-------- | :-------- | 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 dbb2e74f..39555dfd 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 @@ -72,6 +72,9 @@ public class SaOAuth2ServerConfig implements Serializable { /** 指定低级权限,多个用逗号隔开 */ public String lowerScope; + /** 模式4是否返回 AccessToken 字段 */ + public Boolean mode4ReturnAccessToken = false; + /** * oidc 相关配置 */ @@ -292,6 +295,21 @@ public class SaOAuth2ServerConfig implements Serializable { return this; } + /** + * @return mode4ReturnAccessToken + */ + public Boolean getMode4ReturnAccessToken() { + return mode4ReturnAccessToken; + } + + /** + * @param mode4ReturnAccessToken 要设置的 mode4ReturnAccessToken + */ + public SaOAuth2ServerConfig setMode4ReturnAccessToken(Boolean mode4ReturnAccessToken) { + this.mode4ReturnAccessToken = mode4ReturnAccessToken; + return this; + } + /** * 获取 oidc 相关配置 * @@ -346,6 +364,7 @@ public class SaOAuth2ServerConfig implements Serializable { ", openidDigestPrefix='" + openidDigestPrefix + ", higherScope='" + higherScope + ", lowerScope='" + lowerScope + + ", mode4ReturnAccessToken='" + mode4ReturnAccessToken + ", oidc='" + oidc + '}'; } diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/resolver/SaOAuth2DataResolverDefaultImpl.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/resolver/SaOAuth2DataResolverDefaultImpl.java index 5693d9aa..9311abb5 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/resolver/SaOAuth2DataResolverDefaultImpl.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/resolver/SaOAuth2DataResolverDefaultImpl.java @@ -141,7 +141,10 @@ public class SaOAuth2DataResolverDefaultImpl implements SaOAuth2DataResolver { Map map = new LinkedHashMap<>(); map.put("token_type", ct.tokenType); map.put("client_token", ct.clientToken); - // map.put("access_token", ct.clientToken); // 兼容 OAuth2 协议 + // 兼容 OAuth2 协议 + if(SaOAuth2Manager.getServerConfig().mode4ReturnAccessToken) { + map.put("access_token", ct.clientToken); + } map.put("expires_in", ct.getExpiresIn()); map.put("client_id", ct.clientId); map.put("scope", SaOAuth2Manager.getDataConverter().convertScopeListToString(ct.scopes)); -- Gitee From 8c008a1aec08ad7a9f22b4fb02eb801ecd8525ff Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Sat, 24 Aug 2024 04:44:14 +0800 Subject: [PATCH 149/172] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20hideStatusField=20?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E9=A1=B9=EF=BC=8C=E7=94=A8=E4=BA=8E=E6=8C=87?= =?UTF-8?q?=E5=AE=9A=E6=98=AF=E5=90=A6=E5=9C=A8=E8=BF=94=E5=9B=9E=E5=80=BC?= =?UTF-8?q?=E4=B8=AD=E9=9A=90=E8=97=8F=E9=BB=98=E8=AE=A4=E7=9A=84=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E5=AD=97=E6=AE=B5=20(code=E3=80=81msg=E3=80=81data)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/pj/SaOAuth2ServerApplication.java | 6 +++--- .../oauth2/config/SaOAuth2ServerConfig.java | 20 +++++++++++++++++++ .../SaOAuth2DataResolverDefaultImpl.java | 15 +++++++++++--- 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/SaOAuth2ServerApplication.java b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/SaOAuth2ServerApplication.java index 33cadea4..e7bef935 100644 --- a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/SaOAuth2ServerApplication.java +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/SaOAuth2ServerApplication.java @@ -5,10 +5,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** - * 启动:Sa-OAuth2 Server端 - * @author click33 + * 启动:Sa-OAuth2 Server端 + * @author click33 */ -@SpringBootApplication +@SpringBootApplication public class SaOAuth2ServerApplication { public static void main(String[] args) { 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 39555dfd..3791c939 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 @@ -75,6 +75,10 @@ public class SaOAuth2ServerConfig implements Serializable { /** 模式4是否返回 AccessToken 字段 */ public Boolean mode4ReturnAccessToken = false; + /** 是否在返回值中隐藏默认的状态字段 (code、msg、data) */ + public Boolean hideStatusField = false; + + /** * oidc 相关配置 */ @@ -310,6 +314,21 @@ public class SaOAuth2ServerConfig implements Serializable { return this; } + /** + * @return hideStatusField + */ + public Boolean getHideStatusField() { + return hideStatusField; + } + + /** + * @param hideStatusField 要设置的 hideStatusField + */ + public SaOAuth2ServerConfig setHideStatusField(Boolean hideStatusField) { + this.hideStatusField = hideStatusField; + return this; + } + /** * 获取 oidc 相关配置 * @@ -365,6 +384,7 @@ public class SaOAuth2ServerConfig implements Serializable { ", higherScope='" + higherScope + ", lowerScope='" + lowerScope + ", mode4ReturnAccessToken='" + mode4ReturnAccessToken + + ", hideStatusField='" + hideStatusField + ", oidc='" + oidc + '}'; } diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/resolver/SaOAuth2DataResolverDefaultImpl.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/resolver/SaOAuth2DataResolverDefaultImpl.java index 9311abb5..31186e4b 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/resolver/SaOAuth2DataResolverDefaultImpl.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/resolver/SaOAuth2DataResolverDefaultImpl.java @@ -130,11 +130,15 @@ public class SaOAuth2DataResolverDefaultImpl implements SaOAuth2DataResolver { map.put("client_id", at.clientId); map.put("scope", SaOAuth2Manager.getDataConverter().convertScopeListToString(at.scopes)); map.putAll(at.extraData); - return SaResult.ok().setMap(map); + SaResult result = SaResult.ok().setMap(map); + if(SaOAuth2Manager.getServerConfig().hideStatusField) { + result.removeDefaultFields(); + } + return result; } /** - * 构建返回值: password 模式认证 获取 token + * 构建返回值: 凭证式 模式认证 获取 token */ @Override public Map buildClientTokenReturnValue(ClientTokenModel ct) { @@ -149,7 +153,12 @@ public class SaOAuth2DataResolverDefaultImpl implements SaOAuth2DataResolver { map.put("client_id", ct.clientId); map.put("scope", SaOAuth2Manager.getDataConverter().convertScopeListToString(ct.scopes)); map.putAll(ct.extraData); - return SaResult.ok().setMap(map); + + SaResult result = SaResult.ok().setMap(map); + if(SaOAuth2Manager.getServerConfig().hideStatusField) { + result.removeDefaultFields(); + } + return result; } } -- Gitee From 8235fe7633e2c5035885ed3bd7413d6c10d54d5f Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Sat, 24 Aug 2024 17:02:23 +0800 Subject: [PATCH 150/172] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20oauth2=20=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=E5=B8=B8=E8=A7=81=E9=97=AE=E7=AD=94=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sa-token-doc/_sidebar.md | 2 +- sa-token-doc/oauth2/oauth2-questions.md | 48 +++++++++++++++++++++++++ sa-token-doc/use/config.md | 1 + 3 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 sa-token-doc/oauth2/oauth2-questions.md diff --git a/sa-token-doc/_sidebar.md b/sa-token-doc/_sidebar.md index eaa5c341..f11153ff 100644 --- a/sa-token-doc/_sidebar.md +++ b/sa-token-doc/_sidebar.md @@ -64,7 +64,7 @@ - [开启 OIDC 协议](/oauth2/oauth2-oidc) - [OAuth2-与登录会话实现数据互通](/oauth2/oauth2-interworking) - [OAuth2 代码 API 参考](/oauth2/oauth2-dev) - + - [常见问题总结](/oauth2/oauth2-questions) - diff --git a/sa-token-doc/oauth2/oauth2-questions.md b/sa-token-doc/oauth2/oauth2-questions.md new file mode 100644 index 00000000..0b92e069 --- /dev/null +++ b/sa-token-doc/oauth2/oauth2-questions.md @@ -0,0 +1,48 @@ +# Sa-Token-OAuth2整合-常见问题总结 + +OAuth2 集成常见问题整理 + +[[toc]] + +--- + + +### 问:搭建好 oauth2-server 服务后,访问返回:`{"msg": "not handle"}`。 + +返回这个信息,代表你访问的路由有错误,比如说: + +- 统一认证登录地址是:`http://{host}:{port}/oauth2/authorize`。 +- 而你访问的却是:`http://{host}:{port}/oauth2/authorize2`。 + +地址写错了,框架就不会处理这个请求,会直接返回 `{"msg": "not handle"}`,所有开放地址可参考:[SSO 开放接口](/oauth2/oauth2-apidoc) + +如果仔细检查地址后没有写错,却依然返回了这个信息,那有可能是对应的接口没有打开,比如说: + +- sso-server 端的单点注销地址:`http://{host}:{port}/sso/signout`; +- sso-client 端的注销地址:`http://{host}:{port}/sso/logout`; + +都需要在配置文件配置:`sa-token.sso.is-slo=true`后,才会打开。 + + + +### 问:我参照文档搭建 oauth2-server,一直提示:code 无效,请问怎么回事? +一个 code 码只能使用一次,多次使用就会报这个错。 + + + + + +### 问:Sa-Token-OAuth2 怎么集成多账号模式? + +在 `configOAuth2Server` 里指定 oauth2 模块使用的 `StpLogic` 对象即可: + +``` java +// Sa-Token OAuth2 定制化配置 +@Autowired +public void configOAuth2Server(SaOAuth2ServerConfig oauth2Server) { + // 其它配置 ... + + // 指定 oauth2 模块使用的 `StpLogic` 对象 + SaOAuth2Manager.setStpLogic(StpUserUtil.stpLogic); +} +``` \ No newline at end of file diff --git a/sa-token-doc/use/config.md b/sa-token-doc/use/config.md index 210175e7..aa081436 100644 --- a/sa-token-doc/use/config.md +++ b/sa-token-doc/use/config.md @@ -310,6 +310,7 @@ sa-token.sso-client.is-slo=true | higherScope | String | | 指定高级权限,多个用逗号隔开 | | lowerScope | String | | 指定低级权限,多个用逗号隔开 | | mode4ReturnAccessToken | Boolean | false | 模式4是否返回 AccessToken 字段,用于兼容OAuth2标准协议 | +| hideStatusField | Boolean | false | 是否在返回值中隐藏默认的状态字段 (code、msg、data) | | oidc | SaOAuth2OidcConfig | new SaOAuth2OidcConfig() | OIDC 相关配置 | 配置示例: -- Gitee From a1560ce0a783648ab8c0855707bf147ac657a4f7 Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Sat, 24 Aug 2024 17:55:46 +0800 Subject: [PATCH 151/172] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20StpLogic#getOrCrea?= =?UTF-8?q?teLoginSession=20=E6=96=B9=E6=B3=95=EF=BC=8C=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E6=8C=87=E5=AE=9A=E8=B4=A6=E5=8F=B7=20id=20=E7=9A=84=E7=99=BB?= =?UTF-8?q?=E5=BD=95=E4=BC=9A=E8=AF=9D=E6=95=B0=E6=8D=AE=EF=BC=8C=E5=A6=82?= =?UTF-8?q?=E6=9E=9C=E8=8E=B7=E5=8F=96=E4=B8=8D=E5=88=B0=E5=88=99=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=E5=B9=B6=E8=BF=94=E5=9B=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/cn/dev33/satoken/stp/StpLogic.java | 15 ++++++++++++++- .../java/cn/dev33/satoken/stp/StpUtil.java | 12 +++++++++++- .../main/java/com/pj/satoken/StpUserUtil.java | 10 ++++++++++ .../pj/oauth2/SaOAuth2ServerController.java | 7 +++++++ .../main/java/com/pj/satoken/StpUserUtil.java | 10 ++++++++++ sa-token-doc/oauth2/oauth2-interworking.md | 2 +- sa-token-doc/oauth2/oauth2-oidc.md | 19 +++++++++++++++++++ 7 files changed, 72 insertions(+), 3 deletions(-) diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpLogic.java b/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpLogic.java index 0e5050b5..57cfc2c3 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpLogic.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpLogic.java @@ -37,7 +37,6 @@ import cn.dev33.satoken.util.SaTokenConsts; import cn.dev33.satoken.util.SaValue2Box; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.function.Consumer; @@ -591,6 +590,20 @@ public class StpLogic { } + /** + * 获取指定账号 id 的登录会话数据,如果获取不到则创建并返回 + * + * @param id 账号id,建议的类型:(long | int | String) + * @return 返回会话令牌 + */ + public String getOrCreateLoginSession(Object id) { + String tokenValue = getTokenValueByLoginId(id); + if(tokenValue == null) { + tokenValue = createLoginSession(id, new SaLoginModel()); + } + return tokenValue; + } + // --- 注销 /** diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpUtil.java b/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpUtil.java index aaf5ec7a..60cf4b92 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpUtil.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpUtil.java @@ -224,7 +224,17 @@ public class StpUtil { public static String createLoginSession(Object id, SaLoginModel loginModel) { return stpLogic.createLoginSession(id, loginModel); } - + + /** + * 获取指定账号 id 的登录会话数据,如果获取不到则创建并返回 + * + * @param id 账号id,建议的类型:(long | int | String) + * @return 返回会话令牌 + */ + public static String getOrCreateLoginSession(Object id) { + return stpLogic.getOrCreateLoginSession(id); + } + // --- 注销 /** diff --git a/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/StpUserUtil.java b/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/StpUserUtil.java index d2fab5fd..1bbabcf7 100644 --- a/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/StpUserUtil.java +++ b/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/StpUserUtil.java @@ -215,6 +215,16 @@ public class StpUserUtil { return stpLogic.createLoginSession(id, loginModel); } + /** + * 获取指定账号 id 的登录会话数据,如果获取不到则创建并返回 + * + * @param id 账号id,建议的类型:(long | int | String) + * @return 返回会话令牌 + */ + public static String getOrCreateLoginSession(Object id) { + return stpLogic.getOrCreateLoginSession(id); + } + // --- 注销 /** diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java index 929bdd4f..391a22d6 100644 --- a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java @@ -4,6 +4,7 @@ import cn.dev33.satoken.context.SaHolder; import cn.dev33.satoken.oauth2.SaOAuth2Manager; import cn.dev33.satoken.oauth2.config.SaOAuth2ServerConfig; import cn.dev33.satoken.oauth2.processor.SaOAuth2ServerProcessor; +import cn.dev33.satoken.oauth2.strategy.SaOAuth2Strategy; import cn.dev33.satoken.oauth2.template.SaOAuth2Util; import cn.dev33.satoken.stp.StpUtil; import cn.dev33.satoken.util.SaResult; @@ -56,6 +57,12 @@ public class SaOAuth2ServerController { return new ModelAndView("confirm.html", map); }; + // 重写 AccessToken 创建策略,返回会话令牌 + SaOAuth2Strategy.instance.createAccessToken = (clientId, loginId, scopes) -> { + System.out.println("----返回会话令牌"); + return StpUtil.getOrCreateLoginSession(loginId); + }; + } diff --git a/sa-token-demo/sa-token-demo-test/src/main/java/com/pj/satoken/StpUserUtil.java b/sa-token-demo/sa-token-demo-test/src/main/java/com/pj/satoken/StpUserUtil.java index ddaf4d25..9a54330e 100644 --- a/sa-token-demo/sa-token-demo-test/src/main/java/com/pj/satoken/StpUserUtil.java +++ b/sa-token-demo/sa-token-demo-test/src/main/java/com/pj/satoken/StpUserUtil.java @@ -212,6 +212,16 @@ public class StpUserUtil { return stpLogic.createLoginSession(id, loginModel); } + /** + * 获取指定账号 id 的登录会话数据,如果获取不到则创建并返回 + * + * @param id 账号id,建议的类型:(long | int | String) + * @return 返回会话令牌 + */ + public static String getOrCreateLoginSession(Object id) { + return stpLogic.getOrCreateLoginSession(id); + } + // --- 注销 /** diff --git a/sa-token-doc/oauth2/oauth2-interworking.md b/sa-token-doc/oauth2/oauth2-interworking.md index 77c0b33c..0ca981f0 100644 --- a/sa-token-doc/oauth2/oauth2-interworking.md +++ b/sa-token-doc/oauth2/oauth2-interworking.md @@ -27,7 +27,7 @@ public void configOAuth2Server(SaOAuth2ServerConfig oauth2Server) { // 重写 AccessToken 创建策略,返回会话令牌 SaOAuth2Strategy.instance.createAccessToken = (clientId, loginId, scopes) -> { System.out.println("----返回会话令牌"); - return StpUtil.createLoginSession(loginId); + return StpUtil.getOrCreateLoginSession(loginId); }; } diff --git a/sa-token-doc/oauth2/oauth2-oidc.md b/sa-token-doc/oauth2/oauth2-oidc.md index 754822ef..a275d0f8 100644 --- a/sa-token-doc/oauth2/oauth2-oidc.md +++ b/sa-token-doc/oauth2/oauth2-oidc.md @@ -47,6 +47,25 @@ public class SaOAuth2DataLoaderImpl implements SaOAuth2DataLoader { } ``` +3、在 `application.yml` 配置文件中配置 jwt 生成秘钥: + + + +``` yaml +sa-token: + # jwt秘钥 + jwt-secret-key: asdasdasifhueuiwyurfewbfjsdafjk +``` + +``` properties +# jwt秘钥 +sa-token.jwt-secret-key: asdasdasifhueuiwyurfewbfjsdafjk +``` + + +注:为了安全起见请不要直接复制官网示例这个字符串(随便按几个字符就好了) + + ### 2、测试 -- Gitee From d3b337a6a6d0e0bb66ab277c5d42d8a29160753c Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Sat, 24 Aug 2024 18:55:48 +0800 Subject: [PATCH 152/172] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20revokeClientToken?= =?UTF-8?q?=E3=80=81revokeClientTokenByIndex=EF=BC=9A=20Client-Token=20?= =?UTF-8?q?=E5=9B=9E=E6=94=B6=E5=87=BD=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dev33/satoken/oauth2/dao/SaOAuth2Dao.java | 9 ++++ .../oauth2/template/SaOAuth2Template.java | 41 +++++++++++++++++++ .../satoken/oauth2/template/SaOAuth2Util.java | 21 +++++++++- 3 files changed, 69 insertions(+), 2 deletions(-) diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/dao/SaOAuth2Dao.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/dao/SaOAuth2Dao.java index 801e22cf..136612e8 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/dao/SaOAuth2Dao.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/dao/SaOAuth2Dao.java @@ -236,6 +236,15 @@ public interface SaOAuth2Dao { getSaTokenDao().delete(splicingClientTokenIndexKey(clientId)); } + /** + * 删除:Past-Token + * @param pastToken 值 + */ + default void deletePastToken(String pastToken) { + // 其实就是删除 ClientToken + deleteClientToken(pastToken); + } + /** * 删除:Past-Token索引 * @param clientId 应用id diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java index ea5d33ca..f9862b61 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java @@ -374,6 +374,47 @@ public class SaOAuth2Template { } + /** + * 回收指定的 ClientToken + * + * @param clientToken / + */ + public void revokeClientToken(String clientToken) { + SaOAuth2Dao dao = SaOAuth2Manager.getDao(); + ClientTokenModel ctModel = dao.getClientToken(clientToken); + if(ctModel == null) { + return; + } + // 删 ct、索引 + dao.deleteClientToken(clientToken); + dao.deleteClientTokenIndex(ctModel.clientId); + } + + /** + * 回收指定的 ClientToken,根据索引: clientId + * + * @param clientId / + */ + public void revokeClientTokenByIndex(String clientId) { + SaOAuth2Dao dao = SaOAuth2Manager.getDao(); + + // 删 clientToken + String clientToken = dao.getClientTokenValue(clientId); + if(clientToken != null) { + dao.deleteClientToken(clientToken); + dao.deleteClientTokenIndex(clientId); + } + + // 删 pastToken + String pastToken = dao.getPastTokenValue(clientId); + if(pastToken != null) { + dao.deletePastToken(pastToken); + dao.deletePastTokenIndex(clientId); + } + + } + + // ------------------- 包装其它 bean 的方法 diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Util.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Util.java index 1e2fe37c..223d7dcb 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Util.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Util.java @@ -21,7 +21,6 @@ import cn.dev33.satoken.oauth2.data.model.ClientTokenModel; import cn.dev33.satoken.oauth2.data.model.CodeModel; import cn.dev33.satoken.oauth2.data.model.RefreshTokenModel; import cn.dev33.satoken.oauth2.data.model.loader.SaClientModel; -import cn.dev33.satoken.oauth2.processor.SaOAuth2ServerProcessor; import java.util.List; @@ -175,7 +174,25 @@ public class SaOAuth2Util { public static AccessTokenModel checkAccessTokenParam(String clientId, String clientSecret, String accessToken) { return SaOAuth2Manager.getTemplate().checkAccessTokenParam(clientId, clientSecret, accessToken); } - + + /** + * 回收指定的 ClientToken + * + * @param clientToken / + */ + public static void revokeClientToken(String clientToken) { + SaOAuth2Manager.getTemplate().revokeClientToken(clientToken); + } + + /** + * 回收指定的 ClientToken,根据索引:clientId + * + * @param clientId / + */ + public static void revokeClientTokenByIndex(String clientId) { + SaOAuth2Manager.getTemplate().revokeClientTokenByIndex(clientId); + } + // ------------------- save 数据 /** -- Gitee From e0a609b12862842926af6a49339fb19e9a403dbf Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Sat, 24 Aug 2024 20:18:23 +0800 Subject: [PATCH 153/172] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=20state=20=E5=80=BC?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/application.yml | 2 +- sa-token-doc/oauth2/oauth2-questions.md | 17 ++++++-- .../dev33/satoken/oauth2/dao/SaOAuth2Dao.java | 40 +++++++++++++++++++ .../data/generate/SaOAuth2DataGenerate.java | 6 +++ .../SaOAuth2DataGenerateDefaultImpl.java | 15 +++++++ 5 files changed, 76 insertions(+), 4 deletions(-) diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/resources/application.yml b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/resources/application.yml index f12e1b5b..4c661c3c 100644 --- a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/resources/application.yml +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/resources/application.yml @@ -4,7 +4,7 @@ server: # sa-token配置 sa-token: # token名称 (同时也是 Cookie 名称) - token-name: sa-token-oauth2-server + token-name: satoken # 是否打印操作日志 is-log: true # jwt 秘钥 diff --git a/sa-token-doc/oauth2/oauth2-questions.md b/sa-token-doc/oauth2/oauth2-questions.md index 0b92e069..4c7a4f33 100644 --- a/sa-token-doc/oauth2/oauth2-questions.md +++ b/sa-token-doc/oauth2/oauth2-questions.md @@ -30,8 +30,6 @@ OAuth2 集成常见问题整理 - - ### 问:Sa-Token-OAuth2 怎么集成多账号模式? 在 `configOAuth2Server` 里指定 oauth2 模块使用的 `StpLogic` 对象即可: @@ -45,4 +43,17 @@ public void configOAuth2Server(SaOAuth2ServerConfig oauth2Server) { // 指定 oauth2 模块使用的 `StpLogic` 对象 SaOAuth2Manager.setStpLogic(StpUserUtil.stpLogic); } -``` \ No newline at end of file +``` + + + + +### 问:授权码流程中 state 参数是干吗用的? + +state 参数用于验证授权码流程的发起端和接受端是否为同一个客户端,以防止OAuth-Server账号伪装攻击。 + +授权流程发起端必须保证: +- state 参数必须足够随机,不可被预测。 +- state 参数与授权码流程发起客户端 一 一 对 应,授权流程发起时创建的 state 必须与接受时返回的 state 值一致。 +- 安全起见,一个 state 参数只允许使用一次。 + diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/dao/SaOAuth2Dao.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/dao/SaOAuth2Dao.java index 136612e8..f3110717 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/dao/SaOAuth2Dao.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/dao/SaOAuth2Dao.java @@ -158,6 +158,17 @@ public interface SaOAuth2Dao { } } + /** + * 持久化:state + * @param state / + */ + default void saveState(String state) { + if( ! SaFoxUtil.isEmpty(state)) { + long ttl = SaOAuth2Manager.getServerConfig().getCodeTimeout(); + getSaTokenDao().set(splicingStateSaveKey(state), state, ttl); + } + } + // ------------------- delete数据 @@ -262,6 +273,14 @@ public interface SaOAuth2Dao { getSaTokenDao().delete(splicingGrantScopeKey(clientId, loginId)); } + /** + * 删除:state记录 + * @param state / + */ + default void deleteGrantScope(String state) { + getSaTokenDao().delete(splicingStateSaveKey(state)); + } + // ------------------- get 数据 @@ -372,6 +391,18 @@ public interface SaOAuth2Dao { return SaOAuth2Manager.getDataConverter().convertScopeStringToList(value); } + /** + * 获取:state + * @param state / + * @return / + */ + default String getState(String state) { + if(SaFoxUtil.isEmpty(state)) { + return null; + } + return getSaTokenDao().get(splicingStateSaveKey(state)); + } + // ------------------- 拼接key @@ -469,6 +500,15 @@ public interface SaOAuth2Dao { return getSaTokenConfig().getTokenName() + ":oauth2:grant-scope:" + clientId + ":" + loginId; } + /** + * 拼接key:state 参数持久化 + * @param state / + * @return key + */ + default String splicingStateSaveKey(String state) { + return getSaTokenConfig().getTokenName() + ":oauth2:state:" + state; + } + // -------- bean 对象代理 diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerate.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerate.java index 6fbee6a3..197dfde7 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerate.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerate.java @@ -91,4 +91,10 @@ public interface SaOAuth2DataGenerate { */ void revokeAccessToken(String accessToken); + /** + * 检查 state 是否被重复使用 + * @param state / + */ + void checkState(String state); + } diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerateDefaultImpl.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerateDefaultImpl.java index 0090a2db..266342bf 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerateDefaultImpl.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerateDefaultImpl.java @@ -245,6 +245,7 @@ public class SaOAuth2DataGenerateDefaultImpl implements SaOAuth2DataGenerate { public String buildRedirectUri(String redirectUri, String code, String state) { String url = SaFoxUtil.joinParam(redirectUri, SaOAuth2Consts.Param.code, code); if( ! SaFoxUtil.isEmpty(state)) { + checkState(state); url = SaFoxUtil.joinParam(url, SaOAuth2Consts.Param.state, state); } return url; @@ -261,6 +262,7 @@ public class SaOAuth2DataGenerateDefaultImpl implements SaOAuth2DataGenerate { public String buildImplicitRedirectUri(String redirectUri, String token, String state) { String url = SaFoxUtil.joinSharpParam(redirectUri, SaOAuth2Consts.Param.token, token); if( ! SaFoxUtil.isEmpty(state)) { + checkState(state); url = SaFoxUtil.joinSharpParam(url, SaOAuth2Consts.Param.state, state); } return url; @@ -291,5 +293,18 @@ public class SaOAuth2DataGenerateDefaultImpl implements SaOAuth2DataGenerate { dao.deleteRefreshTokenIndex(at.clientId, at.loginId); } + /** + * 检查 state 是否被重复使用 + * @param state / + */ + @Override + public void checkState(String state) { + String value = SaOAuth2Manager.getDao().getState(state); + if(SaFoxUtil.isNotEmpty(value)) { + throw new SaOAuth2Exception("多次请求的 state 不可重复: " + state); + } + SaOAuth2Manager.getDao().saveState(state); + } + } -- Gitee From 7f20c8d4502c44626c1774bfde3f465d7b2cbd84 Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Sun, 25 Aug 2024 10:32:52 +0800 Subject: [PATCH 154/172] =?UTF-8?q?=E5=AE=8C=E5=96=84=20SaOAuth2Util=20?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/cn/dev33/satoken/util/SaFoxUtil.java | 30 + .../pj/oauth2/SaOAuth2ServerController.java | 4 +- sa-token-doc/oauth2/oauth2-check-domain.md | 2 +- sa-token-doc/oauth2/oauth2-dev.md | 113 +++- .../data/generate/SaOAuth2DataGenerate.java | 6 - .../SaOAuth2DataGenerateDefaultImpl.java | 27 +- .../data/loader/SaOAuth2DataLoader.java | 7 +- .../SaOAuth2AccessTokenException.java | 73 +++ .../SaOAuth2AccessTokenScopeException.java | 87 +++ .../SaOAuth2ClientModelException.java | 86 +++ .../SaOAuth2ClientModelScopeException.java | 87 +++ .../SaOAuth2ClientTokenException.java | 73 +++ .../SaOAuth2ClientTokenScopeException.java | 87 +++ .../oauth2/exception/SaOAuth2Exception.java | 8 +- .../SaOAuth2RefreshTokenException.java | 86 +++ .../processor/SaOAuth2ServerProcessor.java | 9 +- .../oauth2/template/SaOAuth2Template.java | 563 ++++++++++++------ .../satoken/oauth2/template/SaOAuth2Util.java | 362 ++++++----- 18 files changed, 1326 insertions(+), 384 deletions(-) create mode 100644 sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2AccessTokenException.java create mode 100644 sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2AccessTokenScopeException.java create mode 100644 sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2ClientModelException.java create mode 100644 sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2ClientModelScopeException.java create mode 100644 sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2ClientTokenException.java create mode 100644 sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2ClientTokenScopeException.java create mode 100644 sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2RefreshTokenException.java diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/util/SaFoxUtil.java b/sa-token-core/src/main/java/cn/dev33/satoken/util/SaFoxUtil.java index 8044a223..63bfbb60 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/util/SaFoxUtil.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/util/SaFoxUtil.java @@ -106,14 +106,35 @@ public class SaFoxUtil { /** * 指定数组是否为null或者空数组 + *

该方法已过时,建议使用 isEmptyArray 方法

* @param / * @param array / * @return / */ + @Deprecated public static boolean isEmpty(T[] array) { + return isEmptyArray(array); + } + + /** + * 指定数组是否为null或者空数组 + * @param / + * @param array / + * @return / + */ + public static boolean isEmptyArray(T[] array) { return array == null || array.length == 0; } + /** + * 指定集合是否为null或者空数组 + * @param list / + * @return / + */ + public static boolean isEmptyList(List list) { + return list == null || list.isEmpty(); + } + /** * 比较两个对象是否相等 * @param a 第一个对象 @@ -626,6 +647,15 @@ public class SaFoxUtil { return new ArrayList<>(Arrays.asList(str)); } + /** + * String 集合转数组 + * @param list 集合 + * @return 数组 + */ + public static String[] toArray(List list) { + return list.toArray(new String[0]); + } + public static List logLevelList = Arrays.asList("", "trace", "debug", "info", "warn", "error", "fatal"); /** diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java index 391a22d6..94a95446 100644 --- a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java @@ -75,10 +75,10 @@ public class SaOAuth2ServerController { String accessToken = SaOAuth2Manager.getDataResolver().readAccessToken(SaHolder.getRequest()); Object loginId = SaOAuth2Util.getLoginIdByAccessToken(accessToken); System.out.println("-------- 此Access-Token对应的账号id: " + loginId); - + // 校验 Access-Token 是否具有权限: userinfo SaOAuth2Util.checkScope(accessToken, "userinfo"); - + // 模拟账号信息 (真实环境需要查询数据库获取信息) Map map = new LinkedHashMap<>(); // map.put("userId", loginId); 一般原则下,oauth2-server 不能把 userId 返回给 oauth2-client diff --git a/sa-token-doc/oauth2/oauth2-check-domain.md b/sa-token-doc/oauth2/oauth2-check-domain.md index a6e8faf6..841d3ff0 100644 --- a/sa-token-doc/oauth2/oauth2-check-domain.md +++ b/sa-token-doc/oauth2/oauth2-check-domain.md @@ -94,7 +94,7 @@ URL 没有通过校验,拒绝授权! - 反例:`http://*.sa-oauth-client.com/` *详见源码: [SaOAuth2Template.java](https://gitee.com/dromara/sa-token/blob/master/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java) -`checkAllowUrlListStaticMethod` 方法。* +`checkRedirectUriListNormalStaticMethod` 方法。* 参考:[github/issue/529](https://github.com/dromara/Sa-Token/issues/529) 感谢这位 `@m4ra7h0n` 用户反馈的漏洞。 diff --git a/sa-token-doc/oauth2/oauth2-dev.md b/sa-token-doc/oauth2/oauth2-dev.md index def72b9b..45f7097a 100644 --- a/sa-token-doc/oauth2/oauth2-dev.md +++ b/sa-token-doc/oauth2/oauth2-dev.md @@ -1,49 +1,118 @@ # Sa-Token-OAuth2 Server端 二次开发用到的所有函数说明 -官方示例只提供了基本的授权流程,以及userinfo资源的开放,如果您需要开放更多的接口,则二次开发时用到以下相关API方法 +官方示例只提供了基本的授权流程,以及 userinfo 资源的开放,如果您需要开放更多的接口,则二次开发时可能用到以下相关 API 方法 --- -## Sa-OAuth2 模块常用方法 +### Client 信息相关 ``` java -// 根据 id 获取 Client 信息, 如果 Client 为空,则抛出异常 +// 获取 ClientModel,根据 clientId +SaOAuth2Util.getClientModel(clientId); + +// 校验 clientId 信息并返回 ClientModel,如果找不到对应 Client 信息则抛出异常 SaOAuth2Util.checkClientModel(clientId); -// 获取 Access-Token,如果Access-Token为空则抛出异常 +// 校验:clientId 与 clientSecret 是否正确 +SaOAuth2Util.checkClientSecret(clientId, clientSecret); + +// 校验:clientId 与 clientSecret 是否正确,并且是否签约了指定 scopes +SaOAuth2Util.checkClientSecretAndScope(clientId, clientSecret, scopes); + +// 判断:该 Client 是否签约了指定的 Scope,返回 true 或 false +SaOAuth2Util.isContractScope(clientId, scopes); + +// 校验:该 Client 是否签约了指定的 Scope,如果没有则抛出异常 +SaOAuth2Util.checkContractScope(clientId, scopes); + +// 校验:该 Client 是否签约了指定的 Scope,如果没有则抛出异常 +SaOAuth2Util.checkContractScope(clientModel, scopes); + +// 校验:该 Client 使用指定 url 作为回调地址,是否合法 +SaOAuth2Util.checkRedirectUri(clientId, url); + +// 判断:指定 loginId 是否对一个 Client 授权给了指定 Scope +SaOAuth2Util.isGrantScope(loginId, clientId, scopes); +``` + + +### Access-Token 相关 +``` java +// 获取 AccessTokenModel,无效的 AccessToken 会返回 null +SaOAuth2Util.getAccessToken(accessToken); + +// 校验 Access-Token,成功返回 AccessTokenModel,失败则抛出异常 SaOAuth2Util.checkAccessToken(accessToken); -// 获取 Client-Token,如果Client-Token为空则抛出异常 -SaOAuth2Util.checkClientToken(clientToken); +// 获取 Access-Token,根据索引: clientId、loginId +SaOAuth2Util.getAccessTokenValue(clientId, loginId); + +// 判断:指定 Access-Token 是否具有指定 Scope 列表,返回 true 或 false +SaOAuth2Util.hasAccessTokenScope(accessToken, ...scopes); + +// 校验:指定 Access-Token 是否具有指定 Scope 列表,如果不具备则抛出异常 +SaOAuth2Util.checkAccessTokenScope(accessToken, ...scopes); // 获取 Access-Token 所代表的LoginId SaOAuth2Util.getLoginIdByAccessToken(accessToken); -// 校验:指定 Access-Token 是否具有指定 Scope -SaOAuth2Util.checkScope(accessToken, scopes); +// 获取 Access-Token 所代表的 clientId +SaOAuth2Util.getClientIdByAccessToken(accessToken); + +// 回收 Access-Token +SaOAuth2Util.revokeAccessToken(accessToken); + +// 回收 Access-Token,根据索引: clientId、loginId +SaOAuth2Util.revokeAccessTokenByIndex(clientId, loginId); +``` + + +### Refresh-Token 相关 +``` java +// 获取 RefreshTokenModel,无效的 RefreshToken 会返回 null +SaOAuth2Util.getRefreshToken(refreshToken); -// 根据 code码 生成 Access-Token -SaOAuth2Util.generateAccessToken(code); +// 校验 Refresh-Token,成功返回 RefreshTokenModel,失败则抛出异常 +SaOAuth2Util.checkRefreshToken(refreshToken); -// 根据 Refresh-Token 生成一个新的 Access-Token +// 获取 Refresh-Token,根据索引: clientId、loginId +SaOAuth2Util.getRefreshTokenValue(clientId, Object loginId); + +// 根据 RefreshToken 刷新出一个 AccessToken SaOAuth2Util.refreshAccessToken(refreshToken); +``` -// 构建 Client-Token -SaOAuth2Util.generateClientToken(clientId, scope); -// 校验 Client-Token 是否含有指定 Scope -SaOAuth2Util.checkClientTokenScope(clientToken, scopes); +### Client-Token 相关 -// 回收 Access-Token -SaOAuth2Util.revokeAccessToken(accessToken); +``` java +// 获取 ClientTokenModel,无效的 ClientToken 会返回 null +SaOAuth2Util.getClientToken(clientToken); -// 持久化:用户授权记录 -SaOAuth2Util.saveGrantScope(clientId, loginId, scope); +// 校验 Client-Token,成功返回 ClientTokenModel,失败则抛出异常 +SaOAuth2Util.checkClientToken(clientToken); -// 获取:Refresh-Token Model -SaOAuth2Util.getRefreshToken(refreshToken); +// 获取 ClientToken,根据索引: clientId +SaOAuth2Util.getClientTokenValue(clientId); + +// 判断:指定 Client-Token 是否具有指定 Scope 列表,返回 true 或 false +SaOAuth2Util.hasClientTokenScope(clientToken, ...scopes); + +// 校验:指定 Client-Token 是否具有指定 Scope 列表,如果不具备则抛出异常 +SaOAuth2Util.checkClientTokenScope(clientToken, ...scopes); + +// 回收 ClientToken +SaOAuth2Util.revokeClientToken(clientToken); + +// 回收 ClientToken,根据索引: clientId +SaOAuth2Util.revokeClientTokenByIndex(clientId); + +// 回收 PastToken,根据索引: clientId +SaOAuth2Util.revokePastTokenByIndex(clientId); ``` -详情请参考源码:[码云:SaOAuth2Util.java](https://gitee.com/dromara/sa-token/blob/master/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/logic/SaOAuth2Util.java) +--- + +详情请参考源码:[码云:SaOAuth2Util.java](https://gitee.com/dromara/sa-token/blob/master/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Util.java) diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerate.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerate.java index 197dfde7..aa9fe197 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerate.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerate.java @@ -85,12 +85,6 @@ public interface SaOAuth2DataGenerate { */ String buildImplicitRedirectUri(String redirectUri, String token, String state); - /** - * 回收 Access-Token - * @param accessToken Access-Token值 - */ - void revokeAccessToken(String accessToken); - /** * 检查 state 是否被重复使用 * @param state / diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerateDefaultImpl.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerateDefaultImpl.java index 266342bf..33ad2055 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerateDefaultImpl.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerateDefaultImpl.java @@ -126,7 +126,7 @@ public class SaOAuth2DataGenerateDefaultImpl implements SaOAuth2DataGenerate { // 删除旧 Refresh-Token dao.deleteRefreshToken(rt.refreshToken); - // 创建并保持新的 Refresh-Token + // 创建并保存新的 Refresh-Token rt = SaOAuth2Manager.getDataConverter().convertRefreshTokenToRefreshToken(rt); dao.saveRefreshToken(rt); dao.saveRefreshTokenIndex(rt); @@ -268,31 +268,6 @@ public class SaOAuth2DataGenerateDefaultImpl implements SaOAuth2DataGenerate { return url; } - /** - * 回收 Access-Token - * @param accessToken Access-Token值 - */ - @Override - public void revokeAccessToken(String accessToken) { - - SaOAuth2Dao dao = SaOAuth2Manager.getDao(); - - // 如果查不到任何东西, 直接返回 - AccessTokenModel at = dao.getAccessToken(accessToken); - if(at == null) { - return; - } - - // 删除 Access-Token - dao.deleteAccessToken(accessToken); - dao.deleteAccessTokenIndex(at.clientId, at.loginId); - - // 删除对应的 Refresh-Token - String refreshToken = dao.getRefreshTokenValue(at.clientId, at.loginId); - dao.deleteRefreshToken(refreshToken); - dao.deleteRefreshTokenIndex(at.clientId, at.loginId); - } - /** * 检查 state 是否被重复使用 * @param state / 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 c8f93ef6..aa8815a7 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 @@ -17,7 +17,8 @@ package cn.dev33.satoken.oauth2.data.loader; import cn.dev33.satoken.oauth2.SaOAuth2Manager; import cn.dev33.satoken.oauth2.data.model.loader.SaClientModel; -import cn.dev33.satoken.oauth2.exception.SaOAuth2Exception; +import cn.dev33.satoken.oauth2.error.SaOAuth2ErrorCode; +import cn.dev33.satoken.oauth2.exception.SaOAuth2ClientModelException; import cn.dev33.satoken.secure.SaSecureUtil; /** @@ -47,7 +48,9 @@ public interface SaOAuth2DataLoader { default SaClientModel getClientModelNotNull(String clientId) { SaClientModel clientModel = getClientModel(clientId); if(clientModel == null) { - throw new SaOAuth2Exception("未找到对应的 Client 信息"); + throw new SaOAuth2ClientModelException("无效 client_id: " + clientId) + .setClientId(clientId) + .setCode(SaOAuth2ErrorCode.CODE_30105); } return clientModel; } diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2AccessTokenException.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2AccessTokenException.java new file mode 100644 index 00000000..89b6ac65 --- /dev/null +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2AccessTokenException.java @@ -0,0 +1,73 @@ +/* + * 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.exception; + +/** + * 一个异常:代表 Access-Token 相关错误 + * + * @author click33 + * @since 1.39.0 + */ +public class SaOAuth2AccessTokenException extends SaOAuth2Exception { + + /** + * 序列化版本号 + */ + private static final long serialVersionUID = 6806129545290130114L; + + /** + * 一个异常:代表 Access-Token 相关错误 + * @param cause 根异常原因 + */ + public SaOAuth2AccessTokenException(Throwable cause) { + super(cause); + } + + /** + * 一个异常:代表 Access-Token 相关错误 + * @param message 异常描述 + */ + public SaOAuth2AccessTokenException(String message) { + super(message); + } + + /** + * 具体引起异常的 Access-Token 值 + */ + public String accessToken; + + public String getAccessToken() { + return accessToken; + } + + public SaOAuth2AccessTokenException setAccessToken(String accessToken) { + this.accessToken = accessToken; + return this; + } + + /** + * 如果 flag==true,则抛出 message 异常 + * @param flag 标记 + * @param message 异常信息 + * @param code 异常细分码 + */ + public static void throwBy(boolean flag, String message, int code) { + if(flag) { + throw new SaOAuth2AccessTokenException(message).setCode(code); + } + } + +} diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2AccessTokenScopeException.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2AccessTokenScopeException.java new file mode 100644 index 00000000..cecc4141 --- /dev/null +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2AccessTokenScopeException.java @@ -0,0 +1,87 @@ +/* + * 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.exception; + +/** + * 一个异常:代表 Access-Token Scope 相关错误 + * + * @author click33 + * @since 1.39.0 + */ +public class SaOAuth2AccessTokenScopeException extends SaOAuth2AccessTokenException { + + /** + * 序列化版本号 + */ + private static final long serialVersionUID = 6806129545290130114L; + + /** + * 一个异常:代表 Access-Token Scope 相关错误 + * @param cause 根异常原因 + */ + public SaOAuth2AccessTokenScopeException(Throwable cause) { + super(cause); + } + + /** + * 一个异常:代表 Access-Token Scope 相关错误 + * @param message 异常描述 + */ + public SaOAuth2AccessTokenScopeException(String message) { + super(message); + } + + /** + * 具体引起异常的 Access-Token 值 + */ + public String accessToken; + + /** + * 具体引起异常的 scope 值 + */ + public String scope; + + public String getAccessToken() { + return accessToken; + } + + public SaOAuth2AccessTokenScopeException setAccessToken(String accessToken) { + this.accessToken = accessToken; + return this; + } + + public String getScope() { + return scope; + } + + public SaOAuth2AccessTokenScopeException setScope(String scope) { + this.scope = scope; + return this; + } + + /** + * 如果 flag==true,则抛出 message 异常 + * @param flag 标记 + * @param message 异常信息 + * @param code 异常细分码 + */ + public static void throwBy(boolean flag, String message, int code) { + if(flag) { + throw new SaOAuth2AccessTokenScopeException(message).setCode(code); + } + } + +} diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2ClientModelException.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2ClientModelException.java new file mode 100644 index 00000000..59c36238 --- /dev/null +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2ClientModelException.java @@ -0,0 +1,86 @@ +/* + * 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.exception; + +/** + * 一个异常:代表 ClientModel 相关错误 + * + * @author click33 + * @since 1.39.0 + */ +public class SaOAuth2ClientModelException extends SaOAuth2Exception { + + /** + * 序列化版本号 + */ + private static final long serialVersionUID = 6806129545290130114L; + + /** + * 一个异常:代表 ClientModel 相关错误 + * @param cause 根异常原因 + */ + public SaOAuth2ClientModelException(Throwable cause) { + super(cause); + } + + /** + * 一个异常:代表 ClientModel 相关错误 + * @param message 异常描述 + */ + public SaOAuth2ClientModelException(String message) { + super(message); + } + + /** + * 具体引起异常的 ClientId 值 + */ + public String clientId; + + public String getClientId() { + return clientId; + } + + public SaOAuth2ClientModelException setClientId(String clientId) { + this.clientId = clientId; + return this; + } + + /** + * 如果 flag==true,则抛出 message 异常 + * @param flag 标记 + * @param message 异常信息 + * @param code 异常细分码 + */ + public static void throwBy(boolean flag, String message, int code) { + if(flag) { + throw new SaOAuth2ClientModelException(message).setCode(code); + } + } + + /** + * 如果 flag==true,则抛出 message 异常 + * @param flag 标记 + * @param message 异常信息 + * @param clientId 应用id + * @param code 异常细分码 + */ + public static void throwBy(boolean flag, String message, String clientId, int code) { + if(flag) { + throw new SaOAuth2ClientModelException(message).setClientId(clientId).setCode(code); + } + } + +} diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2ClientModelScopeException.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2ClientModelScopeException.java new file mode 100644 index 00000000..ad1b2507 --- /dev/null +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2ClientModelScopeException.java @@ -0,0 +1,87 @@ +/* + * 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.exception; + +/** + * 一个异常:代表 ClientModel Scope 相关错误 + * + * @author click33 + * @since 1.39.0 + */ +public class SaOAuth2ClientModelScopeException extends SaOAuth2ClientModelException { + + /** + * 序列化版本号 + */ + private static final long serialVersionUID = 6806129545290130114L; + + /** + * 一个异常:代表 ClientModel Scope 相关错误 + * @param cause 根异常原因 + */ + public SaOAuth2ClientModelScopeException(Throwable cause) { + super(cause); + } + + /** + * 一个异常:代表 ClientModel Scope 相关错误 + * @param message 异常描述 + */ + public SaOAuth2ClientModelScopeException(String message) { + super(message); + } + + /** + * 具体引起异常的 ClientId 值 + */ + public String clientId; + + /** + * 具体引起异常的 scope 值 + */ + public String scope; + + public String getClientId() { + return clientId; + } + + public SaOAuth2ClientModelScopeException setClientId(String clientId) { + this.clientId = clientId; + return this; + } + + public String getScope() { + return scope; + } + + public SaOAuth2ClientModelScopeException setScope(String scope) { + this.scope = scope; + return this; + } + + /** + * 如果 flag==true,则抛出 message 异常 + * @param flag 标记 + * @param message 异常信息 + * @param code 异常细分码 + */ + public static void throwBy(boolean flag, String message, int code) { + if(flag) { + throw new SaOAuth2ClientModelScopeException(message).setCode(code); + } + } + +} diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2ClientTokenException.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2ClientTokenException.java new file mode 100644 index 00000000..2d69dbb8 --- /dev/null +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2ClientTokenException.java @@ -0,0 +1,73 @@ +/* + * 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.exception; + +/** + * 一个异常:代表 Client-Token 相关错误 + * + * @author click33 + * @since 1.39.0 + */ +public class SaOAuth2ClientTokenException extends SaOAuth2Exception { + + /** + * 序列化版本号 + */ + private static final long serialVersionUID = 6806129545290130114L; + + /** + * 一个异常:代表 Client-Token 相关错误 + * @param cause 根异常原因 + */ + public SaOAuth2ClientTokenException(Throwable cause) { + super(cause); + } + + /** + * 一个异常:代表 Client-Token 相关错误 + * @param message 异常描述 + */ + public SaOAuth2ClientTokenException(String message) { + super(message); + } + + /** + * 具体引起异常的 Client-Token 值 + */ + public String clientToken; + + public String getClientToken() { + return clientToken; + } + + public SaOAuth2ClientTokenException setClientToken(String clientToken) { + this.clientToken = clientToken; + return this; + } + + /** + * 如果 flag==true,则抛出 message 异常 + * @param flag 标记 + * @param message 异常信息 + * @param code 异常细分码 + */ + public static void throwBy(boolean flag, String message, int code) { + if(flag) { + throw new SaOAuth2ClientTokenException(message).setCode(code); + } + } + +} diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2ClientTokenScopeException.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2ClientTokenScopeException.java new file mode 100644 index 00000000..987dea68 --- /dev/null +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2ClientTokenScopeException.java @@ -0,0 +1,87 @@ +/* + * 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.exception; + +/** + * 一个异常:代表 Client-Token Scope 相关错误 + * + * @author click33 + * @since 1.39.0 + */ +public class SaOAuth2ClientTokenScopeException extends SaOAuth2ClientTokenException { + + /** + * 序列化版本号 + */ + private static final long serialVersionUID = 6806129545290130114L; + + /** + * 一个异常:代表 Client-Token Scope 相关错误 + * @param cause 根异常原因 + */ + public SaOAuth2ClientTokenScopeException(Throwable cause) { + super(cause); + } + + /** + * 一个异常:代表 Client-Token Scope 相关错误 + * @param message 异常描述 + */ + public SaOAuth2ClientTokenScopeException(String message) { + super(message); + } + + /** + * 具体引起异常的 Client-Token 值 + */ + public String clientToken; + + /** + * 具体引起异常的 scope 值 + */ + public String scope; + + public String getClientToken() { + return clientToken; + } + + public SaOAuth2ClientTokenScopeException setClientToken(String clientToken) { + this.clientToken = clientToken; + return this; + } + + public String getScope() { + return scope; + } + + public SaOAuth2ClientTokenScopeException setScope(String scope) { + this.scope = scope; + return this; + } + + /** + * 如果 flag==true,则抛出 message 异常 + * @param flag 标记 + * @param message 异常信息 + * @param code 异常细分码 + */ + public static void throwBy(boolean flag, String message, int code) { + if(flag) { + throw new SaOAuth2ClientTokenScopeException(message).setCode(code); + } + } + +} diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2Exception.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2Exception.java index c1b96496..04b7d3cc 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2Exception.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2Exception.java @@ -18,7 +18,7 @@ package cn.dev33.satoken.oauth2.exception; import cn.dev33.satoken.exception.SaTokenException; /** - * 一个异常:代表OAuth2认证流程错误 + * 一个异常:代表 OAuth2 认证流程错误 * * @author click33 * @since 1.33.0 @@ -31,7 +31,7 @@ public class SaOAuth2Exception extends SaTokenException { private static final long serialVersionUID = 6806129545290130114L; /** - * 一个异常:代表OAuth2认证流程错误 + * 一个异常:代表 OAuth2 认证流程错误 * @param cause 根异常原因 */ public SaOAuth2Exception(Throwable cause) { @@ -39,7 +39,7 @@ public class SaOAuth2Exception extends SaTokenException { } /** - * 一个异常:代表OAuth2认证流程错误 + * 一个异常:代表 OAuth2 认证流程错误 * @param message 异常描述 */ public SaOAuth2Exception(String message) { @@ -47,7 +47,7 @@ public class SaOAuth2Exception extends SaTokenException { } /** - * 如果flag==true,则抛出message异常 + * 如果 flag==true,则抛出 message 异常 * @param flag 标记 * @param message 异常信息 * @param code 异常细分码 diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2RefreshTokenException.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2RefreshTokenException.java new file mode 100644 index 00000000..e110a31e --- /dev/null +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2RefreshTokenException.java @@ -0,0 +1,86 @@ +/* + * 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.exception; + +/** + * 一个异常:代表 Refresh-Token 相关错误 + * + * @author click33 + * @since 1.39.0 + */ +public class SaOAuth2RefreshTokenException extends SaOAuth2Exception { + + /** + * 序列化版本号 + */ + private static final long serialVersionUID = 6806129545290130114L; + + /** + * 一个异常:代表 Refresh-Token 相关错误 + * @param cause 根异常原因 + */ + public SaOAuth2RefreshTokenException(Throwable cause) { + super(cause); + } + + /** + * 一个异常:代表 Refresh-Token 相关错误 + * @param message 异常描述 + */ + public SaOAuth2RefreshTokenException(String message) { + super(message); + } + + /** + * 具体引起异常的 Refresh-Token 值 + */ + public String refreshToken; + + public String getRefreshToken() { + return refreshToken; + } + + public SaOAuth2RefreshTokenException setRefreshToken(String refreshToken) { + this.refreshToken = refreshToken; + return this; + } + + /** + * 如果 flag==true,则抛出 message 异常 + * @param flag 标记 + * @param message 异常信息 + * @param code 异常细分码 + */ + public static void throwBy(boolean flag, String message, int code) { + if(flag) { + throw new SaOAuth2RefreshTokenException(message).setCode(code); + } + } + + /** + * 如果 flag==true,则抛出 message 异常 + * @param flag 标记 + * @param message 异常信息 + * @param refreshToken refreshToken + * @param code 异常细分码 + */ + public static void throwBy(boolean flag, String message, String refreshToken, int code) { + if(flag) { + throw new SaOAuth2RefreshTokenException(message).setRefreshToken(refreshToken).setCode(code); + } + } + +} diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/processor/SaOAuth2ServerProcessor.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/processor/SaOAuth2ServerProcessor.java index 552c46b4..f8e56867 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/processor/SaOAuth2ServerProcessor.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/processor/SaOAuth2ServerProcessor.java @@ -37,6 +37,7 @@ import cn.dev33.satoken.oauth2.exception.SaOAuth2Exception; import cn.dev33.satoken.oauth2.strategy.SaOAuth2Strategy; import cn.dev33.satoken.oauth2.template.SaOAuth2Template; import cn.dev33.satoken.router.SaHttpMethod; +import cn.dev33.satoken.util.SaFoxUtil; import cn.dev33.satoken.util.SaResult; import java.util.List; @@ -130,10 +131,10 @@ public class SaOAuth2ServerProcessor { RequestAuthModel ra = SaOAuth2Manager.getDataResolver().readRequestAuthModel(req, SaOAuth2Manager.getStpLogic().getLoginId()); // 4、校验:重定向域名是否合法 - oauth2Template.checkRightUrl(ra.clientId, ra.redirectUri); + oauth2Template.checkRedirectUri(ra.clientId, ra.redirectUri); // 5、校验:此次申请的Scope,该Client是否已经签约 - oauth2Template.checkContract(ra.clientId, ra.scopes); + oauth2Template.checkContractScope(ra.clientId, ra.scopes); // 6、判断:如果此次申请的Scope,该用户尚未授权,则转到授权页面 boolean isNeedCarefulConfirm = oauth2Template.isNeedCarefulConfirm(ra.loginId, ra.clientId, ra.scopes); @@ -205,7 +206,7 @@ public class SaOAuth2ServerProcessor { oauth2Template.checkAccessTokenParam(clientId, clientSecret, accessToken); // 回收 Access-Token - SaOAuth2Manager.getDataGenerate().revokeAccessToken(accessToken); + oauth2Template.revokeAccessToken(accessToken); // 返回 return SaOAuth2Manager.getDataResolver().buildRevokeTokenReturnValue(); @@ -306,7 +307,7 @@ public class SaOAuth2ServerProcessor { List scopes = SaOAuth2Manager.getDataConverter().convertScopeStringToList(req.getParam(Param.scope)); //校验 ClientScope - oauth2Template.checkContract(clientId, scopes); + oauth2Template.checkContractScope(clientId, scopes); // 校验 ClientSecret oauth2Template.checkClientSecret(clientId, clientSecret); diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java index f9862b61..e35cef93 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java @@ -23,11 +23,10 @@ import cn.dev33.satoken.oauth2.data.model.CodeModel; import cn.dev33.satoken.oauth2.data.model.RefreshTokenModel; import cn.dev33.satoken.oauth2.data.model.loader.SaClientModel; import cn.dev33.satoken.oauth2.error.SaOAuth2ErrorCode; -import cn.dev33.satoken.oauth2.exception.SaOAuth2Exception; +import cn.dev33.satoken.oauth2.exception.*; import cn.dev33.satoken.strategy.SaStrategy; import cn.dev33.satoken.util.SaFoxUtil; -import java.util.HashSet; import java.util.List; /** @@ -38,157 +37,118 @@ import java.util.List; */ public class SaOAuth2Template { - // ------------------- 数据加载 + // ----------------- ClientModel 相关 ----------------- /** - * 根据id获取Client信息 - * @param clientId 应用id - * @return ClientModel + * 获取 ClientModel,根据 clientId + * + * @param clientId / + * @return / */ public SaClientModel getClientModel(String clientId) { return SaOAuth2Manager.getDataLoader().getClientModel(clientId); } - // ------------------- 资源校验API /** - * 根据id获取Client信息, 如果Client为空,则抛出异常 - * @param clientId 应用id - * @return ClientModel + * 校验 clientId 信息并返回 ClientModel,如果找不到对应 Client 信息则抛出异常 + * @param clientId / + * @return / */ public SaClientModel checkClientModel(String clientId) { SaClientModel clientModel = getClientModel(clientId); if(clientModel == null) { - throw new SaOAuth2Exception("无效client_id: " + clientId).setCode(SaOAuth2ErrorCode.CODE_30105); + throw new SaOAuth2ClientModelException("无效 client_id: " + clientId) + .setClientId(clientId) + .setCode(SaOAuth2ErrorCode.CODE_30105); } return clientModel; } + /** - * 获取 Access-Token,如果AccessToken为空则抛出异常 - * @param accessToken . - * @return . - */ - public AccessTokenModel checkAccessToken(String accessToken) { - AccessTokenModel at = SaOAuth2Manager.getDao().getAccessToken(accessToken); - SaOAuth2Exception.throwBy(at == null, "无效access_token:" + accessToken, SaOAuth2ErrorCode.CODE_30106); - return at; - } - /** - * 获取 Client-Token,如果ClientToken为空则抛出异常 - * @param clientToken . - * @return . - */ - public ClientTokenModel checkClientToken(String clientToken) { - ClientTokenModel ct = SaOAuth2Manager.getDao().getClientToken(clientToken); - SaOAuth2Exception.throwBy(ct == null, "无效:client_token" + clientToken, SaOAuth2ErrorCode.CODE_30107); - return ct; - } - /** - * 获取 Access-Token 所代表的LoginId - * @param accessToken Access-Token - * @return LoginId - */ - public Object getLoginIdByAccessToken(String accessToken) { - return checkAccessToken(accessToken).loginId; - } - /** - * 校验:指定 Access-Token 是否具有指定 Scope - * @param accessToken Access-Token - * @param scopes 需要校验的权限列表 - */ - public void checkScope(String accessToken, String... scopes) { - if(scopes == null || scopes.length == 0) { - return; - } - AccessTokenModel at = checkAccessToken(accessToken); - List scopeList = at.scopes; - for (String scope : scopes) { - SaOAuth2Exception.throwBy( ! scopeList.contains(scope), "该 Access-Token 不具备 Scope:" + scope, SaOAuth2ErrorCode.CODE_30108); - } - } - /** - * 校验:指定 Client-Token 是否具有指定 Scope - * @param clientToken Client-Token - * @param scopes 需要校验的权限列表 + * 校验:clientId 与 clientSecret 是否正确 + * @param clientId 应用id + * @param clientSecret 秘钥 + * @return SaClientModel对象 */ - public void checkClientTokenScope(String clientToken, String... scopes) { - if(scopes == null || scopes.length == 0) { - return; - } - ClientTokenModel ct = checkClientToken(clientToken); - List scopeList = ct.scopes; - for (String scope : scopes) { - SaOAuth2Exception.throwBy( ! scopeList.contains(scope), "该 Client-Token 不具备 Scope:" + scope, SaOAuth2ErrorCode.CODE_30109); - } + public SaClientModel checkClientSecret(String clientId, String clientSecret) { + SaClientModel cm = checkClientModel(clientId); + SaOAuth2ClientModelException.throwBy(cm.clientSecret == null || ! cm.clientSecret.equals(clientSecret), "无效client_secret: " + clientSecret, + clientId, SaOAuth2ErrorCode.CODE_30115); + return cm; } - - // ------------------- check 数据校验 - /** - * 判断:指定 loginId 是否对一个 Client 授权给了指定 Scope - * @param loginId 账号id + * 校验:clientId 与 clientSecret 是否正确,并且是否签约了指定 scopes * @param clientId 应用id + * @param clientSecret 秘钥 * @param scopes 权限 - * @return 是否已经授权 + * @return SaClientModel对象 */ - public boolean isGrant(Object loginId, String clientId, List scopes) { - SaOAuth2Dao dao = SaOAuth2Manager.getDao(); - List grantScopeList = dao.getGrantScope(clientId, loginId); - return scopes.isEmpty() || new HashSet<>(grantScopeList).containsAll(scopes); + public SaClientModel checkClientSecretAndScope(String clientId, String clientSecret, List scopes) { + SaClientModel cm = checkClientSecret(clientId, clientSecret); + checkContractScope(cm, scopes); + return cm; } /** - * 判断:指定 loginId 在指定 Client 请求指定 Scope 时,是否需要手动确认授权 - * @param loginId 账号id + * 判断:该 Client 是否签约了指定的 Scope,返回 true 或 false * @param clientId 应用id * @param scopes 权限 - * @return 是否已经授权 + * @return / */ - public boolean isNeedCarefulConfirm(Object loginId, String clientId, List scopes) { - // 如果请求的权限为空,则不需要确认 - if(scopes == null || scopes.isEmpty()) { - return false; - } - - // 如果包含高级权限,则必须手动确认授权 - List higherScopeList = getHigherScopeList(); - if(SaFoxUtil.list1ContainList2AnyElement(scopes, higherScopeList)) { + public boolean isContractScope(String clientId, List scopes) { + try { + checkContractScope(clientId, scopes); return true; - } - - // 如果包含低级权限,则先将低级权限剔除掉 - List lowerScopeList = getLowerScopeList(); - scopes = SaFoxUtil.list1RemoveByList2(scopes, lowerScopeList); - - // 如果剔除后的权限为空,则不需要确认 - if(scopes.isEmpty()) { + } catch (SaOAuth2ClientModelException e) { return false; } - - // 根据近期授权记录,判断是否需要确认 - return !isGrant(loginId, clientId, scopes); } /** - * 校验:该Client是否签约了指定的Scope + * 校验:该 Client 是否签约了指定的 Scope,如果没有则抛出异常 * @param clientId 应用id - * @param scopes 权限(多个用逗号隔开) + * @param scopes 权限列表 + * @return / + */ + public SaClientModel checkContractScope(String clientId, List scopes) { + return checkContractScope(checkClientModel(clientId), scopes); + } + + /** + * 校验:该 Client 是否签约了指定的 Scope,如果没有则抛出异常 + * @param cm 应用 + * @param scopes 权限列表 + * @return / */ - public void checkContract(String clientId, List scopes) { - List clientScopeList = checkClientModel(clientId).contractScopes; - if( ! new HashSet<>(clientScopeList).containsAll(scopes)) { - throw new SaOAuth2Exception("请求的Scope暂未签约").setCode(SaOAuth2ErrorCode.CODE_30112); + public SaClientModel checkContractScope(SaClientModel cm, List scopes) { + if(SaFoxUtil.isEmptyList(scopes)) { + return cm; } + for (String scope : scopes) { + if(! cm.contractScopes.contains(scope)) { + throw new SaOAuth2ClientModelScopeException("该 client 暂未签约 scope: " + scope) + .setClientId(cm.clientId) + .setScope(scope) + .setCode(SaOAuth2ErrorCode.CODE_30112); + } + } + return cm; } + + // --------- redirect_uri 相关 + /** - * 校验:该Client使用指定url作为回调地址,是否合法 + * 校验:该 Client 使用指定 url 作为回调地址,是否合法 * @param clientId 应用id * @param url 指定url */ - public void checkRightUrl(String clientId, String url) { + public void checkRedirectUri(String clientId, String url) { // 1、是否是一个有效的url if( ! SaFoxUtil.isUrl(url)) { - throw new SaOAuth2Exception("无效redirect_url:" + url).setCode(SaOAuth2ErrorCode.CODE_30113); + throw new SaOAuth2ClientModelException("无效redirect_url:" + url) + .setClientId(clientId) + .setCode(SaOAuth2ErrorCode.CODE_30113); } // 2、截取掉?后面的部分 @@ -219,31 +179,34 @@ public class SaOAuth2Template { // http://sa-oauth-server.com:8000/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com/@getInfo // // 但是为了安全起见,这么做还是有必要的 - throw new SaOAuth2Exception("无效 redirect_url(不允许出现@字符):" + url); + throw new SaOAuth2ClientModelException("无效 redirect_url(不允许出现@字符):" + url) + .setClientId(clientId); } // 4、是否在[允许地址列表]之中 SaClientModel clientModel = checkClientModel(clientId); - checkAllowUrlList(clientModel.allowRedirectUris); + checkRedirectUriListNormal(clientModel.allowRedirectUris); if( ! SaStrategy.instance.hasElement.apply(clientModel.allowRedirectUris, url)) { - throw new SaOAuth2Exception("非法 redirect_url: " + url).setCode(SaOAuth2ErrorCode.CODE_30114); + throw new SaOAuth2ClientModelException("非法 redirect_url: " + url) + .setClientId(clientId) + .setCode(SaOAuth2ErrorCode.CODE_30114); } } /** - * 校验配置的 AllowUrl 是否合规,如果不合规则抛出异常 - * @param allowUrlList 待校验的 allow-url 地址列表 + * 校验配置的 allowRedirectUris 是否合规,如果不合规则抛出异常 + * @param redirectUriList 待校验的 allow-url 地址列表 */ - public void checkAllowUrlList(List allowUrlList){ - checkAllowUrlListStaticMethod(allowUrlList); + public void checkRedirectUriListNormal(List redirectUriList){ + checkRedirectUriListNormalStaticMethod(redirectUriList); } /** - * 校验配置的 AllowUrl 是否合规,如果不合规则抛出异常 - * @param allowUrlList 待校验的 allow-url 地址列表 + * 校验配置的 allowRedirectUris 是否合规,如果不合规则抛出异常,静态方法内部实现 + * @param redirectUriList 待校验的 allow-url 地址列表 */ - public static void checkAllowUrlListStaticMethod(List allowUrlList){ - for (String url : allowUrlList) { + public static void checkRedirectUriListNormalStaticMethod(List redirectUriList){ + for (String url : redirectUriList) { int index = url.indexOf("*"); // 如果配置了 * 字符,则必须出现在最后一位,否则属于无效配置项 if(index != -1 && index != url.length() - 1) { @@ -273,36 +236,55 @@ public class SaOAuth2Template { } } + // --------- 授权相关 + /** - * 校验:clientId 与 clientSecret 是否正确 + * 判断:指定 loginId 是否对一个 Client 授权给了指定 Scope + * @param loginId 账号id * @param clientId 应用id - * @param clientSecret 秘钥 - * @return SaClientModel对象 + * @param scopes 权限 + * @return 是否已经授权 */ - public SaClientModel checkClientSecret(String clientId, String clientSecret) { - SaClientModel cm = checkClientModel(clientId); - SaOAuth2Exception.throwBy(cm.clientSecret == null || ! cm.clientSecret.equals(clientSecret), - "无效client_secret: " + clientSecret, SaOAuth2ErrorCode.CODE_30115); - return cm; + public boolean isGrantScope(Object loginId, String clientId, List scopes) { + List grantScopeList = SaOAuth2Manager.getDao().getGrantScope(clientId, loginId); + return SaFoxUtil.list1ContainList2AllElement(grantScopeList, scopes); } + /** - * 校验:clientId 与 clientSecret 是否正确,并且是否签约了指定 scopes + * 判断:指定 loginId 在指定 Client 请求指定 Scope 时,是否需要手动确认授权 + * @param loginId 账号id * @param clientId 应用id - * @param clientSecret 秘钥 * @param scopes 权限 - * @return SaClientModel对象 + * @return 是否已经授权 */ - public SaClientModel checkClientSecretAndScope(String clientId, String clientSecret, List scopes) { - // 先校验 clientSecret - SaClientModel cm = checkClientSecret(clientId, clientSecret); - // 再校验 是否签约 - List clientScopeList = cm.contractScopes; - if( ! new HashSet<>(clientScopeList).containsAll(scopes)) { - throw new SaOAuth2Exception("请求的Scope暂未签约").setCode(SaOAuth2ErrorCode.CODE_30116); + public boolean isNeedCarefulConfirm(Object loginId, String clientId, List scopes) { + // 如果请求的权限为空,则不需要确认 + if(scopes == null || scopes.isEmpty()) { + return false; } - // 返回数据 - return cm; + + // 如果包含高级权限,则必须手动确认授权 + List higherScopeList = getHigherScopeList(); + if(SaFoxUtil.list1ContainList2AnyElement(scopes, higherScopeList)) { + return true; + } + + // 如果包含低级权限,则先将低级权限剔除掉 + List lowerScopeList = getLowerScopeList(); + scopes = SaFoxUtil.list1RemoveByList2(scopes, lowerScopeList); + + // 如果剔除后的权限为空,则不需要确认 + if(scopes.isEmpty()) { + return false; + } + + // 根据近期授权记录,判断是否需要确认 + return !isGrantScope(loginId, clientId, scopes); } + + + // --------- 请求数据校验相关 + /** * 校验:使用 code 获取 token 时提供的参数校验 * @param code 授权码 @@ -317,23 +299,24 @@ public class SaOAuth2Template { // 校验:Code是否存在 CodeModel cm = dao.getCode(code); - SaOAuth2Exception.throwBy(cm == null, "无效code: " + code, SaOAuth2ErrorCode.CODE_30117); + SaOAuth2Exception.throwBy(cm == null, "无效 code: " + code, SaOAuth2ErrorCode.CODE_30117); // 校验:ClientId是否一致 - SaOAuth2Exception.throwBy( ! cm.clientId.equals(clientId), "无效client_id: " + clientId, SaOAuth2ErrorCode.CODE_30118); + SaOAuth2Exception.throwBy( ! cm.clientId.equals(clientId), "无效 client_id: " + clientId, SaOAuth2ErrorCode.CODE_30118); // 校验:Secret是否正确 String dbSecret = checkClientModel(clientId).clientSecret; - SaOAuth2Exception.throwBy(dbSecret == null || ! dbSecret.equals(clientSecret), "无效client_secret: " + clientSecret, SaOAuth2ErrorCode.CODE_30119); + SaOAuth2Exception.throwBy(dbSecret == null || ! dbSecret.equals(clientSecret), "无效 client_secret: " + clientSecret, SaOAuth2ErrorCode.CODE_30119); // 如果提供了redirectUri,则校验其是否与请求Code时提供的一致 if( ! SaFoxUtil.isEmpty(redirectUri)) { - SaOAuth2Exception.throwBy( ! redirectUri.equals(cm.redirectUri), "无效redirect_uri: " + redirectUri, SaOAuth2ErrorCode.CODE_30120); + SaOAuth2Exception.throwBy( ! redirectUri.equals(cm.redirectUri), "无效 redirect_uri: " + redirectUri, SaOAuth2ErrorCode.CODE_30120); } // 返回CodeModel return cm; } + /** * 校验:使用 Refresh-Token 刷新 Access-Token 时提供的参数校验 * @param clientId 应用id @@ -347,18 +330,20 @@ public class SaOAuth2Template { // 校验:Refresh-Token是否存在 RefreshTokenModel rt = dao.getRefreshToken(refreshToken); - SaOAuth2Exception.throwBy(rt == null, "无效refresh_token: " + refreshToken, SaOAuth2ErrorCode.CODE_30121); + SaOAuth2RefreshTokenException.throwBy(rt == null, "无效 refresh_token: " + refreshToken, refreshToken, SaOAuth2ErrorCode.CODE_30121); // 校验:ClientId是否一致 - SaOAuth2Exception.throwBy( ! rt.clientId.equals(clientId), "无效client_id: " + clientId, SaOAuth2ErrorCode.CODE_30122); + SaOAuth2ClientModelException.throwBy( ! rt.clientId.equals(clientId), "无效 client_id: " + clientId, clientId, SaOAuth2ErrorCode.CODE_30122); // 校验:Secret是否正确 String dbSecret = checkClientModel(clientId).clientSecret; - SaOAuth2Exception.throwBy(dbSecret == null || ! dbSecret.equals(clientSecret), "无效client_secret: " + clientSecret, SaOAuth2ErrorCode.CODE_30123); + SaOAuth2ClientModelException.throwBy(dbSecret == null || ! dbSecret.equals(clientSecret), "无效client_secret: " + clientSecret, + clientId, SaOAuth2ErrorCode.CODE_30123); - // 返回Refresh-Token + // 返回 Refresh-Token return rt; } + /** * 校验:Access-Token、clientId、clientSecret 三者是否匹配成功 * @param clientId 应用id @@ -368,30 +353,271 @@ public class SaOAuth2Template { */ public AccessTokenModel checkAccessTokenParam(String clientId, String clientSecret, String accessToken) { AccessTokenModel at = checkAccessToken(accessToken); - SaOAuth2Exception.throwBy( ! at.clientId.equals(clientId), "无效client_id:" + clientId, SaOAuth2ErrorCode.CODE_30124); + SaOAuth2ClientModelException.throwBy( ! at.clientId.equals(clientId), "无效 client_id:" + clientId, clientId, SaOAuth2ErrorCode.CODE_30124); checkClientSecret(clientId, clientSecret); return at; } + // ----------------- Access-Token 相关 ----------------- + /** - * 回收指定的 ClientToken + * 获取 AccessTokenModel,无效的 AccessToken 会返回 null + * @param accessToken / + * @return / + */ + public AccessTokenModel getAccessToken(String accessToken) { + return SaOAuth2Manager.getDao().getAccessToken(accessToken); + } + + /** + * 校验 Access-Token,成功返回 AccessTokenModel,失败则抛出异常 + * @param accessToken / + * @return / + */ + public AccessTokenModel checkAccessToken(String accessToken) { + AccessTokenModel at = SaOAuth2Manager.getDao().getAccessToken(accessToken); + if(at == null) { + throw new SaOAuth2AccessTokenException("无效 access_token: " + accessToken) + .setAccessToken(accessToken) + .setCode(SaOAuth2ErrorCode.CODE_30106); + } + return at; + } + + /** + * 获取 Access-Token,根据索引: clientId、loginId + * @param clientId / + * @param loginId / + * @return / + */ + public String getAccessTokenValue(String clientId, Object loginId) { + return SaOAuth2Manager.getDao().getAccessTokenValue(clientId, loginId); + } + + /** + * 判断:指定 Access-Token 是否具有指定 Scope 列表,返回 true 或 false + * @param accessToken Access-Token + * @param scopes 需要校验的权限列表 + */ + public boolean hasAccessTokenScope(String accessToken, String... scopes) { + try { + checkAccessTokenScope(accessToken, scopes); + return true; + } catch (SaOAuth2AccessTokenException e) { + return false; + } + } + + /** + * 校验:指定 Access-Token 是否具有指定 Scope 列表,如果不具备则抛出异常 + * @param accessToken Access-Token + * @param scopes 需要校验的权限列表 + */ + public void checkAccessTokenScope(String accessToken, String... scopes) { + if(SaFoxUtil.isEmptyArray(scopes)) { + return; + } + ClientTokenModel ct = checkClientToken(accessToken); + for (String scope : scopes) { + if(! ct.scopes.contains(scope)) { + throw new SaOAuth2AccessTokenScopeException("该 access_token 不具备 scope:" + scope) + .setAccessToken(accessToken) + .setScope(scope) + .setCode(SaOAuth2ErrorCode.CODE_30108); + } + } + } + + /** + * 获取 Access-Token 所代表的LoginId + * @param accessToken Access-Token + * @return LoginId + */ + public Object getLoginIdByAccessToken(String accessToken) { + return checkAccessToken(accessToken).loginId; + } + + /** + * 获取 Access-Token 所代表的 clientId + * @param accessToken Access-Token + * @return LoginId + */ + public Object getClientIdByAccessToken(String accessToken) { + return checkAccessToken(accessToken).clientId; + } + + /** + * 回收 Access-Token + * @param accessToken Access-Token值 + */ + public void revokeAccessToken(String accessToken) { + AccessTokenModel at = getAccessToken(accessToken); + if(at == null) { + return; + } + + // 删 at、索引 + SaOAuth2Dao dao = SaOAuth2Manager.getDao(); + dao.deleteAccessToken(accessToken); + dao.deleteAccessTokenIndex(at.clientId, at.loginId); + + // 删对应的 rt、索引 +// String rtValue = dao.getRefreshTokenValue(at.clientId, at.loginId); +// dao.deleteRefreshToken(rtValue); +// dao.deleteRefreshTokenIndex(at.clientId, at.loginId); + } + + /** + * 回收 Access-Token,根据索引: clientId、loginId + * @param clientId / + * @param loginId / + */ + public void revokeAccessTokenByIndex(String clientId, Object loginId) { + SaOAuth2Dao dao = SaOAuth2Manager.getDao(); + + // 删 at、删索引 + String accessToken = getAccessTokenValue(clientId, loginId); + if(accessToken != null) { + dao.deleteAccessToken(accessToken); + dao.deleteAccessTokenIndex(clientId, loginId); + } + } + + + // ----------------- Refresh-Token 相关 ----------------- + + /** + * 获取 RefreshTokenModel,无效的 RefreshToken 会返回 null + * @param refreshToken / + * @return / + */ + public RefreshTokenModel getRefreshToken(String refreshToken) { + return SaOAuth2Manager.getDao().getRefreshToken(refreshToken); + } + + /** + * 校验 Refresh-Token,成功返回 RefreshTokenModel,失败则抛出异常 + * @param refreshToken / + * @return / + */ + public RefreshTokenModel checkRefreshToken(String refreshToken) { + RefreshTokenModel rt = SaOAuth2Manager.getDao().getRefreshToken(refreshToken); + if(rt == null) { + throw new SaOAuth2RefreshTokenException("无效 refresh_token: " + refreshToken) + .setRefreshToken(refreshToken) + .setCode(SaOAuth2ErrorCode.CODE_30111); + } + return rt; + } + + /** + * 获取 Refresh-Token,根据索引: clientId、loginId + * @param clientId / + * @param loginId / + * @return / + */ + public String getRefreshTokenValue(String clientId, Object loginId) { + return SaOAuth2Manager.getDao().getRefreshTokenValue(clientId, loginId); + } + + /** + * 根据 RefreshToken 刷新出一个 AccessToken + * @param refreshToken / + * @return / + */ + public AccessTokenModel refreshAccessToken(String refreshToken) { + return SaOAuth2Manager.getDataGenerate().refreshAccessToken(refreshToken); + } + + + // ----------------- Client-Token 相关 ----------------- + + /** + * 获取 ClientTokenModel,无效的 ClientToken 会返回 null + * @param clientToken / + * @return / + */ + public ClientTokenModel getClientToken(String clientToken) { + return SaOAuth2Manager.getDao().getClientToken(clientToken); + } + + /** + * 校验 Client-Token,成功返回 ClientTokenModel,失败则抛出异常 + * @param clientToken / + * @return / + */ + public ClientTokenModel checkClientToken(String clientToken) { + ClientTokenModel ct = getClientToken(clientToken); + if(ct == null) { + throw new SaOAuth2ClientTokenException("无效 client_token: " + clientToken) + .setClientToken(clientToken) + .setCode(SaOAuth2ErrorCode.CODE_30107); + } + return ct; + } + + /** + * 获取 ClientToken,根据索引: clientId + * @param clientId / + * @return / + */ + public String getClientTokenValue(String clientId) { + return SaOAuth2Manager.getDao().getClientTokenValue(clientId); + } + + /** + * 判断:指定 Client-Token 是否具有指定 Scope 列表,返回 true 或 false + * @param clientToken Client-Token + * @param scopes 需要校验的权限列表 + */ + public boolean hasClientTokenScope(String clientToken, String... scopes) { + try { + checkClientTokenScope(clientToken, scopes); + return true; + } catch (SaOAuth2ClientTokenException e) { + return false; + } + } + + /** + * 校验:指定 Client-Token 是否具有指定 Scope 列表,如果不具备则抛出异常 + * @param clientToken Client-Token + * @param scopes 需要校验的权限列表 + */ + public void checkClientTokenScope(String clientToken, String... scopes) { + if(SaFoxUtil.isEmptyArray(scopes)) { + return; + } + ClientTokenModel ct = checkClientToken(clientToken); + for (String scope : scopes) { + if(! ct.scopes.contains(scope)) { + throw new SaOAuth2ClientTokenScopeException("该 client_token 不具备 scope:" + scope) + .setClientToken(clientToken) + .setScope(scope) + .setCode(SaOAuth2ErrorCode.CODE_30109); + } + } + } + + /** + * 回收 ClientToken * * @param clientToken / */ public void revokeClientToken(String clientToken) { - SaOAuth2Dao dao = SaOAuth2Manager.getDao(); - ClientTokenModel ctModel = dao.getClientToken(clientToken); - if(ctModel == null) { + ClientTokenModel ct = getClientToken(clientToken); + if(ct == null) { return; } - // 删 ct、索引 + // 删 ct、删索引 + SaOAuth2Dao dao = SaOAuth2Manager.getDao(); dao.deleteClientToken(clientToken); - dao.deleteClientTokenIndex(ctModel.clientId); + dao.deleteClientTokenIndex(ct.clientId); } /** - * 回收指定的 ClientToken,根据索引: clientId + * 回收 ClientToken,根据索引: clientId * * @param clientId / */ @@ -399,33 +625,30 @@ public class SaOAuth2Template { SaOAuth2Dao dao = SaOAuth2Manager.getDao(); // 删 clientToken - String clientToken = dao.getClientTokenValue(clientId); + String clientToken = getClientTokenValue(clientId); if(clientToken != null) { dao.deleteClientToken(clientToken); dao.deleteClientTokenIndex(clientId); } + } + /** + * 回收 PastToken,根据索引: clientId + * + * @param clientId / + */ + public void revokePastTokenByIndex(String clientId) { + SaOAuth2Dao dao = SaOAuth2Manager.getDao(); // 删 pastToken String pastToken = dao.getPastTokenValue(clientId); if(pastToken != null) { dao.deletePastToken(pastToken); dao.deletePastTokenIndex(clientId); } - } - - // ------------------- 包装其它 bean 的方法 - - /** - * 获取:Access-Token Model - * @param accessToken / - * @return / - */ - public AccessTokenModel getAccessToken(String accessToken) { - return SaOAuth2Manager.getDao().getAccessToken(accessToken); - } + // ----------------- 包装其它 bean 的方法 ----------------- /** * 持久化:用户授权记录 @@ -455,8 +678,4 @@ public class SaOAuth2Template { return SaOAuth2Manager.getDataConverter().convertScopeStringToList(lowerScope); } - - - - } diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Util.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Util.java index 223d7dcb..4d667933 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Util.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Util.java @@ -18,240 +18,312 @@ package cn.dev33.satoken.oauth2.template; import cn.dev33.satoken.oauth2.SaOAuth2Manager; import cn.dev33.satoken.oauth2.data.model.AccessTokenModel; import cn.dev33.satoken.oauth2.data.model.ClientTokenModel; -import cn.dev33.satoken.oauth2.data.model.CodeModel; import cn.dev33.satoken.oauth2.data.model.RefreshTokenModel; import cn.dev33.satoken.oauth2.data.model.loader.SaClientModel; import java.util.List; /** - * Sa-Token-OAuth2 模块 工具类 + * Sa-Token OAuth2 模块 工具类 * * @author click33 * @since 1.23.0 */ public class SaOAuth2Util { - - // ------------------- 资源校验API - + + // ----------------- ClientModel 相关 ----------------- + /** - * 根据id获取Client信息, 如果Client为空,则抛出异常 - * @param clientId 应用id - * @return ClientModel + * 获取 ClientModel,根据 clientId + * + * @param clientId / + * @return / + */ + public static SaClientModel getClientModel(String clientId) { + return SaOAuth2Manager.getTemplate().getClientModel(clientId); + } + + /** + * 校验 clientId 信息并返回 ClientModel,如果找不到对应 Client 信息则抛出异常 + * @param clientId / + * @return / */ public static SaClientModel checkClientModel(String clientId) { return SaOAuth2Manager.getTemplate().checkClientModel(clientId); } - + /** - * 获取 Access-Token,如果AccessToken为空则抛出异常 - * @param accessToken . - * @return . + * 校验:clientId 与 clientSecret 是否正确 + * @param clientId 应用id + * @param clientSecret 秘钥 + * @return SaClientModel对象 */ - public static AccessTokenModel checkAccessToken(String accessToken) { - return SaOAuth2Manager.getTemplate().checkAccessToken(accessToken); + public static SaClientModel checkClientSecret(String clientId, String clientSecret) { + return SaOAuth2Manager.getTemplate().checkClientSecret(clientId, clientSecret); } - + /** - * 获取 Client-Token,如果ClientToken为空则抛出异常 - * @param clientToken . - * @return . + * 校验:clientId 与 clientSecret 是否正确,并且是否签约了指定 scopes + * @param clientId 应用id + * @param clientSecret 秘钥 + * @param scopes 权限 + * @return SaClientModel对象 */ - public static ClientTokenModel checkClientToken(String clientToken) { - return SaOAuth2Manager.getTemplate().checkClientToken(clientToken); + public static SaClientModel checkClientSecretAndScope(String clientId, String clientSecret, List scopes) { + return SaOAuth2Manager.getTemplate().checkClientSecretAndScope(clientId, clientSecret, scopes); } - + /** - * 获取 Access-Token 所代表的LoginId - * @param accessToken Access-Token - * @return LoginId + * 判断:该 Client 是否签约了指定的 Scope,返回 true 或 false + * @param clientId 应用id + * @param scopes 权限 + * @return / */ - public static Object getLoginIdByAccessToken(String accessToken) { - return SaOAuth2Manager.getTemplate().getLoginIdByAccessToken(accessToken); + public static boolean isContractScope(String clientId, List scopes) { + return SaOAuth2Manager.getTemplate().isContractScope(clientId, scopes); } - + /** - * 校验:指定 Access-Token 是否具有指定 Scope - * @param accessToken Access-Token - * @param scopes 需要校验的权限列表 + * 校验:该 Client 是否签约了指定的 Scope,如果没有则抛出异常 + * @param clientId 应用id + * @param scopes 权限列表 + * @return / */ - public static void checkScope(String accessToken, String... scopes) { - SaOAuth2Manager.getTemplate().checkScope(accessToken, scopes); + public static SaClientModel checkContractScope(String clientId, List scopes) { + return SaOAuth2Manager.getTemplate().checkContractScope(clientId, scopes); } - + /** - * 校验:指定 Client-Token 是否具有指定 Scope - * @param clientToken Client-Token - * @param scopes 需要校验的权限列表 + * 校验:该 Client 是否签约了指定的 Scope,如果没有则抛出异常 + * @param cm 应用 + * @param scopes 权限列表 + * @return / */ - public static void checkClientTokenScope(String clientToken, String... scopes) { - SaOAuth2Manager.getTemplate().checkClientTokenScope(clientToken, scopes); + public static SaClientModel checkContractScope(SaClientModel cm, List scopes) { + return SaOAuth2Manager.getTemplate().checkContractScope(cm, scopes); } + // --------- redirect_uri 相关 - // ------------------- 数据校验 - /** - * 判断:指定 loginId 是否对一个 Client 授权给了指定 Scope - * @param loginId 账号id - * @param clientId 应用id + * 校验:该 Client 使用指定 url 作为回调地址,是否合法 + * @param clientId 应用id + * @param url 指定url + */ + public static void checkRedirectUri(String clientId, String url) { + SaOAuth2Manager.getTemplate().checkRedirectUri(clientId, url); + } + + // --------- 授权相关 + + /** + * 判断:指定 loginId 是否对一个 Client 授权给了指定 Scope + * @param loginId 账号id + * @param clientId 应用id * @param scopes 权限 * @return 是否已经授权 */ - public static boolean isGrant(Object loginId, String clientId, List scopes) { - return SaOAuth2Manager.getTemplate().isGrant(loginId, clientId, scopes); + public static boolean isGrantScope(Object loginId, String clientId, List scopes) { + return SaOAuth2Manager.getTemplate().isGrantScope(loginId, clientId, scopes); } - + + + // ----------------- Access-Token 相关 ----------------- + /** - * 校验:该Client是否签约了指定的Scope - * @param clientId 应用id - * @param scopes 权限(多个用逗号隔开) + * 获取 AccessTokenModel,无效的 AccessToken 会返回 null + * @param accessToken / + * @return / */ - public static void checkContract(String clientId, List scopes) { - SaOAuth2Manager.getTemplate().checkContract(clientId, scopes); + public static AccessTokenModel getAccessToken(String accessToken) { + return SaOAuth2Manager.getTemplate().getAccessToken(accessToken); } - + /** - * 校验:该Client使用指定url作为回调地址,是否合法 - * @param clientId 应用id - * @param url 指定url + * 校验 Access-Token,成功返回 AccessTokenModel,失败则抛出异常 + * @param accessToken / + * @return / */ - public static void checkRightUrl(String clientId, String url) { - SaOAuth2Manager.getTemplate().checkRightUrl(clientId, url); + public static AccessTokenModel checkAccessToken(String accessToken) { + return SaOAuth2Manager.getTemplate().checkAccessToken(accessToken); } - + /** - * 校验:clientId 与 clientSecret 是否正确 - * @param clientId 应用id - * @param clientSecret 秘钥 - * @return SaClientModel对象 + * 获取 Access-Token,根据索引: clientId、loginId + * @param clientId / + * @param loginId / + * @return / */ - public static SaClientModel checkClientSecret(String clientId, String clientSecret) { - return SaOAuth2Manager.getTemplate().checkClientSecret(clientId, clientSecret); + public static String getAccessTokenValue(String clientId, Object loginId) { + return SaOAuth2Manager.getTemplate().getAccessTokenValue(clientId, loginId); } - + /** - * 校验:clientId 与 clientSecret 是否正确,并且是否签约了指定 scopes - * @param clientId 应用id - * @param clientSecret 秘钥 - * @param scopes 权限 - * @return SaClientModel对象 + * 判断:指定 Access-Token 是否具有指定 Scope 列表,返回 true 或 false + * @param accessToken Access-Token + * @param scopes 需要校验的权限列表 */ - public static SaClientModel checkClientSecretAndScope(String clientId, String clientSecret, List scopes) { - return SaOAuth2Manager.getTemplate().checkClientSecretAndScope(clientId, clientSecret, scopes); + public static boolean hasAccessTokenScope(String accessToken, String... scopes) { + return SaOAuth2Manager.getTemplate().hasAccessTokenScope(accessToken, scopes); } - + /** - * 校验:使用 code 获取 token 时提供的参数校验 - * @param code 授权码 - * @param clientId 应用id - * @param clientSecret 秘钥 - * @param redirectUri 重定向地址 - * @return CodeModel对象 + * 校验:指定 Access-Token 是否具有指定 Scope 列表,如果不具备则抛出异常 + * @param accessToken Access-Token + * @param scopes 需要校验的权限列表 */ - public static CodeModel checkGainTokenParam(String code, String clientId, String clientSecret, String redirectUri) { - return SaOAuth2Manager.getTemplate().checkGainTokenParam(code, clientId, clientSecret, redirectUri); + public static void checkAccessTokenScope(String accessToken, String... scopes) { + SaOAuth2Manager.getTemplate().checkAccessTokenScope(accessToken, scopes); } /** - * 校验:使用 Refresh-Token 刷新 Access-Token 时提供的参数校验 - * @param clientId 应用id - * @param clientSecret 秘钥 - * @param refreshToken Refresh-Token - * @return CodeModel对象 + * 获取 Access-Token 所代表的LoginId + * @param accessToken Access-Token + * @return LoginId */ - public static RefreshTokenModel checkRefreshTokenParam(String clientId, String clientSecret, String refreshToken) { - return SaOAuth2Manager.getTemplate().checkRefreshTokenParam(clientId, clientSecret, refreshToken); + public static Object getLoginIdByAccessToken(String accessToken) { + return SaOAuth2Manager.getTemplate().getLoginIdByAccessToken(accessToken); } - + /** - * 校验:Access-Token、clientId、clientSecret 三者是否匹配成功 - * @param clientId 应用id - * @param clientSecret 秘钥 - * @param accessToken Access-Token - * @return SaClientModel对象 + * 获取 Access-Token 所代表的 clientId + * @param accessToken Access-Token + * @return LoginId */ - public static AccessTokenModel checkAccessTokenParam(String clientId, String clientSecret, String accessToken) { - return SaOAuth2Manager.getTemplate().checkAccessTokenParam(clientId, clientSecret, accessToken); + public static Object getClientIdByAccessToken(String accessToken) { + return SaOAuth2Manager.getTemplate().getClientIdByAccessToken(accessToken); } /** - * 回收指定的 ClientToken - * - * @param clientToken / + * 回收 Access-Token + * @param accessToken Access-Token值 */ - public static void revokeClientToken(String clientToken) { - SaOAuth2Manager.getTemplate().revokeClientToken(clientToken); + public static void revokeAccessToken(String accessToken) { + SaOAuth2Manager.getTemplate().revokeAccessToken(accessToken); } /** - * 回收指定的 ClientToken,根据索引:clientId - * + * 回收 Access-Token,根据索引: clientId、loginId * @param clientId / + * @param loginId / */ - public static void revokeClientTokenByIndex(String clientId) { - SaOAuth2Manager.getTemplate().revokeClientTokenByIndex(clientId); + public static void revokeAccessTokenByIndex(String clientId, Object loginId) { + SaOAuth2Manager.getTemplate().revokeAccessTokenByIndex(clientId, loginId); } - // ------------------- save 数据 - + + // ----------------- Refresh-Token 相关 ----------------- + /** - * 持久化:用户授权记录 - * @param clientId 应用id - * @param loginId 账号id - * @param scopes 权限列表 + * 获取 RefreshTokenModel,无效的 RefreshToken 会返回 null + * @param refreshToken / + * @return / */ - public static void saveGrantScope(String clientId, Object loginId, List scopes) { - SaOAuth2Manager.getTemplate().saveGrantScope(clientId, loginId, scopes); + public static RefreshTokenModel getRefreshToken(String refreshToken) { + return SaOAuth2Manager.getTemplate().getRefreshToken(refreshToken); } - - - // ------------------- get 数据 - + /** - * 获取:Code Model - * @param code . - * @return . + * 校验 Refresh-Token,成功返回 RefreshTokenModel,失败则抛出异常 + * @param refreshToken / + * @return / */ - public static CodeModel getCode(String code) { - return SaOAuth2Manager.getDao().getCode(code); + public static RefreshTokenModel checkRefreshToken(String refreshToken) { + return SaOAuth2Manager.getTemplate().checkRefreshToken(refreshToken); } /** - * 获取:Access-Token Model - * @param accessToken . - * @return . + * 获取 Refresh-Token,根据索引: clientId、loginId + * @param clientId / + * @param loginId / + * @return / */ - public static AccessTokenModel getAccessToken(String accessToken) { - return SaOAuth2Manager.getDao().getAccessToken(accessToken); + public static String getRefreshTokenValue(String clientId, Object loginId) { + return SaOAuth2Manager.getTemplate().getRefreshTokenValue(clientId, loginId); } - + /** - * 获取:Refresh-Token Model - * @param refreshToken . - * @return . + * 根据 RefreshToken 刷新出一个 AccessToken + * @param refreshToken / + * @return / */ - public static RefreshTokenModel getRefreshToken(String refreshToken) { - return SaOAuth2Manager.getDao().getRefreshToken(refreshToken); + public static AccessTokenModel refreshAccessToken(String refreshToken) { + return SaOAuth2Manager.getTemplate().refreshAccessToken(refreshToken); } - + + + // ----------------- Client-Token 相关 ----------------- + /** - * 获取:Client-Token Model - * @param clientToken . - * @return . + * 获取 ClientTokenModel,无效的 ClientToken 会返回 null + * @param clientToken / + * @return / */ public static ClientTokenModel getClientToken(String clientToken) { - return SaOAuth2Manager.getDao().getClientToken(clientToken); + return SaOAuth2Manager.getTemplate().getClientToken(clientToken); } - + /** - * 获取:用户授权记录 - * @param clientId 应用id - * @param loginId 账号id - * @return 权限 + * 校验 Client-Token,成功返回 ClientTokenModel,失败则抛出异常 + * @param clientToken / + * @return / + */ + public static ClientTokenModel checkClientToken(String clientToken) { + return SaOAuth2Manager.getTemplate().checkClientToken(clientToken); + } + + /** + * 获取 ClientToken,根据索引: clientId + * @param clientId / + * @return / + */ + public static String getClientTokenValue(String clientId) { + return SaOAuth2Manager.getTemplate().getClientTokenValue(clientId); + } + + /** + * 判断:指定 Client-Token 是否具有指定 Scope 列表,返回 true 或 false + * @param clientToken Client-Token + * @param scopes 需要校验的权限列表 + */ + public static boolean hasClientTokenScope(String clientToken, String... scopes) { + return SaOAuth2Manager.getTemplate().hasClientTokenScope(clientToken, scopes); + } + + /** + * 校验:指定 Client-Token 是否具有指定 Scope 列表,如果不具备则抛出异常 + * @param clientToken Client-Token + * @param scopes 需要校验的权限列表 + */ + public static void checkClientTokenScope(String clientToken, String... scopes) { + SaOAuth2Manager.getTemplate().checkClientTokenScope(clientToken, scopes); + } + + /** + * 回收 ClientToken + * + * @param clientToken / + */ + public static void revokeClientToken(String clientToken) { + SaOAuth2Manager.getTemplate().revokeClientToken(clientToken); + } + + /** + * 回收 ClientToken,根据索引: clientId + * + * @param clientId / + */ + public static void revokeClientTokenByIndex(String clientId) { + SaOAuth2Manager.getTemplate().revokeClientTokenByIndex(clientId); + } + + /** + * 回收 PastToken,根据索引: clientId + * + * @param clientId / */ - public static List getGrantScope(String clientId, Object loginId) { - return SaOAuth2Manager.getDao().getGrantScope(clientId, loginId); + public static void revokePastTokenByIndex(String clientId) { + SaOAuth2Manager.getTemplate().revokePastTokenByIndex(clientId); } } -- Gitee From 672f3bd081909f6b3493fc0e5d3c48c55c29c8c0 Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Sun, 25 Aug 2024 10:50:16 +0800 Subject: [PATCH 155/172] =?UTF-8?q?=E9=94=99=E8=AF=AF=E8=B0=83=E7=94=A8API?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/pj/oauth2/SaOAuth2ServerController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java index 94a95446..f2acd722 100644 --- a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java @@ -77,7 +77,7 @@ public class SaOAuth2ServerController { System.out.println("-------- 此Access-Token对应的账号id: " + loginId); // 校验 Access-Token 是否具有权限: userinfo - SaOAuth2Util.checkScope(accessToken, "userinfo"); + SaOAuth2Util.checkAccessTokenScope(accessToken, "userinfo"); // 模拟账号信息 (真实环境需要查询数据库获取信息) Map map = new LinkedHashMap<>(); -- Gitee From 6a9f25093dacf50a0664625afdf44b7419f7ddcb Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Sun, 25 Aug 2024 11:46:43 +0800 Subject: [PATCH 156/172] =?UTF-8?q?=E7=BB=86=E8=8A=82=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sa-token-demo/sa-token-demo-solon/pom.xml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sa-token-demo/sa-token-demo-solon/pom.xml b/sa-token-demo/sa-token-demo-solon/pom.xml index ecd93e43..c3178eaa 100644 --- a/sa-token-demo/sa-token-demo-solon/pom.xml +++ b/sa-token-demo/sa-token-demo-solon/pom.xml @@ -10,12 +10,15 @@ org.noear solon-parent - 2.7.0 + 2.9.1 + 17 + 17 + 17 1.38.0 UTF-8 UTF-8 -- Gitee From beb958f2744e47c4f97512c937a13980939698ed Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Sun, 25 Aug 2024 14:02:50 +0800 Subject: [PATCH 157/172] =?UTF-8?q?=E6=96=B0=E5=A2=9Esa-token-oauth2=20?= =?UTF-8?q?=E6=B3=A8=E8=A7=A3=E9=89=B4=E6=9D=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pj/oauth2/SaOAuthClientController.java | 2 +- .../pj/oauth2/SaOAuth2ServerController.java | 9 +- .../oauth2/custom/CustomOidcScopeHandler.java | 64 +++++----- .../custom/PhoneCodeGrantTypeHandler.java | 114 +++++++++--------- .../oauth2/custom/PhoneLoginController.java | 52 ++++---- .../oauth2/custom/UserinfoScopeHandler.java | 82 ++++++------- .../GlobalExceptionHandler.java | 2 +- .../java/com/pj/satoken/SaTokenConfigure.java | 27 +++++ .../main/java/com/pj/test/TestController.java | 72 +++++++++++ sa-token-doc/_sidebar.md | 1 + sa-token-doc/oauth2/oauth2-at-check.md | 94 +++++++++++++++ .../oauth2/annotation/SaCheckAccessToken.java | 42 +++++++ .../annotation/SaCheckClientIdSecret.java | 35 ++++++ .../oauth2/annotation/SaCheckClientToken.java | 42 +++++++ .../handler/SaCheckAccessTokenHandler.java | 48 ++++++++ .../handler/SaCheckClientIdSecretHandler.java | 46 +++++++ .../handler/SaCheckClientTokenHandler.java | 48 ++++++++ .../satoken/oauth2/consts/SaOAuth2Consts.java | 1 + .../data/resolver/SaOAuth2DataResolver.java | 8 ++ .../SaOAuth2DataResolverDefaultImpl.java | 27 +++++ .../processor/SaOAuth2ServerProcessor.java | 12 +- .../oauth2/template/SaOAuth2Template.java | 11 +- .../spring/oauth2/SaOAuth2BeanRegister.java | 34 ++++++ 23 files changed, 698 insertions(+), 175 deletions(-) rename sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/{current => satoken}/GlobalExceptionHandler.java (94%) create mode 100644 sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/satoken/SaTokenConfigure.java create mode 100644 sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/test/TestController.java create mode 100644 sa-token-doc/oauth2/oauth2-at-check.md create mode 100644 sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/annotation/SaCheckAccessToken.java create mode 100644 sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/annotation/SaCheckClientIdSecret.java create mode 100644 sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/annotation/SaCheckClientToken.java create mode 100644 sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/annotation/handler/SaCheckAccessTokenHandler.java create mode 100644 sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/annotation/handler/SaCheckClientIdSecretHandler.java create mode 100644 sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/annotation/handler/SaCheckClientTokenHandler.java diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-client/src/main/java/com/pj/oauth2/SaOAuthClientController.java b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-client/src/main/java/com/pj/oauth2/SaOAuthClientController.java index ffa9fe0c..6769068e 100644 --- a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-client/src/main/java/com/pj/oauth2/SaOAuthClientController.java +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-client/src/main/java/com/pj/oauth2/SaOAuthClientController.java @@ -24,7 +24,7 @@ public class SaOAuthClientController { private final String clientId = "1001"; // 应用id private final String clientSecret = "aaaa-bbbb-cccc-dddd-eeee"; // 应用秘钥 private final String serverUrl = "http://sa-oauth-server.com:8000"; // 服务端接口 - + // 进入首页 @RequestMapping("/") public Object index(HttpServletRequest request) { diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java index f2acd722..fee73b06 100644 --- a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java @@ -4,7 +4,6 @@ import cn.dev33.satoken.context.SaHolder; import cn.dev33.satoken.oauth2.SaOAuth2Manager; import cn.dev33.satoken.oauth2.config.SaOAuth2ServerConfig; import cn.dev33.satoken.oauth2.processor.SaOAuth2ServerProcessor; -import cn.dev33.satoken.oauth2.strategy.SaOAuth2Strategy; import cn.dev33.satoken.oauth2.template.SaOAuth2Util; import cn.dev33.satoken.stp.StpUtil; import cn.dev33.satoken.util.SaResult; @@ -57,12 +56,6 @@ public class SaOAuth2ServerController { return new ModelAndView("confirm.html", map); }; - // 重写 AccessToken 创建策略,返回会话令牌 - SaOAuth2Strategy.instance.createAccessToken = (clientId, loginId, scopes) -> { - System.out.println("----返回会话令牌"); - return StpUtil.getOrCreateLoginSession(loginId); - }; - } @@ -89,5 +82,5 @@ public class SaOAuth2ServerController { map.put("address", "山东省 青岛市 城阳区"); return SaResult.ok().setMap(map); } - + } diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/custom/CustomOidcScopeHandler.java b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/custom/CustomOidcScopeHandler.java index 342498e8..835cc377 100644 --- a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/custom/CustomOidcScopeHandler.java +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/custom/CustomOidcScopeHandler.java @@ -1,32 +1,32 @@ -package com.pj.oauth2.custom; - -import cn.dev33.satoken.oauth2.data.model.oidc.IdTokenModel; -import cn.dev33.satoken.oauth2.scope.handler.OidcScopeHandler; -import org.springframework.stereotype.Component; - -/** - * 扩展 OIDC 权限处理器,返回更多字段 - * - * @author click33 - * @since 2024/8/24 - */ -@Component -public class CustomOidcScopeHandler extends OidcScopeHandler { - - @Override - public IdTokenModel workExtraData(IdTokenModel idToken) { - Object userId = idToken.sub; - System.out.println("----- 为 idToken 追加扩展字段 ----- "); - - idToken.extraData.put("uid", userId); // 用户id - idToken.extraData.put("nickname", "lin_xiao_lin"); // 昵称 - idToken.extraData.put("picture", "https://sa-token.cc/logo.png"); // 头像 - idToken.extraData.put("email", "456456@xx.com"); // 邮箱 - idToken.extraData.put("phone_number", "13144556677"); // 手机号 - // 更多字段 ... - // 可参考:https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims - - return idToken; - } - -} \ No newline at end of file +//package com.pj.oauth2.custom; +// +//import cn.dev33.satoken.oauth2.data.model.oidc.IdTokenModel; +//import cn.dev33.satoken.oauth2.scope.handler.OidcScopeHandler; +//import org.springframework.stereotype.Component; +// +///** +// * 扩展 OIDC 权限处理器,返回更多字段 +// * +// * @author click33 +// * @since 2024/8/24 +// */ +//@Component +//public class CustomOidcScopeHandler extends OidcScopeHandler { +// +// @Override +// public IdTokenModel workExtraData(IdTokenModel idToken) { +// Object userId = idToken.sub; +// System.out.println("----- 为 idToken 追加扩展字段 ----- "); +// +// idToken.extraData.put("uid", userId); // 用户id +// idToken.extraData.put("nickname", "lin_xiao_lin"); // 昵称 +// idToken.extraData.put("picture", "https://sa-token.cc/logo.png"); // 头像 +// idToken.extraData.put("email", "456456@xx.com"); // 邮箱 +// idToken.extraData.put("phone_number", "13144556677"); // 手机号 +// // 更多字段 ... +// // 可参考:https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims +// +// return idToken; +// } +// +//} \ No newline at end of file diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/custom/PhoneCodeGrantTypeHandler.java b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/custom/PhoneCodeGrantTypeHandler.java index 6afb64a7..da77857d 100644 --- a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/custom/PhoneCodeGrantTypeHandler.java +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/custom/PhoneCodeGrantTypeHandler.java @@ -1,57 +1,57 @@ -package com.pj.oauth2.custom; - -import cn.dev33.satoken.SaManager; -import cn.dev33.satoken.context.model.SaRequest; -import cn.dev33.satoken.oauth2.SaOAuth2Manager; -import cn.dev33.satoken.oauth2.data.model.AccessTokenModel; -import cn.dev33.satoken.oauth2.data.model.request.RequestAuthModel; -import cn.dev33.satoken.oauth2.exception.SaOAuth2Exception; -import cn.dev33.satoken.oauth2.granttype.handler.SaOAuth2GrantTypeHandlerInterface; -import org.springframework.stereotype.Component; - -import java.util.List; - -/** - * 自定义 phone_code 授权模式处理器 - * - * @author click33 - * @since 2024/8/23 - */ -@Component -public class PhoneCodeGrantTypeHandler implements SaOAuth2GrantTypeHandlerInterface { - - @Override - public String getHandlerGrantType() { - return "phone_code"; - } - - @Override - public AccessTokenModel getAccessToken(SaRequest req, String clientId, List scopes) { - - // 获取前端提交的参数 - String phone = req.getParamNotNull("phone"); - String code = req.getParamNotNull("code"); - String realCode = SaManager.getSaTokenDao().get("phone_code:" + phone); - - // 1、校验验证码是否正确 - if(!code.equals(realCode)) { - throw new SaOAuth2Exception("验证码错误"); - } - - // 2、校验通过,删除验证码 - SaManager.getSaTokenDao().delete("phone_code:" + phone); - - // 3、登录 - long userId = 10001; // 模拟 userId,真实项目应该根据手机号从数据库查询 - - // 4、构建 ra 对象 - RequestAuthModel ra = new RequestAuthModel(); - ra.clientId = clientId; - ra.loginId = userId; - ra.scopes = scopes; - - // 5、生成 Access-Token - AccessTokenModel at = SaOAuth2Manager.getDataGenerate().generateAccessToken(ra, true); - return at; - } -} \ No newline at end of file +//package com.pj.oauth2.custom; +// +//import cn.dev33.satoken.SaManager; +//import cn.dev33.satoken.context.model.SaRequest; +//import cn.dev33.satoken.oauth2.SaOAuth2Manager; +//import cn.dev33.satoken.oauth2.data.model.AccessTokenModel; +//import cn.dev33.satoken.oauth2.data.model.request.RequestAuthModel; +//import cn.dev33.satoken.oauth2.exception.SaOAuth2Exception; +//import cn.dev33.satoken.oauth2.granttype.handler.SaOAuth2GrantTypeHandlerInterface; +//import org.springframework.stereotype.Component; +// +//import java.util.List; +// +///** +// * 自定义 phone_code 授权模式处理器 +// * +// * @author click33 +// * @since 2024/8/23 +// */ +//@Component +//public class PhoneCodeGrantTypeHandler implements SaOAuth2GrantTypeHandlerInterface { +// +// @Override +// public String getHandlerGrantType() { +// return "phone_code"; +// } +// +// @Override +// public AccessTokenModel getAccessToken(SaRequest req, String clientId, List scopes) { +// +// // 获取前端提交的参数 +// String phone = req.getParamNotNull("phone"); +// String code = req.getParamNotNull("code"); +// String realCode = SaManager.getSaTokenDao().get("phone_code:" + phone); +// +// // 1、校验验证码是否正确 +// if(!code.equals(realCode)) { +// throw new SaOAuth2Exception("验证码错误"); +// } +// +// // 2、校验通过,删除验证码 +// SaManager.getSaTokenDao().delete("phone_code:" + phone); +// +// // 3、登录 +// long userId = 10001; // 模拟 userId,真实项目应该根据手机号从数据库查询 +// +// // 4、构建 ra 对象 +// RequestAuthModel ra = new RequestAuthModel(); +// ra.clientId = clientId; +// ra.loginId = userId; +// ra.scopes = scopes; +// +// // 5、生成 Access-Token +// AccessTokenModel at = SaOAuth2Manager.getDataGenerate().generateAccessToken(ra, true); +// return at; +// } +//} \ No newline at end of file diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/custom/PhoneLoginController.java b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/custom/PhoneLoginController.java index 92cb3d99..8f25a174 100644 --- a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/custom/PhoneLoginController.java +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/custom/PhoneLoginController.java @@ -1,26 +1,26 @@ -package com.pj.oauth2.custom; - -import cn.dev33.satoken.SaManager; -import cn.dev33.satoken.util.SaFoxUtil; -import cn.dev33.satoken.util.SaResult; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - * 自定义手机登录接口 - * - * @author click33 - * @since 2024/8/23 - */ -@RestController -public class PhoneLoginController { - - @RequestMapping("/oauth2/sendPhoneCode") - public SaResult sendCode(String phone) { - String code = SaFoxUtil.getRandomNumber(100000, 999999) + ""; - SaManager.getSaTokenDao().set("phone_code:" + phone, code, 60 * 5); - System.out.println("手机号:" + phone + ",验证码:" + code + ",已发送成功"); - return SaResult.ok("验证码发送成功"); - } - -} \ No newline at end of file +//package com.pj.oauth2.custom; +// +//import cn.dev33.satoken.SaManager; +//import cn.dev33.satoken.util.SaFoxUtil; +//import cn.dev33.satoken.util.SaResult; +//import org.springframework.web.bind.annotation.RequestMapping; +//import org.springframework.web.bind.annotation.RestController; +// +///** +// * 自定义手机登录接口 +// * +// * @author click33 +// * @since 2024/8/23 +// */ +//@RestController +//public class PhoneLoginController { +// +// @RequestMapping("/oauth2/sendPhoneCode") +// public SaResult sendCode(String phone) { +// String code = SaFoxUtil.getRandomNumber(100000, 999999) + ""; +// SaManager.getSaTokenDao().set("phone_code:" + phone, code, 60 * 5); +// System.out.println("手机号:" + phone + ",验证码:" + code + ",已发送成功"); +// return SaResult.ok("验证码发送成功"); +// } +// +//} \ No newline at end of file diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/custom/UserinfoScopeHandler.java b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/custom/UserinfoScopeHandler.java index 088e9f57..bc71bebe 100644 --- a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/custom/UserinfoScopeHandler.java +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/custom/UserinfoScopeHandler.java @@ -1,41 +1,41 @@ -package com.pj.oauth2.custom; - -import cn.dev33.satoken.oauth2.data.model.AccessTokenModel; -import cn.dev33.satoken.oauth2.data.model.ClientTokenModel; -import cn.dev33.satoken.oauth2.scope.handler.SaOAuth2ScopeHandlerInterface; -import org.springframework.stereotype.Component; - -import java.util.LinkedHashMap; -import java.util.Map; - -/** - * @author click33 - * @since 2024/8/20 - */ -@Component -public class UserinfoScopeHandler implements SaOAuth2ScopeHandlerInterface { - - @Override - public String getHandlerScope() { - return "userinfo"; - } - - @Override - public void workAccessToken(AccessTokenModel at) { - System.out.println("--------- userinfo 权限,加工 AccessTokenModel --------- "); - // 模拟账号信息 (真实环境需要查询数据库获取信息) - Map map = new LinkedHashMap(); - map.put("userId", "10008"); - map.put("nickname", "shengzhang_"); - map.put("avatar", "http://xxx.com/1.jpg"); - map.put("age", "18"); - map.put("sex", "男"); - map.put("address", "山东省 青岛市 城阳区"); - at.extraData.put("userinfo", map); - } - - @Override - public void workClientToken(ClientTokenModel ct) { - } - -} \ No newline at end of file +//package com.pj.oauth2.custom; +// +//import cn.dev33.satoken.oauth2.data.model.AccessTokenModel; +//import cn.dev33.satoken.oauth2.data.model.ClientTokenModel; +//import cn.dev33.satoken.oauth2.scope.handler.SaOAuth2ScopeHandlerInterface; +//import org.springframework.stereotype.Component; +// +//import java.util.LinkedHashMap; +//import java.util.Map; +// +///** +// * @author click33 +// * @since 2024/8/20 +// */ +//@Component +//public class UserinfoScopeHandler implements SaOAuth2ScopeHandlerInterface { +// +// @Override +// public String getHandlerScope() { +// return "userinfo"; +// } +// +// @Override +// public void workAccessToken(AccessTokenModel at) { +// System.out.println("--------- userinfo 权限,加工 AccessTokenModel --------- "); +// // 模拟账号信息 (真实环境需要查询数据库获取信息) +// Map map = new LinkedHashMap(); +// map.put("userId", "10008"); +// map.put("nickname", "shengzhang_"); +// map.put("avatar", "http://xxx.com/1.jpg"); +// map.put("age", "18"); +// map.put("sex", "男"); +// map.put("address", "山东省 青岛市 城阳区"); +// at.extraData.put("userinfo", map); +// } +// +// @Override +// public void workClientToken(ClientTokenModel ct) { +// } +// +//} \ No newline at end of file diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/current/GlobalExceptionHandler.java b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/satoken/GlobalExceptionHandler.java similarity index 94% rename from sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/current/GlobalExceptionHandler.java rename to sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/satoken/GlobalExceptionHandler.java index f251e478..b97704fa 100644 --- a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/current/GlobalExceptionHandler.java +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/satoken/GlobalExceptionHandler.java @@ -1,4 +1,4 @@ -package com.pj.current; +package com.pj.satoken; import cn.dev33.satoken.util.SaResult; import org.springframework.web.bind.annotation.ExceptionHandler; diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/satoken/SaTokenConfigure.java b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/satoken/SaTokenConfigure.java new file mode 100644 index 00000000..eadad0f3 --- /dev/null +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/satoken/SaTokenConfigure.java @@ -0,0 +1,27 @@ +package com.pj.satoken; + +import cn.dev33.satoken.interceptor.SaInterceptor; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + + +/** + * [Sa-Token 权限认证] 配置类 + * @author click33 + * + */ +@Configuration +public class SaTokenConfigure implements WebMvcConfigurer { + + /** + * 注册 Sa-Token 拦截器打开注解鉴权功能 + */ + @Override + public void addInterceptors(InterceptorRegistry registry) { + // 注册 Sa-Token 拦截器打开注解鉴权功能 + registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**"); + + } + +} diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/test/TestController.java b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/test/TestController.java new file mode 100644 index 00000000..8adc82dc --- /dev/null +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/test/TestController.java @@ -0,0 +1,72 @@ +package com.pj.test; + +import cn.dev33.satoken.oauth2.annotation.SaCheckAccessToken; +import cn.dev33.satoken.oauth2.annotation.SaCheckClientIdSecret; +import cn.dev33.satoken.oauth2.annotation.SaCheckClientToken; +import cn.dev33.satoken.util.SaResult; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * OAuth2 相关注解测试 Controller + * + * @author click33 + * @since 2024/8/25 + */ +@RestController +@RequestMapping("/test") +public class TestController { + + // 测试:携带有效的 access_token 才可以进入请求 + // 你可以在请求参数中携带 access_token 参数,或者从请求头以 Authorization: bearer xxx 的形式携带 + @SaCheckAccessToken + @RequestMapping("/checkAccessToken") + public SaResult checkAccessToken() { + return SaResult.ok("访问成功"); + } + + // 测试:携带有效的 access_token ,并且具备指定 scope 才可以进入请求 + @SaCheckAccessToken(scope = "userinfo") + @RequestMapping("/checkAccessTokenScope") + public SaResult checkAccessTokenScope() { + return SaResult.ok("访问成功"); + } + + // 测试:携带有效的 access_token ,并且具备指定 scope 列表才可以进入请求 + @SaCheckAccessToken(scope = {"openid", "userinfo"}) + @RequestMapping("/checkAccessTokenScopeList") + public SaResult checkAccessTokenScopeList() { + return SaResult.ok("访问成功"); + } + + // 测试:携带有效的 client_token 才可以进入请求 + // 你可以在请求参数中携带 client_token 参数,或者从请求头以 Authorization: bearer xxx 的形式携带 + @SaCheckClientToken + @RequestMapping("/checkClientToken") + public SaResult checkClientToken() { + return SaResult.ok("访问成功"); + } + + // 测试:携带有效的 client_token ,并且具备指定 scope 才可以进入请求 + @SaCheckClientToken(scope = "userinfo") + @RequestMapping("/checkClientTokenScope") + public SaResult checkClientTokenScope() { + return SaResult.ok("访问成功"); + } + + // 测试:携带有效的 client_token ,并且具备指定 scope 列表才可以进入请求 + @SaCheckClientToken(scope = {"openid", "userinfo"}) + @RequestMapping("/checkClientTokenScopeList") + public SaResult checkClientTokenScopeList() { + return SaResult.ok("访问成功"); + } + + // 测试:携带有效的 client_id 和 client_secret 信息,才可以进入请求 + // 你可以在请求参数中携带 client_id 和 client_secret 参数,或者从请求头以 Authorization: Basic base64(client_id:client_secret) 的形式携带 + @SaCheckClientIdSecret + @RequestMapping("/checkClientIdSecret") + public SaResult checkClientIdSecret() { + return SaResult.ok("访问成功"); + } + +} \ No newline at end of file diff --git a/sa-token-doc/_sidebar.md b/sa-token-doc/_sidebar.md index f11153ff..f23aa033 100644 --- a/sa-token-doc/_sidebar.md +++ b/sa-token-doc/_sidebar.md @@ -63,6 +63,7 @@ - [自定义 grant_type](/oauth2/oauth2-custom-grant_type) - [开启 OIDC 协议](/oauth2/oauth2-oidc) - [OAuth2-与登录会话实现数据互通](/oauth2/oauth2-interworking) + - [使用注解校验 Access-Token](/oauth2/oauth2-at-check) - [OAuth2 代码 API 参考](/oauth2/oauth2-dev) - [常见问题总结](/oauth2/oauth2-questions) diff --git a/sa-token-doc/oauth2/oauth2-at-check.md b/sa-token-doc/oauth2/oauth2-at-check.md new file mode 100644 index 00000000..aa0fbfe8 --- /dev/null +++ b/sa-token-doc/oauth2/oauth2-at-check.md @@ -0,0 +1,94 @@ +# Sa-Token OAuth2 模块相关注解 + +sa-token-oauth2 模块扩展了三个注解用于相关数据校验: +- `@SaCheckAccessToken`:指定请求中必须包含有效的 `access_token` ,并且包含指定的 `scope`。 +- `@SaCheckClientToken`:指定请求中必须包含有效的 `client_token` ,并且包含指定的 `scope`。 +- `@SaCheckClientIdSecret`:指定请求中必须包含有效的 `client_id` 和 `client_secret` 信息。 + +和 Sa-Token-Code 模块的注解一样,你必须先注册框架的内置拦截器,才可以使用这些注解,详细参考:[注解鉴权](/use/at-check) 。 + +--- + + +### 1、@SaCheckAccessToken 示例 + +``` java +@RestController +@RequestMapping("/test") +public class TestController { + + // 测试:携带有效的 access_token 才可以进入请求 + // 你可以在请求参数中携带 access_token 参数,或者从请求头以 Authorization: bearer xxx 的形式携带 + @SaCheckAccessToken + @RequestMapping("/checkAccessToken") + public SaResult checkAccessToken() { + return SaResult.ok("访问成功"); + } + + // 测试:携带有效的 access_token ,并且具备指定 scope 才可以进入请求 + @SaCheckAccessToken(scope = "userinfo") + @RequestMapping("/checkAccessTokenScope") + public SaResult checkAccessTokenScope() { + return SaResult.ok("访问成功"); + } + + // 测试:携带有效的 access_token ,并且具备指定 scope 列表才可以进入请求 + @SaCheckAccessToken(scope = {"openid", "userinfo"}) + @RequestMapping("/checkAccessTokenScopeList") + public SaResult checkAccessTokenScopeList() { + return SaResult.ok("访问成功"); + } + +} +``` + + +### 2、@SaCheckClientToken 示例 + +``` java +@RestController +@RequestMapping("/test") +public class TestController { + + // 测试:携带有效的 client_token 才可以进入请求 + // 你可以在请求参数中携带 client_token 参数,或者从请求头以 Authorization: bearer xxx 的形式携带 + @SaCheckClientToken + @RequestMapping("/checkClientToken") + public SaResult checkClientToken() { + return SaResult.ok("访问成功"); + } + + // 测试:携带有效的 client_token ,并且具备指定 scope 才可以进入请求 + @SaCheckClientToken(scope = "userinfo") + @RequestMapping("/checkClientTokenScope") + public SaResult checkClientTokenScope() { + return SaResult.ok("访问成功"); + } + + // 测试:携带有效的 client_token ,并且具备指定 scope 列表才可以进入请求 + @SaCheckClientToken(scope = {"openid", "userinfo"}) + @RequestMapping("/checkClientTokenScopeList") + public SaResult checkClientTokenScopeList() { + return SaResult.ok("访问成功"); + } + +} +``` + + +### 3、@SaCheckClientIdSecret 示例 +``` java +@RestController +@RequestMapping("/test") +public class TestController { + + // 测试:携带有效的 client_id 和 client_secret 信息,才可以进入请求 + // 你可以在请求参数中携带 client_id 和 client_secret 参数,或者从请求头以 Authorization: Basic base64(client_id:client_secret) 的形式携带 + @SaCheckClientIdSecret + @RequestMapping("/checkClientIdSecret") + public SaResult checkClientIdSecret() { + return SaResult.ok("访问成功"); + } + +} +``` diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/annotation/SaCheckAccessToken.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/annotation/SaCheckAccessToken.java new file mode 100644 index 00000000..2f41ccee --- /dev/null +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/annotation/SaCheckAccessToken.java @@ -0,0 +1,42 @@ +/* + * 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.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Access-Token 校验:指定请求中必须包含有效的 access_token ,并且包含指定的 scope + * + *

可标注在方法、类上(效果等同于标注在此类的所有方法上) + * + * @author click33 + * @since 1.39.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD,ElementType.TYPE}) +public @interface SaCheckAccessToken { + + /** + * 需要校验的 scope [ 数组 ] + * + * @return / + */ + String [] scope() default {}; + +} diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/annotation/SaCheckClientIdSecret.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/annotation/SaCheckClientIdSecret.java new file mode 100644 index 00000000..ee849984 --- /dev/null +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/annotation/SaCheckClientIdSecret.java @@ -0,0 +1,35 @@ +/* + * 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.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * ClientSecret 校验:指定请求中必须包含有效的 client_id 和 client_secret 信息 + * + *

可标注在方法、类上(效果等同于标注在此类的所有方法上) + * + * @author click33 + * @since 1.39.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD,ElementType.TYPE}) +public @interface SaCheckClientIdSecret { + +} diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/annotation/SaCheckClientToken.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/annotation/SaCheckClientToken.java new file mode 100644 index 00000000..16e7f8b9 --- /dev/null +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/annotation/SaCheckClientToken.java @@ -0,0 +1,42 @@ +/* + * 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.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Client-Token 校验:指定请求中必须包含有效的 client_token ,并且包含指定的 scope + * + *

可标注在方法、类上(效果等同于标注在此类的所有方法上) + * + * @author click33 + * @since 1.39.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD,ElementType.TYPE}) +public @interface SaCheckClientToken { + + /** + * 需要校验的 scope [ 数组 ] + * + * @return / + */ + String [] scope() default {}; + +} diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/annotation/handler/SaCheckAccessTokenHandler.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/annotation/handler/SaCheckAccessTokenHandler.java new file mode 100644 index 00000000..c7caf663 --- /dev/null +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/annotation/handler/SaCheckAccessTokenHandler.java @@ -0,0 +1,48 @@ +/* + * 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.annotation.handler; + +import cn.dev33.satoken.annotation.handler.SaAnnotationHandlerInterface; +import cn.dev33.satoken.context.SaHolder; +import cn.dev33.satoken.oauth2.SaOAuth2Manager; +import cn.dev33.satoken.oauth2.annotation.SaCheckAccessToken; + +import java.lang.reflect.Method; + +/** + * 注解 SaCheckAccessToken 的处理器 + * + * @author click33 + * @since 1.39.0 + */ +public class SaCheckAccessTokenHandler implements SaAnnotationHandlerInterface { + + @Override + public Class getHandlerAnnotationClass() { + return SaCheckAccessToken.class; + } + + @Override + public void checkMethod(SaCheckAccessToken at, Method method) { + _checkMethod(at.scope()); + } + + public static void _checkMethod(String[] scope) { + String accessToken = SaOAuth2Manager.getDataResolver().readAccessToken(SaHolder.getRequest()); + SaOAuth2Manager.getTemplate().checkAccessTokenScope(accessToken, scope); + } + +} \ No newline at end of file diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/annotation/handler/SaCheckClientIdSecretHandler.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/annotation/handler/SaCheckClientIdSecretHandler.java new file mode 100644 index 00000000..6f1179e9 --- /dev/null +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/annotation/handler/SaCheckClientIdSecretHandler.java @@ -0,0 +1,46 @@ +/* + * 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.annotation.handler; + +import cn.dev33.satoken.annotation.handler.SaAnnotationHandlerInterface; +import cn.dev33.satoken.oauth2.annotation.SaCheckClientIdSecret; +import cn.dev33.satoken.oauth2.processor.SaOAuth2ServerProcessor; + +import java.lang.reflect.Method; + +/** + * 注解 SaCheckClientSecret 的处理器 + * + * @author click33 + * @since 1.39.0 + */ +public class SaCheckClientIdSecretHandler implements SaAnnotationHandlerInterface { + + @Override + public Class getHandlerAnnotationClass() { + return SaCheckClientIdSecret.class; + } + + @Override + public void checkMethod(SaCheckClientIdSecret at, Method method) { + _checkMethod(); + } + + public static void _checkMethod() { + SaOAuth2ServerProcessor.instance.checkCurrClientSecret(); + } + +} \ No newline at end of file diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/annotation/handler/SaCheckClientTokenHandler.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/annotation/handler/SaCheckClientTokenHandler.java new file mode 100644 index 00000000..b8c958f1 --- /dev/null +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/annotation/handler/SaCheckClientTokenHandler.java @@ -0,0 +1,48 @@ +/* + * 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.annotation.handler; + +import cn.dev33.satoken.annotation.handler.SaAnnotationHandlerInterface; +import cn.dev33.satoken.context.SaHolder; +import cn.dev33.satoken.oauth2.SaOAuth2Manager; +import cn.dev33.satoken.oauth2.annotation.SaCheckClientToken; + +import java.lang.reflect.Method; + +/** + * 注解 SaCheckAccessToken 的处理器 + * + * @author click33 + * @since 1.39.0 + */ +public class SaCheckClientTokenHandler implements SaAnnotationHandlerInterface { + + @Override + public Class getHandlerAnnotationClass() { + return SaCheckClientToken.class; + } + + @Override + public void checkMethod(SaCheckClientToken at, Method method) { + _checkMethod(at.scope()); + } + + public static void _checkMethod(String[] scope) { + String clientToken = SaOAuth2Manager.getDataResolver().readClientToken(SaHolder.getRequest()); + SaOAuth2Manager.getTemplate().checkClientTokenScope(clientToken, scope); + } + +} \ No newline at end of file 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 bdb8c650..9637d125 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 @@ -52,6 +52,7 @@ public class SaOAuth2Consts { public static String token = "token"; public static String access_token = "access_token"; public static String refresh_token = "refresh_token"; + public static String client_token = "client_token"; public static String grant_type = "grant_type"; public static String username = "username"; public static String password = "password"; diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/resolver/SaOAuth2DataResolver.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/resolver/SaOAuth2DataResolver.java index 5ea2b7dd..49d3e949 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/resolver/SaOAuth2DataResolver.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/resolver/SaOAuth2DataResolver.java @@ -50,6 +50,14 @@ public interface SaOAuth2DataResolver { */ String readAccessToken(SaRequest request); + /** + * 数据读取:从请求对象中读取 ClientToken + * + * @param request / + * @return / + */ + String readClientToken(SaRequest request); + /** * 数据读取:从请求对象中构建 RequestAuthModel * @param req SaRequest对象 diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/resolver/SaOAuth2DataResolverDefaultImpl.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/resolver/SaOAuth2DataResolverDefaultImpl.java index 31186e4b..4cac496b 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/resolver/SaOAuth2DataResolverDefaultImpl.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/resolver/SaOAuth2DataResolverDefaultImpl.java @@ -98,6 +98,33 @@ public class SaOAuth2DataResolverDefaultImpl implements SaOAuth2DataResolver { return null; } + /** + * 数据读取:从请求对象中读取 ClientToken + */ + @Override + public String readClientToken(SaRequest request) { + // 优先从请求参数中获取 + String clientToken = request.getParam(SaOAuth2Consts.Param.client_token); + if(SaFoxUtil.isNotEmpty(clientToken)) { + return clientToken; + } + + // 如果请求参数中没有提供 client_token 参数,则尝试从 Authorization 中获取 + String authorizationValue = request.getHeader(SaOAuth2Consts.Param.Authorization); + if(SaFoxUtil.isEmpty(authorizationValue)) { + return null; + } + + // 判断前缀,裁剪 + String prefix = TokenType.Bearer + " "; + if(authorizationValue.startsWith(prefix)) { + return authorizationValue.substring(prefix.length()); + } + + // 前缀不符合,返回 null + return null; + } + /** * 数据读取:从请求对象中构建 RequestAuthModel */ diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/processor/SaOAuth2ServerProcessor.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/processor/SaOAuth2ServerProcessor.java index f8e56867..e6401002 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/processor/SaOAuth2ServerProcessor.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/processor/SaOAuth2ServerProcessor.java @@ -37,7 +37,6 @@ import cn.dev33.satoken.oauth2.exception.SaOAuth2Exception; import cn.dev33.satoken.oauth2.strategy.SaOAuth2Strategy; import cn.dev33.satoken.oauth2.template.SaOAuth2Template; import cn.dev33.satoken.router.SaHttpMethod; -import cn.dev33.satoken.util.SaFoxUtil; import cn.dev33.satoken.util.SaResult; import java.util.List; @@ -332,6 +331,17 @@ public class SaOAuth2ServerProcessor { return oauth2Template.checkClientModel(clientIdAndSecret.clientId); } + /** + * 校验当前请求中提交的 clientId 和 clientSecret 是否正确,如果正确则返回 SaClientModel 对象 + * + * @return / + */ + public SaClientModel checkCurrClientSecret() { + SaOAuth2Template oauth2Template = SaOAuth2Manager.getTemplate(); + ClientIdAndSecretModel clientIdAndSecret = SaOAuth2Manager.getDataResolver().readClientIdAndSecret(SaHolder.getRequest()); + return oauth2Template.checkClientSecret(clientIdAndSecret.clientId, clientIdAndSecret.clientSecret); + } + /** * 校验 authorize 路由的 ResponseType 参数 */ diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java index e35cef93..7479f24d 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java @@ -415,12 +415,12 @@ public class SaOAuth2Template { * @param scopes 需要校验的权限列表 */ public void checkAccessTokenScope(String accessToken, String... scopes) { + AccessTokenModel at = checkAccessToken(accessToken); if(SaFoxUtil.isEmptyArray(scopes)) { return; } - ClientTokenModel ct = checkClientToken(accessToken); for (String scope : scopes) { - if(! ct.scopes.contains(scope)) { + if(! at.scopes.contains(scope)) { throw new SaOAuth2AccessTokenScopeException("该 access_token 不具备 scope:" + scope) .setAccessToken(accessToken) .setScope(scope) @@ -461,11 +461,6 @@ public class SaOAuth2Template { SaOAuth2Dao dao = SaOAuth2Manager.getDao(); dao.deleteAccessToken(accessToken); dao.deleteAccessTokenIndex(at.clientId, at.loginId); - - // 删对应的 rt、索引 -// String rtValue = dao.getRefreshTokenValue(at.clientId, at.loginId); -// dao.deleteRefreshToken(rtValue); -// dao.deleteRefreshTokenIndex(at.clientId, at.loginId); } /** @@ -586,10 +581,10 @@ public class SaOAuth2Template { * @param scopes 需要校验的权限列表 */ public void checkClientTokenScope(String clientToken, String... scopes) { + ClientTokenModel ct = checkClientToken(clientToken); if(SaFoxUtil.isEmptyArray(scopes)) { return; } - ClientTokenModel ct = checkClientToken(clientToken); for (String scope : scopes) { if(! ct.scopes.contains(scope)) { throw new SaOAuth2ClientTokenScopeException("该 client_token 不具备 scope:" + scope) diff --git a/sa-token-starter/sa-token-spring-boot-autoconfig/src/main/java/cn/dev33/satoken/spring/oauth2/SaOAuth2BeanRegister.java b/sa-token-starter/sa-token-spring-boot-autoconfig/src/main/java/cn/dev33/satoken/spring/oauth2/SaOAuth2BeanRegister.java index 788f97af..b9b99a3c 100644 --- a/sa-token-starter/sa-token-spring-boot-autoconfig/src/main/java/cn/dev33/satoken/spring/oauth2/SaOAuth2BeanRegister.java +++ b/sa-token-starter/sa-token-spring-boot-autoconfig/src/main/java/cn/dev33/satoken/spring/oauth2/SaOAuth2BeanRegister.java @@ -15,7 +15,14 @@ */ package cn.dev33.satoken.spring.oauth2; +import cn.dev33.satoken.annotation.handler.SaAnnotationHandlerInterface; import cn.dev33.satoken.oauth2.SaOAuth2Manager; +import cn.dev33.satoken.oauth2.annotation.SaCheckAccessToken; +import cn.dev33.satoken.oauth2.annotation.SaCheckClientIdSecret; +import cn.dev33.satoken.oauth2.annotation.SaCheckClientToken; +import cn.dev33.satoken.oauth2.annotation.handler.SaCheckAccessTokenHandler; +import cn.dev33.satoken.oauth2.annotation.handler.SaCheckClientIdSecretHandler; +import cn.dev33.satoken.oauth2.annotation.handler.SaCheckClientTokenHandler; import cn.dev33.satoken.oauth2.config.SaOAuth2ServerConfig; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.context.properties.ConfigurationProperties; @@ -41,4 +48,31 @@ public class SaOAuth2BeanRegister { return new SaOAuth2ServerConfig(); } + // 自定义注解处理器 + @Bean + public SaAnnotationHandlerInterface getSaCheckAccessTokenHandler() { + return new SaCheckAccessTokenHandler(); + } + @Bean + public SaAnnotationHandlerInterface getSaCheckClientTokenHandler() { + return new SaCheckClientTokenHandler(); + } + @Bean + public SaAnnotationHandlerInterface getSaCheckClientIdSecretHandler() { + return new SaCheckClientIdSecretHandler(); + } + + /* + // 这种写法有问题,当项目还有自定义的注解处理器时,项目中的自定义注解处理器将会覆盖掉此处 List 中的注解处理器 +// @Bean +// public List> getXxx() { +// return Arrays.asList( +// new SaCheckAccessTokenHandler(), +// new SaCheckClientTokenHandler(), +// new SaCheckClientSecretHandler() +// ); +// } + + */ + } -- Gitee From 60b7c9036fa9f05120aa9b2a5220ad73b2c54581 Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Sun, 25 Aug 2024 20:00:05 +0800 Subject: [PATCH 158/172] =?UTF-8?q?=E9=87=8D=E6=9E=84=E6=94=B9=E5=90=8D=20?= =?UTF-8?q?PastToken=20->=20LowerClientToken?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/templates/index.html | 2 +- .../oauth2/config/SaOAuth2ServerConfig.java | 18 ++++---- .../dev33/satoken/oauth2/dao/SaOAuth2Dao.java | 45 ++++++++++--------- .../SaOAuth2DataGenerateDefaultImpl.java | 14 +++--- .../data/model/loader/SaClientModel.java | 20 ++++----- .../oauth2/template/SaOAuth2Template.java | 14 +++--- .../satoken/oauth2/template/SaOAuth2Util.java | 6 +-- .../dev33/satoken/dao/SaSessionForJson.java | 1 - 8 files changed, 61 insertions(+), 59 deletions(-) diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-client/src/main/resources/templates/index.html b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-client/src/main/resources/templates/index.html index adc3bd0a..0e8d6858 100644 --- a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-client/src/main/resources/templates/index.html +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-client/src/main/resources/templates/index.html @@ -82,7 +82,7 @@

模式四:凭证式(Client Credentials)

以上三种模式获取的都是用户的 Access-Token,代表用户对第三方应用的授权,在OAuth2.0中还有一种针对 Client级别的授权, 即:Client-Token,代表应用自身的资源授权

-

Client-Token具有延迟作废特性,即:在每次获取最新Client-Token的时候,旧Client-Token不会立即过期,而是作为Past-Token再次 +

Client-Token具有延迟作废特性,即:在每次获取最新Client-Token的时候,旧Client-Token不会立即过期,而是作为Lower-Client-Token再次 储存起来,资源请求方只要携带其中之一便可通过Token校验,这种特性保证了在大量并发请求时不会出现“新旧Token交替造成的授权失效”, 保证了服务的高可用

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 3791c939..abdbc03d 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 @@ -60,8 +60,8 @@ public class SaOAuth2ServerConfig implements Serializable { /** Client-Token 保存的时间(单位:秒) 默认两个小时 */ public long clientTokenTimeout = 60 * 60 * 2; - /** Past-Client-Token 保存的时间(单位:秒) 默认为 -1,代表延续 Client-Token 有效期 */ - public long pastClientTokenTimeout = -1; + /** Lower-Client-Token 保存的时间(单位:秒) 默认为 -1,代表延续 Client-Token 有效期 */ + public long lowerClientTokenTimeout = -1; /** 默认 openid 生成算法中使用的摘要前缀 */ public String openidDigestPrefix = SaOAuth2Consts.OPENID_DEFAULT_DIGEST_PREFIX; @@ -228,18 +228,18 @@ public class SaOAuth2ServerConfig implements Serializable { } /** - * @return pastClientTokenTimeout + * @return lowerClientTokenTimeout */ - public long getPastClientTokenTimeout() { - return pastClientTokenTimeout; + public long getLowerClientTokenTimeout() { + return lowerClientTokenTimeout; } /** - * @param pastClientTokenTimeout 要设置的 pastClientTokenTimeout + * @param lowerClientTokenTimeout 要设置的 lowerClientTokenTimeout * @return 对象自身 */ - public SaOAuth2ServerConfig setPastClientTokenTimeout(long pastClientTokenTimeout) { - this.pastClientTokenTimeout = pastClientTokenTimeout; + public SaOAuth2ServerConfig setLowerClientTokenTimeout(long lowerClientTokenTimeout) { + this.lowerClientTokenTimeout = lowerClientTokenTimeout; return this; } @@ -379,7 +379,7 @@ public class SaOAuth2ServerConfig implements Serializable { ", accessTokenTimeout=" + accessTokenTimeout + ", refreshTokenTimeout=" + refreshTokenTimeout + ", clientTokenTimeout=" + clientTokenTimeout + - ", pastClientTokenTimeout=" + pastClientTokenTimeout + + ", lowerClientTokenTimeout=" + lowerClientTokenTimeout + ", openidDigestPrefix='" + openidDigestPrefix + ", higherScope='" + higherScope + ", lowerScope='" + lowerScope + diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/dao/SaOAuth2Dao.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/dao/SaOAuth2Dao.java index f3110717..803c8b03 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/dao/SaOAuth2Dao.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/dao/SaOAuth2Dao.java @@ -23,10 +23,13 @@ import cn.dev33.satoken.oauth2.data.model.AccessTokenModel; import cn.dev33.satoken.oauth2.data.model.ClientTokenModel; import cn.dev33.satoken.oauth2.data.model.CodeModel; import cn.dev33.satoken.oauth2.data.model.RefreshTokenModel; +import cn.dev33.satoken.oauth2.data.model.loader.SaClientModel; import cn.dev33.satoken.util.SaFoxUtil; import java.util.List; +import static cn.dev33.satoken.oauth2.template.SaOAuth2Util.checkClientModel; + /** * Sa-Token OAuth2 数据持久层 * @@ -126,20 +129,20 @@ public interface SaOAuth2Dao { } /** - * 持久化:Past-Token-索引 + * 持久化:Lower-Client-Token 索引 * @param ct / */ - default void savePastTokenIndex(ClientTokenModel ct) { + default void saveLowerClientTokenIndex(ClientTokenModel ct) { if(ct == null) { return; } long ttl = ct.getExpiresIn(); - // TODO PastToken ttl 是否有必要单独配置个字段? -// SaClientModel cm = checkClientModel(ct.clientId); -// if (cm.getPastClientTokenTimeout() != -1) { -// ttl = cm.getPastClientTokenTimeout(); -// } - getSaTokenDao().set(splicingPastTokenIndexKey(ct.clientId), ct.clientToken, ttl); + // 如果此 client 单独配置了 Lower-Client-Token 的 TTL,则使用单独配置 + SaClientModel cm = checkClientModel(ct.clientId); + if (cm.getLowerClientTokenTimeout() != -1) { + ttl = cm.getLowerClientTokenTimeout(); + } + getSaTokenDao().set(splicingLowerClientTokenIndexKey(ct.clientId), ct.clientToken, ttl); } /** @@ -248,20 +251,20 @@ public interface SaOAuth2Dao { } /** - * 删除:Past-Token - * @param pastToken 值 + * 删除:Lower-Client-Token + * @param lowerClientToken 值 */ - default void deletePastToken(String pastToken) { + default void deleteLowerClientToken(String lowerClientToken) { // 其实就是删除 ClientToken - deleteClientToken(pastToken); + deleteClientToken(lowerClientToken); } /** - * 删除:Past-Token索引 + * 删除:Lower-Client-Token索引 * @param clientId 应用id */ - default void deletePastTokenIndex(String clientId) { - getSaTokenDao().delete(splicingPastTokenIndexKey(clientId)); + default void deleteLowerClientTokenIndex(String clientId) { + getSaTokenDao().delete(splicingLowerClientTokenIndexKey(clientId)); } /** @@ -372,12 +375,12 @@ public interface SaOAuth2Dao { } /** - * 获取:Past-Token Value + * 获取:Lower-Client-Token Value * @param clientId 应用id * @return . */ - default String getPastTokenValue(String clientId) { - return getSaTokenDao().get(splicingPastTokenIndexKey(clientId)); + default String getLowerClientTokenValue(String clientId) { + return getSaTokenDao().get(splicingLowerClientTokenIndexKey(clientId)); } /** @@ -482,12 +485,12 @@ public interface SaOAuth2Dao { } /** - * 拼接key:Past-Token 索引 + * 拼接key:Lower-Client-Token 索引 * @param clientId clientId * @return key */ - default String splicingPastTokenIndexKey(String clientId) { - return getSaTokenConfig().getTokenName() + ":oauth2:past-token-index:" + clientId; + default String splicingLowerClientTokenIndexKey(String clientId) { + return getSaTokenConfig().getTokenName() + ":oauth2:lower-client-token-index:" + clientId; } /** diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerateDefaultImpl.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerateDefaultImpl.java index 33ad2055..36c1e9fe 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerateDefaultImpl.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerateDefaultImpl.java @@ -204,17 +204,17 @@ public class SaOAuth2DataGenerateDefaultImpl implements SaOAuth2DataGenerate { SaOAuth2Dao dao = SaOAuth2Manager.getDao(); - // 1、删掉旧 Past-Token - dao.deleteClientToken(dao.getPastTokenValue(clientId)); + // 1、删掉旧 Lower-Client-Token + dao.deleteClientToken(dao.getLowerClientTokenValue(clientId)); - // 2、将旧Client-Token 标记为新 Past-Token + // 2、将旧Client-Token 标记为新 Lower-Client-Token ClientTokenModel oldCt = dao.getClientToken(dao.getClientTokenValue(clientId)); - dao.savePastTokenIndex(oldCt); + dao.saveLowerClientTokenIndex(oldCt); - // 2.5、如果配置了 PastClientToken 的 ttl ,则需要更新一下 + // 2.5、如果配置了 Lower-Client-Token 的 ttl ,则需要更新一下 SaClientModel cm = SaOAuth2Manager.getDataLoader().getClientModelNotNull(clientId); - if(oldCt != null && cm.getPastClientTokenTimeout() != -1) { - oldCt.expiresTime = System.currentTimeMillis() + (cm.getPastClientTokenTimeout() * 1000); + if(oldCt != null && cm.getLowerClientTokenTimeout() != -1) { + oldCt.expiresTime = System.currentTimeMillis() + (cm.getLowerClientTokenTimeout() * 1000); dao.saveClientToken(oldCt); } 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 46e64344..393da7c8 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 @@ -70,8 +70,8 @@ public class SaClientModel implements Serializable { /** 单独配置此Client:Client-Token 保存的时间(单位秒) [默认取全局配置] */ public long clientTokenTimeout; - /** 单独配置此Client:Past-Client-Token 保存的时间(单位:秒) [默认取全局配置] */ - public long pastClientTokenTimeout; + /** 单独配置此Client:Lower-Client-Token 保存的时间(单位:秒) [默认取全局配置] */ + public long lowerClientTokenTimeout; public SaClientModel() { @@ -80,7 +80,7 @@ public class SaClientModel implements Serializable { this.accessTokenTimeout = config.getAccessTokenTimeout(); this.refreshTokenTimeout = config.getRefreshTokenTimeout(); this.clientTokenTimeout = config.getClientTokenTimeout(); - this.pastClientTokenTimeout = config.getPastClientTokenTimeout(); + this.lowerClientTokenTimeout = config.getLowerClientTokenTimeout(); } public SaClientModel(String clientId, String clientSecret, List contractScopes, List allowRedirectUris) { super(); @@ -236,18 +236,18 @@ public class SaClientModel implements Serializable { } /** - * @return 此Client:Past-Client-Token 保存的时间(单位:秒) [默认取全局配置] + * @return 此Client:Lower-Client-Token 保存的时间(单位:秒) [默认取全局配置] */ - public long getPastClientTokenTimeout() { - return pastClientTokenTimeout; + public long getLowerClientTokenTimeout() { + return lowerClientTokenTimeout; } /** - * @param pastClientTokenTimeout 单独配置此Client:Past-Client-Token 保存的时间(单位:秒) [默认取全局配置] + * @param lowerClientTokenTimeout 单独配置此Client:Lower-Client-Token 保存的时间(单位:秒) [默认取全局配置] * @return 对象自身 */ - public SaClientModel setPastClientTokenTimeout(long pastClientTokenTimeout) { - this.pastClientTokenTimeout = pastClientTokenTimeout; + public SaClientModel setLowerClientTokenTimeout(long lowerClientTokenTimeout) { + this.lowerClientTokenTimeout = lowerClientTokenTimeout; return this; } @@ -265,7 +265,7 @@ public class SaClientModel implements Serializable { ", accessTokenTimeout=" + accessTokenTimeout + ", refreshTokenTimeout=" + refreshTokenTimeout + ", clientTokenTimeout=" + clientTokenTimeout + - ", pastClientTokenTimeout=" + pastClientTokenTimeout + + ", lowerClientTokenTimeout=" + lowerClientTokenTimeout + '}'; } diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java index 7479f24d..2e6a506c 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java @@ -628,17 +628,17 @@ public class SaOAuth2Template { } /** - * 回收 PastToken,根据索引: clientId + * 回收 Lower-Client-Token,根据索引: clientId * * @param clientId / */ - public void revokePastTokenByIndex(String clientId) { + public void revokeLowerClientTokenByIndex(String clientId) { SaOAuth2Dao dao = SaOAuth2Manager.getDao(); - // 删 pastToken - String pastToken = dao.getPastTokenValue(clientId); - if(pastToken != null) { - dao.deletePastToken(pastToken); - dao.deletePastTokenIndex(clientId); + // 删 Lower-Client-Token + String lowerClientToken = dao.getLowerClientTokenValue(clientId); + if(lowerClientToken != null) { + dao.deleteLowerClientToken(lowerClientToken); + dao.deleteLowerClientTokenIndex(clientId); } } diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Util.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Util.java index 4d667933..a59077e4 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Util.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Util.java @@ -318,12 +318,12 @@ public class SaOAuth2Util { } /** - * 回收 PastToken,根据索引: clientId + * 回收 Lower-Client-Token,根据索引: clientId * * @param clientId / */ - public static void revokePastTokenByIndex(String clientId) { - SaOAuth2Manager.getTemplate().revokePastTokenByIndex(clientId); + public static void revokeLowerClientTokenByIndex(String clientId) { + SaOAuth2Manager.getTemplate().revokeLowerClientTokenByIndex(clientId); } } diff --git a/sa-token-plugin/sa-token-redisx/src/main/java/cn/dev33/satoken/dao/SaSessionForJson.java b/sa-token-plugin/sa-token-redisx/src/main/java/cn/dev33/satoken/dao/SaSessionForJson.java index 680230f9..f5a89f87 100644 --- a/sa-token-plugin/sa-token-redisx/src/main/java/cn/dev33/satoken/dao/SaSessionForJson.java +++ b/sa-token-plugin/sa-token-redisx/src/main/java/cn/dev33/satoken/dao/SaSessionForJson.java @@ -19,7 +19,6 @@ import cn.dev33.satoken.session.SaSession; import cn.dev33.satoken.util.SaFoxUtil; import org.noear.snack.ONode; -//todo: 不能删;为保持与旧的序列化兼容 /** * Snack3 定制版 SaSession,重写类型转换API * -- Gitee From ffd557c5d7c30a2e0f67a3fd664de046e0aaae65 Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Sun, 25 Aug 2024 21:25:10 +0800 Subject: [PATCH 159/172] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E6=89=93=E5=8D=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sa-token-doc/sso/sso-questions.md | 24 +++++++++---------- .../dev33/satoken/oauth2/dao/SaOAuth2Dao.java | 4 +--- .../oauth2/strategy/SaOAuth2Strategy.java | 8 ++----- .../satoken/solon/dao/SaSessionForJson.java | 2 +- 4 files changed, 16 insertions(+), 22 deletions(-) diff --git a/sa-token-doc/sso/sso-questions.md b/sa-token-doc/sso/sso-questions.md index 618f0bda..93649329 100644 --- a/sa-token-doc/sso/sso-questions.md +++ b/sa-token-doc/sso/sso-questions.md @@ -25,7 +25,7 @@ SSO 集成常见问题整理 - sso-server 端的单点注销地址:`http://{host}:{port}/sso/signout`; - sso-client 端的注销地址:`http://{host}:{port}/sso/logout`; -都需要在配置文件配置:`sa-token.sso.is-slo=true`后,才会打开。 +都需要在配置文件配置:`sa-token.sso-server.is-slo=true`(client端为 `sa-token.sso-client.is-slo=true` )后,才会打开。 ### 问:我参照文档搭建SSO-Client,一直提示:Ticket无效,请问怎么回事? @@ -85,7 +85,7 @@ public class SaSsoServerApplication { ### 问:模式三配置一堆 xxx-url ,有办法简化一下吗? -可以使用 `sa-token.sso.server-url` 来简化: +可以使用 `sa-token.sso-client.server-url` 来简化: 配置含义:配置 Server 端主机总地址,拼接在 authUrl、checkTicketUrl、getDataUrl、sloUrl 属性前面,用以简化各种 url 配置。 @@ -93,7 +93,7 @@ public class SaSsoServerApplication { ``` yaml sa-token: - sso: + sso-client: # SSO-Server端 统一认证地址 auth-url: http://sa-sso-server.com:9000/sso/auth # SSO-Server端 ticket校验地址 @@ -107,7 +107,7 @@ sa-token: 一堆 xxx-url 配置比较繁琐,且含有大量重复字符,现在我们可以将其简化为: ``` yaml sa-token: - sso: + sso-client: server-url: http://sa-sso-server.com:9000 ``` @@ -135,22 +135,22 @@ sa-token: **方法二,根据配置项来分析,例如:** - 先看配置项 `sa-token.cookie.domain`,如果此配置项有值,一般是在使用模式一开发,否则就是模式二或者模式三。 -- 再看配置项 `sa-token.sso.is-http` ,如果有值且为 true,一般是在使用模式三,否则就是模式二。 +- 再看配置项 `sa-token.sso-client.is-http` ,如果有值且为 true,一般是在使用模式三,否则就是模式二。 -**方法三,根据配置项 `sa-token.sso.mode` 的提示来判断** +**方法三,根据配置项 `sa-token.sso-client.mode` 的提示来判断** -`sa-token.sso.mode` 是框架预留的约定型配置项,此配置项不对代码逻辑产生任何影响,只为系统做一个标记,标注此系统用到了SSO的哪个模式。 +`sa-token.sso-client.mode` 是框架预留的约定型配置项,此配置项不对代码逻辑产生任何影响,只为系统做一个标记,标注此系统用到了SSO的哪个模式。 -例如你可以将其配置为 `sa-token.sso.mode=client-2`,代表当前系统为 sso-client 端,使用 SSO 模式二来对接。 +例如你可以将其配置为 `sa-token.sso-client.mode=client-2`,代表当前系统为 sso-client 端,使用 SSO 模式二来对接。 需要注意,这个配置项不是必须的,你不写也不会对代码造成任何影响,只有在你需要为系统做一个明确的标记时才需要去配置它,方便后人阅读代码时快速分析使用的模式。 例如我们可以使用以下约定: -- `sa-token.sso.mode=client-2`:代表当前系统为 sso-client 端,使用 SSO 模式二来对接。 -- `sa-token.sso.mode=client-2,h5`:代表当前系统为 sso-client 端,使用 SSO 模式二来对接,并且是前后端分离模式。 -- `sa-token.sso.mode=server-123`:代表当前系统为 sso-server 端,同时开放了 SSO 模式一、模式二、模式三。 -- `sa-token.sso.mode=server-2,client-2`:代表当前系统既是 sso-server 端,又是 sso-clent 端,使用模式二来对接。 +- `sa-token.sso-client.mode=client-2`:代表当前系统为 sso-client 端,使用 SSO 模式二来对接。 +- `sa-token.sso-client.mode=client-2,h5`:代表当前系统为 sso-client 端,使用 SSO 模式二来对接,并且是前后端分离模式。 +- `sa-token.sso-server.mode=server-123`:代表当前系统为 sso-server 端,同时开放了 SSO 模式一、模式二、模式三。 +- `sa-token.sso-server.mode=server-2,client-2`:代表当前系统既是 sso-server 端,又是 sso-clent 端,使用模式二来对接。 - 等等等等... 此配置项可以是任意字符串,你也可以自己整理一套合适的表达规则。 diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/dao/SaOAuth2Dao.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/dao/SaOAuth2Dao.java index 803c8b03..0c4b3eef 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/dao/SaOAuth2Dao.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/dao/SaOAuth2Dao.java @@ -153,9 +153,7 @@ public interface SaOAuth2Dao { */ default void saveGrantScope(String clientId, Object loginId, List scopes) { if( ! SaFoxUtil.isEmpty(scopes)) { - // TODO ttl 计算规则优化 - long ttl = SaOAuth2Manager.getServerConfig().getAccessTokenTimeout(); - // long ttl = checkClientModel(clientId).getAccessTokenTimeout(); + long ttl = checkClientModel(clientId).getAccessTokenTimeout(); String value = SaOAuth2Manager.getDataConverter().convertScopeListToString(scopes); getSaTokenDao().set(splicingGrantScopeKey(clientId, loginId), value, ttl); } 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 939b9ff9..e9ca6372 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 @@ -78,9 +78,7 @@ public final class SaOAuth2Strategy { */ public void registerScopeHandler(SaOAuth2ScopeHandlerInterface handler) { scopeHandlerMap.put(handler.getHandlerScope(), handler); - // TODO 优化日志输出 - SaManager.getLog().info("新增权限处理器:" + handler.getHandlerScope()); - // SaTokenEventCenter.doRegisterAnnotationHandler(handler); + SaManager.getLog().info("自定义 SCOPE [{}] (处理器: {})", handler.getHandlerScope(), handler.getClass().getCanonicalName()); } /** @@ -147,9 +145,7 @@ public final class SaOAuth2Strategy { */ public void registerGrantTypeHandler(SaOAuth2GrantTypeHandlerInterface handler) { grantTypeHandlerMap.put(handler.getHandlerGrantType(), handler); - // TODO 优化日志输出 - SaManager.getLog().info("新增GrantType处理器:" + handler.getHandlerGrantType()); - // SaTokenEventCenter.doRegisterAnnotationHandler(handler); + SaManager.getLog().info("自定义 GRANT_TYPE [{}] (处理器: {})", handler.getHandlerGrantType(), handler.getClass().getCanonicalName()); } /** diff --git a/sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/dao/SaSessionForJson.java b/sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/dao/SaSessionForJson.java index ced5e993..98150c58 100644 --- a/sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/dao/SaSessionForJson.java +++ b/sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/dao/SaSessionForJson.java @@ -19,7 +19,7 @@ import cn.dev33.satoken.session.SaSession; import cn.dev33.satoken.util.SaFoxUtil; import org.noear.snack.ONode; -//todo: 不能删;为保持与旧的序列化兼容 +// 不能删;为保持与旧的序列化兼容 /** * Snack3 定制版 SaSession,重写类型转换API * -- Gitee From b552dd334cb24150761e23cd0a604fc63b033f20 Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Mon, 26 Aug 2024 02:34:34 +0800 Subject: [PATCH 160/172] =?UTF-8?q?=E4=BC=98=E5=8C=96=20sa-token-oauth2=20?= =?UTF-8?q?=E5=BC=82=E5=B8=B8=E7=BB=86=E5=88=86=E7=8A=B6=E6=80=81=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sa-token-doc/_sidebar.md | 10 +-- sa-token-doc/fun/data-structure.md | 4 +- sa-token-doc/fun/exception-code.md | 55 +++++++------- sa-token-doc/oauth2/oauth2-check-domain.md | 2 +- sa-token-doc/oauth2/oauth2-custom-scope.md | 2 +- .../data/convert/SaOAuth2DataConverter.java | 7 +- .../SaOAuth2DataConverterDefaultImpl.java | 8 +- .../SaOAuth2DataGenerateDefaultImpl.java | 8 +- .../data/resolver/SaOAuth2DataResolver.java | 4 +- .../SaOAuth2DataResolverDefaultImpl.java | 36 +++++---- .../oauth2/error/SaOAuth2ErrorCode.java | 55 ++++++-------- .../SaOAuth2AuthorizationCodeException.java | 74 +++++++++++++++++++ .../handler/RefreshTokenGrantTypeHandler.java | 7 +- .../processor/SaOAuth2ServerProcessor.java | 12 +-- .../oauth2/strategy/SaOAuth2Strategy.java | 9 ++- .../oauth2/template/SaOAuth2Template.java | 31 ++++---- 16 files changed, 195 insertions(+), 129 deletions(-) create mode 100644 sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2AuthorizationCodeException.java diff --git a/sa-token-doc/_sidebar.md b/sa-token-doc/_sidebar.md index f23aa033..b926c996 100644 --- a/sa-token-doc/_sidebar.md +++ b/sa-token-doc/_sidebar.md @@ -56,19 +56,19 @@ - [OAuth2-Server搭建](/oauth2/oauth2-server) - [OAuth2-Server端开放 API 接口](/oauth2/oauth2-apidoc) - [配置 client 域名校验 ](/oauth2/oauth2-check-domain) - - [定制化登录页面与授权页面](/oauth2/oauth2-custom-login) - - [自定义 API 路由 ](/oauth2/oauth2-custom-api) - - [自定义 Scope 权限以处理器](/oauth2/oauth2-custom-scope) + - [自定义 Scope 权限及处理器](/oauth2/oauth2-custom-scope) - [为 Scope 划分等级](/oauth2/oauth2-scope-level) - [自定义 grant_type](/oauth2/oauth2-custom-grant_type) + - [定制化登录页面与授权页面](/oauth2/oauth2-custom-login) + - [自定义 API 路由 ](/oauth2/oauth2-custom-api) - [开启 OIDC 协议](/oauth2/oauth2-oidc) - - [OAuth2-与登录会话实现数据互通](/oauth2/oauth2-interworking) - [使用注解校验 Access-Token](/oauth2/oauth2-at-check) + - [OAuth2-与登录会话实现数据互通](/oauth2/oauth2-interworking) - [OAuth2 代码 API 参考](/oauth2/oauth2-dev) - [常见问题总结](/oauth2/oauth2-questions) - - + - **微服务** - [分布式Session会话](/micro/dcs-session) diff --git a/sa-token-doc/fun/data-structure.md b/sa-token-doc/fun/data-structure.md index a74bafcf..f5595551 100644 --- a/sa-token-doc/fun/data-structure.md +++ b/sa-token-doc/fun/data-structure.md @@ -311,9 +311,9 @@ clientId 反查 Client-Token {tokenName}:oauth2:client-token-index:{clientId} ``` -Past-Token 次级应用令牌索引 +Lower-Client-Token 次级应用令牌索引 ``` js -{tokenName}:oauth2:past-token-index:{clientId} +{tokenName}:oauth2:lower-client-token-index:{clientId} ``` ### 3.5、用户授权记录 diff --git a/sa-token-doc/fun/exception-code.md b/sa-token-doc/fun/exception-code.md index b86aeb9d..b2df661e 100644 --- a/sa-token-doc/fun/exception-code.md +++ b/sa-token-doc/fun/exception-code.md @@ -160,37 +160,32 @@ SaToken 中的所有异常都是继承于 `SaTokenException` 的,也就是说 #### sa-token-oauth2 相关: -| code码值 | 含义 | -| :-------- | :-------- | -| 30101 | client_id 不可为空 | -| 30102 | scope 不可为空 | +| code码值 | 含义 | +| :-------- | :-------- | +| 30101 | client_id 不可为空 | +| 30102 | scope 不可为空 | | 30103 | redirect_uri 不可为空 | -| 30104 | LoginId 不可为空 | -| 30105 | 无效client_id | -| 30106 | 无效access_token | -| 30107 | 无效 client_token | -| 30108 | Access-Token 不具备指定的 Scope | -| 30109 | Client-Token 不具备指定的 Scope | -| 30110 | 无效 code 码 | -| 30111 | 无效 Refresh-Token | -| 30112 | 请求的Scope暂未签约 | -| 30113 | 无效redirect_url | -| 30114 | 非法redirect_url | -| 30115 | 无效client_secret | -| 30116 | 请求的Scope暂未签约 | -| 30117 | 无效code | -| 30118 | 无效client_id | -| 30119 | 无效client_secret | -| 30120 | 无效redirect_uri | -| 30121 | 无效refresh_token | -| 30122 | 无效client_id | -| 30123 | 无效client_secret | -| 30124 | 无效client_id | -| 30125 | 无效response_type | -| 30131 | 暂未开放授权码模式 | -| 30132 | 暂未开放隐藏式模式 | -| 30133 | 暂未开放密码式模式 | -| 30134 | 暂未开放凭证式模式 | +| 30104 | LoginId 不可为空 | +| 30105 | 无效 client_id | +| 30106 | 无效 access_token | +| 30107 | 无效 client_token | +| 30108 | Access-Token 不具备指定的 Scope | +| 30109 | Client-Token 不具备指定的 Scope | +| 30110 | 无效 code 码 | +| 30111 | 无效 Refresh-Token | +| 30112 | 请求的 Scope 暂未签约 | +| 30113 | 无效 redirect_url | +| 30114 | 非法 redirect_url | +| 30115 | 无效 client_secret | +| 30120 | redirect_uri 不一致 | +| 30122 | client_id 不一致 | +| 30125 | 无效 response_type | +| 30126 | 无效 grant_type | +| 30127 | 无效 state | +| 30141 | 系统暂未开放的授权模式 | +| 30142 | 应用暂未开放的授权模式 | +| 30151 | 无效的请求 Method | +| 30191 | 其它异常 | #### sa-token-jwt 插件相关: diff --git a/sa-token-doc/oauth2/oauth2-check-domain.md b/sa-token-doc/oauth2/oauth2-check-domain.md index 841d3ff0..de3250ca 100644 --- a/sa-token-doc/oauth2/oauth2-check-domain.md +++ b/sa-token-doc/oauth2/oauth2-check-domain.md @@ -86,7 +86,7 @@ URL 没有通过校验,拒绝授权! - 反例:`http://sa-oauth-client.com@sa-token.cc` *详见源码:[SaOAuth2Template.java](https://gitee.com/dromara/sa-token/blob/master/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java) -`checkRightUrl` 方法。* +`checkRedirectUri` 方法。* 2、AllowUrls 配置的地址 `*` 通配符只允许出现在字符串末尾,不允许出现在字符串中间位置。 diff --git a/sa-token-doc/oauth2/oauth2-custom-scope.md b/sa-token-doc/oauth2/oauth2-custom-scope.md index a857913d..e3e6e881 100644 --- a/sa-token-doc/oauth2/oauth2-custom-scope.md +++ b/sa-token-doc/oauth2/oauth2-custom-scope.md @@ -1,4 +1,4 @@ -# OAuth2-自定义权限处理器 +# OAuth2-自定义 Scope 权限及处理器 --- diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/convert/SaOAuth2DataConverter.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/convert/SaOAuth2DataConverter.java index fddeb33a..e66097d1 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/convert/SaOAuth2DataConverter.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/convert/SaOAuth2DataConverter.java @@ -44,12 +44,11 @@ public interface SaOAuth2DataConverter { String convertScopeListToString(List scopeList); /** - * 转换 AllowUrl 数据格式:String -> List - * @param allowUrl / + * 转换 redirect_uri 数据格式:String -> List + * @param redirectUris / * @return / */ - List convertAllowUrlStringToList(String allowUrl); - + List convertRedirectUriStringToList(String redirectUris); /** * 将 Code 转换为 Access-Token diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/convert/SaOAuth2DataConverterDefaultImpl.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/convert/SaOAuth2DataConverterDefaultImpl.java index 03073085..b1738570 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/convert/SaOAuth2DataConverterDefaultImpl.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/convert/SaOAuth2DataConverterDefaultImpl.java @@ -59,14 +59,14 @@ public class SaOAuth2DataConverterDefaultImpl implements SaOAuth2DataConverter { } /** - * 转换 AllowUrl 数据格式:String -> List + * 转换 redirect_uri 数据格式:String -> List */ @Override - public List convertAllowUrlStringToList(String allowUrl) { - if(SaFoxUtil.isEmpty(allowUrl)) { + public List convertRedirectUriStringToList(String redirectUris) { + if(SaFoxUtil.isEmpty(redirectUris)) { return new ArrayList<>(); } - return SaFoxUtil.convertStringToList(allowUrl); + return SaFoxUtil.convertStringToList(redirectUris); } /** diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerateDefaultImpl.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerateDefaultImpl.java index 36c1e9fe..b9f5186e 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerateDefaultImpl.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerateDefaultImpl.java @@ -26,7 +26,9 @@ import cn.dev33.satoken.oauth2.data.model.RefreshTokenModel; import cn.dev33.satoken.oauth2.data.model.loader.SaClientModel; import cn.dev33.satoken.oauth2.data.model.request.RequestAuthModel; import cn.dev33.satoken.oauth2.error.SaOAuth2ErrorCode; +import cn.dev33.satoken.oauth2.exception.SaOAuth2AuthorizationCodeException; import cn.dev33.satoken.oauth2.exception.SaOAuth2Exception; +import cn.dev33.satoken.oauth2.exception.SaOAuth2RefreshTokenException; import cn.dev33.satoken.oauth2.strategy.SaOAuth2Strategy; import cn.dev33.satoken.util.SaFoxUtil; @@ -79,7 +81,7 @@ public class SaOAuth2DataGenerateDefaultImpl implements SaOAuth2DataGenerate { // 1、先校验 CodeModel cm = dao.getCode(code); - SaOAuth2Exception.throwBy(cm == null, "无效code", SaOAuth2ErrorCode.CODE_30110); + SaOAuth2AuthorizationCodeException.throwBy(cm == null, "无效 code: " + code, code, SaOAuth2ErrorCode.CODE_30110); // 2、删除旧Token dao.deleteAccessToken(dao.getAccessTokenValue(cm.clientId, cm.loginId)); @@ -118,7 +120,7 @@ public class SaOAuth2DataGenerateDefaultImpl implements SaOAuth2DataGenerate { // 获取 Refresh-Token 信息 RefreshTokenModel rt = dao.getRefreshToken(refreshToken); - SaOAuth2Exception.throwBy(rt == null, "无效refresh_token: " + refreshToken, SaOAuth2ErrorCode.CODE_30111); + SaOAuth2RefreshTokenException.throwBy(rt == null, "无效 refresh_token: " + refreshToken, refreshToken, SaOAuth2ErrorCode.CODE_30111); // 如果配置了[每次刷新产生新的Refresh-Token] SaClientModel clientModel = SaOAuth2Manager.getDataLoader().getClientModelNotNull(rt.clientId); @@ -276,7 +278,7 @@ public class SaOAuth2DataGenerateDefaultImpl implements SaOAuth2DataGenerate { public void checkState(String state) { String value = SaOAuth2Manager.getDao().getState(state); if(SaFoxUtil.isNotEmpty(value)) { - throw new SaOAuth2Exception("多次请求的 state 不可重复: " + state); + throw new SaOAuth2Exception("多次请求的 state 不可重复: " + state).setCode(SaOAuth2ErrorCode.CODE_30127); } SaOAuth2Manager.getDao().saveState(state); } diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/resolver/SaOAuth2DataResolver.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/resolver/SaOAuth2DataResolver.java index 49d3e949..37aacbd1 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/resolver/SaOAuth2DataResolver.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/resolver/SaOAuth2DataResolver.java @@ -72,7 +72,7 @@ public interface SaOAuth2DataResolver { * @param at token信息 * @return / */ - Map buildTokenReturnValue(AccessTokenModel at); + Map buildAccessTokenReturnValue(AccessTokenModel at); /** * 构建返回值: RefreshToken 刷新 Access-Token @@ -80,7 +80,7 @@ public interface SaOAuth2DataResolver { * @return / */ default Map buildRefreshTokenReturnValue(AccessTokenModel at) { - return buildTokenReturnValue(at); + return buildAccessTokenReturnValue(at); } /** diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/resolver/SaOAuth2DataResolverDefaultImpl.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/resolver/SaOAuth2DataResolverDefaultImpl.java index 4cac496b..64ade8a6 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/resolver/SaOAuth2DataResolverDefaultImpl.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/resolver/SaOAuth2DataResolverDefaultImpl.java @@ -18,12 +18,13 @@ package cn.dev33.satoken.oauth2.data.resolver; import cn.dev33.satoken.context.model.SaRequest; import cn.dev33.satoken.httpauth.basic.SaHttpBasicUtil; import cn.dev33.satoken.oauth2.SaOAuth2Manager; -import cn.dev33.satoken.oauth2.consts.SaOAuth2Consts; +import cn.dev33.satoken.oauth2.consts.SaOAuth2Consts.Param; import cn.dev33.satoken.oauth2.consts.SaOAuth2Consts.TokenType; import cn.dev33.satoken.oauth2.data.model.AccessTokenModel; import cn.dev33.satoken.oauth2.data.model.ClientTokenModel; import cn.dev33.satoken.oauth2.data.model.request.ClientIdAndSecretModel; import cn.dev33.satoken.oauth2.data.model.request.RequestAuthModel; +import cn.dev33.satoken.oauth2.error.SaOAuth2ErrorCode; import cn.dev33.satoken.oauth2.exception.SaOAuth2Exception; import cn.dev33.satoken.util.SaFoxUtil; import cn.dev33.satoken.util.SaResult; @@ -50,8 +51,8 @@ public class SaOAuth2DataResolverDefaultImpl implements SaOAuth2DataResolver { @Override public ClientIdAndSecretModel readClientIdAndSecret(SaRequest request) { // 优先从请求参数中获取 - String clientId = request.getParam(SaOAuth2Consts.Param.client_id); - String clientSecret = request.getParam(SaOAuth2Consts.Param.client_secret); + String clientId = request.getParam(Param.client_id); + String clientSecret = request.getParam(Param.client_secret); if(SaFoxUtil.isNotEmpty(clientId)) { return new ClientIdAndSecretModel(clientId, clientSecret); } @@ -68,22 +69,22 @@ public class SaOAuth2DataResolverDefaultImpl implements SaOAuth2DataResolver { } // 如果都没有提供,则抛出异常 - throw new SaOAuth2Exception("请提供 client 信息"); + throw new SaOAuth2Exception("请提供 client 信息").setCode(SaOAuth2ErrorCode.CODE_30191); } /** - * 数据读取:从请求对象中读取 AccessToken + * 数据读取:从请求对象中读取 AccessToken,获取不到返回 null */ @Override public String readAccessToken(SaRequest request) { // 优先从请求参数中获取 - String accessToken = request.getParam(SaOAuth2Consts.Param.access_token); + String accessToken = request.getParam(Param.access_token); if(SaFoxUtil.isNotEmpty(accessToken)) { return accessToken; } // 如果请求参数中没有提供 access_token 参数,则尝试从 Authorization 中获取 - String authorizationValue = request.getHeader(SaOAuth2Consts.Param.Authorization); + String authorizationValue = request.getHeader(Param.Authorization); if(SaFoxUtil.isEmpty(authorizationValue)) { return null; } @@ -99,18 +100,18 @@ public class SaOAuth2DataResolverDefaultImpl implements SaOAuth2DataResolver { } /** - * 数据读取:从请求对象中读取 ClientToken + * 数据读取:从请求对象中读取 ClientToken,获取不到返回 null */ @Override public String readClientToken(SaRequest request) { // 优先从请求参数中获取 - String clientToken = request.getParam(SaOAuth2Consts.Param.client_token); + String clientToken = request.getParam(Param.client_token); if(SaFoxUtil.isNotEmpty(clientToken)) { return clientToken; } // 如果请求参数中没有提供 client_token 参数,则尝试从 Authorization 中获取 - String authorizationValue = request.getHeader(SaOAuth2Consts.Param.Authorization); + String authorizationValue = request.getHeader(Param.Authorization); if(SaFoxUtil.isEmpty(authorizationValue)) { return null; } @@ -131,13 +132,11 @@ public class SaOAuth2DataResolverDefaultImpl implements SaOAuth2DataResolver { @Override public RequestAuthModel readRequestAuthModel(SaRequest req, Object loginId) { RequestAuthModel ra = new RequestAuthModel(); - ra.clientId = req.getParamNotNull(SaOAuth2Consts.Param.client_id); - ra.responseType = req.getParamNotNull(SaOAuth2Consts.Param.response_type); - ra.redirectUri = req.getParamNotNull(SaOAuth2Consts.Param.redirect_uri); - ra.state = req.getParam(SaOAuth2Consts.Param.state); - // 数据解析 - String scope = req.getParam(SaOAuth2Consts.Param.scope, ""); - ra.scopes = SaOAuth2Manager.getDataConverter().convertScopeStringToList(scope); + ra.clientId = req.getParamNotNull(Param.client_id); + ra.responseType = req.getParamNotNull(Param.response_type); + ra.redirectUri = req.getParamNotNull(Param.redirect_uri); + ra.state = req.getParam(Param.state); + ra.scopes = SaOAuth2Manager.getDataConverter().convertScopeStringToList(req.getParam(Param.scope)); ra.loginId = loginId; return ra; } @@ -147,7 +146,7 @@ public class SaOAuth2DataResolverDefaultImpl implements SaOAuth2DataResolver { * 构建返回值: 获取 token */ @Override - public Map buildTokenReturnValue(AccessTokenModel at) { + public Map buildAccessTokenReturnValue(AccessTokenModel at) { Map map = new LinkedHashMap<>(); map.put("token_type", at.tokenType); map.put("access_token", at.accessToken); @@ -172,7 +171,6 @@ public class SaOAuth2DataResolverDefaultImpl implements SaOAuth2DataResolver { Map map = new LinkedHashMap<>(); map.put("token_type", ct.tokenType); map.put("client_token", ct.clientToken); - // 兼容 OAuth2 协议 if(SaOAuth2Manager.getServerConfig().mode4ReturnAccessToken) { map.put("access_token", ct.clientToken); } diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/error/SaOAuth2ErrorCode.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/error/SaOAuth2ErrorCode.java index b6f3cbd7..37026d7e 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/error/SaOAuth2ErrorCode.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/error/SaOAuth2ErrorCode.java @@ -35,10 +35,10 @@ public interface SaOAuth2ErrorCode { /** LoginId 不可为空 */ int CODE_30104 = 30104; - /** 无效client_id */ + /** 无效 client_id */ int CODE_30105 = 30105; - /** 无效access_token */ + /** 无效 access_token */ int CODE_30106 = 30106; /** 无效 client_token */ @@ -50,57 +50,39 @@ public interface SaOAuth2ErrorCode { /** Client-Token 不具备指定的 Scope */ int CODE_30109 = 30109; - /** 无效 code 码 */ + /** 无效 Code 码 */ int CODE_30110 = 30110; /** 无效 Refresh-Token */ int CODE_30111 = 30111; - /** 请求的Scope暂未签约 */ + /** 请求的 Scope 暂未签约 */ int CODE_30112 = 30112; - /** 无效redirect_url */ + /** 无效 redirect_url */ int CODE_30113 = 30113; - /** 非法redirect_url */ + /** 非法 redirect_url */ int CODE_30114 = 30114; /** 无效client_secret */ int CODE_30115 = 30115; - /** 请求的Scope暂未签约 */ - int CODE_30116 = 30116; - - /** 无效code */ - int CODE_30117 = 30117; - - /** 无效client_id */ - int CODE_30118 = 30118; - - /** 无效client_secret */ - int CODE_30119 = 30119; - - /** 无效redirect_uri */ + /** redirect_uri 不一致 */ int CODE_30120 = 30120; - /** 无效refresh_token */ - int CODE_30121 = 30121; - - /** 无效client_id */ + /** client_id 不一致 */ int CODE_30122 = 30122; - /** 无效client_secret */ - int CODE_30123 = 30123; - - /** 无效client_id */ - int CODE_30124 = 30124; - - /** 无效response_type */ + /** 无效 response_type */ int CODE_30125 = 30125; - /** 无效grant_type */ + /** 无效 grant_type */ int CODE_30126 = 30126; + /** 无效 state */ + int CODE_30127 = 30127; + /** 暂未开放授权码模式 */ int CODE_30131 = 30131; @@ -113,7 +95,16 @@ public interface SaOAuth2ErrorCode { /** 暂未开放凭证式模式 */ int CODE_30134 = 30134; - /** 无效的请求 Method */ + /** 系统暂未开放的授权模式 */ int CODE_30141 = 30141; + /** 应用暂未开放的授权模式 */ + int CODE_30142 = 30142; + + /** 无效的请求 Method */ + int CODE_30151 = 30151; + + /** 其它异常 */ + int CODE_30191 = 30191; + } diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2AuthorizationCodeException.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2AuthorizationCodeException.java new file mode 100644 index 00000000..5b8d45db --- /dev/null +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2AuthorizationCodeException.java @@ -0,0 +1,74 @@ +/* + * 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.exception; + +/** + * 一个异常:代表 Code 授权码相关错误 + * + * @author click33 + * @since 1.39.0 + */ +public class SaOAuth2AuthorizationCodeException extends SaOAuth2Exception { + + /** + * 序列化版本号 + */ + private static final long serialVersionUID = 6806129545290130114L; + + /** + * 一个异常:代表 Access-Token 相关错误 + * @param cause 根异常原因 + */ + public SaOAuth2AuthorizationCodeException(Throwable cause) { + super(cause); + } + + /** + * 一个异常:代表 Access-Token 相关错误 + * @param message 异常描述 + */ + public SaOAuth2AuthorizationCodeException(String message) { + super(message); + } + + /** + * 具体引起异常的 code 值 + */ + public String authorizationCode; + + public String getAuthorizationCode() { + return authorizationCode; + } + + public SaOAuth2AuthorizationCodeException setAuthorizationCode(String authorizationCode) { + this.authorizationCode = authorizationCode; + return this; + } + + /** + * 如果 flag==true,则抛出 message 异常 + * @param flag 标记 + * @param message 异常信息 + * @param authorizationCode 引入异常的 code 值 + * @param code 异常细分码 + */ + public static void throwBy(boolean flag, String message, String authorizationCode, int code) { + if(flag) { + throw new SaOAuth2AuthorizationCodeException(message).setAuthorizationCode(authorizationCode).setCode(code); + } + } + +} diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/granttype/handler/RefreshTokenGrantTypeHandler.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/granttype/handler/RefreshTokenGrantTypeHandler.java index 238e35c1..df0143f5 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/granttype/handler/RefreshTokenGrantTypeHandler.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/granttype/handler/RefreshTokenGrantTypeHandler.java @@ -22,7 +22,8 @@ import cn.dev33.satoken.oauth2.consts.SaOAuth2Consts; import cn.dev33.satoken.oauth2.data.model.AccessTokenModel; import cn.dev33.satoken.oauth2.data.model.RefreshTokenModel; import cn.dev33.satoken.oauth2.error.SaOAuth2ErrorCode; -import cn.dev33.satoken.oauth2.exception.SaOAuth2Exception; +import cn.dev33.satoken.oauth2.exception.SaOAuth2ClientModelException; +import cn.dev33.satoken.oauth2.exception.SaOAuth2RefreshTokenException; import java.util.List; @@ -46,10 +47,10 @@ public class RefreshTokenGrantTypeHandler implements SaOAuth2GrantTypeHandlerInt // 校验:Refresh-Token 是否存在 RefreshTokenModel rt = SaOAuth2Manager.getDao().getRefreshToken(refreshToken); - SaOAuth2Exception.throwBy(rt == null, "无效refresh_token: " + refreshToken, SaOAuth2ErrorCode.CODE_30121); + SaOAuth2RefreshTokenException.throwBy(rt == null, "无效refresh_token: " + refreshToken, refreshToken, SaOAuth2ErrorCode.CODE_30111); // 校验:Refresh-Token 代表的 ClientId 与提供的 ClientId 是否一致 - SaOAuth2Exception.throwBy( ! rt.clientId.equals(clientId), "无效client_id: " + clientId, SaOAuth2ErrorCode.CODE_30122); + SaOAuth2ClientModelException.throwBy( ! rt.clientId.equals(clientId), "无效client_id: " + clientId, clientId, SaOAuth2ErrorCode.CODE_30122); // 获取新 Access-Token AccessTokenModel accessTokenModel = SaOAuth2Manager.getDataGenerate().refreshAccessToken(refreshToken); diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/processor/SaOAuth2ServerProcessor.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/processor/SaOAuth2ServerProcessor.java index e6401002..840d9f4d 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/processor/SaOAuth2ServerProcessor.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/processor/SaOAuth2ServerProcessor.java @@ -157,7 +157,7 @@ public class SaOAuth2ServerProcessor { } // 默认返回 - throw new SaOAuth2Exception("无效response_type: " + ra.responseType).setCode(SaOAuth2ErrorCode.CODE_30125); + throw new SaOAuth2Exception("无效 response_type: " + ra.responseType).setCode(SaOAuth2ErrorCode.CODE_30125); } /** @@ -166,7 +166,7 @@ public class SaOAuth2ServerProcessor { */ public Object token() { AccessTokenModel accessTokenModel = SaOAuth2Strategy.instance.grantTypeAuth.apply(SaHolder.getRequest()); - return SaOAuth2Manager.getDataResolver().buildTokenReturnValue(accessTokenModel); + return SaOAuth2Manager.getDataResolver().buildAccessTokenReturnValue(accessTokenModel); } /** @@ -239,7 +239,7 @@ public class SaOAuth2ServerProcessor { // 此请求只允许 POST 方式 if(!req.isMethod(SaHttpMethod.POST)) { - throw new SaOAuth2Exception("无效请求方式:" + req.getMethod()).setCode(SaOAuth2ErrorCode.CODE_30141); + throw new SaOAuth2Exception("无效请求方式:" + req.getMethod()).setCode(SaOAuth2ErrorCode.CODE_30151); } // 确认授权 @@ -366,7 +366,7 @@ public class SaOAuth2ServerProcessor { } // 其它 else { - throw new SaOAuth2Exception("无效 response_type: " + req.getParam(Param.response_type)).setCode(SaOAuth2ErrorCode.CODE_30125); + throw new SaOAuth2Exception("无效 response_type: " + responseType).setCode(SaOAuth2ErrorCode.CODE_30125); } } @@ -374,14 +374,14 @@ public class SaOAuth2ServerProcessor { * 系统未开放此授权模式时抛出异常 */ public void throwErrorSystemNotEnableModel() { - throw new SaOAuth2Exception("系统暂未开放此授权模式").setCode(SaOAuth2ErrorCode.CODE_30131); + throw new SaOAuth2Exception("系统暂未开放此授权模式").setCode(SaOAuth2ErrorCode.CODE_30141); } /** * 应用未开放此授权模式时抛出异常 */ public void throwErrorClientNotEnableModel() { - throw new SaOAuth2Exception("应用暂未开放此授权模式").setCode(SaOAuth2ErrorCode.CODE_30131); + throw new SaOAuth2Exception("应用暂未开放此授权模式").setCode(SaOAuth2ErrorCode.CODE_30142); } } 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 e9ca6372..ec722221 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 @@ -22,6 +22,7 @@ import cn.dev33.satoken.oauth2.consts.GrantType; import cn.dev33.satoken.oauth2.consts.SaOAuth2Consts; import cn.dev33.satoken.oauth2.data.model.loader.SaClientModel; import cn.dev33.satoken.oauth2.data.model.request.ClientIdAndSecretModel; +import cn.dev33.satoken.oauth2.error.SaOAuth2ErrorCode; import cn.dev33.satoken.oauth2.exception.SaOAuth2Exception; import cn.dev33.satoken.oauth2.function.strategy.*; import cn.dev33.satoken.oauth2.granttype.handler.AuthorizationCodeGrantTypeHandler; @@ -162,16 +163,16 @@ public final class SaOAuth2Strategy { String grantType = req.getParamNotNull(SaOAuth2Consts.Param.grant_type); SaOAuth2GrantTypeHandlerInterface grantTypeHandler = grantTypeHandlerMap.get(grantType); if(grantTypeHandler == null) { - throw new RuntimeException("无效 grant_type: " + grantType); + throw new SaOAuth2Exception("无效 grant_type: " + grantType).setCode(SaOAuth2ErrorCode.CODE_30126); } // 看看全局是否开启了此 grantType SaOAuth2ServerConfig config = SaOAuth2Manager.getServerConfig(); if(grantType.equals(GrantType.authorization_code) && !config.getEnableAuthorizationCode() ) { - throw new SaOAuth2Exception("系统未开放的 grant_type: " + grantType); + throw new SaOAuth2Exception("系统未开放的 grant_type: " + grantType).setCode(SaOAuth2ErrorCode.CODE_30126); } if(grantType.equals(GrantType.password) && !config.getEnablePassword() ) { - throw new SaOAuth2Exception("系统未开放的 grant_type: " + grantType); + throw new SaOAuth2Exception("系统未开放的 grant_type: " + grantType).setCode(SaOAuth2ErrorCode.CODE_30126); } // 校验 clientSecret 和 scope @@ -181,7 +182,7 @@ public final class SaOAuth2Strategy { // 检测应用是否开启此 grantType if(!clientModel.getAllowGrantTypes().contains(grantType)) { - throw new SaOAuth2Exception("应用未开放的 grant_type: " + grantType); + throw new SaOAuth2Exception("应用未开放的 grant_type: " + grantType).setCode(SaOAuth2ErrorCode.CODE_30141); } // 调用 处理器 diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java index 2e6a506c..5ce1b8fb 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java @@ -72,8 +72,11 @@ public class SaOAuth2Template { */ public SaClientModel checkClientSecret(String clientId, String clientSecret) { SaClientModel cm = checkClientModel(clientId); - SaOAuth2ClientModelException.throwBy(cm.clientSecret == null || ! cm.clientSecret.equals(clientSecret), "无效client_secret: " + clientSecret, - clientId, SaOAuth2ErrorCode.CODE_30115); + if(cm.clientSecret == null || ! cm.clientSecret.equals(clientSecret)) { + throw new SaOAuth2ClientModelException("无效 client_secret: " + clientSecret) + .setClientId(clientId) + .setCode(SaOAuth2ErrorCode.CODE_30115); + } return cm; } @@ -146,7 +149,7 @@ public class SaOAuth2Template { public void checkRedirectUri(String clientId, String url) { // 1、是否是一个有效的url if( ! SaFoxUtil.isUrl(url)) { - throw new SaOAuth2ClientModelException("无效redirect_url:" + url) + throw new SaOAuth2ClientModelException("无效 redirect_url:" + url) .setClientId(clientId) .setCode(SaOAuth2ErrorCode.CODE_30113); } @@ -180,7 +183,8 @@ public class SaOAuth2Template { // // 但是为了安全起见,这么做还是有必要的 throw new SaOAuth2ClientModelException("无效 redirect_url(不允许出现@字符):" + url) - .setClientId(clientId); + .setClientId(clientId) + .setCode(SaOAuth2ErrorCode.CODE_30113); } // 4、是否在[允许地址列表]之中 @@ -231,7 +235,8 @@ public class SaOAuth2Template { // http://sa-oauth-server.com:8000/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://shop.sa-oauth2-client.com/ // // 但是为了安全起见,这么做还是有必要的 - throw new SaOAuth2Exception("无效的 allow-url 配置(*通配符只允许出现在最后一位):" + url); + throw new SaOAuth2Exception("无效的 allow-url 配置(*通配符只允许出现在最后一位):" + url) + .setCode(SaOAuth2ErrorCode.CODE_30114); } } } @@ -299,18 +304,18 @@ public class SaOAuth2Template { // 校验:Code是否存在 CodeModel cm = dao.getCode(code); - SaOAuth2Exception.throwBy(cm == null, "无效 code: " + code, SaOAuth2ErrorCode.CODE_30117); + SaOAuth2AuthorizationCodeException.throwBy(cm == null, "无效 code: " + code, code, SaOAuth2ErrorCode.CODE_30110); // 校验:ClientId是否一致 - SaOAuth2Exception.throwBy( ! cm.clientId.equals(clientId), "无效 client_id: " + clientId, SaOAuth2ErrorCode.CODE_30118); + SaOAuth2ClientModelException.throwBy( ! cm.clientId.equals(clientId), "无效 client_id: " + clientId, clientId, SaOAuth2ErrorCode.CODE_30105); // 校验:Secret是否正确 String dbSecret = checkClientModel(clientId).clientSecret; - SaOAuth2Exception.throwBy(dbSecret == null || ! dbSecret.equals(clientSecret), "无效 client_secret: " + clientSecret, SaOAuth2ErrorCode.CODE_30119); + SaOAuth2ClientModelException.throwBy(dbSecret == null || ! dbSecret.equals(clientSecret), "无效 client_secret: " + clientSecret, clientId, SaOAuth2ErrorCode.CODE_30115); // 如果提供了redirectUri,则校验其是否与请求Code时提供的一致 if( ! SaFoxUtil.isEmpty(redirectUri)) { - SaOAuth2Exception.throwBy( ! redirectUri.equals(cm.redirectUri), "无效 redirect_uri: " + redirectUri, SaOAuth2ErrorCode.CODE_30120); + SaOAuth2ClientModelException.throwBy( ! redirectUri.equals(cm.redirectUri), "无效 redirect_uri: " + redirectUri, clientId, SaOAuth2ErrorCode.CODE_30120); } // 返回CodeModel @@ -330,15 +335,15 @@ public class SaOAuth2Template { // 校验:Refresh-Token是否存在 RefreshTokenModel rt = dao.getRefreshToken(refreshToken); - SaOAuth2RefreshTokenException.throwBy(rt == null, "无效 refresh_token: " + refreshToken, refreshToken, SaOAuth2ErrorCode.CODE_30121); + SaOAuth2RefreshTokenException.throwBy(rt == null, "无效 refresh_token: " + refreshToken, refreshToken, SaOAuth2ErrorCode.CODE_30111); // 校验:ClientId是否一致 SaOAuth2ClientModelException.throwBy( ! rt.clientId.equals(clientId), "无效 client_id: " + clientId, clientId, SaOAuth2ErrorCode.CODE_30122); // 校验:Secret是否正确 String dbSecret = checkClientModel(clientId).clientSecret; - SaOAuth2ClientModelException.throwBy(dbSecret == null || ! dbSecret.equals(clientSecret), "无效client_secret: " + clientSecret, - clientId, SaOAuth2ErrorCode.CODE_30123); + SaOAuth2ClientModelException.throwBy(dbSecret == null || ! dbSecret.equals(clientSecret), "无效 client_secret: " + clientSecret, + clientId, SaOAuth2ErrorCode.CODE_30115); // 返回 Refresh-Token return rt; @@ -353,7 +358,7 @@ public class SaOAuth2Template { */ public AccessTokenModel checkAccessTokenParam(String clientId, String clientSecret, String accessToken) { AccessTokenModel at = checkAccessToken(accessToken); - SaOAuth2ClientModelException.throwBy( ! at.clientId.equals(clientId), "无效 client_id:" + clientId, clientId, SaOAuth2ErrorCode.CODE_30124); + SaOAuth2ClientModelException.throwBy( ! at.clientId.equals(clientId), "无效 client_id:" + clientId, clientId, SaOAuth2ErrorCode.CODE_30122); checkClientSecret(clientId, clientSecret); return at; } -- Gitee From b1180a219d651fc1406062060bc0b78dcfa86854 Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Mon, 26 Aug 2024 18:50:38 +0800 Subject: [PATCH 161/172] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=9F=BA=E4=BA=8E?= =?UTF-8?q?=E5=86=85=E5=AD=98=E5=BD=A2=E5=BC=8F=E7=9A=84=20client=20?= =?UTF-8?q?=E4=BF=A1=E6=81=AF=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sa-token-doc/_sidebar.md | 1 + sa-token-doc/oauth2/oauth2-data-loader.md | 208 ++++++++++++++++++ sa-token-doc/oauth2/oauth2-server.md | 37 +++- .../oauth2/config/SaOAuth2ServerConfig.java | 39 +++- .../data/loader/SaOAuth2DataLoader.java | 3 +- 5 files changed, 279 insertions(+), 9 deletions(-) create mode 100644 sa-token-doc/oauth2/oauth2-data-loader.md diff --git a/sa-token-doc/_sidebar.md b/sa-token-doc/_sidebar.md index b926c996..8cdb52ad 100644 --- a/sa-token-doc/_sidebar.md +++ b/sa-token-doc/_sidebar.md @@ -55,6 +55,7 @@ - [OAuth2.0简述](/oauth2/readme) - [OAuth2-Server搭建](/oauth2/oauth2-server) - [OAuth2-Server端开放 API 接口](/oauth2/oauth2-apidoc) + - [自定义数据加载器](/oauth2/oauth2-data-loader) - [配置 client 域名校验 ](/oauth2/oauth2-check-domain) - [自定义 Scope 权限及处理器](/oauth2/oauth2-custom-scope) - [为 Scope 划分等级](/oauth2/oauth2-scope-level) diff --git a/sa-token-doc/oauth2/oauth2-data-loader.md b/sa-token-doc/oauth2/oauth2-data-loader.md new file mode 100644 index 00000000..92d6055e --- /dev/null +++ b/sa-token-doc/oauth2/oauth2-data-loader.md @@ -0,0 +1,208 @@ +# OAuth2-自定义数据加载器 + + + +### 1、基于内存的数据加载 + +在之前搭建 OAuth2-Server 示例中,我们演示了 client 信息配置方案: +``` java +// Sa-Token OAuth2 定制化配置 +@Autowired +public void configOAuth2Server(SaOAuth2ServerConfig oauth2Server) { + + // 添加 client + oauth2Server.addClient( + new SaClientModel() + .setClientId("1001") // client id + .setClientSecret("aaaa-bbbb-cccc-dddd-eeee") // client 秘钥 + .addAllowRedirectUris("*") // 所有允许授权的 url + .addContractScopes("openid", "userid", "userinfo") // 所有签约的权限 + .addAllowGrantTypes( // 所有允许的授权模式 + GrantType.authorization_code, // 授权码式 + GrantType.implicit, // 隐式式 + GrantType.refresh_token, // 刷新令牌 + GrantType.password, // 密码式 + GrantType.client_credentials, // 客户端模式 + ) + ) + + // 可以添加更多 client 信息,只要保持 clientId 唯一就行了 + // oauth2Server.addClient(...) + +} +``` + +你也可以在 `application.yml` 配置中 `client` 信息: + + + +``` yaml +# sa-token配置 +sa-token: + # OAuth2.0 配置 + oauth2-server: + # client 列表 + clients: + # 客户端1 + 1001: + # 客户端id + client-id: 1001 + # 客户端秘钥 + client-secret: aaaa-bbbb-cccc-dddd-eeee + # 所有允许授权的 url + allow-redirect-uris: + - http://sa-oauth-client.com:8002 + - http://sa-oauth-client.com:8002/* + # 所有签约的权限 + contract-scopes: + - openid + - userid + - userinfo + # 所有允许的授权模式 + allow-grant-types: + - authorization_code + - implicit + - refresh_token + - password + - client_credentials + # 客户端2 + 1002: + # 客户端id + client-id: 1002 + # 更多配置 ... +``` + +``` properties +########### 客户端1 +# 客户端id +sa-token.oauth2-server.clients.1001.client-id=1001 +# 客户端秘钥 +sa-token.oauth2-server.clients.1001.client-secret=aaaa-bbbb-cccc-dddd-eeee +# 所有允许授权的 url +sa-token.oauth2-server.clients.1001.allow-redirect-uris[0]=http://sa-oauth-client.com:8002 +sa-token.oauth2-server.clients.1001.allow-redirect-uris[1]=http://sa-oauth-client.com:8002/* +# 所有签约的权限 +sa-token.oauth2-server.clients.1001.contract-scopes[0]=openid +sa-token.oauth2-server.clients.1001.contract-scopes[1]=userid +sa-token.oauth2-server.clients.1001.contract-scopes[2]=userinfo +# 所有允许的授权模式 +sa-token.oauth2-server.clients.1001.allow-grant-types[0]=authorization_code +sa-token.oauth2-server.clients.1001.allow-grant-types[1]=implicit +sa-token.oauth2-server.clients.1001.allow-grant-types[2]=refresh_token +sa-token.oauth2-server.clients.1001.allow-grant-types[3]=password +sa-token.oauth2-server.clients.1001.allow-grant-types[4]=client_credentials + +########### 客户端2 +sa-token.oauth2-server.clients.1002.client-id=1002 +sa-token.oauth2-server.clients.1002.client-secret=... +``` + + + +这两种方案都是基于内存形式的 client 信息配置,只适合简单的测试,一般真实项目的 client 信息都是保存在数据库中的,下面演示一下如何在数据库中动态获取 client 信息 + + +### 2、基于数据库的数据加载 + +你只需要自定义数据加载器:新建 `SaOAuth2DataLoaderImpl` 实现 `SaOAuth2DataLoader` 接口。 + + +``` java +/** + * Sa-Token OAuth2:自定义数据加载器 + */ +@Component +public class SaOAuth2DataLoaderImpl implements SaOAuth2DataLoader { + + // 根据 clientId 获取 Client 信息 + @Override + public SaClientModel getClientModel(String clientId) { + // 此为模拟数据,真实环境需要从数据库查询 + if("1001".equals(clientId)) { + return new SaClientModel() + .setClientId("1001") // client id + .setClientSecret("aaaa-bbbb-cccc-dddd-eeee") // client 秘钥 + .addAllowRedirectUris("*") // 所有允许授权的 url + .addContractScopes("openid", "userid", "userinfo") // 所有签约的权限 + .addAllowGrantTypes( // 所有允许的授权模式 + GrantType.authorization_code, // 授权码式 + GrantType.implicit, // 隐式式 + GrantType.refresh_token, // 刷新令牌 + GrantType.password, // 密码式 + GrantType.client_credentials // 客户端模式 + ) + ; + } + return null; + } + + // 根据 clientId 和 loginId 获取 openid + @Override + public String getOpenid(String clientId, Object loginId) { + // 此处使用框架默认算法生成 openid,真实环境建议改为从数据库查询 + return SaOAuth2DataLoader.super.getOpenid(clientId, loginId); + } + +} +``` + +此种形式更加灵活,后续文档将默认按照此种形式来展示示例。 + + +### 3、自定义 openid 生成算法 + +openid 是用户在某一 client 下的唯一标识,其有如下特点: + +- 一个用户在同一个 client 下,openid 是固定的,每次请求都会返回相同的值。 +- 一个用户在不同的 client 下,openid 是不同的,会返回不同的值。 + +oauth2-client 在每次授权时可根据返回的 openid 值来确定用户身份。 + +框架默认的 openid 生成算法为: +``` java +md5(prefix + "_" + clientId + "_" + loginId); +``` + +其中的 prefix 前缀默认值为:`openid_default_digest_prefix`,你可以通过以下方式配置: + + + +``` yaml +# sa-token配置 +sa-token: + oauth2-server: + # 默认 openid 生成算法中使用的摘要前缀 + openid-digest-prefix: xxxxxx +``` + +``` properties +# 默认 openid 生成算法中使用的摘要前缀 +sa-token.oauth2-server.openid-digest-prefix=xxxxxx +``` + + +正常来讲,openid 算法需要保证: + +1. 单个 clientId 下同一 loginId 生成的 `openid` 一致。[必须] +2. 多个 clientId 下同一 loginId 生成的 `openid` 不一致。[非常建议] +3. 客户端无法通过 clientId + loginId 推测 `openid` 值。[建议] +4. 客户端无法通过 clientId + loginId + openid 推测该 loginId 在其它 clientId 下的 `openid` 值。[建议] +5. oauth2-server 自身由 `openid` 可以反查出对应的 clientId 和 loginId。[根据业务需求而定是否满足] + +框架内置的算法,可以满足 1和2,如果自定义了 `sa-token.oauth2-server.openid-digest-prefix` 配置,可以满足3。 + +如果自定义配置的 prefix 长度较短,或比较简单呈现规律性,则有客户端根据 clientId + loginId + openid 穷举爆破出 `prefix` 的风险, +从而获得提前计算彩虹表来推测出其它 clientId、loginId 对应 openid 值的能力。 + +如果自定义的 prefix 前缀比较复杂,让客户端无法爆破,则可以满足4。但依然无法满足5。 + +所以 openid 算法的最优解,应该是 oauth2-server 采用随机字符串作为 openid,然后自建数据库表来维护其映射关系,这样可以同时满足12345。 + +表结构参考如下: + +- id:数据id,主键。 +- client_id:应用id。 +- user_id:用户账号id。 +- openid:对应的 openid 值,随机字符串。 +- create_time:数据创建时间。 +- xxx:其它需要扩展的字段。 diff --git a/sa-token-doc/oauth2/oauth2-server.md b/sa-token-doc/oauth2/oauth2-server.md index ab385de2..66d0e2a7 100644 --- a/sa-token-doc/oauth2/oauth2-server.md +++ b/sa-token-doc/oauth2/oauth2-server.md @@ -59,6 +59,7 @@ implementation 'org.apache.commons:commons-pool2' ### 3、开放服务 + - -2、新建`SaOAuth2ServerController` +1、新建`SaOAuth2ServerController` ``` java /** * Sa-Token OAuth2 Server端 控制器 @@ -122,6 +123,25 @@ public class SaOAuth2ServerController { @Autowired public void setSaOAuth2Config(SaOAuth2Config oauth2Server) { + // 添加 client 信息 + oauth2Server.addClient( + new SaClientModel() + .setClientId("1001") // client id + .setClientSecret("aaaa-bbbb-cccc-dddd-eeee") // client 秘钥 + .addAllowRedirectUris("*") // 所有允许授权的 url + .addContractScopes("openid", "userid", "userinfo") // 所有签约的权限 + .addAllowGrantTypes( // 所有允许的授权模式 + GrantType.authorization_code, // 授权码式 + GrantType.implicit, // 隐式式 + GrantType.refresh_token, // 刷新令牌 + GrantType.password, // 密码式 + GrantType.client_credentials, // 客户端模式 + ) + ); + + // 可以添加更多 client 信息,只要保持 clientId 唯一就行了 + // oauth2Server.addClient(...) + // 配置:未登录时返回的View oauth2Server.notLoginView = () -> { String msg = "当前会话在OAuth-Server端尚未登录,请先访问" @@ -154,12 +174,15 @@ public class SaOAuth2ServerController { return res; }; } - + } ``` -注意:在 `doLoginHandle` 函数里如果要获取 name, pwd 以外的参数,可通过 `SaHolder.getRequest().getParam("xxx")` 来获取。 +注意: +- 在 `doLoginHandle` 函数里如果要获取 name, pwd 以外的参数,可通过 `SaHolder.getRequest().getParam("xxx")` 来获取。 +- 你可以在 [框架配置](/use/config?id=SaClientModel属性定义) 了解有关 `SaClientModel` 对象所有属性的详细定义。 + -3、全局异常处理 +2、全局异常处理 ``` java @RestControllerAdvice public class GlobalExceptionHandler { @@ -171,7 +194,7 @@ public class GlobalExceptionHandler { } ``` -4、创建启动类: +3、创建启动类: ``` java /** * 启动:Sa-OAuth2 Server端 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 abdbc03d..a01b3c8c 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 @@ -16,12 +16,15 @@ package cn.dev33.satoken.oauth2.config; import cn.dev33.satoken.oauth2.consts.SaOAuth2Consts; +import cn.dev33.satoken.oauth2.data.model.loader.SaClientModel; import cn.dev33.satoken.oauth2.function.SaOAuth2ConfirmViewFunction; import cn.dev33.satoken.oauth2.function.SaOAuth2DoLoginHandleFunction; import cn.dev33.satoken.oauth2.function.SaOAuth2NotLoginViewFunction; import cn.dev33.satoken.util.SaResult; import java.io.Serializable; +import java.util.LinkedHashMap; +import java.util.Map; /** * Sa-Token OAuth2 Server 端 配置类 Model @@ -78,12 +81,14 @@ public class SaOAuth2ServerConfig implements Serializable { /** 是否在返回值中隐藏默认的状态字段 (code、msg、data) */ public Boolean hideStatusField = false; - /** * oidc 相关配置 */ SaOAuth2OidcConfig oidc = new SaOAuth2OidcConfig(); + /** client 列表 */ + public Map clients = new LinkedHashMap<>(); + /** * @return enableCode */ @@ -349,6 +354,23 @@ public class SaOAuth2ServerConfig implements Serializable { return this; } + /** + * 获取 client 列表 + * @return / + */ + public Map getClients() { + return clients; + } + + /** + * 写入 client 列表 + * @return / + */ + public SaOAuth2ServerConfig setClients(Map clients) { + this.clients = clients; + return this; + } + // -------------------- SaOAuth2Handle 所有回调函数 -------------------- @@ -388,4 +410,19 @@ public class SaOAuth2ServerConfig implements Serializable { ", oidc='" + oidc + '}'; } + + + /** + * 注册 client + * @return / + */ + public SaOAuth2ServerConfig addClient(SaClientModel client) { + if(this.clients == null) { + this.clients = new LinkedHashMap<>(); + } + this.clients.put(client.getClientId(), client); + return this; + } + + } 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 aa8815a7..635f07ad 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 @@ -36,7 +36,8 @@ public interface SaOAuth2DataLoader { * @return ClientModel */ default SaClientModel getClientModel(String clientId) { - return null; + // 默认从内存配置中读取数据 + return SaOAuth2Manager.getServerConfig().getClients().get(clientId); } /** -- Gitee From 0229efa5fd09c02840fa97408344fc8a8c6622c7 Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Mon, 26 Aug 2024 23:14:59 +0800 Subject: [PATCH 162/172] =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=A4=BA=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/templates/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-client/src/main/resources/templates/index.html b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-client/src/main/resources/templates/index.html index 0e8d6858..6399985e 100644 --- a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-client/src/main/resources/templates/index.html +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-client/src/main/resources/templates/index.html @@ -48,11 +48,11 @@ 当请求链接不包含 scope 权限,或请求的 scope 近期已授权时,将无需用户手动确认,做到静默授权 http://sa-oauth-server.com:8000/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/ - + 当请求链接包含具体的 scope 权限时,将需要用户手动确认,此时 OAuth-Server 会返回更多的数据 - http://sa-oauth-server.com:8000/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/&scope=openid,userid,userinfo,oidc + http://sa-oauth-server.com:8000/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/&scope=openid,userinfo 我们可以拿着 Refresh-Token 去刷新我们的 Access-Token,每次刷新后旧Token将作废 -- Gitee From 3eaa6b9baf3d3fa7ba7c3aed114372a7e9d114c1 Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Wed, 28 Aug 2024 00:28:41 +0800 Subject: [PATCH 163/172] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=89=8D=E7=AB=AF?= =?UTF-8?q?=E6=8F=90=E4=BA=A4=E5=90=8C=E5=90=8Dcookie=E6=97=B6=E7=9A=84?= =?UTF-8?q?=E6=A1=86=E6=9E=B6=E9=94=99=E8=AF=BB=E7=8E=B0=E8=B1=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../satoken/context/model/SaRequest.java | 14 +++++++ .../dubbo/model/SaRequestForDubbo.java | 20 ++++++++++ .../dubbo3/model/SaRequestForDubbo3.java | 20 ++++++++++ .../context/grpc/model/SaRequestForGrpc.java | 20 ++++++++++ .../servlet/model/SaRequestForServlet.java | 27 +++++++++++++ .../reactor/model/SaRequestForReactor.java | 38 +++++++++++++++++++ .../reactor/model/SaRequestForReactor.java | 38 +++++++++++++++++++ .../servlet/model/SaRequestForServlet.java | 27 +++++++++++++ .../solon/model/SaRequestForSolon.java | 35 ++++++++++++++++- 9 files changed, 237 insertions(+), 2 deletions(-) diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/context/model/SaRequest.java b/sa-token-core/src/main/java/cn/dev33/satoken/context/model/SaRequest.java index 8a0b3d45..91c0f73f 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/context/model/SaRequest.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/context/model/SaRequest.java @@ -131,6 +131,20 @@ public interface SaRequest { */ String getCookieValue(String name); + /** + * 在 [ Cookie作用域 ] 里获取一个值 (第一个此名称的) + * @param name 键 + * @return 值 + */ + String getCookieFirstValue(String name); + + /** + * 在 [ Cookie作用域 ] 里获取一个值 (最后一个此名称的) + * @param name 键 + * @return 值 + */ + String getCookieLastValue(String name); + /** * 返回当前请求path (不包括上下文名称) * @return / diff --git a/sa-token-plugin/sa-token-dubbo/src/main/java/cn/dev33/satoken/context/dubbo/model/SaRequestForDubbo.java b/sa-token-plugin/sa-token-dubbo/src/main/java/cn/dev33/satoken/context/dubbo/model/SaRequestForDubbo.java index f411be02..5cebcd25 100644 --- a/sa-token-plugin/sa-token-dubbo/src/main/java/cn/dev33/satoken/context/dubbo/model/SaRequestForDubbo.java +++ b/sa-token-plugin/sa-token-dubbo/src/main/java/cn/dev33/satoken/context/dubbo/model/SaRequestForDubbo.java @@ -95,6 +95,26 @@ public class SaRequestForDubbo implements SaRequest { return null; } + /** + * 在 [ Cookie作用域 ] 里获取一个值 (第一个此名称的) + */ + @Override + public String getCookieFirstValue(String name){ + // 不传播 cookie 参数 + return null; + } + + /** + * 在 [ Cookie作用域 ] 里获取一个值 (最后一个此名称的) + * @param name 键 + * @return 值 + */ + @Override + public String getCookieLastValue(String name){ + // 不传播 cookie 参数 + return null; + } + /** * 返回当前请求path (不包括上下文名称) */ diff --git a/sa-token-plugin/sa-token-dubbo3/src/main/java/cn/dev33/satoken/context/dubbo3/model/SaRequestForDubbo3.java b/sa-token-plugin/sa-token-dubbo3/src/main/java/cn/dev33/satoken/context/dubbo3/model/SaRequestForDubbo3.java index 1200f1c0..73e6d2a1 100644 --- a/sa-token-plugin/sa-token-dubbo3/src/main/java/cn/dev33/satoken/context/dubbo3/model/SaRequestForDubbo3.java +++ b/sa-token-plugin/sa-token-dubbo3/src/main/java/cn/dev33/satoken/context/dubbo3/model/SaRequestForDubbo3.java @@ -95,6 +95,26 @@ public class SaRequestForDubbo3 implements SaRequest { return null; } + /** + * 在 [ Cookie作用域 ] 里获取一个值 (第一个此名称的) + */ + @Override + public String getCookieFirstValue(String name){ + // 不传播 cookie 参数 + return null; + } + + /** + * 在 [ Cookie作用域 ] 里获取一个值 (最后一个此名称的) + * @param name 键 + * @return 值 + */ + @Override + public String getCookieLastValue(String name){ + // 不传播 cookie 参数 + return null; + } + /** * 返回当前请求path (不包括上下文名称) */ diff --git a/sa-token-plugin/sa-token-grpc/src/main/java/cn/dev33/satoken/context/grpc/model/SaRequestForGrpc.java b/sa-token-plugin/sa-token-grpc/src/main/java/cn/dev33/satoken/context/grpc/model/SaRequestForGrpc.java index 184d8007..f6ca8a4d 100644 --- a/sa-token-plugin/sa-token-grpc/src/main/java/cn/dev33/satoken/context/grpc/model/SaRequestForGrpc.java +++ b/sa-token-plugin/sa-token-grpc/src/main/java/cn/dev33/satoken/context/grpc/model/SaRequestForGrpc.java @@ -82,6 +82,26 @@ public class SaRequestForGrpc implements SaRequest { return null; } + /** + * 在 [ Cookie作用域 ] 里获取一个值 (第一个此名称的) + */ + @Override + public String getCookieFirstValue(String name){ + // 不传播 cookie 参数 + return null; + } + + /** + * 在 [ Cookie作用域 ] 里获取一个值 (最后一个此名称的) + * @param name 键 + * @return 值 + */ + @Override + public String getCookieLastValue(String name){ + // 不传播 cookie 参数 + return null; + } + /** * 返回当前请求path (不包括上下文名称) */ diff --git a/sa-token-starter/sa-token-jakarta-servlet/src/main/java/cn/dev33/satoken/servlet/model/SaRequestForServlet.java b/sa-token-starter/sa-token-jakarta-servlet/src/main/java/cn/dev33/satoken/servlet/model/SaRequestForServlet.java index 57f7f216..cc677c3c 100644 --- a/sa-token-starter/sa-token-jakarta-servlet/src/main/java/cn/dev33/satoken/servlet/model/SaRequestForServlet.java +++ b/sa-token-starter/sa-token-jakarta-servlet/src/main/java/cn/dev33/satoken/servlet/model/SaRequestForServlet.java @@ -109,6 +109,14 @@ public class SaRequestForServlet implements SaRequest { */ @Override public String getCookieValue(String name) { + return getCookieLastValue(name); + } + + /** + * 在 [ Cookie作用域 ] 里获取一个值 (第一个此名称的) + */ + @Override + public String getCookieFirstValue(String name){ Cookie[] cookies = request.getCookies(); if (cookies != null) { for (Cookie cookie : cookies) { @@ -120,6 +128,25 @@ public class SaRequestForServlet implements SaRequest { return null; } + /** + * 在 [ Cookie作用域 ] 里获取一个值 (最后一个此名称的) + * @param name 键 + * @return 值 + */ + @Override + public String getCookieLastValue(String name){ + String value = null; + Cookie[] cookies = request.getCookies(); + if (cookies != null) { + for (Cookie cookie : cookies) { + if (cookie != null && name.equals(cookie.getName())) { + value = cookie.getValue(); + } + } + } + return value; + } + /** * 返回当前请求path (不包括上下文名称) */ diff --git a/sa-token-starter/sa-token-reactor-spring-boot-starter/src/main/java/cn/dev33/satoken/reactor/model/SaRequestForReactor.java b/sa-token-starter/sa-token-reactor-spring-boot-starter/src/main/java/cn/dev33/satoken/reactor/model/SaRequestForReactor.java index 7b798cb9..b209afd9 100644 --- a/sa-token-starter/sa-token-reactor-spring-boot-starter/src/main/java/cn/dev33/satoken/reactor/model/SaRequestForReactor.java +++ b/sa-token-starter/sa-token-reactor-spring-boot-starter/src/main/java/cn/dev33/satoken/reactor/model/SaRequestForReactor.java @@ -101,6 +101,14 @@ public class SaRequestForReactor implements SaRequest { */ @Override public String getCookieValue(String name) { + return getCookieLastValue(name); + } + + /** + * 在 [ Cookie作用域 ] 里获取一个值 (第一个此名称的) + */ + @Override + public String getCookieFirstValue(String name){ HttpCookie cookie = request.getCookies().getFirst(name); if(cookie == null) { return null; @@ -108,6 +116,36 @@ public class SaRequestForReactor implements SaRequest { return cookie.getValue(); } + /** + * 在 [ Cookie作用域 ] 里获取一个值 (最后一个此名称的) + * @param name 键 + * @return 值 + */ + @Override + public String getCookieLastValue(String name){ + String value = null; + String cookieStr = getHeader("Cookie"); + if(SaFoxUtil.isNotEmpty(cookieStr)) { + String[] cookieItems = cookieStr.split(";"); + for (String item : cookieItems) { + String[] kv = item.split("="); + if (kv.length == 2) { + if (kv[0].trim().equals(name)) { + value = kv[1].trim(); + } + } + } + } + return value; + + // 此种写法无法获取到最后一个 Cookie,WebFlux 底层代码应该是有bug,前端提交多个同名Cookie时只能解析出第一个来 +// List cookies = request.getCookies().get(name); +// if(cookies.isEmpty()) { +// return null; +// } +// return cookies.get(cookies.size() - 1).getValue(); + } + /** * 返回当前请求path (不包括上下文名称) */ diff --git a/sa-token-starter/sa-token-reactor-spring-boot3-starter/src/main/java/cn/dev33/satoken/reactor/model/SaRequestForReactor.java b/sa-token-starter/sa-token-reactor-spring-boot3-starter/src/main/java/cn/dev33/satoken/reactor/model/SaRequestForReactor.java index c8a5c588..e656a4b6 100644 --- a/sa-token-starter/sa-token-reactor-spring-boot3-starter/src/main/java/cn/dev33/satoken/reactor/model/SaRequestForReactor.java +++ b/sa-token-starter/sa-token-reactor-spring-boot3-starter/src/main/java/cn/dev33/satoken/reactor/model/SaRequestForReactor.java @@ -101,6 +101,14 @@ public class SaRequestForReactor implements SaRequest { */ @Override public String getCookieValue(String name) { + return getCookieLastValue(name); + } + + /** + * 在 [ Cookie作用域 ] 里获取一个值 (第一个此名称的) + */ + @Override + public String getCookieFirstValue(String name){ HttpCookie cookie = request.getCookies().getFirst(name); if(cookie == null) { return null; @@ -108,6 +116,36 @@ public class SaRequestForReactor implements SaRequest { return cookie.getValue(); } + /** + * 在 [ Cookie作用域 ] 里获取一个值 (最后一个此名称的) + * @param name 键 + * @return 值 + */ + @Override + public String getCookieLastValue(String name){ + String value = null; + String cookieStr = getHeader("Cookie"); + if(SaFoxUtil.isNotEmpty(cookieStr)) { + String[] cookieItems = cookieStr.split(";"); + for (String item : cookieItems) { + String[] kv = item.split("="); + if (kv.length == 2) { + if (kv[0].trim().equals(name)) { + value = kv[1].trim(); + } + } + } + } + return value; + + // 此种写法无法获取到最后一个 Cookie,WebFlux 底层代码应该是有bug,前端提交多个同名Cookie时只能解析出第一个来 + // List cookies = request.getCookies().get(name); + // if(cookies.isEmpty()) { + // return null; + // } + // return cookies.get(cookies.size() - 1).getValue(); + } + /** * 返回当前请求path (不包括上下文名称) */ diff --git a/sa-token-starter/sa-token-servlet/src/main/java/cn/dev33/satoken/servlet/model/SaRequestForServlet.java b/sa-token-starter/sa-token-servlet/src/main/java/cn/dev33/satoken/servlet/model/SaRequestForServlet.java index d1228b51..5088143c 100644 --- a/sa-token-starter/sa-token-servlet/src/main/java/cn/dev33/satoken/servlet/model/SaRequestForServlet.java +++ b/sa-token-starter/sa-token-servlet/src/main/java/cn/dev33/satoken/servlet/model/SaRequestForServlet.java @@ -109,6 +109,14 @@ public class SaRequestForServlet implements SaRequest { */ @Override public String getCookieValue(String name) { + return getCookieLastValue(name); + } + + /** + * 在 [ Cookie作用域 ] 里获取一个值 (第一个此名称的) + */ + @Override + public String getCookieFirstValue(String name){ Cookie[] cookies = request.getCookies(); if (cookies != null) { for (Cookie cookie : cookies) { @@ -120,6 +128,25 @@ public class SaRequestForServlet implements SaRequest { return null; } + /** + * 在 [ Cookie作用域 ] 里获取一个值 (最后一个此名称的) + * @param name 键 + * @return 值 + */ + @Override + public String getCookieLastValue(String name){ + String value = null; + Cookie[] cookies = request.getCookies(); + if (cookies != null) { + for (Cookie cookie : cookies) { + if (cookie != null && name.equals(cookie.getName())) { + value = cookie.getValue(); + } + } + } + return value; + } + /** * 返回当前请求path (不包括上下文名称) */ diff --git a/sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/model/SaRequestForSolon.java b/sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/model/SaRequestForSolon.java index 3de3469a..2b820bff 100644 --- a/sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/model/SaRequestForSolon.java +++ b/sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/model/SaRequestForSolon.java @@ -68,8 +68,39 @@ public class SaRequestForSolon implements SaRequest { } @Override - public String getCookieValue(String s) { - return ctx.cookie(s); + public String getCookieValue(String name) { + return getCookieLastValue(name); + } + + /** + * 在 [ Cookie作用域 ] 里获取一个值 (第一个此名称的) + */ + @Override + public String getCookieFirstValue(String name){ + return ctx.cookie(name); + } + + /** + * 在 [ Cookie作用域 ] 里获取一个值 (最后一个此名称的) + * @param name 键 + * @return 值 + */ + @Override + public String getCookieLastValue(String name){ + String value = null; + String cookieStr = ctx.header("Cookie"); + if(SaFoxUtil.isNotEmpty(cookieStr)) { + String[] cookieItems = cookieStr.split(";"); + for (String item : cookieItems) { + String[] kv = item.split("="); + if (kv.length == 2) { + if (kv[0].trim().equals(name)) { + value = kv[1].trim(); + } + } + } + } + return value; } @Override -- Gitee From e1141ef942d1775801566c273730a40f5ba86678 Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Wed, 28 Aug 2024 02:05:02 +0800 Subject: [PATCH 164/172] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=96=87=E6=A1=A3SSO?= =?UTF-8?q?=E9=9B=86=E6=88=90=E6=B5=81=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/pj/sso/SsoClientController.java | 6 +- sa-token-doc/sso/sso-type2.md | 121 +----------------- sa-token-doc/sso/sso-type3.md | 106 ++++++++++++++- 3 files changed, 111 insertions(+), 122 deletions(-) diff --git a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso2-client/src/main/java/com/pj/sso/SsoClientController.java b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso2-client/src/main/java/com/pj/sso/SsoClientController.java index 4eeed193..361d1cfb 100644 --- a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso2-client/src/main/java/com/pj/sso/SsoClientController.java +++ b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso2-client/src/main/java/com/pj/sso/SsoClientController.java @@ -1,5 +1,6 @@ package com.pj.sso; +import cn.dev33.satoken.sso.SaSsoManager; import cn.dev33.satoken.sso.config.SaSsoClientConfig; import cn.dev33.satoken.sso.processor.SaSsoClientProcessor; import cn.dev33.satoken.stp.StpUtil; @@ -20,10 +21,11 @@ public class SsoClientController { // 首页 @RequestMapping("/") public String index() { + String solUrl = SaSsoManager.getClientConfig().splicingSloUrl(); String str = "

Sa-Token SSO-Client 应用端

" + "

当前会话是否登录:" + StpUtil.isLogin() + "

" + - "

登录 " + - "注销

"; + "

登录 " + + "注销

"; + "

登录 " + + " - -Forest 是一个轻量级 http 请求工具,详情参考:[Forest](https://forest.dtflyx.com/) - -因为我们已经在控制台手动打印 url 请求日志了,所以此处 `forest.log-enabled=false` 关闭 Forest 框架自身的日志打印,这不是必须的,你可以将其打开。 - - -#### 5.2、SSO-Client 端新增配置:API调用秘钥 - -在 `application.yml` 增加: - - - -``` yaml -sa-token: - sign: - # API 接口调用秘钥 - secret-key: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor - -forest: - # 关闭 forest 请求日志打印 - log-enabled: false -``` - -``` properties -# 接口调用秘钥 -sa-token.sign.secret-key=kQwIOrYvnXmSDkwEiFngrKidMcdrgKor - -# 关闭 forest 请求日志打印 -forest.log-enabled=false -``` - - -注意 secretkey 秘钥需要与SSO认证中心的一致 - -#### 5.3、SSO-Client 配置 http 请求处理器 -``` java -// 配置SSO相关参数 -@Autowired -private void configSso(SaSsoClientConfig ssoClient) { - // 配置Http请求处理器 - ssoClient.sendHttp = url -> { - System.out.println("------ 发起请求:" + url); - String resStr = Forest.get(url).executeAsString(); - System.out.println("------ 请求结果:" + resStr); - return resStr; - }; -} -``` - -#### 5.3、启动测试 -重启项目,依次登录三个 client: -- [http://sa-sso-client1.com:9001/](http://sa-sso-client1.com:9001/) -- [http://sa-sso-client2.com:9001/](http://sa-sso-client2.com:9001/) -- [http://sa-sso-client3.com:9001/](http://sa-sso-client3.com:9001/) - -![sso-type3-client-index.png](https://oss.dev33.cn/sa-token/doc/sso/sso-type3-client-index.png 's-w-sh') - -在任意一个 client 里,点击 **`[注销]`** 按钮,即可单点注销成功(打开另外两个client,刷新一下页面,登录态丢失)。 - - - -![sso-type3-slo-index.png](https://oss.dev33.cn/sa-token/doc/sso/sso-type3-slo-index.png 's-w-sh') - -PS:这里我们为了方便演示,使用的是超链接跳页面的形式,正式项目中使用 Ajax 调用接口即可做到无刷单点登录退出。 - -例如,我们使用 [Apifox 接口测试工具](https://www.apifox.cn/) 可以做到同样的效果: - -![sso-slo-apifox.png](https://oss.dev33.cn/sa-token/doc/sso/sso-slo-apifox.png 's-w-sh') - -测试完毕! - - -### 6、跨 Redis 的单点登录 +### 5、跨 Redis 的单点登录 以上流程解决了跨域模式下的单点登录,但是后端仍然采用了共享Redis来同步会话,如果我们的架构设计中Client端与Server端无法共享Redis,又该怎么完成单点登录? 这就要采用模式三了,且往下看:[SSO模式三:Http请求获取会话](/sso/sso-type3) diff --git a/sa-token-doc/sso/sso-type3.md b/sa-token-doc/sso/sso-type3.md index 39fe8c4a..5ec4ea98 100644 --- a/sa-token-doc/sso/sso-type3.md +++ b/sa-token-doc/sso/sso-type3.md @@ -22,7 +22,32 @@ ### 2、在Client 端更改 Ticket 校验方式 -在 application.yml 新增配置: + +#### 2.1、增加 pom.xml 配置 + + + +``` xml + + + com.dtflys.forest + forest-spring-boot-starter + 1.5.26 + +``` + +``` gradle +// Http请求工具 +implementation 'com.dtflys.forest:forest-spring-boot-starter:1.5.26' +``` + + +Forest 是一个轻量级 http 请求工具,详情参考:[Forest](https://forest.dtflyx.com/) + + +#### 2.2、SSO-Client 端新增配置:API调用秘钥 + +在 `application.yml` 增加: @@ -31,14 +56,47 @@ sa-token: sso-client: # 打开模式三(使用Http请求校验ticket) is-http: true + sign: + # API 接口调用秘钥 + secret-key: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor + +forest: + # 关闭 forest 请求日志打印 + log-enabled: false ``` ``` properties # 打开模式三(使用Http请求校验ticket) sa-token.sso-client.is-http=true +# 接口调用秘钥 +sa-token.sign.secret-key=kQwIOrYvnXmSDkwEiFngrKidMcdrgKor + +# 关闭 forest 请求日志打印 +forest.log-enabled=false ``` +因为我们已经在控制台手动打印 url 请求日志了,所以此处 `forest.log-enabled=false` 关闭 Forest 框架自身的日志打印,这不是必须的,你可以将其打开。 + +注意 secretkey 秘钥需要与SSO认证中心的一致 + +#### 2.3、SSO-Client 配置 http 请求处理器 +``` java +// 配置SSO相关参数 +@Autowired +private void configSso(SaSsoClientConfig ssoClient) { + // 配置Http请求处理器 + ssoClient.sendHttp = url -> { + System.out.println("------ 发起请求:" + url); + String resStr = Forest.get(url).executeAsString(); + System.out.println("------ 请求结果:" + resStr); + return resStr; + }; +} +``` + + +#### 2.4、测试 重启项目,访问测试: - [http://sa-sso-client1.com:9001/](http://sa-sso-client1.com:9001/) @@ -49,6 +107,10 @@ sa-token.sso-client.is-http=true > 注:如果已测试运行模式二,可先将Redis中的数据清空,以防旧数据对测试造成干扰 + + + + ### 3、获取 UserInfo 除了账号id,我们可能还需要将用户的昵称、头像等信息从 Server端 带到 Client端,即:用户资料的拉取。 @@ -192,10 +254,10 @@ public Object getFansList(Long loginId) { 访问测试:[http://sa-sso-client1.com:9001/sso/myFansList](http://sa-sso-client1.com:9001/sso/myFansList) +### 5、无刷单点注销 -### 5、单点注销 +有了单点登录,就必然伴随着单点注销(一处注销,全端下线) -有关 SSO 单点注销的步骤,在上一章节的“无刷单点注销”部分已讲解完毕(模式二和模式三通用),所以此处就不再赘述了。 此处简单介绍一下 SSO 模式三的单点注销链路过程: @@ -212,6 +274,44 @@ public Object getFansList(Long loginId) { 这些逻辑 Sa-Token 内部已经封装完毕,你只需按照文档步骤集成即可。 +#### 5.1、更改注销方案 + +将 sso-client 首页路由方法里的注销链接换成 `/sso/logout` 接口: +``` java +// SSO-Client端:首页 +@RequestMapping("/") +public String index() { + String str = "

Sa-Token SSO-Client 应用端

" + + "

当前会话是否登录:" + StpUtil.isLogin() + "

" + + "

登录" + + " 注销

"; + return str; +} +``` + +#### 5.2、启动测试 +重启项目,依次登录三个 client: +- [http://sa-sso-client1.com:9001/](http://sa-sso-client1.com:9001/) +- [http://sa-sso-client2.com:9001/](http://sa-sso-client2.com:9001/) +- [http://sa-sso-client3.com:9001/](http://sa-sso-client3.com:9001/) + +![sso-type3-client-index.png](https://oss.dev33.cn/sa-token/doc/sso/sso-type3-client-index.png 's-w-sh') + +在任意一个 client 里,点击 **`[注销]`** 按钮,即可单点注销成功(打开另外两个client,刷新一下页面,登录态丢失)。 + + + +![sso-type3-slo-index.png](https://oss.dev33.cn/sa-token/doc/sso/sso-type3-slo-index.png 's-w-sh') + +PS:这里我们为了方便演示,使用的是超链接跳页面的形式,正式项目中使用 Ajax 调用接口即可做到无刷单点登录退出。 + +例如,我们使用 [Apifox 接口测试工具](https://www.apifox.cn/) 可以做到同样的效果: + +![sso-slo-apifox.png](https://oss.dev33.cn/sa-token/doc/sso/sso-slo-apifox.png 's-w-sh') + +测试完毕! + + ### 6、后记 当我们熟读三种模式的单点登录之后,其实不难发现:所谓单点登录,其本质就是多个系统之间的会话共享。 -- Gitee From f8afd89154a48ed763f6d9e452a430d56f92e327 Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Wed, 28 Aug 2024 09:58:06 +0800 Subject: [PATCH 165/172] v1.39.0 update --- README.md | 2 +- pom.xml | 2 +- sa-token-bom/pom.xml | 2 +- .../cn/dev33/satoken/util/SaTokenConsts.java | 2 +- .../sa-token-demo-alone-redis-cluster/pom.xml | 2 +- .../sa-token-demo-alone-redis/pom.xml | 2 +- sa-token-demo/sa-token-demo-beetl/pom.xml | 2 +- .../sa-token-demo-bom-import/pom.xml | 4 +- sa-token-demo/sa-token-demo-case/pom.xml | 2 +- .../sa-token-demo-dubbo-consumer/pom.xml | 2 +- .../sa-token-demo-dubbo-provider/pom.xml | 2 +- .../sa-token-demo-dubbo3-consumer/pom.xml | 2 +- .../sa-token-demo-dubbo3-provider/pom.xml | 2 +- sa-token-demo/sa-token-demo-grpc/pom.xml | 2 +- .../sa-token-demo-hutool-timed-cache/pom.xml | 2 +- sa-token-demo/sa-token-demo-jwt/pom.xml | 2 +- .../sa-token-demo-oauth2-client/pom.xml | 2 +- .../sa-token-demo-oauth2-server/pom.xml | 2 +- .../src/main/resources/application.yml | 17 ++++---- .../sa-token-demo-quick-login/pom.xml | 2 +- .../sa-token-demo-remember-me-server/pom.xml | 2 +- .../sa-token-demo-solon-redisson/pom.xml | 2 +- sa-token-demo/sa-token-demo-solon/pom.xml | 2 +- .../sa-token-demo-springboot-redis/pom.xml | 2 +- .../sa-token-demo-springboot-redisson/pom.xml | 2 +- .../sa-token-demo-springboot/pom.xml | 2 +- .../sa-token-demo-springboot3-redis/pom.xml | 2 +- sa-token-demo/sa-token-demo-ssm/pom.xml | 2 +- .../sa-token-demo-sso-server-solon/pom.xml | 2 +- .../sa-token-demo-sso1-client-solon/pom.xml | 2 +- .../sa-token-demo-sso2-client-solon/pom.xml | 2 +- .../sa-token-demo-sso3-client-solon/pom.xml | 2 +- .../sa-token-demo-sso-server/pom.xml | 2 +- .../sa-token-demo-sso1-client/pom.xml | 2 +- .../sa-token-demo-sso2-client/pom.xml | 2 +- .../sa-token-demo-sso3-client-test2/pom.xml | 2 +- .../sa-token-demo-sso3-client/pom.xml | 2 +- sa-token-demo/sa-token-demo-test/pom.xml | 2 +- sa-token-demo/sa-token-demo-thymeleaf/pom.xml | 2 +- .../sa-token-demo-webflux-springboot3/pom.xml | 2 +- sa-token-demo/sa-token-demo-webflux/pom.xml | 2 +- .../sa-token-demo-websocket-spring/pom.xml | 2 +- sa-token-demo/sa-token-demo-websocket/pom.xml | 2 +- sa-token-dependencies/pom.xml | 2 +- sa-token-doc/README.md | 2 +- sa-token-doc/doc.html | 5 ++- .../fun/auth-framework-function-test.md | 2 +- sa-token-doc/index.html | 2 +- sa-token-doc/more/update-log.md | 43 +++++++++++++++++++ sa-token-doc/start/new-version.md | 4 +- 50 files changed, 103 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index 5badb14f..98c665c7 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@

logo

-

Sa-Token v1.38.0

+

Sa-Token v1.39.0

一个轻量级 Java 权限认证框架,让鉴权变得简单、优雅!

diff --git a/pom.xml b/pom.xml index 9dce4b56..cc136113 100644 --- a/pom.xml +++ b/pom.xml @@ -37,7 +37,7 @@ - 1.38.0 + 1.39.0 1.8 utf-8 utf-8 diff --git a/sa-token-bom/pom.xml b/sa-token-bom/pom.xml index a47f6ee2..74e4b510 100644 --- a/sa-token-bom/pom.xml +++ b/sa-token-bom/pom.xml @@ -13,7 +13,7 @@ https://github.com/dromara/sa-token - 1.38.0 + 1.39.0 diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/util/SaTokenConsts.java b/sa-token-core/src/main/java/cn/dev33/satoken/util/SaTokenConsts.java index baebafe7..c54c2722 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/util/SaTokenConsts.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/util/SaTokenConsts.java @@ -36,7 +36,7 @@ public class SaTokenConsts { /** * Sa-Token 当前版本号 */ - public static final String VERSION_NO = "v1.38.0"; + public static final String VERSION_NO = "v1.39.0"; /** * Sa-Token 开源地址 Gitee diff --git a/sa-token-demo/sa-token-demo-alone-redis-cluster/pom.xml b/sa-token-demo/sa-token-demo-alone-redis-cluster/pom.xml index 240b63a3..b1835aaf 100644 --- a/sa-token-demo/sa-token-demo-alone-redis-cluster/pom.xml +++ b/sa-token-demo/sa-token-demo-alone-redis-cluster/pom.xml @@ -16,7 +16,7 @@ - 1.38.0 + 1.39.0 diff --git a/sa-token-demo/sa-token-demo-alone-redis/pom.xml b/sa-token-demo/sa-token-demo-alone-redis/pom.xml index 8d24f792..c036ca14 100644 --- a/sa-token-demo/sa-token-demo-alone-redis/pom.xml +++ b/sa-token-demo/sa-token-demo-alone-redis/pom.xml @@ -17,7 +17,7 @@ - 1.38.0 + 1.39.0 diff --git a/sa-token-demo/sa-token-demo-beetl/pom.xml b/sa-token-demo/sa-token-demo-beetl/pom.xml index a51ea399..aa208375 100644 --- a/sa-token-demo/sa-token-demo-beetl/pom.xml +++ b/sa-token-demo/sa-token-demo-beetl/pom.xml @@ -16,7 +16,7 @@ - 1.38.0 + 1.39.0 diff --git a/sa-token-demo/sa-token-demo-bom-import/pom.xml b/sa-token-demo/sa-token-demo-bom-import/pom.xml index 0e0ee7f4..2a03843b 100644 --- a/sa-token-demo/sa-token-demo-bom-import/pom.xml +++ b/sa-token-demo/sa-token-demo-bom-import/pom.xml @@ -16,7 +16,7 @@ - 1.38.0 + 1.39.0 @@ -73,7 +73,7 @@ cn.dev33 sa-token-bom - 1.38.0 + 1.39.0 pom import diff --git a/sa-token-demo/sa-token-demo-case/pom.xml b/sa-token-demo/sa-token-demo-case/pom.xml index dbb0fd82..f3bf964c 100644 --- a/sa-token-demo/sa-token-demo-case/pom.xml +++ b/sa-token-demo/sa-token-demo-case/pom.xml @@ -17,7 +17,7 @@ - 1.38.0 + 1.39.0 diff --git a/sa-token-demo/sa-token-demo-dubbo/sa-token-demo-dubbo-consumer/pom.xml b/sa-token-demo/sa-token-demo-dubbo/sa-token-demo-dubbo-consumer/pom.xml index 6f3fd5f4..5954ce96 100644 --- a/sa-token-demo/sa-token-demo-dubbo/sa-token-demo-dubbo-consumer/pom.xml +++ b/sa-token-demo/sa-token-demo-dubbo/sa-token-demo-dubbo-consumer/pom.xml @@ -17,7 +17,7 @@ 1.8 3.1.1 - 1.38.0 + 1.39.0 2.7.21 1.4.2 diff --git a/sa-token-demo/sa-token-demo-dubbo/sa-token-demo-dubbo-provider/pom.xml b/sa-token-demo/sa-token-demo-dubbo/sa-token-demo-dubbo-provider/pom.xml index aeb6f90d..522a5ada 100644 --- a/sa-token-demo/sa-token-demo-dubbo/sa-token-demo-dubbo-provider/pom.xml +++ b/sa-token-demo/sa-token-demo-dubbo/sa-token-demo-dubbo-provider/pom.xml @@ -17,7 +17,7 @@ 1.8 3.1.1 - 1.38.0 + 1.39.0 2.7.21 1.4.2 diff --git a/sa-token-demo/sa-token-demo-dubbo/sa-token-demo-dubbo3-consumer/pom.xml b/sa-token-demo/sa-token-demo-dubbo/sa-token-demo-dubbo3-consumer/pom.xml index 9a7a1af2..b88b05cf 100644 --- a/sa-token-demo/sa-token-demo-dubbo/sa-token-demo-dubbo3-consumer/pom.xml +++ b/sa-token-demo/sa-token-demo-dubbo/sa-token-demo-dubbo3-consumer/pom.xml @@ -17,7 +17,7 @@ 1.8 3.1.1 - 1.38.0 + 1.39.0 3.2.2 2.2.2 diff --git a/sa-token-demo/sa-token-demo-dubbo/sa-token-demo-dubbo3-provider/pom.xml b/sa-token-demo/sa-token-demo-dubbo/sa-token-demo-dubbo3-provider/pom.xml index c420e235..cca02604 100644 --- a/sa-token-demo/sa-token-demo-dubbo/sa-token-demo-dubbo3-provider/pom.xml +++ b/sa-token-demo/sa-token-demo-dubbo/sa-token-demo-dubbo3-provider/pom.xml @@ -17,7 +17,7 @@ 1.8 3.1.1 - 1.38.0 + 1.39.0 3.2.2 2.2.2 diff --git a/sa-token-demo/sa-token-demo-grpc/pom.xml b/sa-token-demo/sa-token-demo-grpc/pom.xml index df0cc5ca..1d0c04f2 100644 --- a/sa-token-demo/sa-token-demo-grpc/pom.xml +++ b/sa-token-demo/sa-token-demo-grpc/pom.xml @@ -27,7 +27,7 @@ UTF-8 UTF-8 1.18.10 - 1.38.0 + 1.39.0 diff --git a/sa-token-demo/sa-token-demo-hutool-timed-cache/pom.xml b/sa-token-demo/sa-token-demo-hutool-timed-cache/pom.xml index 18897f60..3c906b61 100644 --- a/sa-token-demo/sa-token-demo-hutool-timed-cache/pom.xml +++ b/sa-token-demo/sa-token-demo-hutool-timed-cache/pom.xml @@ -17,7 +17,7 @@ - 1.38.0 + 1.39.0 diff --git a/sa-token-demo/sa-token-demo-jwt/pom.xml b/sa-token-demo/sa-token-demo-jwt/pom.xml index 2359117a..d498d672 100644 --- a/sa-token-demo/sa-token-demo-jwt/pom.xml +++ b/sa-token-demo/sa-token-demo-jwt/pom.xml @@ -16,7 +16,7 @@ - 1.38.0 + 1.39.0 diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-client/pom.xml b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-client/pom.xml index 57ef7f5d..c32a5002 100644 --- a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-client/pom.xml +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-client/pom.xml @@ -17,7 +17,7 @@ 1.8 3.1.1 - 1.38.0 + 1.39.0 diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/pom.xml b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/pom.xml index db0b6c7d..2eb121b3 100644 --- a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/pom.xml +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/pom.xml @@ -17,7 +17,7 @@ 1.8 3.1.1 - 1.38.0 + 1.39.0 diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/resources/application.yml b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/resources/application.yml index 4c661c3c..5b2e9ebb 100644 --- a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/resources/application.yml +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/resources/application.yml @@ -1,15 +1,15 @@ -server: +server: port: 8000 # sa-token配置 -sa-token: +sa-token: # token名称 (同时也是 Cookie 名称) token-name: satoken # 是否打印操作日志 is-log: true # jwt 秘钥 jwt-secret-key: saxsaxsaxsax - # OAuth2.0 配置 + # OAuth2.0 配置 oauth2-server: # 是否全局开启授权码模式 enable-authorization-code: true @@ -24,8 +24,8 @@ sa-token: # 定义哪些 scope 是低级权限,多个用逗号隔开 # lower-scope: userinfo -spring: - # redis配置 +spring: + # redis配置 redis: # Redis数据库索引(默认为0) database: 1 @@ -34,7 +34,7 @@ spring: # Redis服务器连接端口 port: 6379 # Redis服务器连接密码(默认为空) - # password: + # password: # 连接超时时间(毫秒) timeout: 1000ms lettuce: @@ -47,6 +47,5 @@ spring: max-idle: 10 # 连接池中的最小空闲连接 min-idle: 0 - - - \ No newline at end of file + + diff --git a/sa-token-demo/sa-token-demo-quick-login/pom.xml b/sa-token-demo/sa-token-demo-quick-login/pom.xml index 51bb3868..549954f0 100644 --- a/sa-token-demo/sa-token-demo-quick-login/pom.xml +++ b/sa-token-demo/sa-token-demo-quick-login/pom.xml @@ -16,7 +16,7 @@ - 1.38.0 + 1.39.0 diff --git a/sa-token-demo/sa-token-demo-remember-me/sa-token-demo-remember-me-server/pom.xml b/sa-token-demo/sa-token-demo-remember-me/sa-token-demo-remember-me-server/pom.xml index ccc15d35..53bdb851 100644 --- a/sa-token-demo/sa-token-demo-remember-me/sa-token-demo-remember-me-server/pom.xml +++ b/sa-token-demo/sa-token-demo-remember-me/sa-token-demo-remember-me-server/pom.xml @@ -17,7 +17,7 @@ - 1.38.0 + 1.39.0 diff --git a/sa-token-demo/sa-token-demo-solon-redisson/pom.xml b/sa-token-demo/sa-token-demo-solon-redisson/pom.xml index d60eb51e..6118605f 100644 --- a/sa-token-demo/sa-token-demo-solon-redisson/pom.xml +++ b/sa-token-demo/sa-token-demo-solon-redisson/pom.xml @@ -16,7 +16,7 @@ - 1.38.0 + 1.39.0 UTF-8 UTF-8 diff --git a/sa-token-demo/sa-token-demo-solon/pom.xml b/sa-token-demo/sa-token-demo-solon/pom.xml index c3178eaa..6edda287 100644 --- a/sa-token-demo/sa-token-demo-solon/pom.xml +++ b/sa-token-demo/sa-token-demo-solon/pom.xml @@ -19,7 +19,7 @@ 17 17 17 - 1.38.0 + 1.39.0 UTF-8 UTF-8 diff --git a/sa-token-demo/sa-token-demo-springboot-redis/pom.xml b/sa-token-demo/sa-token-demo-springboot-redis/pom.xml index f60c4ed8..261b2252 100644 --- a/sa-token-demo/sa-token-demo-springboot-redis/pom.xml +++ b/sa-token-demo/sa-token-demo-springboot-redis/pom.xml @@ -17,7 +17,7 @@ - 1.38.0 + 1.39.0 diff --git a/sa-token-demo/sa-token-demo-springboot-redisson/pom.xml b/sa-token-demo/sa-token-demo-springboot-redisson/pom.xml index b3a373cd..351e9fbe 100644 --- a/sa-token-demo/sa-token-demo-springboot-redisson/pom.xml +++ b/sa-token-demo/sa-token-demo-springboot-redisson/pom.xml @@ -17,7 +17,7 @@ - 1.38.0 + 1.39.0 diff --git a/sa-token-demo/sa-token-demo-springboot/pom.xml b/sa-token-demo/sa-token-demo-springboot/pom.xml index a6f41c64..3fe2c51b 100644 --- a/sa-token-demo/sa-token-demo-springboot/pom.xml +++ b/sa-token-demo/sa-token-demo-springboot/pom.xml @@ -17,7 +17,7 @@ - 1.38.0 + 1.39.0 diff --git a/sa-token-demo/sa-token-demo-springboot3-redis/pom.xml b/sa-token-demo/sa-token-demo-springboot3-redis/pom.xml index 8cd82921..f717e272 100644 --- a/sa-token-demo/sa-token-demo-springboot3-redis/pom.xml +++ b/sa-token-demo/sa-token-demo-springboot3-redis/pom.xml @@ -16,7 +16,7 @@ - 1.38.0 + 1.39.0 diff --git a/sa-token-demo/sa-token-demo-ssm/pom.xml b/sa-token-demo/sa-token-demo-ssm/pom.xml index 0e35d6e1..5a23365f 100644 --- a/sa-token-demo/sa-token-demo-ssm/pom.xml +++ b/sa-token-demo/sa-token-demo-ssm/pom.xml @@ -27,7 +27,7 @@ 5.3.7 2.16.1 - 1.38.0 + 1.39.0 diff --git a/sa-token-demo/sa-token-demo-sso-for-solon/sa-token-demo-sso-server-solon/pom.xml b/sa-token-demo/sa-token-demo-sso-for-solon/sa-token-demo-sso-server-solon/pom.xml index 5c7d651c..e0f63a10 100644 --- a/sa-token-demo/sa-token-demo-sso-for-solon/sa-token-demo-sso-server-solon/pom.xml +++ b/sa-token-demo/sa-token-demo-sso-for-solon/sa-token-demo-sso-server-solon/pom.xml @@ -16,7 +16,7 @@ - 1.38.0 + 1.39.0 2.7.0 diff --git a/sa-token-demo/sa-token-demo-sso-for-solon/sa-token-demo-sso1-client-solon/pom.xml b/sa-token-demo/sa-token-demo-sso-for-solon/sa-token-demo-sso1-client-solon/pom.xml index 0437b6fe..5d7cea06 100644 --- a/sa-token-demo/sa-token-demo-sso-for-solon/sa-token-demo-sso1-client-solon/pom.xml +++ b/sa-token-demo/sa-token-demo-sso-for-solon/sa-token-demo-sso1-client-solon/pom.xml @@ -16,7 +16,7 @@ - 1.38.0 + 1.39.0 diff --git a/sa-token-demo/sa-token-demo-sso-for-solon/sa-token-demo-sso2-client-solon/pom.xml b/sa-token-demo/sa-token-demo-sso-for-solon/sa-token-demo-sso2-client-solon/pom.xml index 66048e33..d59ea913 100644 --- a/sa-token-demo/sa-token-demo-sso-for-solon/sa-token-demo-sso2-client-solon/pom.xml +++ b/sa-token-demo/sa-token-demo-sso-for-solon/sa-token-demo-sso2-client-solon/pom.xml @@ -16,7 +16,7 @@ - 1.38.0 + 1.39.0 diff --git a/sa-token-demo/sa-token-demo-sso-for-solon/sa-token-demo-sso3-client-solon/pom.xml b/sa-token-demo/sa-token-demo-sso-for-solon/sa-token-demo-sso3-client-solon/pom.xml index 17b2d5c2..a9b6736e 100644 --- a/sa-token-demo/sa-token-demo-sso-for-solon/sa-token-demo-sso3-client-solon/pom.xml +++ b/sa-token-demo/sa-token-demo-sso-for-solon/sa-token-demo-sso3-client-solon/pom.xml @@ -16,7 +16,7 @@ - 1.38.0 + 1.39.0 diff --git a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-server/pom.xml b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-server/pom.xml index dc7c6767..bf320ff8 100644 --- a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-server/pom.xml +++ b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-server/pom.xml @@ -16,7 +16,7 @@ - 1.38.0 + 1.39.0 diff --git a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso1-client/pom.xml b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso1-client/pom.xml index 50ef2657..afb16a4a 100644 --- a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso1-client/pom.xml +++ b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso1-client/pom.xml @@ -16,7 +16,7 @@ - 1.38.0 + 1.39.0 diff --git a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso2-client/pom.xml b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso2-client/pom.xml index cd026558..513333b7 100644 --- a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso2-client/pom.xml +++ b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso2-client/pom.xml @@ -16,7 +16,7 @@ - 1.38.0 + 1.39.0 diff --git a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-test2/pom.xml b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-test2/pom.xml index 3c32f669..df69f8ed 100644 --- a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-test2/pom.xml +++ b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-test2/pom.xml @@ -16,7 +16,7 @@ - 1.38.0 + 1.39.0 diff --git a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client/pom.xml b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client/pom.xml index d45bff0b..f0ae9427 100644 --- a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client/pom.xml +++ b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client/pom.xml @@ -16,7 +16,7 @@ - 1.38.0 + 1.39.0 diff --git a/sa-token-demo/sa-token-demo-test/pom.xml b/sa-token-demo/sa-token-demo-test/pom.xml index df740e04..9a424918 100644 --- a/sa-token-demo/sa-token-demo-test/pom.xml +++ b/sa-token-demo/sa-token-demo-test/pom.xml @@ -18,7 +18,7 @@ - 1.38.0 + 1.39.0 com.pj.SaTokenApplication diff --git a/sa-token-demo/sa-token-demo-thymeleaf/pom.xml b/sa-token-demo/sa-token-demo-thymeleaf/pom.xml index 4175c35c..811190d6 100644 --- a/sa-token-demo/sa-token-demo-thymeleaf/pom.xml +++ b/sa-token-demo/sa-token-demo-thymeleaf/pom.xml @@ -16,7 +16,7 @@ - 1.38.0 + 1.39.0 diff --git a/sa-token-demo/sa-token-demo-webflux-springboot3/pom.xml b/sa-token-demo/sa-token-demo-webflux-springboot3/pom.xml index 609b90c7..6f0dee7e 100644 --- a/sa-token-demo/sa-token-demo-webflux-springboot3/pom.xml +++ b/sa-token-demo/sa-token-demo-webflux-springboot3/pom.xml @@ -16,7 +16,7 @@ - 1.38.0 + 1.39.0 diff --git a/sa-token-demo/sa-token-demo-webflux/pom.xml b/sa-token-demo/sa-token-demo-webflux/pom.xml index 058edaa9..88ed9408 100644 --- a/sa-token-demo/sa-token-demo-webflux/pom.xml +++ b/sa-token-demo/sa-token-demo-webflux/pom.xml @@ -16,7 +16,7 @@ - 1.38.0 + 1.39.0 diff --git a/sa-token-demo/sa-token-demo-websocket-spring/pom.xml b/sa-token-demo/sa-token-demo-websocket-spring/pom.xml index 44134e1c..27c4376e 100644 --- a/sa-token-demo/sa-token-demo-websocket-spring/pom.xml +++ b/sa-token-demo/sa-token-demo-websocket-spring/pom.xml @@ -17,7 +17,7 @@ - 1.38.0 + 1.39.0 diff --git a/sa-token-demo/sa-token-demo-websocket/pom.xml b/sa-token-demo/sa-token-demo-websocket/pom.xml index a15d39b8..bf610483 100644 --- a/sa-token-demo/sa-token-demo-websocket/pom.xml +++ b/sa-token-demo/sa-token-demo-websocket/pom.xml @@ -17,7 +17,7 @@ - 1.38.0 + 1.39.0 diff --git a/sa-token-dependencies/pom.xml b/sa-token-dependencies/pom.xml index 5be7f46f..67e00ea8 100644 --- a/sa-token-dependencies/pom.xml +++ b/sa-token-dependencies/pom.xml @@ -12,7 +12,7 @@ Sa-Token Dependencies - 1.38.0 + 1.39.0 2.5.15 diff --git a/sa-token-doc/README.md b/sa-token-doc/README.md index 56105163..e3446cae 100644 --- a/sa-token-doc/README.md +++ b/sa-token-doc/README.md @@ -1,7 +1,7 @@

logo

-

Sa-Token v1.38.0

+

Sa-Token v1.39.0

一个轻量级 Java 权限认证框架,让鉴权变得简单、优雅!

diff --git a/sa-token-doc/doc.html b/sa-token-doc/doc.html index e64e6fa6..842136ff 100644 --- a/sa-token-doc/doc.html +++ b/sa-token-doc/doc.html @@ -18,7 +18,7 @@

Sa-Token

- v1.38.0 + v1.39.0
@@ -28,6 +28,7 @@