mirror of
https://gitee.com/dromara/sa-token.git
synced 2024-11-29 18:37:49 +08:00
新增sa-token-oauth2 注解鉴权
This commit is contained in:
parent
6a9f25093d
commit
beb958f274
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
//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;
|
||||
// }
|
||||
//
|
||||
//}
|
@ -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<String> 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;
|
||||
}
|
||||
}
|
||||
//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<String> 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;
|
||||
// }
|
||||
//}
|
@ -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("验证码发送成功");
|
||||
}
|
||||
|
||||
}
|
||||
//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("验证码发送成功");
|
||||
// }
|
||||
//
|
||||
//}
|
@ -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<String, Object> map = new LinkedHashMap<String, Object>();
|
||||
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) {
|
||||
}
|
||||
|
||||
}
|
||||
//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<String, Object> map = new LinkedHashMap<String, Object>();
|
||||
// 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) {
|
||||
// }
|
||||
//
|
||||
//}
|
@ -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;
|
@ -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("/**");
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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("访问成功");
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
<!-- - [前后端分离模式整合方案](/oauth2/4) -->
|
||||
|
94
sa-token-doc/oauth2/oauth2-at-check.md
Normal file
94
sa-token-doc/oauth2/oauth2-at-check.md
Normal file
@ -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("访问成功");
|
||||
}
|
||||
|
||||
}
|
||||
```
|
@ -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
|
||||
*
|
||||
* <p> 可标注在方法、类上(效果等同于标注在此类的所有方法上)
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.39.0
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.METHOD,ElementType.TYPE})
|
||||
public @interface SaCheckAccessToken {
|
||||
|
||||
/**
|
||||
* 需要校验的 scope [ 数组 ]
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
String [] scope() default {};
|
||||
|
||||
}
|
@ -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 信息
|
||||
*
|
||||
* <p> 可标注在方法、类上(效果等同于标注在此类的所有方法上)
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.39.0
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.METHOD,ElementType.TYPE})
|
||||
public @interface SaCheckClientIdSecret {
|
||||
|
||||
}
|
@ -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
|
||||
*
|
||||
* <p> 可标注在方法、类上(效果等同于标注在此类的所有方法上)
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.39.0
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.METHOD,ElementType.TYPE})
|
||||
public @interface SaCheckClientToken {
|
||||
|
||||
/**
|
||||
* 需要校验的 scope [ 数组 ]
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
String [] scope() default {};
|
||||
|
||||
}
|
@ -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<SaCheckAccessToken> {
|
||||
|
||||
@Override
|
||||
public Class<SaCheckAccessToken> 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);
|
||||
}
|
||||
|
||||
}
|
@ -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<SaCheckClientIdSecret> {
|
||||
|
||||
@Override
|
||||
public Class<SaCheckClientIdSecret> getHandlerAnnotationClass() {
|
||||
return SaCheckClientIdSecret.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkMethod(SaCheckClientIdSecret at, Method method) {
|
||||
_checkMethod();
|
||||
}
|
||||
|
||||
public static void _checkMethod() {
|
||||
SaOAuth2ServerProcessor.instance.checkCurrClientSecret();
|
||||
}
|
||||
|
||||
}
|
@ -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<SaCheckClientToken> {
|
||||
|
||||
@Override
|
||||
public Class<SaCheckClientToken> 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);
|
||||
}
|
||||
|
||||
}
|
@ -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";
|
||||
|
@ -50,6 +50,14 @@ public interface SaOAuth2DataResolver {
|
||||
*/
|
||||
String readAccessToken(SaRequest request);
|
||||
|
||||
/**
|
||||
* 数据读取:从请求对象中读取 ClientToken
|
||||
*
|
||||
* @param request /
|
||||
* @return /
|
||||
*/
|
||||
String readClientToken(SaRequest request);
|
||||
|
||||
/**
|
||||
* 数据读取:从请求对象中构建 RequestAuthModel
|
||||
* @param req SaRequest对象
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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 参数
|
||||
*/
|
||||
|
@ -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)
|
||||
|
@ -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<SaCheckAccessToken> getSaCheckAccessTokenHandler() {
|
||||
return new SaCheckAccessTokenHandler();
|
||||
}
|
||||
@Bean
|
||||
public SaAnnotationHandlerInterface<SaCheckClientToken> getSaCheckClientTokenHandler() {
|
||||
return new SaCheckClientTokenHandler();
|
||||
}
|
||||
@Bean
|
||||
public SaAnnotationHandlerInterface<SaCheckClientIdSecret> getSaCheckClientIdSecretHandler() {
|
||||
return new SaCheckClientIdSecretHandler();
|
||||
}
|
||||
|
||||
/*
|
||||
// 这种写法有问题,当项目还有自定义的注解处理器时,项目中的自定义注解处理器将会覆盖掉此处 List 中的注解处理器
|
||||
// @Bean
|
||||
// public List<SaAnnotationHandlerInterface<?>> getXxx() {
|
||||
// return Arrays.asList(
|
||||
// new SaCheckAccessTokenHandler(),
|
||||
// new SaCheckClientTokenHandler(),
|
||||
// new SaCheckClientSecretHandler()
|
||||
// );
|
||||
// }
|
||||
|
||||
*/
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user