新增sa-token-oauth2 注解鉴权

This commit is contained in:
click33 2024-08-25 14:02:50 +08:00
parent 6a9f25093d
commit beb958f274
23 changed files with 698 additions and 175 deletions

View File

@ -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) {

View File

@ -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);
}
}

View File

@ -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;
// }
//
//}

View File

@ -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;
// }
//}

View File

@ -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("验证码发送成功");
// }
//
//}

View File

@ -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) {
// }
//
//}

View File

@ -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;

View File

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

View File

@ -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("访问成功");
}
}

View File

@ -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) -->

View 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("访问成功");
}
}
```

View File

@ -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 {};
}

View File

@ -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 {
}

View File

@ -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 {};
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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";

View File

@ -50,6 +50,14 @@ public interface SaOAuth2DataResolver {
*/
String readAccessToken(SaRequest request);
/**
* 数据读取从请求对象中读取 ClientToken
*
* @param request /
* @return /
*/
String readClientToken(SaRequest request);
/**
* 数据读取从请求对象中构建 RequestAuthModel
* @param req SaRequest对象

View File

@ -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
*/

View File

@ -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 参数
*/

View File

@ -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)

View File

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