merge dev into master

v1.39.0 update

Created-by: shengzhang_
Author-id: 774020
MR-id: 152152
Commit-by: click33;zhongjun;goodsWox;grasse;巴掌大叔;cuiguiyang
Merged-by: shengzhang_
E2E-issues: 
Description: 兼容请求/oauth2/token接口时Basic中携带clientId和clientSecret的场景
更新赞助者列表
补全 dromara 项目列表
更新赞助列表信息
!308 处理解析 JWT 时的 JSONException
...

See merge request: dromara/sa-token!2
This commit is contained in:
shengzhang_
2024-08-28 10:02:48 +08:00
294 changed files with 14985 additions and 4320 deletions

View File

@@ -1,7 +1,7 @@
<p align="center">
<img alt="logo" src="https://sa-token.cc/logo.png" width="150" height="150">
</p>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">Sa-Token v1.38.0</h1>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">Sa-Token v1.39.0</h1>
<h4 align="center">一个轻量级 Java 权限认证框架,让鉴权变得简单、优雅!</h4>
<p align="center">
<a href="https://gitee.com/dromara/sa-token/stargazers"><img src="https://gitee.com/dromara/sa-token/badge/star.svg?theme=gvp"></a>
@@ -185,6 +185,7 @@ Sa-Token-OAuth2 模块分为四种授权模式,解决不同场景下的授权
- [[ hippo4j ]](https://gitee.com/agentart/hippo4j):强大的动态线程池框架,附带监控报警功能。
- [[ hertzbeat ]](https://gitee.com/dromara/hertzbeat)易用友好的开源实时监控告警系统无需Agent高性能集群强大自定义监控能力。
- [[ Solon ]](https://gitee.com/noear/solon):一个更现代感的应用开发框架:更快、更小、更自由。
- [[ Chat2DB ]](https://github.com/chat2db/Chat2DB)一个AI驱动的数据库管理和BI工具支持Mysql、pg、Oracle、Redis等22种数据库的管理。
@@ -198,7 +199,7 @@ Sa-Token-OAuth2 模块分为四种授权模式,解决不同场景下的授权
### 交流群
<!-- QQ交流群685792424 [点击加入](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=Y05Ld4125W92YSwZ0gA8e3RhG9Q4Vsfx&authKey=IomXuIuhP9g8G7l%2ByfkrRsS7i%2Fna0lIBpkTXxx%2BQEaz0NNEyJq00kgeiC4dUyNLS&noverify=0&group_code=685792424)-->
QQ交流群936523917 [点击加入](https://qm.qq.com/q/xfoMJA5Az0)
QQ交流群823181187 [点击加入](https://qm.qq.com/q/EBIJVZBVGE)
微信交流群:

View File

@@ -37,7 +37,7 @@
<!-- 一些属性 -->
<properties>
<revision>1.38.0</revision>
<revision>1.39.0</revision>
<jdk.version>1.8</jdk.version>
<project.build.sourceEncoding>utf-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>utf-8</project.reporting.outputEncoding>

View File

@@ -13,7 +13,7 @@
<url>https://github.com/dromara/sa-token</url>
<properties>
<revision>1.38.0</revision>
<revision>1.39.0</revision>
</properties>
<dependencyManagement>

View File

@@ -39,13 +39,6 @@ public @interface SaCheckOr {
*/
SaCheckLogin[] login() default {};
/**
* 设定 @SaCheckPermission参考 {@link SaCheckPermission}
*
* @return /
*/
SaCheckPermission[] permission() default {};
/**
* 设定 @SaCheckRole参考 {@link SaCheckRole}
*
@@ -53,6 +46,13 @@ public @interface SaCheckOr {
*/
SaCheckRole[] role() default {};
/**
* 设定 @SaCheckPermission参考 {@link SaCheckPermission}
*
* @return /
*/
SaCheckPermission[] permission() default {};
/**
* 设定 @SaCheckSafe参考 {@link SaCheckSafe}
*

View File

@@ -0,0 +1,52 @@
/*
* 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.annotation.handler;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
/**
* 所有注解处理器的父接口
*
* @author click33
* @since 2024/8/2
*/
public interface SaAnnotationHandlerInterface<T extends Annotation> {
/**
* 获取所要处理的注解类型
* @return /
*/
Class<T> getHandlerAnnotationClass();
/**
* 所需要执行的校验方法
* @param at 注解对象
* @param method 被标注的注解的方法引用
*/
@SuppressWarnings("unchecked")
default void check(Annotation at, Method method) {
checkMethod((T) at, method);
}
/**
* 所需要执行的校验方法(转换类型后)
* @param at 注解对象
* @param method 被标注的注解的方法引用
*/
void checkMethod(T at, Method method);
}

View File

@@ -0,0 +1,51 @@
/*
* 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.annotation.handler;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.annotation.SaCheckDisable;
import cn.dev33.satoken.stp.StpLogic;
import java.lang.reflect.Method;
/**
* 注解 SaCheckDisable 的处理器
*
* @author click33
* @since 2024/8/2
*/
public class SaCheckDisableHandler implements SaAnnotationHandlerInterface<SaCheckDisable> {
@Override
public Class<SaCheckDisable> getHandlerAnnotationClass() {
return SaCheckDisable.class;
}
@Override
public void checkMethod(SaCheckDisable at, Method method) {
_checkMethod(at.type(), at.value(), at.level());
}
public static void _checkMethod(String type, String[] value, int level) {
StpLogic stpLogic = SaManager.getStpLogic(type, false);
Object loginId = stpLogic.getLoginId();
for (String service : value) {
stpLogic.checkDisableLevel(loginId, service, level);
}
}
}

View File

@@ -0,0 +1,45 @@
/*
* 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.annotation.handler;
import cn.dev33.satoken.annotation.SaCheckHttpBasic;
import cn.dev33.satoken.httpauth.basic.SaHttpBasicUtil;
import java.lang.reflect.Method;
/**
* 注解 SaCheckHttpBasic 的处理器
*
* @author click33
* @since 2024/8/2
*/
public class SaCheckHttpBasicHandler implements SaAnnotationHandlerInterface<SaCheckHttpBasic> {
@Override
public Class<SaCheckHttpBasic> getHandlerAnnotationClass() {
return SaCheckHttpBasic.class;
}
@Override
public void checkMethod(SaCheckHttpBasic at, Method method) {
_checkMethod(at.realm(), at.account());
}
public static void _checkMethod(String realm, String account) {
SaHttpBasicUtil.check(realm, account);
}
}

View File

@@ -0,0 +1,64 @@
/*
* 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.annotation.handler;
import cn.dev33.satoken.annotation.SaCheckHttpDigest;
import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.httpauth.digest.SaHttpDigestUtil;
import cn.dev33.satoken.util.SaFoxUtil;
import java.lang.reflect.Method;
/**
* 注解 SaCheckHttpDigest 的处理器
*
* @author click33
* @since 2024/8/2
*/
public class SaCheckHttpDigestHandler implements SaAnnotationHandlerInterface<SaCheckHttpDigest> {
@Override
public Class<SaCheckHttpDigest> getHandlerAnnotationClass() {
return SaCheckHttpDigest.class;
}
@Override
public void checkMethod(SaCheckHttpDigest at, Method method) {
_checkMethod(at.username(), at.password(), at.realm(), at.value());
}
public static void _checkMethod(String username, String password, String realm, String value) {
// 如果配置了 value则以 value 优先
if(SaFoxUtil.isNotEmpty(value)){
String[] arr = value.split(":");
if(arr.length != 2){
throw new SaTokenException("注解参数配置错误格式应如username:password");
}
SaHttpDigestUtil.check(arr[0], arr[1]);
return;
}
// 如果配置了 username则分别获取参数
if(SaFoxUtil.isNotEmpty(username)){
SaHttpDigestUtil.check(username, password, realm);
return;
}
// 都没有配置,则根据全局配置参数进行校验
SaHttpDigestUtil.check();
}
}

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.annotation.handler;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.stp.StpLogic;
import java.lang.reflect.Method;
/**
* 注解 SaCheckLogin 的处理器
*
* @author click33
* @since 2024/8/2
*/
public class SaCheckLoginHandler implements SaAnnotationHandlerInterface<SaCheckLogin> {
@Override
public Class<SaCheckLogin> getHandlerAnnotationClass() {
return SaCheckLogin.class;
}
@Override
public void checkMethod(SaCheckLogin at, Method method) {
_checkMethod(at.type());
}
public static void _checkMethod(String type) {
StpLogic stpLogic = SaManager.getStpLogic(type, false);
stpLogic.checkLogin();
}
}

View File

@@ -0,0 +1,87 @@
/*
* 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.annotation.handler;
import cn.dev33.satoken.annotation.*;
import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.strategy.SaAnnotationStrategy;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* 注解 SaCheckOr 的处理器
*
* @author click33
* @since 2024/8/2
*/
public class SaCheckOrHandler implements SaAnnotationHandlerInterface<SaCheckOr> {
@Override
public Class<SaCheckOr> getHandlerAnnotationClass() {
return SaCheckOr.class;
}
@Override
public void checkMethod(SaCheckOr at, Method method) {
_checkMethod(at.login(), at.role(), at.permission(), at.safe(), at.httpBasic(), at.httpDigest(), at.disable(), method);
}
public static void _checkMethod(
SaCheckLogin[] login,
SaCheckRole[] role,
SaCheckPermission[] permission,
SaCheckSafe[] safe,
SaCheckHttpBasic[] httpBasic,
SaCheckHttpDigest[] httpDigest,
SaCheckDisable[] disable,
Method method
) {
// 先把所有注解塞到一个 list 里
List<Annotation> annotationList = new ArrayList<>();
annotationList.addAll(Arrays.asList(login));
annotationList.addAll(Arrays.asList(role));
annotationList.addAll(Arrays.asList(permission));
annotationList.addAll(Arrays.asList(safe));
annotationList.addAll(Arrays.asList(disable));
annotationList.addAll(Arrays.asList(httpBasic));
annotationList.addAll(Arrays.asList(httpDigest));
// 如果 atList 为空,说明 SaCheckOr 上不包含任何注解校验,我们直接跳过即可
if(annotationList.isEmpty()) {
return;
}
// 逐个开始校验 >>>
List<SaTokenException> errorList = new ArrayList<>();
for (Annotation item : annotationList) {
try {
SaAnnotationStrategy.instance.annotationHandlerMap.get(item.annotationType()).check(item, method);
// 只要有一个校验通过,就可以直接返回了
return;
} catch (SaTokenException e) {
errorList.add(e);
}
}
// 执行至此,说明所有注解校验都通过不了,此时 errorList 里面会有多个异常,我们随便抛出一个即可
throw errorList.get(0);
}
}

View File

@@ -0,0 +1,68 @@
/*
* 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.annotation.handler;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaMode;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.util.SaFoxUtil;
import java.lang.reflect.Method;
/**
* 注解 SaCheckPermission 的处理器
*
* @author click33
* @since 2024/8/2
*/
public class SaCheckPermissionHandler implements SaAnnotationHandlerInterface<SaCheckPermission> {
@Override
public Class<SaCheckPermission> getHandlerAnnotationClass() {
return SaCheckPermission.class;
}
@Override
public void checkMethod(SaCheckPermission at, Method method) {
_checkMethod(at.type(), at.value(), at.mode(), at.orRole());
}
public static void _checkMethod(String type, String[] value, SaMode mode, String[] orRole) {
StpLogic stpLogic = SaManager.getStpLogic(type, false);
String[] permissionArray = value;
try {
if(mode == SaMode.AND) {
stpLogic.checkPermissionAnd(permissionArray);
} else {
stpLogic.checkPermissionOr(permissionArray);
}
} catch (NotPermissionException e) {
// 权限认证校验未通过,再开始角色认证校验
for (String role : orRole) {
String[] rArr = SaFoxUtil.convertStringToArray(role);
// 某一项 role 认证通过,则可以提前退出了,代表通过
if (stpLogic.hasRoleAnd(rArr)) {
return;
}
}
throw e;
}
}
}

View File

@@ -0,0 +1,54 @@
/*
* 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.annotation.handler;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.annotation.SaCheckRole;
import cn.dev33.satoken.annotation.SaMode;
import cn.dev33.satoken.stp.StpLogic;
import java.lang.reflect.Method;
/**
* 注解 SaCheckRole 的处理器
*
* @author click33
* @since 2024/8/2
*/
public class SaCheckRoleHandler implements SaAnnotationHandlerInterface<SaCheckRole> {
@Override
public Class<SaCheckRole> getHandlerAnnotationClass() {
return SaCheckRole.class;
}
@Override
public void checkMethod(SaCheckRole at, Method method) {
_checkMethod(at.type(), at.value(), at.mode());
}
public static void _checkMethod(String type, String[] value, SaMode mode) {
StpLogic stpLogic = SaManager.getStpLogic(type, false);
String[] roleArray = value;
if(mode == SaMode.AND) {
stpLogic.checkRoleAnd(roleArray);
} else {
stpLogic.checkRoleOr(roleArray);
}
}
}

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.annotation.handler;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.annotation.SaCheckSafe;
import cn.dev33.satoken.stp.StpLogic;
import java.lang.reflect.Method;
/**
* 注解 SaCheckSafe 的处理器
*
* @author click33
* @since 2024/8/2
*/
public class SaCheckSafeHandler implements SaAnnotationHandlerInterface<SaCheckSafe> {
@Override
public Class<SaCheckSafe> getHandlerAnnotationClass() {
return SaCheckSafe.class;
}
@Override
public void checkMethod(SaCheckSafe at, Method method) {
_checkMethod(at.type(), at.value());
}
public static void _checkMethod(String type, String value) {
StpLogic stpLogic = SaManager.getStpLogic(type, false);
stpLogic.checkSafe(value);
}
}

View File

@@ -0,0 +1,45 @@
/*
* 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.annotation.handler;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.dev33.satoken.router.SaRouter;
import java.lang.reflect.Method;
/**
* 注解 SaIgnore 的处理器
*
* @author click33
* @since 2024/8/2
*/
public class SaIgnoreHandler implements SaAnnotationHandlerInterface<SaIgnore> {
@Override
public Class<SaIgnore> getHandlerAnnotationClass() {
return SaIgnore.class;
}
@Override
public void checkMethod(SaIgnore at, Method method) {
_checkMethod();
}
public static void _checkMethod() {
SaRouter.stop();
}
}

View File

@@ -1,99 +0,0 @@
/*
* 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.basic;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.error.SaErrorCode;
import cn.dev33.satoken.exception.NotBasicAuthException;
import cn.dev33.satoken.secure.SaBase64Util;
import cn.dev33.satoken.util.SaFoxUtil;
/**
* <h2> 已更换至包cn.dev33.satoken.httpauth.basic </h2>
* <h2> 已更换名称SaHttpBasicTemplate </h2>
*
* Sa-Token Http Basic 认证模块
*
* @author click33
* @since 1.26.0
*/
@Deprecated
public class SaBasicTemplate {
/**
* 默认的 Realm 领域名称
*/
public static final String DEFAULT_REALM = "Sa-Token";
/**
* 在校验失败时,设置响应头,并抛出异常
* @param realm 领域
*/
public void throwNotBasicAuthException(String realm) {
SaHolder.getResponse().setStatus(401).setHeader("WWW-Authenticate", "Basic Realm=" + realm);
throw new NotBasicAuthException().setCode(SaErrorCode.CODE_10311);
}
/**
* 获取浏览器提交的 Basic 参数 (裁剪掉前缀并解码)
* @return 值
*/
public String getAuthorizationValue() {
// 获取前端提交的请求头 Authorization 参数
String authorization = SaHolder.getRequest().getHeader("Authorization");
// 如果不是以 Basic 作为前缀,则视为无效
if(authorization == null || ! authorization.startsWith("Basic ")) {
return null;
}
// 裁剪前缀并解码
return SaBase64Util.decode(authorization.substring(6));
}
/**
* 对当前会话进行 Basic 校验(使用全局配置的账号密码),校验不通过则抛出异常
*/
public void check() {
check(DEFAULT_REALM, SaManager.getConfig().getBasic());
}
/**
* 对当前会话进行 Basic 校验(手动设置账号密码),校验不通过则抛出异常
* @param account 账号(格式为 user:password
*/
public void check(String account) {
check(DEFAULT_REALM, account);
}
/**
* 对当前会话进行 Basic 校验(手动设置 Realm 和 账号密码),校验不通过则抛出异常
* @param realm 领域
* @param account 账号(格式为 user:password
*/
public void check(String realm, String account) {
if(SaFoxUtil.isEmpty(account)) {
account = SaManager.getConfig().getBasic();
}
String authorization = getAuthorizationValue();
if(SaFoxUtil.isEmpty(authorization) || ! authorization.equals(account)) {
throwNotBasicAuthException(realm);
}
}
}

View File

@@ -1,70 +0,0 @@
/*
* 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.basic;
/**
* <h2> 已更换至包cn.dev33.satoken.httpauth.basic </h2>
* <h2> 已更换名称SaHttpBasicUtil </h2>
*
* Sa-Token Http Basic 认证模块Util 工具类
*
* @author click33
* @since 1.26.0
*/
@Deprecated
public class SaBasicUtil {
private SaBasicUtil() {
}
/**
* 底层使用的 SaBasicTemplate 对象
*/
public static SaBasicTemplate saBasicTemplate = new SaBasicTemplate();
/**
* 获取浏览器提交的 Basic 参数 (裁剪掉前缀并解码)
* @return 值
*/
public static String getAuthorizationValue() {
return saBasicTemplate.getAuthorizationValue();
}
/**
* 对当前会话进行 Basic 校验(使用全局配置的账号密码),校验不通过则抛出异常
*/
public static void check() {
saBasicTemplate.check();
}
/**
* 对当前会话进行 Basic 校验(手动设置账号密码),校验不通过则抛出异常
* @param account 账号(格式为 user:password
*/
public static void check(String account) {
saBasicTemplate.check(account);
}
/**
* 对当前会话进行 Basic 校验(手动设置 Realm 和 账号密码),校验不通过则抛出异常
* @param realm 领域
* @param account 账号(格式为 user:password
*/
public static void check(String realm, String account) {
saBasicTemplate.check(realm, account);
}
}

View File

@@ -17,6 +17,7 @@ package cn.dev33.satoken.context.model;
import cn.dev33.satoken.error.SaErrorCode;
import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.router.SaHttpMethod;
import cn.dev33.satoken.util.SaFoxUtil;
import java.util.List;
@@ -130,6 +131,20 @@ public interface SaRequest {
*/
String getCookieValue(String name);
/**
* 在 [ Cookie作用域 ] 里获取一个值 (第一个此名称的)
* @param name 键
* @return 值
*/
String getCookieFirstValue(String name);
/**
* 在 [ Cookie作用域 ] 里获取一个值 (最后一个此名称的)
* @param name 键
* @return 值
*/
String getCookieLastValue(String name);
/**
* 返回当前请求path (不包括上下文名称)
* @return /
@@ -156,7 +171,25 @@ public interface SaRequest {
* @return /
*/
String getMethod();
/**
* 返回当前请求 Method 是否为指定值
* @param method method
* @return /
*/
default boolean isMethod(String method) {
return getMethod().equals(method);
}
/**
* 返回当前请求 Method 是否为指定值
* @param method method
* @return /
*/
default boolean isMethod(SaHttpMethod method) {
return getMethod().equals(method.name());
}
/**
* 判断此请求是否为 Ajax 异步请求
* @return /

View File

@@ -21,7 +21,7 @@ package cn.dev33.satoken.exception;
* @author click33
* @since 1.26.0
*/
public class NotBasicAuthException extends SaTokenException {
public class NotHttpBasicAuthException extends SaTokenException {
/**
* 序列化版本号
@@ -34,7 +34,7 @@ public class NotBasicAuthException extends SaTokenException {
/**
* 一个异常代表会话未通过 Http Basic 认证
*/
public NotBasicAuthException() {
public NotHttpBasicAuthException() {
super(BE_MESSAGE);
}

View File

@@ -29,6 +29,6 @@ import java.util.function.BiFunction;
* @since 1.35.0
*/
@FunctionalInterface
public interface SaGetAnnotationFunction extends BiFunction<AnnotatedElement, Class<? extends Annotation> , Annotation> {
public interface SaGetAnnotationFunction extends BiFunction<AnnotatedElement, Class<? extends Annotation>, Annotation> {
}

View File

@@ -18,7 +18,7 @@ package cn.dev33.satoken.httpauth.basic;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.error.SaErrorCode;
import cn.dev33.satoken.exception.NotBasicAuthException;
import cn.dev33.satoken.exception.NotHttpBasicAuthException;
import cn.dev33.satoken.secure.SaBase64Util;
import cn.dev33.satoken.util.SaFoxUtil;
@@ -41,7 +41,7 @@ public class SaHttpBasicTemplate {
*/
public void throwNotBasicAuthException(String realm) {
SaHolder.getResponse().setStatus(401).setHeader("WWW-Authenticate", "Basic Realm=" + realm);
throw new NotBasicAuthException().setCode(SaErrorCode.CODE_10311);
throw new NotHttpBasicAuthException().setCode(SaErrorCode.CODE_10311);
}
/**

View File

@@ -263,11 +263,16 @@ public class SaHttpDigestTemplate {
check(arr[0], arr[1]);
}
// ----------------- 过期方法 -----------------
/**
* 根据注解 ( @SaCheckHttpDigest ) 鉴权
*
* @param at 注解对象
*/
@Deprecated
public void checkByAnnotation(SaCheckHttpDigest at) {
// 如果配置了 value则以 value 优先

View File

@@ -90,11 +90,16 @@ public class SaHttpDigestUtil {
saHttpDigestTemplate.check();
}
// ----------------- 过期方法 -----------------
/**
* 根据注解 ( @SaCheckHttpDigest ) 鉴权
*
* @param at 注解对象
*/
@Deprecated
public static void checkByAnnotation(SaCheckHttpDigest at) {
saHttpDigestTemplate.checkByAnnotation(at);
}

View File

@@ -18,6 +18,7 @@ package cn.dev33.satoken.listener;
import java.util.ArrayList;
import java.util.List;
import cn.dev33.satoken.annotation.handler.SaAnnotationHandlerInterface;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.error.SaErrorCode;
import cn.dev33.satoken.exception.SaTokenException;
@@ -287,6 +288,16 @@ public class SaTokenEventCenter {
}
}
/**
* 事件发布:有新的注解处理器载入到框架中
* @param handler 注解处理器
*/
public static void doRegisterAnnotationHandler(SaAnnotationHandlerInterface<?> handler) {
for (SaTokenListener listener : listenerList) {
listener.doRegisterAnnotationHandler(handler);
}
}
/**
* 事件发布:有新的 StpLogic 载入到框架中
* @param stpLogic /

View File

@@ -15,6 +15,7 @@
*/
package cn.dev33.satoken.listener;
import cn.dev33.satoken.annotation.handler.SaAnnotationHandlerInterface;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpLogic;
@@ -125,6 +126,12 @@ public interface SaTokenListener {
*/
default void doRegisterComponent(String compName, Object compObj) {}
/**
* 注册了自定义注解处理器
* @param handler 注解处理器
*/
default void doRegisterAnnotationHandler(SaAnnotationHandlerInterface<?> handler) {}
/**
* StpLogic 对象替换
* @param stpLogic /

View File

@@ -15,13 +15,14 @@
*/
package cn.dev33.satoken.listener;
import static cn.dev33.satoken.SaManager.log;
import cn.dev33.satoken.annotation.handler.SaAnnotationHandlerInterface;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.util.SaFoxUtil;
import static cn.dev33.satoken.SaManager.log;
/**
* Sa-Token 侦听器的一个实现Log 打印
*
@@ -130,6 +131,17 @@ public class SaTokenListenerForLog implements SaTokenListener {
log.info("全局组件 {} 载入成功: {}", compName, canonicalName);
}
/**
* 注册了自定义注解处理器
* @param handler 注解处理器
*/
@Override
public void doRegisterAnnotationHandler(SaAnnotationHandlerInterface<?> handler) {
if(handler != null) {
log.info("注解扩展 @{} (处理器: {})", handler.getHandlerAnnotationClass().getSimpleName(), handler.getClass().getCanonicalName());
}
}
/**
* StpLogic 对象替换
* @param stpLogic /

View File

@@ -96,7 +96,7 @@ public class SaSession implements SaSetValueInterface, Serializable {
/**
* 所有挂载数据
*/
private final Map<String, Object> dataMap = new ConcurrentHashMap<>();
private Map<String, Object> dataMap = new ConcurrentHashMap<>();
// ----------------------- 构建相关
@@ -522,14 +522,26 @@ public class SaSession implements SaSetValueInterface, Serializable {
return dataMap;
}
/**
* 设置数据挂载集合 (改变底层对象引用,将 dataMap 整个对象替换)
* @param dataMap 数据集合
*
* @return 对象自身
*/
public SaSession setDataMap(Map<String, Object> dataMap) {
this.dataMap = dataMap;
return this;
}
/**
* 写入数据集合 (不改变底层对象引用,只将此 dataMap 所有数据进行替换)
* @param dataMap 数据集合
*/
public void refreshDataMap(Map<String, Object> dataMap) {
public SaSession refreshDataMap(Map<String, Object> dataMap) {
this.dataMap.clear();
this.dataMap.putAll(dataMap);
this.update();
return this;
}
//

View File

@@ -300,6 +300,9 @@ public class SaLoginModel {
if(getTimeoutOrGlobalConfig() == SaTokenDao.NEVER_EXPIRE) {
return Integer.MAX_VALUE;
}
if (timeout > Integer.MAX_VALUE) {
return Integer.MAX_VALUE;
}
return (int)(long)timeout;
}

View File

@@ -36,7 +36,7 @@ import cn.dev33.satoken.util.SaFoxUtil;
import cn.dev33.satoken.util.SaTokenConsts;
import cn.dev33.satoken.util.SaValue2Box;
import java.util.Collections;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
@@ -590,6 +590,20 @@ public class StpLogic {
}
/**
* 获取指定账号 id 的登录会话数据,如果获取不到则创建并返回
*
* @param id 账号id建议的类型long | int | String
* @return 返回会话令牌
*/
public String getOrCreateLoginSession(Object id) {
String tokenValue = getTokenValueByLoginId(id);
if(tokenValue == null) {
tokenValue = createLoginSession(id, new SaLoginModel());
}
return tokenValue;
}
// --- 注销
/**
@@ -2114,7 +2128,7 @@ public class StpLogic {
// 如果该账号的 Account-Session 为 null说明此账号尚没有客户端在登录此时返回空集合
SaSession session = getSessionByLoginId(loginId, false);
if(session == null) {
return Collections.emptyList();
return new ArrayList<>();
}
// 按照设备类型进行筛选
@@ -2132,7 +2146,7 @@ public class StpLogic {
// 如果该账号的 Account-Session 为 null说明此账号尚没有客户端在登录此时返回空集合
SaSession session = getSessionByLoginId(loginId, false);
if(session == null) {
return Collections.emptyList();
return new ArrayList<>();
}
// 按照设备类型进行筛选
@@ -2233,79 +2247,6 @@ public class StpLogic {
public List<String> searchTokenSessionId(String keyword, int start, int size, boolean sortType) {
return getSaTokenDao().searchData(splicingKeyTokenSession(""), keyword, start, size, sortType);
}
// ------------------- 注解鉴权 -------------------
/**
* 根据注解 ( @SaCheckLogin ) 鉴权
*
* @param at 注解对象
*/
public void checkByAnnotation(SaCheckLogin at) {
this.checkLogin();
}
/**
* 根据注解 ( @SaCheckRole ) 鉴权
*
* @param at 注解对象
*/
public void checkByAnnotation(SaCheckRole at) {
String[] roleArray = at.value();
if(at.mode() == SaMode.AND) {
this.checkRoleAnd(roleArray);
} else {
this.checkRoleOr(roleArray);
}
}
/**
* 根据注解 ( @SaCheckPermission ) 鉴权
*
* @param at 注解对象
*/
public void checkByAnnotation(SaCheckPermission at) {
String[] permissionArray = at.value();
try {
if(at.mode() == SaMode.AND) {
this.checkPermissionAnd(permissionArray);
} else {
this.checkPermissionOr(permissionArray);
}
} catch (NotPermissionException e) {
// 权限认证校验未通过,再开始角色认证校验
for (String role : at.orRole()) {
String[] rArr = SaFoxUtil.convertStringToArray(role);
// 某一项 role 认证通过,则可以提前退出了,代表通过
if (hasRoleAnd(rArr)) {
return;
}
}
throw e;
}
}
/**
* 根据注解 ( @SaCheckSafe ) 鉴权
*
* @param at 注解对象
*/
public void checkByAnnotation(SaCheckSafe at) {
this.checkSafe(at.value());
}
/**
* 根据注解 ( @SaCheckDisable ) 鉴权
*
* @param at 注解对象
*/
public void checkByAnnotation(SaCheckDisable at) {
Object loginId = getLoginId();
for (String service : at.value()) {
this.checkDisableLevel(loginId, service, at.level());
}
}
// ------------------- 账号封禁 -------------------
@@ -2937,4 +2878,84 @@ public class StpLogic {
return false;
}
// ------------------- 过期方法 -------------------
/**
* 根据注解 ( @SaCheckLogin ) 鉴权
*
* @param at 注解对象
*/
@Deprecated
public void checkByAnnotation(SaCheckLogin at) {
this.checkLogin();
}
/**
* 根据注解 ( @SaCheckRole ) 鉴权
*
* @param at 注解对象
*/
@Deprecated
public void checkByAnnotation(SaCheckRole at) {
String[] roleArray = at.value();
if(at.mode() == SaMode.AND) {
this.checkRoleAnd(roleArray);
} else {
this.checkRoleOr(roleArray);
}
}
/**
* 根据注解 ( @SaCheckPermission ) 鉴权
*
* @param at 注解对象
*/
@Deprecated
public void checkByAnnotation(SaCheckPermission at) {
String[] permissionArray = at.value();
try {
if(at.mode() == SaMode.AND) {
this.checkPermissionAnd(permissionArray);
} else {
this.checkPermissionOr(permissionArray);
}
} catch (NotPermissionException e) {
// 权限认证校验未通过,再开始角色认证校验
for (String role : at.orRole()) {
String[] rArr = SaFoxUtil.convertStringToArray(role);
// 某一项 role 认证通过,则可以提前退出了,代表通过
if (hasRoleAnd(rArr)) {
return;
}
}
throw e;
}
}
/**
* 根据注解 ( @SaCheckSafe ) 鉴权
*
* @param at 注解对象
*/
@Deprecated
public void checkByAnnotation(SaCheckSafe at) {
this.checkSafe(at.value());
}
/**
* 根据注解 ( @SaCheckDisable ) 鉴权
*
* @param at 注解对象
*/
@Deprecated
public void checkByAnnotation(SaCheckDisable at) {
Object loginId = getLoginId();
for (String service : at.value()) {
this.checkDisableLevel(loginId, service, at.level());
}
}
}

View File

@@ -224,7 +224,17 @@ public class StpUtil {
public static String createLoginSession(Object id, SaLoginModel loginModel) {
return stpLogic.createLoginSession(id, loginModel);
}
/**
* 获取指定账号 id 的登录会话数据,如果获取不到则创建并返回
*
* @param id 账号id建议的类型long | int | String
* @return 返回会话令牌
*/
public static String getOrCreateLoginSession(Object id) {
return stpLogic.getOrCreateLoginSession(id);
}
// --- 注销
/**

View File

@@ -0,0 +1,133 @@
/*
* 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.strategy;
import cn.dev33.satoken.annotation.*;
import cn.dev33.satoken.annotation.handler.*;
import cn.dev33.satoken.fun.strategy.SaCheckMethodAnnotationFunction;
import cn.dev33.satoken.fun.strategy.SaGetAnnotationFunction;
import cn.dev33.satoken.fun.strategy.SaIsAnnotationPresentFunction;
import cn.dev33.satoken.listener.SaTokenEventCenter;
import java.lang.annotation.Annotation;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Sa-Token 注解鉴权相关策略
*
* @author click33
* @since 1.39.0
*/
public final class SaAnnotationStrategy {
private SaAnnotationStrategy() {
registerDefaultAnnotationHandler();
}
/**
* 全局单例引用
*/
public static final SaAnnotationStrategy instance = new SaAnnotationStrategy();
// ----------------------- 所有策略
/**
* 注解处理器集合
*/
public Map<Class<?>, SaAnnotationHandlerInterface<?>> annotationHandlerMap = new LinkedHashMap<>();
/**
* 注册所有默认的注解处理器
*/
public void registerDefaultAnnotationHandler() {
annotationHandlerMap.put(SaIgnore.class, new SaIgnoreHandler());
annotationHandlerMap.put(SaCheckLogin.class, new SaCheckLoginHandler());
annotationHandlerMap.put(SaCheckRole.class, new SaCheckRoleHandler());
annotationHandlerMap.put(SaCheckPermission.class, new SaCheckPermissionHandler());
annotationHandlerMap.put(SaCheckSafe.class, new SaCheckSafeHandler());
annotationHandlerMap.put(SaCheckDisable.class, new SaCheckDisableHandler());
annotationHandlerMap.put(SaCheckHttpBasic.class, new SaCheckHttpBasicHandler());
annotationHandlerMap.put(SaCheckHttpDigest.class, new SaCheckHttpDigestHandler());
annotationHandlerMap.put(SaCheckOr.class, new SaCheckOrHandler());
}
/**
* 注册一个注解处理器
*/
public void registerAnnotationHandler(SaAnnotationHandlerInterface<?> handler) {
annotationHandlerMap.put(handler.getHandlerAnnotationClass(), handler);
SaTokenEventCenter.doRegisterAnnotationHandler(handler);
}
/**
* 注册一个注解处理器,到首位
*/
public void registerAnnotationHandlerToFirst(SaAnnotationHandlerInterface<?> handler) {
Map<Class<?>, SaAnnotationHandlerInterface<?>> newMap = new LinkedHashMap<>();
newMap.put(handler.getHandlerAnnotationClass(), handler);
newMap.putAll(annotationHandlerMap);
this.annotationHandlerMap = newMap;
SaTokenEventCenter.doRegisterAnnotationHandler(handler);
}
/**
* 移除一个注解处理器
*/
public void removeAnnotationHandler(Class<?> cls) {
annotationHandlerMap.remove(cls);
}
/**
* 对一个 [Method] 对象进行注解校验 (注解鉴权内部实现)
*/
@SuppressWarnings("unchecked")
public SaCheckMethodAnnotationFunction checkMethodAnnotation = (method) -> {
// 遍历所有的注解处理器,检查此 method 是否具有这些指定的注解
for (Map.Entry<Class<?>, SaAnnotationHandlerInterface<?>> entry: annotationHandlerMap.entrySet()) {
// 先校验 Method 所属 Class 上的注解
Annotation classTakeAnnotation = instance.getAnnotation.apply(method.getDeclaringClass(), (Class<Annotation>)entry.getKey());
if(classTakeAnnotation != null) {
entry.getValue().check(classTakeAnnotation, method);
}
// 再校验 Method 上的注解
Annotation methodTakeAnnotation = instance.getAnnotation.apply(method, (Class<Annotation>)entry.getKey());
if(methodTakeAnnotation != null) {
entry.getValue().check(methodTakeAnnotation, method);
}
}
};
/**
* 从元素上获取注解
*/
public SaGetAnnotationFunction getAnnotation = (element, annotationClass)->{
// 默认使用jdk的注解处理器
return element.getAnnotation(annotationClass);
};
/**
* 判断一个 Method 或其所属 Class 是否包含指定注解
*/
public SaIsAnnotationPresentFunction isAnnotationPresent = (method, annotationClass) -> {
return instance.getAnnotation.apply(method, annotationClass) != null ||
instance.getAnnotation.apply(method.getDeclaringClass(), annotationClass) != null;
};
}

View File

@@ -16,19 +16,14 @@
package cn.dev33.satoken.strategy;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.annotation.*;
import cn.dev33.satoken.exception.RequestPathInvalidException;
import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.fun.strategy.*;
import cn.dev33.satoken.httpauth.basic.SaHttpBasicUtil;
import cn.dev33.satoken.httpauth.digest.SaHttpDigestUtil;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.util.SaFoxUtil;
import cn.dev33.satoken.util.SaTokenConsts;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
@@ -133,185 +128,6 @@ public final class SaStrategy {
return false;
};
/**
* 对一个 [Method] 对象进行注解校验 (注解鉴权内部实现)
*/
public SaCheckMethodAnnotationFunction checkMethodAnnotation = (method) -> {
// 先校验 Method 所属 Class 上的注解
instance.checkElementAnnotation.accept(method.getDeclaringClass());
// 再校验 Method 上的注解
instance.checkElementAnnotation.accept(method);
};
/**
* 对一个 [元素] 对象进行注解校验 (注解鉴权内部实现)
*/
public SaCheckElementAnnotationFunction checkElementAnnotation = (element) -> {
// 校验 @SaCheckLogin 注解
SaCheckLogin checkLogin = (SaCheckLogin) SaStrategy.instance.getAnnotation.apply(element, SaCheckLogin.class);
if(checkLogin != null) {
SaManager.getStpLogic(checkLogin.type(), false).checkByAnnotation(checkLogin);
}
// 校验 @SaCheckRole 注解
SaCheckRole checkRole = (SaCheckRole) SaStrategy.instance.getAnnotation.apply(element, SaCheckRole.class);
if(checkRole != null) {
SaManager.getStpLogic(checkRole.type(), false).checkByAnnotation(checkRole);
}
// 校验 @SaCheckPermission 注解
SaCheckPermission checkPermission = (SaCheckPermission) SaStrategy.instance.getAnnotation.apply(element, SaCheckPermission.class);
if(checkPermission != null) {
SaManager.getStpLogic(checkPermission.type(), false).checkByAnnotation(checkPermission);
}
// 校验 @SaCheckSafe 注解
SaCheckSafe checkSafe = (SaCheckSafe) SaStrategy.instance.getAnnotation.apply(element, SaCheckSafe.class);
if(checkSafe != null) {
SaManager.getStpLogic(checkSafe.type(), false).checkByAnnotation(checkSafe);
}
// 校验 @SaCheckDisable 注解
SaCheckDisable checkDisable = (SaCheckDisable) SaStrategy.instance.getAnnotation.apply(element, SaCheckDisable.class);
if(checkDisable != null) {
SaManager.getStpLogic(checkDisable.type(), false).checkByAnnotation(checkDisable);
}
// 校验 @SaCheckHttpBasic 注解
SaCheckHttpBasic checkHttpBasic = (SaCheckHttpBasic) SaStrategy.instance.getAnnotation.apply(element, SaCheckHttpBasic.class);
if(checkHttpBasic != null) {
SaHttpBasicUtil.check(checkHttpBasic.realm(), checkHttpBasic.account());
}
// 校验 @SaCheckHttpDigest 注解
SaCheckHttpDigest checkHttpDigest = (SaCheckHttpDigest) SaStrategy.instance.getAnnotation.apply(element, SaCheckHttpDigest.class);
if(checkHttpDigest != null) {
SaHttpDigestUtil.checkByAnnotation(checkHttpDigest);
}
// 校验 @SaCheckOr 注解
SaCheckOr checkOr = (SaCheckOr) SaStrategy.instance.getAnnotation.apply(element, SaCheckOr.class);
if(checkOr != null) {
SaStrategy.instance.checkOrAnnotation.accept(checkOr);
}
};
/**
* 对一个 @SaCheckOr 进行注解校验
*/
public SaCheckOrAnnotationFunction checkOrAnnotation = (at) -> {
// 记录校验过程中所有的异常
List<SaTokenException> errorList = new ArrayList<>();
// 逐个开始校验 >>>
// 1、校验注解@SaCheckLogin
SaCheckLogin[] checkLoginArray = at.login();
for (SaCheckLogin item : checkLoginArray) {
try {
SaManager.getStpLogic(item.type(), false).checkByAnnotation(item);
return;
} catch (SaTokenException e) {
errorList.add(e);
}
}
// 2、校验注解@SaCheckRole
SaCheckRole[] checkRoleArray = at.role();
for (SaCheckRole item : checkRoleArray) {
try {
SaManager.getStpLogic(item.type(), false).checkByAnnotation(item);
return;
} catch (SaTokenException e) {
errorList.add(e);
}
}
// 3、校验注解@SaCheckPermission
SaCheckPermission[] checkPermissionArray = at.permission();
for (SaCheckPermission item : checkPermissionArray) {
try {
SaManager.getStpLogic(item.type(), false).checkByAnnotation(item);
return;
} catch (SaTokenException e) {
errorList.add(e);
}
}
// 4、校验注解@SaCheckSafe
SaCheckSafe[] checkSafeArray = at.safe();
for (SaCheckSafe item : checkSafeArray) {
try {
SaManager.getStpLogic(item.type(), false).checkByAnnotation(item);
return;
} catch (SaTokenException e) {
errorList.add(e);
}
}
// 5、校验注解@SaCheckDisable
SaCheckDisable[] checkDisableArray = at.disable();
for (SaCheckDisable item : checkDisableArray) {
try {
SaManager.getStpLogic(item.type(), false).checkByAnnotation(item);
return;
} catch (SaTokenException e) {
errorList.add(e);
}
}
// 6、校验注解@SaCheckBasic
SaCheckHttpBasic[] checkHttpBasicArray = at.httpBasic();
for (SaCheckHttpBasic item : checkHttpBasicArray) {
try {
SaHttpBasicUtil.check(item.realm(), item.account());
return;
} catch (SaTokenException e) {
errorList.add(e);
}
}
// 7、校验注解@SaCheckDigest
SaCheckHttpDigest[] checkHttpDigestArray = at.httpDigest();
for (SaCheckHttpDigest item : checkHttpDigestArray) {
try {
SaHttpDigestUtil.checkByAnnotation(item);
return;
} catch (SaTokenException e) {
errorList.add(e);
}
}
// 如果执行到这里,有两种可能:
// 可能 1. SaCheckOr 注解上不包含任何注解校验,此时 errorList 里面一个异常都没有,我们直接跳过即可
// 可能 2. 所有注解校验都通过不了,此时 errorList 里面会有多个异常,我们随便抛出一个即可
if(errorList.size() == 0) {
// return;
} else {
throw errorList.get(0);
}
};
/**
* 从元素上获取注解
*/
public SaGetAnnotationFunction getAnnotation = (element, annotationClass)->{
// 默认使用jdk的注解处理器
return element.getAnnotation(annotationClass);
};
/**
* 判断一个 Method 或其所属 Class 是否包含指定注解
*/
public SaIsAnnotationPresentFunction isAnnotationPresent = (method, annotationClass) -> {
return instance.getAnnotation.apply(method, annotationClass) != null ||
instance.getAnnotation.apply(method.getDeclaringClass(), annotationClass) != null;
};
/**
* 生成唯一式 token 的算法
*/
@@ -427,62 +243,6 @@ public final class SaStrategy {
return this;
}
/**
* 对一个 [Method] 对象进行注解校验 (注解鉴权内部实现)
*
* @param checkMethodAnnotation /
* @return /
*/
public SaStrategy setCheckMethodAnnotation(SaCheckMethodAnnotationFunction checkMethodAnnotation) {
this.checkMethodAnnotation = checkMethodAnnotation;
return this;
}
/**
* 对一个 [元素] 对象进行注解校验 (注解鉴权内部实现)
*
* @param checkElementAnnotation /
* @return /
*/
public SaStrategy setCheckElementAnnotation(SaCheckElementAnnotationFunction checkElementAnnotation) {
this.checkElementAnnotation = checkElementAnnotation;
return this;
}
/**
* 对一个 @SaCheckOr 进行注解校验
* <p> 参数 [SaCheckOr 注解的实例]
*
* @param checkOrAnnotation /
* @return /
*/
public SaStrategy setCheckOrAnnotation(SaCheckOrAnnotationFunction checkOrAnnotation) {
this.checkOrAnnotation = checkOrAnnotation;
return this;
}
/**
* 从元素上获取注解
*
* @param getAnnotation /
* @return /
*/
public SaStrategy setGetAnnotation(SaGetAnnotationFunction getAnnotation) {
this.getAnnotation = getAnnotation;
return this;
}
/**
* 判断一个 Method 或其所属 Class 是否包含指定注解
*
* @param isAnnotationPresent /
* @return /
*/
public SaStrategy setIsAnnotationPresent(SaIsAnnotationPresentFunction isAnnotationPresent) {
this.isAnnotationPresent = isAnnotationPresent;
return this;
}
/**
* 生成唯一式 token 的算法
*

View File

@@ -29,7 +29,6 @@ import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import java.util.regex.Pattern;
/**
* Sa-Token 内部工具类
@@ -76,6 +75,17 @@ public class SaFoxUtil {
return sb.toString();
}
/**
* 生成指定区间的 int 值
*
* @param min 最小值(包括)
* @param max 最大值(包括)
* @return /
*/
public static int getRandomNumber(int min, int max) {
return ThreadLocalRandom.current().nextInt(min, max + 1);
}
/**
* 指定元素是否为null或者空字符串
* @param str 指定元素
@@ -96,14 +106,35 @@ public class SaFoxUtil {
/**
* 指定数组是否为null或者空数组
* <h3> 该方法已过时,建议使用 isEmptyArray 方法 </h3>
* @param <T> /
* @param array /
* @return /
*/
@Deprecated
public static <T> boolean isEmpty(T[] array) {
return isEmptyArray(array);
}
/**
* 指定数组是否为null或者空数组
* @param <T> /
* @param array /
* @return /
*/
public static <T> boolean isEmptyArray(T[] array) {
return array == null || array.length == 0;
}
/**
* 指定集合是否为null或者空数组
* @param list /
* @return /
*/
public static boolean isEmptyList(List<?> list) {
return list == null || list.isEmpty();
}
/**
* 比较两个对象是否相等
* @param a 第一个对象
@@ -563,7 +594,7 @@ public class SaFoxUtil {
* @return 字符串
*/
public static String convertListToString(List<?> list) {
if(list == null || list.size() == 0) {
if(list == null || list.isEmpty()) {
return "";
}
StringBuilder str = new StringBuilder();
@@ -616,6 +647,15 @@ public class SaFoxUtil {
return new ArrayList<>(Arrays.asList(str));
}
/**
* String 集合转数组
* @param list 集合
* @return 数组
*/
public static String[] toArray(List<String> list) {
return list.toArray(new String[0]);
}
public static List<String> logLevelList = Arrays.asList("", "trace", "debug", "info", "warn", "error", "fatal");
/**
@@ -679,4 +719,63 @@ public class SaFoxUtil {
return false;
}
/**
* list1 是否完全包含 list2 中所有元素
* @param list1 集合1
* @param list2 集合2
* @return /
*/
public static boolean list1ContainList2AllElement(List<String> list1, List<String> list2){
if(list2 == null || list2.isEmpty()) {
return true;
}
if(list1 == null || list1.isEmpty()) {
return false;
}
for (String str : list2) {
if(!list1.contains(str)) {
return false;
}
}
return true;
}
/**
* list1 是否包含 list2 中任意一个元素
* @param list1 集合1
* @param list2 集合2
* @return /
*/
public static boolean list1ContainList2AnyElement(List<String> list1, List<String> list2){
if(list1 == null || list1.isEmpty() || list2 == null || list2.isEmpty()) {
return false;
}
for (String str : list2) {
if(list1.contains(str)) {
return true;
}
}
return false;
}
/**
* 从 list1 中剔除 list2 所包含的元素 (克隆副本操作,不影响 list1
* @param list1 集合1
* @param list2 集合2
* @return /
*/
public static List<String> list1RemoveByList2(List<String> list1, List<String> list2){
if(list1 == null) {
return null;
}
if(list1.isEmpty() || list2 == null || list2.isEmpty()) {
return new ArrayList<>(list1);
}
List<String> listX = new ArrayList<>(list1);
for (String str : list2) {
listX.remove(str);
}
return listX;
}
}

View File

@@ -15,6 +15,8 @@
*/
package cn.dev33.satoken.util;
import cn.dev33.satoken.SaManager;
import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -150,7 +152,42 @@ public class SaResult extends LinkedHashMap<String, Object> implements Serializa
}
return this;
}
/**
* 写入一个 json 字符串, 连缀风格
* @param jsonString json 字符串
* @return 对象自身
*/
public SaResult setJsonString(String jsonString) {
Map<String, Object> map = SaManager.getSaJsonTemplate().parseJsonToMap(jsonString);
return setMap(map);
}
/**
* 移除默认属性code、msg、data, 连缀风格
* @return 对象自身
*/
public SaResult removeDefaultFields() {
this.remove("code");
this.remove("msg");
this.remove("data");
return this;
}
/**
* 移除非默认属性code、msg、data, 连缀风格
* @return 对象自身
*/
public SaResult removeNonDefaultFields() {
for (String key : this.keySet()) {
if("code".equals(key) || "msg".equals(key) || "data".equals(key)) {
continue;
}
this.remove(key);
}
return this;
}
// ============================ 静态方法快速构建 ==================================
@@ -180,7 +217,11 @@ public class SaResult extends LinkedHashMap<String, Object> implements Serializa
public static SaResult get(int code, String msg, Object data) {
return new SaResult(code, msg, data);
}
// 构建一个空的
public static SaResult empty() {
return new SaResult();
}
/* (non-Javadoc)
* @see java.lang.Object#toString()

View File

@@ -36,7 +36,7 @@ public class SaTokenConsts {
/**
* Sa-Token 当前版本号
*/
public static final String VERSION_NO = "v1.38.0";
public static final String VERSION_NO = "v1.39.0";
/**
* Sa-Token 开源地址 Gitee

View File

@@ -16,7 +16,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
</properties>
<dependencies>

View File

@@ -17,7 +17,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
</properties>
<dependencies>

View File

@@ -16,7 +16,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
</properties>
<dependencies>

View File

@@ -16,7 +16,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
</properties>
<dependencies>
@@ -73,7 +73,7 @@
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-bom</artifactId>
<version>1.38.0</version>
<version>1.39.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>

View File

@@ -17,7 +17,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
</properties>
<dependencies>
@@ -51,7 +51,7 @@
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- @ConfigurationProperties -->
<dependency>
<groupId>org.springframework.boot</groupId>

View File

@@ -1,10 +1,9 @@
package com.pj;
import cn.dev33.satoken.SaManager;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import cn.dev33.satoken.SaManager;
/**
* Sa-Token 示例
* @author click33

View File

@@ -1,10 +1,9 @@
package com.pj.cases.test;
import cn.dev33.satoken.util.SaResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import cn.dev33.satoken.util.SaResult;
/**
* 测试专用 Controller
* @author click33
@@ -17,14 +16,14 @@ public class TestController {
// 测试 浏览器访问: http://localhost:8081/test/test
@RequestMapping("test")
public SaResult test() {
System.out.println("------------进来了");
System.out.println("------------进来了");
return SaResult.ok();
}
// 测试 浏览器访问: http://localhost:8081/test/test2
@RequestMapping("test2")
public SaResult test2() {
System.out.println("------------进来了");
System.out.println("------------进来了");
return SaResult.ok();
}

View File

@@ -51,25 +51,25 @@ public class SecureController {
}
// RSA加密 ---- http://localhost:8081/secure/rsa
@RequestMapping("rsa")
public SaResult rsa() {
// 定义私钥和公钥
String privateKey = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAO+wmt01pwm9lHMdq7A8gkEigk0XKMfjv+4IjAFhWCSiTeP7dtlnceFJbkWxvbc7Qo3fCOpwmfcskwUc3VSgyiJkNJDs9ivPbvlt8IU2bZ+PBDxYxSCJFrgouVOpAr8ar/b6gNuYTi1vt3FkGtSjACFb002/68RKUTye8/tdcVilAgMBAAECgYA1COmrSqTUJeuD8Su9ChZ0HROhxR8T45PjMmbwIz7ilDsR1+E7R4VOKPZKW4Kz2VvnklMhtJqMs4MwXWunvxAaUFzQTTg2Fu/WU8Y9ha14OaWZABfChMZlpkmpJW9arKmI22ZuxCEsFGxghTiJQ3tK8npj5IZq5vk+6mFHQ6aJAQJBAPghz91Dpuj+0bOUfOUmzi22obWCBncAD/0CqCLnJlpfOoa9bOcXSusGuSPuKy5KiGyblHMgKI6bq7gcM2DWrGUCQQD3SkOcmia2s/6i7DUEzMKaB0bkkX4Ela/xrfV+A3GzTPv9bIBamu0VIHznuiZbeNeyw7sVo4/GTItq/zn2QJdBAkEA8xHsVoyXTVeShaDIWJKTFyT5dJ1TR++/udKIcuiNIap34tZdgGPI+EM1yoTduBM7YWlnGwA9urW0mj7F9e9WIQJAFjxqSfmeg40512KP/ed/lCQVXtYqU7U2BfBTg8pBfhLtEcOg4wTNTroGITwe2NjL5HovJ2n2sqkNXEio6Ji0QQJAFLW1Kt80qypMqot+mHhS+0KfdOpaKeMWMSR4Ij5VfE63WzETEeWAMQESxzhavN1WOTb3/p6icgcVbgPQBaWhGg==";
String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDvsJrdNacJvZRzHauwPIJBIoJNFyjH47/uCIwBYVgkok3j+3bZZ3HhSW5Fsb23O0KN3wjqcJn3LJMFHN1UoMoiZDSQ7PYrz275bfCFNm2fjwQ8WMUgiRa4KLlTqQK/Gq/2+oDbmE4tb7dxZBrUowAhW9NNv+vESlE8nvP7XXFYpQIDAQAB";
// 文本
String text = "Sa-Token 一个轻量级java权限认证框架";
// 使用公钥加密
String ciphertext = SaSecureUtil.rsaEncryptByPublic(publicKey, text);
System.out.println("公钥加密后:" + ciphertext);
// 使用私钥解密
String text2 = SaSecureUtil.rsaDecryptByPrivate(privateKey, ciphertext);
System.out.println("私钥解密后:" + text2);
return SaResult.ok();
}
// @RequestMapping("rsa")
// public SaResult rsa() {
// // 定义私钥和公钥
// String privateKey = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAO+wmt01pwm9lHMdq7A8gkEigk0XKMfjv+4IjAFhWCSiTeP7dtlnceFJbkWxvbc7Qo3fCOpwmfcskwUc3VSgyiJkNJDs9ivPbvlt8IU2bZ+PBDxYxSCJFrgouVOpAr8ar/b6gNuYTi1vt3FkGtSjACFb002/68RKUTye8/tdcVilAgMBAAECgYA1COmrSqTUJeuD8Su9ChZ0HROhxR8T45PjMmbwIz7ilDsR1+E7R4VOKPZKW4Kz2VvnklMhtJqMs4MwXWunvxAaUFzQTTg2Fu/WU8Y9ha14OaWZABfChMZlpkmpJW9arKmI22ZuxCEsFGxghTiJQ3tK8npj5IZq5vk+6mFHQ6aJAQJBAPghz91Dpuj+0bOUfOUmzi22obWCBncAD/0CqCLnJlpfOoa9bOcXSusGuSPuKy5KiGyblHMgKI6bq7gcM2DWrGUCQQD3SkOcmia2s/6i7DUEzMKaB0bkkX4Ela/xrfV+A3GzTPv9bIBamu0VIHznuiZbeNeyw7sVo4/GTItq/zn2QJdBAkEA8xHsVoyXTVeShaDIWJKTFyT5dJ1TR++/udKIcuiNIap34tZdgGPI+EM1yoTduBM7YWlnGwA9urW0mj7F9e9WIQJAFjxqSfmeg40512KP/ed/lCQVXtYqU7U2BfBTg8pBfhLtEcOg4wTNTroGITwe2NjL5HovJ2n2sqkNXEio6Ji0QQJAFLW1Kt80qypMqot+mHhS+0KfdOpaKeMWMSR4Ij5VfE63WzETEeWAMQESxzhavN1WOTb3/p6icgcVbgPQBaWhGg==";
// String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDvsJrdNacJvZRzHauwPIJBIoJNFyjH47/uCIwBYVgkok3j+3bZZ3HhSW5Fsb23O0KN3wjqcJn3LJMFHN1UoMoiZDSQ7PYrz275bfCFNm2fjwQ8WMUgiRa4KLlTqQK/Gq/2+oDbmE4tb7dxZBrUowAhW9NNv+vESlE8nvP7XXFYpQIDAQAB";
//
// // 文本
// String text = "Sa-Token 一个轻量级java权限认证框架";
//
// // 使用公钥加密
// String ciphertext = SaSecureUtil.rsaEncryptByPublic(publicKey, text);
// System.out.println("公钥加密后:" + ciphertext);
//
// // 使用私钥解密
// String text2 = SaSecureUtil.rsaDecryptByPrivate(privateKey, ciphertext);
// System.out.println("私钥解密后:" + text2);
//
// return SaResult.ok();
// }
// Base64 编码 ---- http://localhost:8081/secure/base64
@RequestMapping("base64")

View File

@@ -4,7 +4,7 @@ import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import cn.dev33.satoken.exception.DisableServiceException;
import cn.dev33.satoken.exception.NotBasicAuthException;
import cn.dev33.satoken.exception.NotHttpBasicAuthException;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
@@ -57,8 +57,8 @@ public class GlobalException {
}
// 拦截Http Basic 校验失败异常
@ExceptionHandler(NotBasicAuthException.class)
public SaResult handlerException(NotBasicAuthException e) {
@ExceptionHandler(NotHttpBasicAuthException.class)
public SaResult handlerException(NotHttpBasicAuthException e) {
e.printStackTrace();
return SaResult.error(e.getMessage());
}

View File

@@ -5,7 +5,7 @@ import cn.dev33.satoken.filter.SaServletFilter;
import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.strategy.SaStrategy;
import cn.dev33.satoken.strategy.SaAnnotationStrategy;
import cn.dev33.satoken.util.SaResult;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -32,14 +32,14 @@ public class SaTokenConfigure implements WebMvcConfigurer {
// 注册 Sa-Token 拦截器打开注解鉴权功能
registry.addInterceptor(new SaInterceptor(handle -> {
// SaManager.getLog().debug("----- 请求path={} 提交token={}", SaHolder.getRequest().getRequestPath(), StpUtil.getTokenValue());
// 指定一条 match 规则
SaRouter
.match("/user/**") // 拦截的 path 列表,可以写多个
.notMatch("/user/doLogin", "/user/doLogin2") // 排除掉的 path 列表,可以写多个
.check(r -> StpUtil.checkLogin()); // 要执行的校验动作,可以写完整的 lambda 表达式
.match("/user/**") // 拦截的 path 列表,可以写多个
.notMatch("/user/doLogin", "/user/doLogin2") // 排除掉的 path 列表,可以写多个
.check(r -> StpUtil.checkLogin()); // 要执行的校验动作,可以写完整的 lambda 表达式
// 权限校验 -- 不同模块认证不同权限
// 权限校验 -- 不同模块认证不同权限
SaRouter.match("/admin/**", r -> StpUtil.checkPermission("admin"));
SaRouter.match("/goods/**", r -> StpUtil.checkPermission("goods"));
SaRouter.match("/orders/**", r -> StpUtil.checkPermission("orders"));
@@ -49,16 +49,16 @@ public class SaTokenConfigure implements WebMvcConfigurer {
// 甚至你可以随意的写一个打印语句
SaRouter.match("/router/print", r -> System.out.println("----啦啦啦----"));
// 写一个完整的 lambda
// 写一个完整的 lambda
SaRouter.match("/router/print2", r -> {
System.out.println("----啦啦啦2----");
// ... 其它代码
// ... 其它代码
});
/*
* 相关路由都定义在 com.pj.cases.use.RouterCheckController 中
* 相关路由都定义在 com.pj.cases.use.RouterCheckController 中
*/
})).addPathPatterns("/**");
}
@@ -117,7 +117,7 @@ public class SaTokenConfigure implements WebMvcConfigurer {
@PostConstruct
public void rewriteSaStrategy() {
// 重写Sa-Token的注解处理器增加注解合并功能
SaStrategy.instance.getAnnotation = (element, annotationClass) -> {
SaAnnotationStrategy.instance.getAnnotation = (element, annotationClass) -> {
return AnnotatedElementUtils.getMergedAnnotation(element, annotationClass);
};
}

View File

@@ -215,6 +215,16 @@ public class StpUserUtil {
return stpLogic.createLoginSession(id, loginModel);
}
/**
* 获取指定账号 id 的登录会话数据,如果获取不到则创建并返回
*
* @param id 账号id建议的类型long | int | String
* @return 返回会话令牌
*/
public static String getOrCreateLoginSession(Object id) {
return stpLogic.getOrCreateLoginSession(id);
}
// --- 注销
/**

View File

@@ -0,0 +1,33 @@
package com.pj.satoken.custom_annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 账号校验:在标注一个方法上时,要求前端必须提交相应的账号密码参数才能访问方法。
*
* @author click33
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE})
public @interface CheckAccount {
/**
* 需要校验的账号
*
* @return /
*/
String name();
/**
* 需要校验的密码
*
* @return /
*/
String pwd();
}

View File

@@ -0,0 +1,18 @@
package com.pj.satoken.custom_annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 登录认证(User版):只有登录之后才能进入该方法
* <p> 可标注在函数、类上(效果等同于标注在此类的所有方法上)
*
* @author click33
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE})
public @interface SaUserCheckLogin {
}

View File

@@ -0,0 +1,50 @@
package com.pj.satoken.custom_annotation;
import cn.dev33.satoken.annotation.SaMode;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 权限认证(User版):必须具有指定权限才能进入该方法
* <p> 可标注在函数、类上(效果等同于标注在此类的所有方法上)
*
* @author click33
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE})
public @interface SaUserCheckPermission {
/**
* 需要校验的权限码
* @return 需要校验的权限码
*/
String [] value() default {};
/**
* 验证模式AND | OR默认AND
* @return 验证模式
*/
SaMode mode() default SaMode.AND;
/**
* 在权限校验不通过时的次要选择,两者只要其一校验成功即可通过校验
*
* <p>
* 例1@SaCheckPermission(value="user-add", orRole="admin")
* 代表本次请求只要具有 user-add权限 或 admin角色 其一即可通过校验。
* </p>
*
* <p>
* 例2 orRole = {"admin", "manager", "staff"},具有三个角色其一即可。 <br>
* 例3 orRole = {"admin, manager, staff"},必须三个角色同时具备。
* </p>
*
* @return /
*/
String[] orRole() default {};
}

View File

@@ -0,0 +1,32 @@
package com.pj.satoken.custom_annotation;
import cn.dev33.satoken.annotation.SaMode;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 角色认证(User版):必须具有指定角色标识才能进入该方法
* <p> 可标注在函数、类上(效果等同于标注在此类的所有方法上)
* @author click33
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE})
public @interface SaUserCheckRole {
/**
* 需要校验的角色标识
* @return 需要校验的角色标识
*/
String [] value() default {};
/**
* 验证模式AND | OR默认AND
* @return 验证模式
*/
SaMode mode() default SaMode.AND;
}

View File

@@ -0,0 +1,28 @@
package com.pj.satoken.custom_annotation;
import cn.dev33.satoken.util.SaTokenConsts;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 二级认证校验(User版):客户端必须完成二级认证之后,才能进入该方法,否则将被抛出异常。
*
* <p> 可标注在方法、类上(效果等同于标注在此类的所有方法上)。
*
* @author click33
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE })
public @interface SaUserCheckSafe {
/**
* 要校验的服务
*
* @return /
*/
String value() default SaTokenConsts.DEFAULT_SAFE_AUTH_SERVICE;
}

View File

@@ -0,0 +1,42 @@
package com.pj.satoken.custom_annotation.handler;
import cn.dev33.satoken.annotation.handler.SaAnnotationHandlerInterface;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.exception.SaTokenException;
import com.pj.satoken.custom_annotation.CheckAccount;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* 注解 CheckAccount 的处理器
*
* @author click33
*
*/
@Component
public class CheckAccountHandler implements SaAnnotationHandlerInterface<CheckAccount> {
// 指定这个处理器要处理哪个注解
@Override
public Class<CheckAccount> getHandlerAnnotationClass() {
return CheckAccount.class;
}
// 每次请求校验注解时,会执行的方法
@Override
public void checkMethod(CheckAccount at, Method method) {
// 获取前端请求提交的参数
String name = SaHolder.getRequest().getParamNotNull("name");
String pwd = SaHolder.getRequest().getParamNotNull("pwd");
// 与注解中指定的值相比较
if(name.equals(at.name()) && pwd.equals(at.pwd()) ) {
// 校验通过,什么也不做
} else {
// 校验不通过,则抛出异常
throw new SaTokenException("账号或密码错误,未通过校验");
}
}
}

View File

@@ -0,0 +1,29 @@
package com.pj.satoken.custom_annotation.handler;
import cn.dev33.satoken.annotation.handler.SaAnnotationHandlerInterface;
import cn.dev33.satoken.annotation.handler.SaCheckLoginHandler;
import com.pj.satoken.StpUserUtil;
import com.pj.satoken.custom_annotation.SaUserCheckLogin;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* 注解 SaUserCheckLogin 的处理器
*
* @author click33
*/
@Component
public class SaUserCheckLoginHandler implements SaAnnotationHandlerInterface<SaUserCheckLogin> {
@Override
public Class<SaUserCheckLogin> getHandlerAnnotationClass() {
return SaUserCheckLogin.class;
}
@Override
public void checkMethod(SaUserCheckLogin at, Method method) {
SaCheckLoginHandler._checkMethod(StpUserUtil.TYPE);
}
}

View File

@@ -0,0 +1,29 @@
package com.pj.satoken.custom_annotation.handler;
import cn.dev33.satoken.annotation.handler.SaAnnotationHandlerInterface;
import cn.dev33.satoken.annotation.handler.SaCheckPermissionHandler;
import com.pj.satoken.StpUserUtil;
import com.pj.satoken.custom_annotation.SaUserCheckPermission;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* 注解 SaUserCheckPermission 的处理器
*
* @author click33
*/
@Component
public class SaUserCheckPermissionHandler implements SaAnnotationHandlerInterface<SaUserCheckPermission> {
@Override
public Class<SaUserCheckPermission> getHandlerAnnotationClass() {
return SaUserCheckPermission.class;
}
@Override
public void checkMethod(SaUserCheckPermission at, Method method) {
SaCheckPermissionHandler._checkMethod(StpUserUtil.TYPE, at.value(), at.mode(), at.orRole());
}
}

View File

@@ -0,0 +1,29 @@
package com.pj.satoken.custom_annotation.handler;
import cn.dev33.satoken.annotation.handler.SaAnnotationHandlerInterface;
import cn.dev33.satoken.annotation.handler.SaCheckRoleHandler;
import com.pj.satoken.StpUserUtil;
import com.pj.satoken.custom_annotation.SaUserCheckRole;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* 注解 SaUserCheckRole 的处理器
*
* @author click33
*/
@Component
public class SaUserCheckRoleHandler implements SaAnnotationHandlerInterface<SaUserCheckRole> {
@Override
public Class<SaUserCheckRole> getHandlerAnnotationClass() {
return SaUserCheckRole.class;
}
@Override
public void checkMethod(SaUserCheckRole at, Method method) {
SaCheckRoleHandler._checkMethod(StpUserUtil.TYPE, at.value(), at.mode());
}
}

View File

@@ -0,0 +1,29 @@
package com.pj.satoken.custom_annotation.handler;
import cn.dev33.satoken.annotation.handler.SaAnnotationHandlerInterface;
import cn.dev33.satoken.annotation.handler.SaCheckSafeHandler;
import com.pj.satoken.StpUserUtil;
import com.pj.satoken.custom_annotation.SaUserCheckSafe;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* 注解 SaUserCheckPermission 的处理器
*
* @author click33
*/
@Component
public class SaUserCheckSafeHandler implements SaAnnotationHandlerInterface<SaUserCheckSafe> {
@Override
public Class<SaUserCheckSafe> getHandlerAnnotationClass() {
return SaUserCheckSafe.class;
}
@Override
public void checkMethod(SaUserCheckSafe at, Method method) {
SaCheckSafeHandler._checkMethod(StpUserUtil.TYPE, at.value());
}
}

View File

@@ -1,13 +1,13 @@
package com.pj.satoken.at;
package com.pj.satoken.merge_annotation;
import cn.dev33.satoken.annotation.SaCheckLogin;
import com.pj.satoken.StpUserUtil;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import cn.dev33.satoken.annotation.SaCheckLogin;
import com.pj.satoken.StpUserUtil;
/**
* 登录认证(User版)只有登录之后才能进入该方法
* <p> 可标注在函数类上效果等同于标注在此类的所有方法上

View File

@@ -1,16 +1,15 @@
package com.pj.satoken.at;
package com.pj.satoken.merge_annotation;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaMode;
import com.pj.satoken.StpUserUtil;
import org.springframework.core.annotation.AliasFor;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.pj.satoken.StpUserUtil;
import org.springframework.core.annotation.AliasFor;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaMode;
/**
* 权限认证(User版)必须具有指定权限才能进入该方法
* <p> 可标注在函数类上效果等同于标注在此类的所有方法上
@@ -35,5 +34,23 @@ public @interface SaUserCheckPermission {
*/
@AliasFor(annotation = SaCheckPermission.class)
SaMode mode() default SaMode.AND;
/**
* 在权限校验不通过时的次要选择两者只要其一校验成功即可通过校验
*
* <p>
* 例1@SaCheckPermission(value="user-add", orRole="admin")
* 代表本次请求只要具有 user-add权限 admin角色 其一即可通过校验
* </p>
*
* <p>
* 例2 orRole = {"admin", "manager", "staff"}具有三个角色其一即可 <br>
* 例3 orRole = {"admin, manager, staff"}必须三个角色同时具备
* </p>
*
* @return /
*/
@AliasFor(annotation = SaCheckPermission.class)
String[] orRole() default {};
}

View File

@@ -1,16 +1,15 @@
package com.pj.satoken.at;
package com.pj.satoken.merge_annotation;
import cn.dev33.satoken.annotation.SaCheckRole;
import cn.dev33.satoken.annotation.SaMode;
import com.pj.satoken.StpUserUtil;
import org.springframework.core.annotation.AliasFor;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.pj.satoken.StpUserUtil;
import org.springframework.core.annotation.AliasFor;
import cn.dev33.satoken.annotation.SaCheckRole;
import cn.dev33.satoken.annotation.SaMode;
/**
* 角色认证(User版)必须具有指定角色标识才能进入该方法
* <p> 可标注在函数类上效果等同于标注在此类的所有方法上

View File

@@ -0,0 +1,31 @@
package com.pj.satoken.merge_annotation;
import cn.dev33.satoken.annotation.SaCheckSafe;
import cn.dev33.satoken.util.SaTokenConsts;
import com.pj.satoken.StpUserUtil;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 二级认证校验(User版):客户端必须完成二级认证之后,才能进入该方法,否则将被抛出异常。
*
* <p> 可标注在方法、类上(效果等同于标注在此类的所有方法上)。
*
* @author click33
*/
@SaCheckSafe(type = StpUserUtil.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE })
public @interface SaUserCheckSafe {
/**
* 要校验的服务
*
* @return /
*/
String value() default SaTokenConsts.DEFAULT_SAFE_AUTH_SERVICE;
}

View File

@@ -17,7 +17,7 @@
<properties>
<java.version>1.8</java.version>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
<sa-token.version>1.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
<dubbo.version>2.7.21</dubbo.version>
<nacos.version>1.4.2</nacos.version>
</properties>

View File

@@ -17,7 +17,7 @@
<properties>
<java.version>1.8</java.version>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
<sa-token.version>1.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
<dubbo.version>2.7.21</dubbo.version>
<nacos.version>1.4.2</nacos.version>
</properties>

View File

@@ -17,7 +17,7 @@
<properties>
<java.version>1.8</java.version>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
<sa-token.version>1.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
<dubbo.version>3.2.2</dubbo.version>
<nacos.version>2.2.2</nacos.version>
</properties>

View File

@@ -17,7 +17,7 @@
<properties>
<java.version>1.8</java.version>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
<sa-token.version>1.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
<dubbo.version>3.2.2</dubbo.version>
<nacos.version>2.2.2</nacos.version>
</properties>

View File

@@ -27,7 +27,7 @@
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<lombok.version>1.18.10</lombok.version>
<sa-token.version>1.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
</properties>
<dependencies>

View File

@@ -17,7 +17,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
</properties>
<dependencies>

View File

@@ -16,7 +16,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
</properties>
<dependencies>

View File

@@ -17,7 +17,7 @@
<java.version>1.8</java.version>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
<!-- 定义 Sa-Token 版本号 -->
<sa-token.version>1.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
</properties>
<dependencies>

View File

@@ -1,17 +1,17 @@
package com.pj.oauth2;
import javax.servlet.http.HttpServletRequest;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import com.ejlchina.okhttps.OkHttps;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.pj.utils.SoMap;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
import com.ejlchina.okhttps.OkHttps;
import com.pj.utils.SoMap;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import javax.servlet.http.HttpServletRequest;
/**
* Sa-OAuth2 Client端 控制器
@@ -21,10 +21,10 @@ import cn.dev33.satoken.util.SaResult;
public class SaOAuthClientController {
// 相关参数配置
private String clientId = "1001"; // 应用id
private String clientSecret = "aaaa-bbbb-cccc-dddd-eeee"; // 应用秘钥
private String serverUrl = "http://sa-oauth-server.com:8001"; // 服务端接口
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) {
@@ -34,7 +34,7 @@ public class SaOAuthClientController {
// 根据Code码进行登录获取 Access-Token 和 openid
@RequestMapping("/codeLogin")
public SaResult codeLogin(String code) {
public SaResult codeLogin(String code) throws JsonProcessingException {
// 调用Server端接口获取 Access-Token 以及其他信息
String str = OkHttps.sync(serverUrl + "/oauth2/token")
.addBodyPara("grant_type", "authorization_code")
@@ -45,26 +45,25 @@ public class SaOAuthClientController {
.getBody()
.toString();
SoMap so = SoMap.getSoMap().setJsonString(str);
System.out.println("返回结果: " + so);
System.out.println("返回结果: " + new ObjectMapper().writeValueAsString(so));
// code不等于200 代表请求失败
if(so.getInt("code") != 200) {
return SaResult.error(so.getString("msg"));
}
// 根据openid获取其对应的userId
SoMap data = so.getMap("data");
long uid = getUserIdByOpenid(data.getString("openid"));
data.set("uid", uid);
// 根据openid获取其对应的userId
long uid = getUserIdByOpenid(so.getString("openid"));
so.set("uid", uid);
// 返回相关参数
StpUtil.login(uid);
return SaResult.data(data);
return SaResult.data(so);
}
// 根据 Refresh-Token 去刷新 Access-Token
@RequestMapping("/refresh")
public SaResult refresh(String refreshToken) {
public SaResult refresh(String refreshToken) throws JsonProcessingException {
// 调用Server端接口通过 Refresh-Token 刷新出一个新的 Access-Token
String str = OkHttps.sync(serverUrl + "/oauth2/refresh")
.addBodyPara("grant_type", "refresh_token")
@@ -75,21 +74,20 @@ public class SaOAuthClientController {
.getBody()
.toString();
SoMap so = SoMap.getSoMap().setJsonString(str);
System.out.println("返回结果: " + so);
System.out.println("返回结果: " + new ObjectMapper().writeValueAsString(so));
// code不等于200 代表请求失败
if(so.getInt("code") != 200) {
return SaResult.error(so.getString("msg"));
}
// 返回相关参数 (data=新的Access-Token )
SoMap data = so.getMap("data");
return SaResult.data(data);
// 返回相关参数
return SaResult.data(so);
}
// 模式三:密码式-授权登录
@RequestMapping("/passwordLogin")
public SaResult passwordLogin(String username, String password) {
public SaResult passwordLogin(String username, String password) throws JsonProcessingException {
// 模式三:密码式-授权登录
String str = OkHttps.sync(serverUrl + "/oauth2/token")
.addBodyPara("grant_type", "password")
@@ -101,26 +99,25 @@ public class SaOAuthClientController {
.getBody()
.toString();
SoMap so = SoMap.getSoMap().setJsonString(str);
System.out.println("返回结果: " + so);
System.out.println("返回结果: " + new ObjectMapper().writeValueAsString(so));
// code不等于200 代表请求失败
if(so.getInt("code") != 200) {
return SaResult.error(so.getString("msg"));
}
// 根据openid获取其对应的userId
SoMap data = so.getMap("data");
long uid = getUserIdByOpenid(data.getString("openid"));
data.set("uid", uid);
// 根据openid获取其对应的userId
long uid = getUserIdByOpenid(so.getString("openid"));
so.set("uid", uid);
// 返回相关参数
StpUtil.login(uid);
return SaResult.data(data);
return SaResult.data(so);
}
// 模式四:获取应用的 Client-Token
@RequestMapping("/clientToken")
public SaResult clientToken() {
public SaResult clientToken() throws JsonProcessingException {
// 调用Server端接口
String str = OkHttps.sync(serverUrl + "/oauth2/client_token")
.addBodyPara("grant_type", "client_credentials")
@@ -130,16 +127,15 @@ public class SaOAuthClientController {
.getBody()
.toString();
SoMap so = SoMap.getSoMap().setJsonString(str);
System.out.println("返回结果: " + so);
System.out.println("返回结果: " + new ObjectMapper().writeValueAsString(so));
// code不等于200 代表请求失败
if(so.getInt("code") != 200) {
return SaResult.error(so.getString("msg"));
}
// 返回相关参数 (data=新的Client-Token )
SoMap data = so.getMap("data");
return SaResult.data(data);
// 返回相关参数
return SaResult.data(so);
}
// 注销登录
@@ -151,7 +147,7 @@ public class SaOAuthClientController {
// 根据 Access-Token 置换相关的资源: 获取账号昵称、头像、性别等信息
@RequestMapping("/getUserinfo")
public SaResult getUserinfo(String accessToken) {
public SaResult getUserinfo(String accessToken) throws JsonProcessingException {
// 调用Server端接口查询开放的资源
String str = OkHttps.sync(serverUrl + "/oauth2/userinfo")
.addBodyPara("access_token", accessToken)
@@ -159,16 +155,15 @@ public class SaOAuthClientController {
.getBody()
.toString();
SoMap so = SoMap.getSoMap().setJsonString(str);
System.out.println("返回结果: " + so);
System.out.println("返回结果: " + new ObjectMapper().writeValueAsString(so));
// code不等于200 代表请求失败
if(so.getInt("code") != 200) {
return SaResult.error(so.getString("msg"));
}
// 返回相关参数 (data=获取到的资源 )
SoMap data = so.getMap("data");
return SaResult.data(data);
// 返回相关参数 (data=获取到的资源 )
return SaResult.data(so);
}
// 全局异常拦截

View File

@@ -42,33 +42,33 @@
<h3>模式一授权码Authorization Code</h3>
<p class="pst">授权码OAuth2.0标准授权流程,先 (重定向) 获取Code授权码再 (Rest API) 获取 Access-Token 和 Openid </p>
<a href="http://sa-oauth-server.com:8001/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/">
<a href="http://sa-oauth-server.com:8000/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/">
<button>点我开始授权登录(静默授权)</button>
</a>
<span class="ps">当请求链接不包含scope权限时将无需用户手动确认做到静默授权当然此时我们也只能获取openid</span>
<code>http://sa-oauth-server.com:8001/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/</code>
<span class="ps">当请求链接不包含 scope 权限,或请求的 scope 近期已授权时,将无需用户手动确认,做到静默授权</span>
<code>http://sa-oauth-server.com:8000/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/</code>
<a href="http://sa-oauth-server.com:8001/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/&scope=userinfo">
<a href="http://sa-oauth-server.com:8000/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/&scope=openid,userinfo">
<button>授权登录(显式授权)</button>
</a>
<span class="ps">当请求链接包含具体的scope权限时将需要用户手动确认此时我们除了openid以外还可以获取更多的资源</span>
<code>http://sa-oauth-server.com:8001/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/&scope=userinfo</code>
<span class="ps">当请求链接包含具体的 scope 权限时,将需要用户手动确认,此时 OAuth-Server 会返回更多的数据</span>
<code>http://sa-oauth-server.com:8000/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/&scope=openid,userinfo</code>
<button onclick="refreshToken()">刷新令牌</button>
<span class="ps">我们可以拿着 Refresh-Token 去刷新我们的 Access-Token每次刷新后旧Token将作废</span>
<code>http://sa-oauth-server.com:8001/oauth2/refresh?grant_type=refresh_token&client_id={value}&client_secret={value}&refresh_token={value}</code>
<code>http://sa-oauth-server.com:8000/oauth2/refresh?grant_type=refresh_token&client_id={value}&client_secret={value}&refresh_token={value}</code>
<button onclick="getUserinfo()">获取账号信息</button>
<span class="ps">使用 Access-Token 置换资源: 获取账号昵称、头像、性别等信息 (Access-Token具备userinfo权限时才可以获取成功) </span>
<code>http://sa-oauth-server.com:8001/oauth2/userinfo?access_token={value}</code>
<code>http://sa-oauth-server.com:8000/oauth2/userinfo?access_token={value}</code>
<br>
<h3>模式二隐藏式Implicit</h3>
<a href="http://sa-oauth-server.com:8001/oauth2/authorize?response_type=token&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/&scope=userinfo">
<a href="http://sa-oauth-server.com:8000/oauth2/authorize?response_type=token&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/&scope=userinfo">
<button>隐藏式</button>
</a>
<span class="ps">越过授权码的步骤直接返回token到前端页面 格式http//:domain.com#token=xxxx-xxxx </span>
<code>http://sa-oauth-server.com:8001/oauth2/authorize?response_type=token&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/&scope=userinfo</code>
<code>http://sa-oauth-server.com:8000/oauth2/authorize?response_type=token&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/&scope=userinfo</code>
<br>
<h3>模式三密码式Password</h3>
@@ -76,18 +76,18 @@
账号:<input name="username">
密码:<input name="password">
<button onclick="passwordLogin()">登录</button>
<code>http://sa-oauth-server.com:8001/oauth2/token?grant_type=password&client_id={value}&client_secret={value}&username={value}&password={value}</code>
<code>http://sa-oauth-server.com:8000/oauth2/token?grant_type=password&client_id={value}&client_secret={value}&username={value}&password={value}</code>
<br>
<h3>模式四凭证式Client Credentials</h3>
<p class="pst">以上三种模式获取的都是用户的 Access-Token代表用户对第三方应用的授权在OAuth2.0中还有一种针对 Client级别的授权
Client-Token代表应用自身的资源授权</p>
<p class="pst">Client-Token具有延迟作废特性在每次获取最新Client-Token的时候旧Client-Token不会立即过期而是作为Past-Token再次
<p class="pst">Client-Token具有延迟作废特性在每次获取最新Client-Token的时候旧Client-Token不会立即过期而是作为Lower-Client-Token再次
储存起来资源请求方只要携带其中之一便可通过Token校验这种特性保证了在大量并发请求时不会出现“新旧Token交替造成的授权失效”
保证了服务的高可用</p>
<button onclick="getClientToken()">获取应用Client-Token</button>
<code>http://sa-oauth-server.com:8001/oauth2/client_token?grant_type=client_credentials&client_id={value}&client_secret={value}</code>
<code>http://sa-oauth-server.com:8000/oauth2/client_token?grant_type=client_credentials&client_id={value}&client_secret={value}</code>
<br><br>
<span>更多资料请参考 Sa-Token 官方文档地址:</span>

View File

@@ -17,7 +17,7 @@
<java.version>1.8</java.version>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
<!-- 定义 Sa-Token 版本号 -->
<sa-token.version>1.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
</properties>
<dependencies>
@@ -58,13 +58,20 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- 热刷新 -->
<!-- sa-token-jwt 签发 OIDC id_token 令牌 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-jwt</artifactId>
<version>${sa-token.version}</version>
</dependency>
<!-- 热刷新 -->
<!--<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>provided</scope>
</dependency>
</dependency>-->
<!-- ConfigurationProperties -->
<dependency>

View File

@@ -1,18 +1,20 @@
package com.pj;
import cn.dev33.satoken.oauth2.SaOAuth2Manager;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 启动Sa-OAuth2 Server端
* @author click33
* 启动Sa-OAuth2 Server端
* @author click33
*/
@SpringBootApplication
@SpringBootApplication
public class SaOAuth2ServerApplication {
public static void main(String[] args) {
SpringApplication.run(SaOAuth2ServerApplication.class, args);
System.out.println("\nSa-Token-OAuth Server端启动成功");
System.out.println("\nSa-Token-OAuth2 Server端启动成功,配置如下:");
System.out.println(SaOAuth2Manager.getServerConfig());
}
}

View File

@@ -0,0 +1,46 @@
package com.pj.oauth2;
import cn.dev33.satoken.oauth2.consts.GrantType;
import cn.dev33.satoken.oauth2.data.loader.SaOAuth2DataLoader;
import cn.dev33.satoken.oauth2.data.model.loader.SaClientModel;
import org.springframework.stereotype.Component;
/**
* Sa-Token OAuth2自定义数据加载器
*
* @author click33
*/
@Component
public class SaOAuth2DataLoaderImpl implements SaOAuth2DataLoader {
// 根据 clientId 获取 Client 信息
@Override
public SaClientModel getClientModel(String clientId) {
// 此为模拟数据,真实环境需要从数据库查询
if("1001".equals(clientId)) {
return new SaClientModel()
.setClientId("1001") // client id
.setClientSecret("aaaa-bbbb-cccc-dddd-eeee") // client 秘钥
.addAllowRedirectUris("*") // 所有允许授权的 url
.addContractScopes("openid", "userid", "userinfo", "oidc") // 所有签约的权限
.addAllowGrantTypes( // 所有允许的授权模式
GrantType.authorization_code, // 授权码式
GrantType.implicit, // 隐式式
GrantType.refresh_token, // 刷新令牌
GrantType.password, // 密码式
GrantType.client_credentials, // 客户端模式
"phone_code" // 自定义授权模式 手机号验证码登录
)
;
}
return null;
}
// 根据 clientId 和 loginId 获取 openid
@Override
public String getOpenid(String clientId, Object loginId) {
// 此处使用框架默认算法生成 openid真实环境建议改为从数据库查询
return SaOAuth2DataLoader.super.getOpenid(clientId, loginId);
}
}

View File

@@ -1,92 +1,86 @@
package com.pj.oauth2;
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.template.SaOAuth2Util;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.oauth2.config.SaOAuth2Config;
import cn.dev33.satoken.oauth2.logic.SaOAuth2Handle;
import cn.dev33.satoken.oauth2.logic.SaOAuth2Util;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
/**
* Sa-OAuth2 Server端 控制器
* Sa-Token-OAuth2 Server端 Controller
*
* @author click33
*
*/
@RestController
public class SaOAuth2ServerController {
// 处理所有OAuth相关请求
// OAuth2-Server 端:处理所有 OAuth2 相关请求
@RequestMapping("/oauth2/*")
public Object request() {
System.out.println("------- 进入请求: " + SaHolder.getRequest().getUrl());
return SaOAuth2Handle.serverRequest();
}
// Sa-OAuth2 定制化配置
@Autowired
public void setSaOAuth2Config(SaOAuth2Config cfg) {
cfg.
// 未登录的视图
setNotLoginView(()->{
return new ModelAndView("login.html");
}).
// 登录处理函数
setDoLoginHandle((name, pwd) -> {
if("sa".equals(name) && "123456".equals(pwd)) {
StpUtil.login(10001);
return SaResult.ok();
}
return SaResult.error("账号名或密码错误");
}).
// 授权确认视图
setConfirmView((clientId, scope)->{
Map<String, Object> map = new HashMap<>();
map.put("clientId", clientId);
map.put("scope", scope);
return new ModelAndView("confirm.html", map);
})
;
return SaOAuth2ServerProcessor.instance.dister();
}
// 全局异常拦截
@ExceptionHandler
public SaResult handlerException(Exception e) {
e.printStackTrace();
return SaResult.error(e.getMessage());
// Sa-Token OAuth2 定制化配置
@Autowired
public void configOAuth2Server(SaOAuth2ServerConfig oauth2Server) {
// 未登录的视图
oauth2Server.notLoginView = ()->{
return new ModelAndView("login.html");
};
// 登录处理函数
oauth2Server.doLoginHandle = (name, pwd) -> {
if("sa".equals(name) && "123456".equals(pwd)) {
StpUtil.login(10001);
return SaResult.ok();
}
return SaResult.error("账号名或密码错误");
};
// 授权确认视图
oauth2Server.confirmView = (clientId, scopes)->{
Map<String, Object> map = new HashMap<>();
map.put("clientId", clientId);
map.put("scope", scopes);
return new ModelAndView("confirm.html", map);
};
}
// ---------- 开放相关资源接口: Client端根据 Access-Token ,置换相关资源 ------------
// 获取Userinfo信息昵称、头像、性别等等
// 获取 userinfo 信息:昵称、头像、性别等等
@RequestMapping("/oauth2/userinfo")
public SaResult userinfo() {
// 获取 Access-Token 对应的账号id
String accessToken = SaHolder.getRequest().getParamNotNull("access_token");
// 获取 Access-Token 对应的账号id
String accessToken = SaOAuth2Manager.getDataResolver().readAccessToken(SaHolder.getRequest());
Object loginId = SaOAuth2Util.getLoginIdByAccessToken(accessToken);
System.out.println("-------- 此Access-Token对应的账号id: " + loginId);
// 校验 Access-Token 是否具有权限: userinfo
SaOAuth2Util.checkScope(accessToken, "userinfo");
SaOAuth2Util.checkAccessTokenScope(accessToken, "userinfo");
// 模拟账号信息 (真实环境需要查询数据库获取信息)
Map<String, Object> map = new LinkedHashMap<String, Object>();
map.put("nickname", "shengzhang_");
Map<String, Object> map = new LinkedHashMap<>();
// map.put("userId", loginId); 一般原则下oauth2-server 不能把 userId 返回给 oauth2-client
map.put("nickname", "林小林");
map.put("avatar", "http://xxx.com/1.jpg");
map.put("age", "18");
map.put("sex", "");
map.put("address", "山东省 青岛市 城阳区");
return SaResult.data(map);
return SaResult.ok().setMap(map);
}
}

View File

@@ -1,39 +0,0 @@
package com.pj.oauth2;
import org.springframework.stereotype.Component;
import cn.dev33.satoken.oauth2.logic.SaOAuth2Template;
import cn.dev33.satoken.oauth2.model.SaClientModel;
/**
* Sa-Token OAuth2.0 整合实现
* @author click33
*/
@Component
public class SaOAuth2TemplateImpl extends SaOAuth2Template {
// 根据 id 获取 Client 信息
@Override
public SaClientModel getClientModel(String clientId) {
// 此为模拟数据,真实环境需要从数据库查询
if("1001".equals(clientId)) {
return new SaClientModel()
.setClientId("1001")
.setClientSecret("aaaa-bbbb-cccc-dddd-eeee")
.setAllowUrl("*")
.setContractScope("userinfo")
.setIsAutoMode(true);
}
return null;
}
// 根据ClientId 和 LoginId 获取openid
@Override
public String getOpenid(String clientId, Object loginId) {
// 此为模拟数据,真实环境需要从数据库查询
return "gr_SwoIN0MC1ewxHX_vfCW3BothWDZMMtx__";
}
// -------------- 其它需要重写的函数
}

View File

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

View File

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

View File

@@ -0,0 +1,26 @@
//package com.pj.oauth2.custom;
//
//import cn.dev33.satoken.SaManager;
//import cn.dev33.satoken.util.SaFoxUtil;
//import cn.dev33.satoken.util.SaResult;
//import org.springframework.web.bind.annotation.RequestMapping;
//import org.springframework.web.bind.annotation.RestController;
//
///**
// * 自定义手机登录接口
// *
// * @author click33
// * @since 2024/8/23
// */
//@RestController
//public class PhoneLoginController {
//
// @RequestMapping("/oauth2/sendPhoneCode")
// public SaResult sendCode(String phone) {
// String code = SaFoxUtil.getRandomNumber(100000, 999999) + "";
// SaManager.getSaTokenDao().set("phone_code:" + phone, code, 60 * 5);
// System.out.println("手机号:" + phone + ",验证码:" + code + ",已发送成功");
// return SaResult.ok("验证码发送成功");
// }
//
//}

View File

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

View File

@@ -0,0 +1,22 @@
package com.pj.satoken;
import cn.dev33.satoken.util.SaResult;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* 全局异常处理
* @author click33
*
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
// 全局异常拦截
@ExceptionHandler
public SaResult handlerException(Exception e) {
e.printStackTrace();
return SaResult.error(e.getMessage());
}
}

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

@@ -1,19 +1,31 @@
server:
port: 8001
server:
port: 8000
# sa-token配置
sa-token:
# token名称 (同时也是cookie名称)
token-name: satoken-server
# OAuth2.0 配置
oauth2:
is-code: true
is-implicit: true
is-password: true
is-client: true
spring:
# redis配置
sa-token:
# token名称 (同时也是 Cookie 名称)
token-name: satoken
# 是否打印操作日志
is-log: true
# jwt 秘钥
jwt-secret-key: saxsaxsaxsax
# OAuth2.0 配置
oauth2-server:
# 是否全局开启授权码模式
enable-authorization-code: true
# 是否全局开启 Implicit 模式
enable-implicit: true
# 是否全局开启密码模式
enable-password: true
# 是否全局开启客户端模式
enable-client-credentials: true
# 定义哪些 scope 是高级权限,多个用逗号隔开
# higher-scope: openid,userid
# 定义哪些 scope 是低级权限,多个用逗号隔开
# lower-scope: userinfo
spring:
# redis配置
redis:
# Redis数据库索引默认为0
database: 1
@@ -22,7 +34,7 @@ spring:
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码默认为空
# password:
# password:
# 连接超时时间(毫秒)
timeout: 1000ms
lettuce:
@@ -35,6 +47,5 @@ spring:
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0

View File

@@ -33,20 +33,31 @@
console.log('-----------');
$.ajax({
url: '/oauth2/doConfirm',
method: "POST",
data: {
client_id: getParam('client_id'),
scope: getParam('scope')
scope: getParam('scope'),
// 以下四个参数必须一起出现
build_redirect_uri: true,
response_type: getParam('response_type'),
redirect_uri: getParam('redirect_uri'),
state: getParam('state'),
},
dataType: 'json',
success: function(res) {
if(res.code == 200) {
console.log('res', res);
if(res.code === 200) {
layer.msg('授权成功!');
setTimeout(function() {
location.reload(true);
if (res.redirect_uri) {
location.href = res.redirect_uri;
} else {
location.reload();
}
}, 800);
} else {
// 重定向至授权失败URL
layer.alert('授权失败');
layer.alert('授权失败:' + res.msg);
}
},
error: function(e) {

View File

@@ -16,7 +16,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
</properties>
<dependencies>

View File

@@ -17,7 +17,7 @@
</parent>
<properties>
<sa-token.version>1.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
</properties>
<dependencies>

View File

@@ -16,7 +16,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>

View File

@@ -10,13 +10,16 @@
<parent>
<groupId>org.noear</groupId>
<artifactId>solon-parent</artifactId>
<version>2.7.0</version>
<version>2.9.1</version>
<relativePath/>
</parent>
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.38.0</sa-token.version>
<java.version>17</java.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.traget>17</maven.compiler.traget>
<sa-token.version>1.39.0</sa-token.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>

View File

@@ -0,0 +1,33 @@
package com.pj.satoken.custom_annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 账号校验:在标注一个方法上时,要求前端必须提交相应的账号密码参数才能访问方法。
*
* @author click33
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE})
public @interface CheckAccount {
/**
* 需要校验的账号
*
* @return /
*/
String name();
/**
* 需要校验的密码
*
* @return /
*/
String pwd();
}

View File

@@ -0,0 +1,42 @@
package com.pj.satoken.custom_annotation.handler;
import cn.dev33.satoken.annotation.handler.SaAnnotationHandlerInterface;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.exception.SaTokenException;
import com.pj.satoken.custom_annotation.CheckAccount;
import org.noear.solon.annotation.Component;
import java.lang.reflect.Method;
/**
* 注解 CheckAccount 的处理器
*
* @author click33
*
*/
@Component
public class CheckAccountHandler implements SaAnnotationHandlerInterface<CheckAccount> {
// 指定这个处理器要处理哪个注解
@Override
public Class<CheckAccount> getHandlerAnnotationClass() {
return CheckAccount.class;
}
// 每次请求校验注解时,会执行的方法
@Override
public void checkMethod(CheckAccount at, Method method) {
// 获取前端请求提交的参数
String name = SaHolder.getRequest().getParamNotNull("name");
String pwd = SaHolder.getRequest().getParamNotNull("pwd");
// 与注解中指定的值相比较
if(name.equals(at.name()) && pwd.equals(at.pwd()) ) {
// 校验通过,什么也不做
} else {
// 校验不通过,则抛出异常
throw new SaTokenException("账号或密码错误,未通过校验");
}
}
}

View File

@@ -17,7 +17,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
</properties>
<dependencies>

View File

@@ -17,7 +17,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
</properties>
<dependencies>

View File

@@ -17,7 +17,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
</properties>
<dependencies>

View File

@@ -16,7 +16,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
</properties>
<dependencies>

View File

@@ -27,7 +27,7 @@
<!--<spring.version>4.2.5.RELEASE</spring.version>-->
<spring.version>5.3.7</spring.version>
<jackson.version>2.16.1</jackson.version>
<sa-token.version>1.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
</properties>
<dependencies>

View File

@@ -16,7 +16,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
<solon.version>2.7.0</solon.version>
</properties>

View File

@@ -16,7 +16,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
</properties>
<dependencies>

View File

@@ -16,7 +16,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
</properties>
<dependencies>

View File

@@ -16,7 +16,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
</properties>
<dependencies>

View File

@@ -50,7 +50,7 @@ var pData = {
sa.ajax("/sso/getRedirectUrl", pData, function(res) {
if(res.code == 200) {
// 已登录并且redirect地址有效开始跳转
location.href = decodeURIComponent(res.data);
location.href = res.data;
} else if(res.code == 401) {
console.log('未登录');
} else {

View File

@@ -16,7 +16,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
</properties>
<dependencies>

Some files were not shown because too many files have changed in this diff Show More