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() +// ); +// } + + */ + }