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] =?UTF-8?q?sa-token-oauth2=20=E6=96=B0=E5=A2=9E=E8=87=AA?= =?UTF-8?q?=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) + * @return 应用允许的所有 grant_type */ - public Boolean getEnableCode() { - return enableCode; + public List getAllowGrantTypes() { + return allowGrantTypes; } - + /** - * @param enableCode 此 Client 是否打开模式:授权码(Authorization Code) - * @return 对象自身 + * 应用允许的所有 grant_type + * @param allowGrantTypes / + * @return / */ - public SaClientModel setEnableCode(Boolean enableCode) { - this.enableCode = enableCode; + public SaClientModel setAllowGrantTypes(List allowGrantTypes) { + this.allowGrantTypes = allowGrantTypes; 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) - */ - public Boolean getEnableClient() { - return enableClient; - } - - /** - * @param enableClient 此 Client 是否打开模式:凭证式(Client Credentials) - * @return 对象自身 - */ - 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 +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); + } + } + }