新增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

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