├── sa-token-doc └── doc │ ├── logo.png │ ├── logo-150.png │ ├── static │ ├── kickout.png │ ├── login-view.png │ └── session-model.png │ ├── fun │ ├── timeline.md │ ├── tech-stack.md │ ├── session-model.md │ ├── token-info.md │ ├── token-timeout.md │ └── not-login-scene.md │ ├── more │ ├── tj-gzh-hz.md │ ├── link.md │ └── common-questions.md │ ├── plugin │ └── aop-at.md │ ├── use │ ├── token-prefix.md │ ├── mutex-login.md │ ├── search-session.md │ ├── kick.md │ ├── mock-person.md │ ├── global-listener.md │ ├── login-auth.md │ ├── dao-extend.md │ ├── at-check.md │ ├── remember-me.md │ ├── not-cookie.md │ ├── many-account.md │ ├── password-secure.md │ ├── token-style.md │ └── global-filter.md │ ├── senior │ └── dcs.md │ └── _sidebar.md ├── sa-token-plugin ├── .gitignore ├── sa-token-dao-redis │ ├── src │ │ └── main │ │ │ └── resources │ │ │ └── META-INF │ │ │ └── spring.factories │ ├── .gitignore │ └── pom.xml ├── sa-token-quick-login │ ├── src │ │ └── main │ │ │ ├── resources │ │ │ ├── META-INF │ │ │ │ └── spring.factories │ │ │ ├── static │ │ │ │ └── sa-res │ │ │ │ │ ├── layer │ │ │ │ │ └── theme │ │ │ │ │ │ └── default │ │ │ │ │ │ ├── icon.png │ │ │ │ │ │ ├── icon-ext.png │ │ │ │ │ │ ├── loading-0.gif │ │ │ │ │ │ ├── loading-1.gif │ │ │ │ │ │ └── loading-2.gif │ │ │ │ │ ├── login.js │ │ │ │ │ └── login.css │ │ │ └── templates │ │ │ │ └── sa-login.html │ │ │ └── java │ │ │ └── cn │ │ │ └── dev33 │ │ │ └── satoken │ │ │ └── quick │ │ │ ├── SaQuickManager.java │ │ │ ├── config │ │ │ └── SaQuickConfig.java │ │ │ ├── web │ │ │ └── SaQuickController.java │ │ │ └── SaQuickBean.java │ ├── .gitignore │ └── pom.xml ├── sa-token-spring-aop │ ├── src │ │ └── main │ │ │ ├── resources │ │ │ └── META-INF │ │ │ │ └── spring.factories │ │ │ └── java │ │ │ └── cn │ │ │ └── dev33 │ │ │ └── satoken │ │ │ └── aop │ │ │ └── SaCheckAspect.java │ ├── .gitignore │ └── pom.xml ├── sa-token-dao-redis-jackson │ ├── src │ │ └── main │ │ │ └── resources │ │ │ └── META-INF │ │ │ └── spring.factories │ ├── .gitignore │ └── pom.xml ├── sa-token-oauth2 │ ├── .gitignore │ ├── src │ │ └── main │ │ │ └── java │ │ │ └── cn │ │ │ └── dev33 │ │ │ └── satoken │ │ │ └── oauth2 │ │ │ ├── logic │ │ │ └── SaOAuth2InterfaceDefaultImpl.java │ │ │ ├── util │ │ │ ├── SaOAuth2Consts.java │ │ │ └── SaOAuth2InsideUtil.java │ │ │ ├── model │ │ │ └── ScopeModel.java │ │ │ ├── SaOAuth2Manager.java │ │ │ └── config │ │ │ └── SaOAuth2Config.java │ ├── README.md │ └── pom.xml └── pom.xml ├── sa-token-starter ├── sa-token-spring-boot-starter │ ├── src │ │ └── main │ │ │ ├── java │ │ │ └── cn │ │ │ │ └── dev33 │ │ │ │ └── satoken │ │ │ │ ├── package-info.java │ │ │ │ ├── spring │ │ │ │ ├── SaPathMatcherHolder.java │ │ │ │ ├── SpringMVCUtil.java │ │ │ │ ├── SaTokenContextForSpring.java │ │ │ │ └── SaTokenSpringAutowired.java │ │ │ │ └── interceptor │ │ │ │ ├── SaAnnotationInterceptor.java │ │ │ │ └── SaRouteInterceptor.java │ │ │ └── resources │ │ │ └── META-INF │ │ │ └── spring.factories │ ├── .gitignore │ └── pom.xml ├── .gitignore ├── sa-token-servlet │ ├── src │ │ └── main │ │ │ └── java │ │ │ └── cn │ │ │ └── dev33 │ │ │ └── satoken │ │ │ └── servlet │ │ │ ├── package-info.java │ │ │ └── model │ │ │ ├── SaStorageForServlet.java │ │ │ ├── SaResponseForServlet.java │ │ │ └── SaRequestForServlet.java │ ├── .gitignore │ └── pom.xml ├── sa-token-reactor-spring-boot-starter │ ├── src │ │ └── main │ │ │ ├── java │ │ │ └── cn │ │ │ │ └── dev33 │ │ │ │ └── satoken │ │ │ │ └── reactor │ │ │ │ ├── package-info.java │ │ │ │ ├── spring │ │ │ │ └── SaPathMatcherHolder.java │ │ │ │ ├── context │ │ │ │ ├── SaReactorHolder.java │ │ │ │ └── SaReactorSyncHolder.java │ │ │ │ └── model │ │ │ │ ├── SaStorageForReactor.java │ │ │ │ ├── SaRequestForReactor.java │ │ │ │ └── SaResponseForReactor.java │ │ │ └── resources │ │ │ └── META-INF │ │ │ └── spring.factories │ ├── .gitignore │ └── pom.xml └── pom.xml ├── sa-token-core ├── .gitignore ├── src │ └── main │ │ └── java │ │ └── cn │ │ └── dev33 │ │ └── satoken │ │ ├── context │ │ ├── model │ │ │ ├── package-info.java │ │ │ ├── SaCookie.java │ │ │ ├── SaStorage.java │ │ │ ├── SaRequest.java │ │ │ └── SaResponse.java │ │ ├── SaTokenContextForThreadLocal.java │ │ ├── SaTokenContext.java │ │ ├── SaHolder.java │ │ └── SaTokenContextDefaultImpl.java │ │ ├── annotation │ │ ├── SaMode.java │ │ ├── SaCheckLogin.java │ │ ├── SaCheckRole.java │ │ └── SaCheckPermission.java │ │ ├── fun │ │ ├── SaFunction.java │ │ └── IsRunFunction.java │ │ ├── filter │ │ ├── SaFilterAuthStrategy.java │ │ └── SaFilterErrorStrategy.java │ │ ├── router │ │ └── SaRouteFunction.java │ │ ├── stp │ │ ├── StpInterfaceDefaultImpl.java │ │ ├── StpInterface.java │ │ └── SaLoginModel.java │ │ ├── exception │ │ ├── SaTokenException.java │ │ ├── NotRoleException.java │ │ ├── NotPermissionException.java │ │ └── DisableLoginException.java │ │ ├── action │ │ └── SaTokenAction.java │ │ ├── session │ │ ├── TokenSign.java │ │ └── SaSessionCustomUtil.java │ │ ├── secure │ │ └── SaBase64Util.java │ │ ├── listener │ │ ├── SaTokenListener.java │ │ └── SaTokenListenerDefaultImpl.java │ │ └── util │ │ └── SaTokenConsts.java └── pom.xml ├── .gitignore ├── sa-token-demo ├── sa-token-demo-jwt │ ├── .gitignore │ ├── src │ │ └── main │ │ │ ├── java │ │ │ └── com │ │ │ │ └── pj │ │ │ │ ├── SaTokenJwtDemoApplication.java │ │ │ │ └── test │ │ │ │ └── TestJwtController.java │ │ │ └── resources │ │ │ └── application.yml │ └── pom.xml ├── sa-token-demo-webflux │ ├── .gitignore │ ├── src │ │ └── main │ │ │ ├── java │ │ │ └── com │ │ │ │ └── pj │ │ │ │ ├── satoken │ │ │ │ ├── StpInterfaceImpl.java │ │ │ │ └── SaTokenConfigure.java │ │ │ │ ├── test │ │ │ │ ├── DefineRoutes.java │ │ │ │ ├── GlobalException.java │ │ │ │ └── TestController.java │ │ │ │ └── SaTokenWebfluxDemoApplication.java │ │ │ └── resources │ │ │ └── application.yml │ └── pom.xml ├── sa-token-demo-quick-login │ ├── .gitignore │ ├── src │ │ └── main │ │ │ ├── java │ │ │ └── com │ │ │ │ └── pj │ │ │ │ ├── SaTokenQuickDemoApplication.java │ │ │ │ ├── test │ │ │ │ └── TestController.java │ │ │ │ └── SaQuicikStartup.java │ │ │ └── resources │ │ │ └── application.yml │ └── pom.xml ├── sa-token-demo-springboot │ ├── .gitignore │ ├── src │ │ └── main │ │ │ ├── java │ │ │ └── com │ │ │ │ └── pj │ │ │ │ ├── SaTokenDemoApplication.java │ │ │ │ ├── test │ │ │ │ ├── UserController.java │ │ │ │ ├── SSOController.java │ │ │ │ └── StressTestController.java │ │ │ │ ├── util │ │ │ │ └── Ttime.java │ │ │ │ └── satoken │ │ │ │ ├── StpInterfaceImpl.java │ │ │ │ └── SaTokenConfigure.java │ │ │ └── resources │ │ │ └── application.yml │ └── pom.xml ├── sa-token-demo-oauth2-client │ ├── .gitignore │ ├── src │ │ └── main │ │ │ ├── resources │ │ │ └── application.yml │ │ │ └── java │ │ │ └── com │ │ │ └── pj │ │ │ ├── SaOAuth2ClientApplication.java │ │ │ └── controller │ │ │ ├── ExceptionHandle.java │ │ │ └── ClientAccController.java │ └── pom.xml └── sa-token-demo-oauth2-server │ ├── .gitignore │ ├── src │ └── main │ │ ├── java │ │ └── com │ │ │ └── pj │ │ │ ├── SaOAuth2ServerApplication.java │ │ │ ├── controller │ │ │ ├── ServerAccController.java │ │ │ └── ExceptionHandle.java │ │ │ └── oauth2 │ │ │ ├── SaOAuth2SpringAutowired.java │ │ │ └── SaOAuth2InterfaceImpl.java │ │ └── resources │ │ ├── application.yml │ │ └── static │ │ ├── login.html │ │ └── auth.html │ └── pom.xml └── mvn clean.bat /sa-token-doc/doc/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Snailclimb/sa-token/HEAD/sa-token-doc/doc/logo.png -------------------------------------------------------------------------------- /sa-token-doc/doc/logo-150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Snailclimb/sa-token/HEAD/sa-token-doc/doc/logo-150.png -------------------------------------------------------------------------------- /sa-token-doc/doc/static/kickout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Snailclimb/sa-token/HEAD/sa-token-doc/doc/static/kickout.png -------------------------------------------------------------------------------- /sa-token-doc/doc/static/login-view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Snailclimb/sa-token/HEAD/sa-token-doc/doc/static/login-view.png -------------------------------------------------------------------------------- /sa-token-doc/doc/static/session-model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Snailclimb/sa-token/HEAD/sa-token-doc/doc/static/session-model.png -------------------------------------------------------------------------------- /sa-token-plugin/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | 3 | node_modules/ 4 | bin/ 5 | .settings/ 6 | unpackage/ 7 | .classpath 8 | .project 9 | 10 | .factorypath 11 | 12 | .idea/ -------------------------------------------------------------------------------- /sa-token-starter/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * sa-token集成SpringBoot的各个组件 3 | */ 4 | package cn.dev33.satoken; -------------------------------------------------------------------------------- /sa-token-plugin/sa-token-dao-redis/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=cn.dev33.satoken.dao.SaTokenDaoRedis -------------------------------------------------------------------------------- /sa-token-plugin/sa-token-quick-login/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=cn.dev33.satoken.quick.SaQuickBean -------------------------------------------------------------------------------- /sa-token-plugin/sa-token-spring-aop/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=cn.dev33.satoken.aop.SaCheckAspect -------------------------------------------------------------------------------- /sa-token-starter/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | 3 | node_modules/ 4 | bin/ 5 | .settings/ 6 | unpackage/ 7 | .classpath 8 | .project 9 | 10 | .factorypath 11 | 12 | .idea/ -------------------------------------------------------------------------------- /sa-token-core/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | 3 | node_modules/ 4 | bin/ 5 | .settings/ 6 | unpackage/ 7 | .classpath 8 | .project 9 | 10 | .factorypath 11 | 12 | .idea/ 13 | .iml -------------------------------------------------------------------------------- /sa-token-starter/sa-token-servlet/src/main/java/cn/dev33/satoken/servlet/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Sa-Token对接ServletAPI容器所需要的实现类接口包 3 | */ 4 | package cn.dev33.satoken.servlet; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | 3 | node_modules/ 4 | bin/ 5 | .settings/ 6 | unpackage/ 7 | .classpath 8 | .project 9 | *.iml 10 | 11 | .factorypath 12 | /.factorypath 13 | 14 | .idea/ 15 | -------------------------------------------------------------------------------- /sa-token-core/src/main/java/cn/dev33/satoken/context/model/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 因为不能确定最终运行的容器属于标准Servlet模型还是非Servlet模型,特封装此包下的包装类进行对接 3 | */ 4 | package cn.dev33.satoken.context.model; -------------------------------------------------------------------------------- /sa-token-demo/sa-token-demo-jwt/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | 3 | node_modules/ 4 | bin/ 5 | .settings/ 6 | unpackage/ 7 | .classpath 8 | .project 9 | 10 | .idea/ 11 | 12 | .factorypath -------------------------------------------------------------------------------- /sa-token-demo/sa-token-demo-webflux/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | 3 | node_modules/ 4 | bin/ 5 | .settings/ 6 | unpackage/ 7 | .classpath 8 | .project 9 | 10 | .idea/ 11 | 12 | .factorypath -------------------------------------------------------------------------------- /sa-token-plugin/sa-token-dao-redis-jackson/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=cn.dev33.satoken.dao.SaTokenDaoRedisJackson -------------------------------------------------------------------------------- /sa-token-plugin/sa-token-spring-aop/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | 3 | node_modules/ 4 | bin/ 5 | .settings/ 6 | unpackage/ 7 | .classpath 8 | .project 9 | 10 | .factorypath 11 | 12 | .idea/ -------------------------------------------------------------------------------- /sa-token-starter/sa-token-servlet/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | 3 | node_modules/ 4 | bin/ 5 | .settings/ 6 | unpackage/ 7 | .classpath 8 | .project 9 | 10 | .factorypath 11 | 12 | .idea/ -------------------------------------------------------------------------------- /sa-token-demo/sa-token-demo-quick-login/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | 3 | node_modules/ 4 | bin/ 5 | .settings/ 6 | unpackage/ 7 | .classpath 8 | .project 9 | 10 | .idea/ 11 | 12 | .factorypath -------------------------------------------------------------------------------- /sa-token-demo/sa-token-demo-springboot/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | 3 | node_modules/ 4 | bin/ 5 | .settings/ 6 | unpackage/ 7 | .classpath 8 | .project 9 | 10 | .idea/ 11 | 12 | .factorypath -------------------------------------------------------------------------------- /sa-token-plugin/sa-token-quick-login/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | 3 | node_modules/ 4 | bin/ 5 | .settings/ 6 | unpackage/ 7 | .classpath 8 | .project 9 | 10 | .factorypath 11 | 12 | .idea/ -------------------------------------------------------------------------------- /sa-token-starter/sa-token-reactor-spring-boot-starter/src/main/java/cn/dev33/satoken/reactor/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * sa-token集成Reactor响应式编程的各个组件 3 | */ 4 | package cn.dev33.satoken.reactor; -------------------------------------------------------------------------------- /sa-token-plugin/sa-token-dao-redis/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | 3 | node_modules/ 4 | bin/ 5 | .settings/ 6 | unpackage/ 7 | .classpath 8 | .project 9 | 10 | .factorypath 11 | 12 | .idea/ 13 | .iml -------------------------------------------------------------------------------- /sa-token-plugin/sa-token-oauth2/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | 3 | node_modules/ 4 | bin/ 5 | .settings/ 6 | unpackage/ 7 | .classpath 8 | .project 9 | 10 | .factorypath 11 | 12 | .idea/ 13 | .iml -------------------------------------------------------------------------------- /sa-token-starter/sa-token-spring-boot-starter/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | 3 | node_modules/ 4 | bin/ 5 | .settings/ 6 | unpackage/ 7 | .classpath 8 | .project 9 | 10 | .factorypath 11 | 12 | .idea/ -------------------------------------------------------------------------------- /sa-token-starter/sa-token-spring-boot-starter/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=cn.dev33.satoken.spring.SaTokenSpringAutowired -------------------------------------------------------------------------------- /sa-token-plugin/sa-token-dao-redis-jackson/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | 3 | node_modules/ 4 | bin/ 5 | .settings/ 6 | unpackage/ 7 | .classpath 8 | .project 9 | 10 | .factorypath 11 | 12 | .idea/ 13 | .iml -------------------------------------------------------------------------------- /sa-token-starter/sa-token-reactor-spring-boot-starter/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | 3 | node_modules/ 4 | bin/ 5 | .settings/ 6 | unpackage/ 7 | .classpath 8 | .project 9 | 10 | .factorypath 11 | 12 | .idea/ -------------------------------------------------------------------------------- /sa-token-starter/sa-token-reactor-spring-boot-starter/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=cn.dev33.satoken.reactor.spring.SaTokenSpringAutowired -------------------------------------------------------------------------------- /sa-token-core/src/main/java/cn/dev33/satoken/context/model/SaCookie.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.context.model; 2 | 3 | /** 4 | * Cookie 包装类 5 | * @author kong 6 | * 7 | */ 8 | public class SaCookie { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /sa-token-demo/sa-token-demo-oauth2-client/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .project 3 | .classpath 4 | .settings 5 | 6 | /.idea/ 7 | 8 | node_modules/ 9 | bin/ 10 | .settings/ 11 | unpackage/ 12 | /.apt_generated/ 13 | /.apt_generated_tests/ 14 | -------------------------------------------------------------------------------- /sa-token-demo/sa-token-demo-oauth2-server/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .project 3 | .classpath 4 | .settings 5 | 6 | /.idea/ 7 | 8 | node_modules/ 9 | bin/ 10 | .settings/ 11 | unpackage/ 12 | /.apt_generated/ 13 | /.apt_generated_tests/ 14 | -------------------------------------------------------------------------------- /sa-token-plugin/sa-token-quick-login/src/main/resources/static/sa-res/layer/theme/default/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Snailclimb/sa-token/HEAD/sa-token-plugin/sa-token-quick-login/src/main/resources/static/sa-res/layer/theme/default/icon.png -------------------------------------------------------------------------------- /sa-token-plugin/sa-token-quick-login/src/main/resources/static/sa-res/layer/theme/default/icon-ext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Snailclimb/sa-token/HEAD/sa-token-plugin/sa-token-quick-login/src/main/resources/static/sa-res/layer/theme/default/icon-ext.png -------------------------------------------------------------------------------- /sa-token-plugin/sa-token-quick-login/src/main/resources/static/sa-res/layer/theme/default/loading-0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Snailclimb/sa-token/HEAD/sa-token-plugin/sa-token-quick-login/src/main/resources/static/sa-res/layer/theme/default/loading-0.gif -------------------------------------------------------------------------------- /sa-token-plugin/sa-token-quick-login/src/main/resources/static/sa-res/layer/theme/default/loading-1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Snailclimb/sa-token/HEAD/sa-token-plugin/sa-token-quick-login/src/main/resources/static/sa-res/layer/theme/default/loading-1.gif -------------------------------------------------------------------------------- /sa-token-plugin/sa-token-quick-login/src/main/resources/static/sa-res/layer/theme/default/loading-2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Snailclimb/sa-token/HEAD/sa-token-plugin/sa-token-quick-login/src/main/resources/static/sa-res/layer/theme/default/loading-2.gif -------------------------------------------------------------------------------- /sa-token-core/src/main/java/cn/dev33/satoken/annotation/SaMode.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.annotation; 2 | 3 | /** 4 | * 注解鉴权的验证模式 5 | * @author kong 6 | * 7 | */ 8 | public enum SaMode { 9 | 10 | /** 11 | * 必须具有所有的选项 12 | */ 13 | AND, 14 | 15 | /** 16 | * 只需具有其中一个选项 17 | */ 18 | OR 19 | 20 | } 21 | -------------------------------------------------------------------------------- /sa-token-core/src/main/java/cn/dev33/satoken/fun/SaFunction.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.fun; 2 | 3 | /** 4 | * 设定一个函数,方便在Lambda表达式下的函数式编程 5 | * 6 | * @author kong 7 | * 8 | */ 9 | @FunctionalInterface 10 | public interface SaFunction { 11 | 12 | /** 13 | * 执行的方法 14 | */ 15 | public void run(); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /sa-token-core/src/main/java/cn/dev33/satoken/filter/SaFilterAuthStrategy.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.filter; 2 | 3 | /** 4 | * sa-token全局过滤器-认证策略 5 | * @author kong 6 | * 7 | */ 8 | public interface SaFilterAuthStrategy { 9 | 10 | /** 11 | * 执行方法 12 | * @param r 无含义参数,留作扩展 13 | */ 14 | public void run(Object r); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/logic/SaOAuth2InterfaceDefaultImpl.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.oauth2.logic; 2 | 3 | /** 4 | * SaOAuth2Interface 默认实现类 (只构建userinfo单个权限) 5 | * @author kong 6 | * 7 | */ 8 | public class SaOAuth2InterfaceDefaultImpl implements SaOAuth2Interface { 9 | 10 | 11 | 12 | } 13 | -------------------------------------------------------------------------------- /sa-token-core/src/main/java/cn/dev33/satoken/filter/SaFilterErrorStrategy.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.filter; 2 | 3 | /** 4 | * sa-token全局过滤器-异常处理策略 5 | * @author kong 6 | * 7 | */ 8 | public interface SaFilterErrorStrategy { 9 | 10 | /** 11 | * 执行方法 12 | * @param e 异常对象 13 | * @return 输出对象(请提前序列化) 14 | */ 15 | public Object run(Throwable e); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/util/SaOAuth2Consts.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.oauth2.util; 2 | 3 | /** 4 | * sa-token oauth2 模块 用到的所有常量 5 | * @author kong 6 | * 7 | */ 8 | public class SaOAuth2Consts { 9 | 10 | /** 11 | * 在保存授权码时用到的key 12 | */ 13 | public static final String UNLIMITED_DOMAIN = "*"; 14 | 15 | 16 | 17 | 18 | 19 | } 20 | -------------------------------------------------------------------------------- /sa-token-plugin/sa-token-oauth2/README.md: -------------------------------------------------------------------------------- 1 | # sa-token-oauth2 内测版 2 | 3 | sa-token-oauth2 模块是 sa-token 实现 oauth2.0 的部分,目前该模块功能完成度较低,为避免不可预知的风险,建议仅做学习测试使用 4 | 5 | ## 启动步骤 6 | 7 | 1. 启动项目 `sa-token-demo-oauth2-server`, 此为OAuth2.0的服务提供方 8 | 2. 启动项目 `sa-token-demo-oauth2-client`, 此为OAuth2.0的客户端 9 | 3. 根据控制台打印,访问测试地址即可:[http://localhost:8002/login.html](http://localhost:8002/login.html) 10 | 11 | 可结合代码注释学习查看 12 | 13 | -------------------------------------------------------------------------------- /sa-token-doc/doc/fun/timeline.md: -------------------------------------------------------------------------------- 1 | # Sa-Token 大事记 2 | 3 | 4 | - **2020-02-04:** 在GitHub提交第一个版本,正式开源 5 | - **2020-09-14:** GitHub star数量破100 6 | - **2020-10-26:** Gitee star数量破100 7 | - **2021-03-01:** 被[HelloGitHub]第59期收录推荐 8 | - **2021-03-26:** GitHub star数量破1k 9 | - **2021-03-30:** 受TLog作者邀请,sa-token加入dromara社区 10 | - **2021-03-30:** 被Gitee官方列为推荐项目 11 | - **2021-03-31:** Gitee star数量破1K 12 | - **2021-04-09:** GitHub star数量破2K 13 | 14 | 15 | -------------------------------------------------------------------------------- /sa-token-demo/sa-token-demo-quick-login/src/main/java/com/pj/SaTokenQuickDemoApplication.java: -------------------------------------------------------------------------------- 1 | package com.pj; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class SaTokenQuickDemoApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(SaTokenQuickDemoApplication.class, args); 11 | } 12 | 13 | } -------------------------------------------------------------------------------- /sa-token-doc/doc/fun/tech-stack.md: -------------------------------------------------------------------------------- 1 | # sa-token 源码用到的所有技术栈 2 | 3 | 包括但不限于以下: 4 | 5 | - Maven多模块项目 6 | - Servlet API、临时Cookie与永久Cookie、Request参数获取 7 | - SpringBoot2.0、Redis、Jackson、Hutool、jwt 8 | - SpringBoot自定义starter、Spring包扫码 + 依赖注入、AOP注解切面、yml配置映射、拦截器 9 | - Java8 接口与default实现、静态方法、枚举、定时器、异常类、泛型、反射、IO流、自定义注解、Lambda表达式、函数式编程 10 | - package-info注释、Serializable序列化接口、synchronized锁 11 | - java加密算法:MD5、SHA1、SHA256、AES、RSA 12 | - OAuth2.0、同域单点登录、集群与分布式、路由Ant匹配 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /sa-token-demo/sa-token-demo-oauth2-server/src/main/java/com/pj/SaOAuth2ServerApplication.java: -------------------------------------------------------------------------------- 1 | package com.pj; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | /** 7 | * 启动 8 | * @author kong 9 | */ 10 | @SpringBootApplication 11 | public class SaOAuth2ServerApplication { 12 | 13 | public static void main(String[] args) { 14 | SpringApplication.run(SaOAuth2ServerApplication.class, args); 15 | System.out.println("\n服务端启动成功"); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /sa-token-demo/sa-token-demo-oauth2-client/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8002 3 | 4 | spring: 5 | # 静态文件路径映射 6 | resources: 7 | static-locations: classpath:/META-INF/resources/,classpath:/resources/, classpath:/static/, classpath:/public/ 8 | # static-locations: file:E:\work\project-yun\sa-token\sa-token-demo-oauth2\sa-token-demo-oauth2-client\src\main\resources\static\ 9 | 10 | # sa-token配置 11 | sa-token: 12 | # token名称 (同时也是cookie名称) 13 | token-name: satoken-client 14 | -------------------------------------------------------------------------------- /sa-token-demo/sa-token-demo-jwt/src/main/java/com/pj/SaTokenJwtDemoApplication.java: -------------------------------------------------------------------------------- 1 | package com.pj; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | import cn.dev33.satoken.SaManager; 7 | 8 | @SpringBootApplication 9 | public class SaTokenJwtDemoApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(SaTokenJwtDemoApplication.class, args); 13 | System.out.println("\n启动成功:sa-token配置如下:" + SaManager.getConfig()); 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /sa-token-demo/sa-token-demo-oauth2-client/src/main/java/com/pj/SaOAuth2ClientApplication.java: -------------------------------------------------------------------------------- 1 | package com.pj; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | /** 7 | * 启动 8 | * @author kong 9 | */ 10 | @SpringBootApplication 11 | public class SaOAuth2ClientApplication { 12 | 13 | public static void main(String[] args) { 14 | SpringApplication.run(SaOAuth2ClientApplication.class, args); 15 | System.out.println("\n客户端启动成功,访问: http://localhost:8002/login.html"); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /sa-token-doc/doc/more/tj-gzh-hz.md: -------------------------------------------------------------------------------- 1 | # 公众号合作 2 | 3 | --- 4 | 5 | ### 推荐须知: 6 | Sa-Token作为一个新兴项目,迫切需要一定的途径进行项目推广
7 | 如果您也是java公众号运营者,欢迎您将sa-token框架推荐给您的粉丝: 8 | 9 | 1. 您无需为sa-token专门撰写文案,只需要复制项目仓库的 Readme 内容即可,可参考:[链接](https://mp.weixin.qq.com/s/xMCedNj6Nti2BwGzS9A0mg) 10 | 2. 在文章底部或内容中留下项目官网或者GitHub仓库链接 11 | 3. 文章需至少 1000+ 的阅读量 12 | 13 | 作为回报,sa-token将: 14 | 1. 在框架官方文档 [[推荐公众号]](/more/tj-gzh) 处留下您的公众号二维码(按照推荐日期倒叙排列) 15 | 2. 在框架官方交流群里@全体成员推广您的公众号一次 16 | 3. 您的公众号所有新推文章都可以将链接发送到sa-token交流群中,增加阅读量(为避免频繁推送连接,请不要超过一周三次) 17 | 18 |
19 | 如果您还有除公众号以外的其它途径可以与sa-token相互推荐,欢迎加群交流……(群链接在首页) 20 | 21 | -------------------------------------------------------------------------------- /sa-token-core/src/main/java/cn/dev33/satoken/router/SaRouteFunction.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.router; 2 | 3 | import cn.dev33.satoken.context.model.SaRequest; 4 | import cn.dev33.satoken.context.model.SaResponse; 5 | 6 | /** 7 | * 执行验证方法的辅助类 8 | * 9 | * @author kong 10 | * 11 | */ 12 | @FunctionalInterface 13 | public interface SaRouteFunction { 14 | 15 | /** 16 | * 执行验证的方法 17 | * 18 | * @param request Request包装对象 19 | * @param response Response包装对象 20 | * @param handler 处理对象 21 | */ 22 | public void run(SaRequest request, SaResponse response, Object handler); 23 | 24 | } 25 | -------------------------------------------------------------------------------- /mvn clean.bat: -------------------------------------------------------------------------------- 1 | 2 | :: 整体clean 3 | call mvn clean 4 | 5 | :: demo模块clean 6 | cd sa-token-demo 7 | 8 | cd sa-token-demo-jwt 9 | call mvn clean 10 | cd .. 11 | 12 | cd sa-token-demo-springboot 13 | call mvn clean 14 | cd .. 15 | 16 | cd sa-token-demo-webflux 17 | call mvn clean 18 | cd .. 19 | 20 | cd sa-token-demo-oauth2-client 21 | call mvn clean 22 | cd .. 23 | 24 | cd sa-token-demo-oauth2-server 25 | call mvn clean 26 | cd .. 27 | 28 | cd sa-token-demo-quick-login 29 | call mvn clean 30 | cd .. 31 | 32 | cd .. 33 | 34 | :: 最后打印 35 | echo; 36 | echo; 37 | echo ----------- clean end ----------- 38 | echo; 39 | pause -------------------------------------------------------------------------------- /sa-token-demo/sa-token-demo-springboot/src/main/java/com/pj/SaTokenDemoApplication.java: -------------------------------------------------------------------------------- 1 | package com.pj; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | import cn.dev33.satoken.SaManager; 7 | 8 | /** 9 | * sa-token整合SpringBoot 示例 10 | * @author kong 11 | * 12 | */ 13 | @SpringBootApplication 14 | public class SaTokenDemoApplication { 15 | 16 | public static void main(String[] args) { 17 | SpringApplication.run(SaTokenDemoApplication.class, args); 18 | System.out.println("\n启动成功:sa-token配置如下:" + SaManager.getConfig()); 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /sa-token-doc/doc/fun/session-model.md: -------------------------------------------------------------------------------- 1 | # Session模型详解 2 | 3 | --- 4 | 5 | 在`sa-token`中, `Session` 分为三种, 分别是: 6 | - `User-Session`: 指的是框架为每个`loginId`分配的`Session` 7 | - `Token-Session`: 指的是框架为每个`token`分配的`Session` 8 | - `自定义Session`: 指的是以一个`特定的值`作为SessionId,来分配的`Session` 9 | 10 | 11 | **假设三个客户端登录同一账号,且配置了不共享token,那么此时的Session模型是:** 12 | 13 | ![session-model](https://oss.dev33.cn/sa-token/doc/session-model3.png 's-w') 14 | 15 | 简而言之: 16 | - `Token-Session` 以token为主,只要token不同,那么对应的Session对象就不同 17 | - `User-Session` 以UserId为主,只要token指向的UserId一致,那么对应的Session对象就一致 18 | - `自定义Session` 以特定的key为主,不同key对应不同的Session对象 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /sa-token-doc/doc/plugin/aop-at.md: -------------------------------------------------------------------------------- 1 | # AOP注解鉴权 2 | --- 3 | 4 | 在 [注解式鉴权](/use/at-check) 章节,我们非常轻松的实现了注解鉴权, 5 | 但是默认的拦截器模式却有一个缺点,那就是无法在`Controller层`以外的代码使用进行校验 6 | 7 | 因此Sa-Token提供AOP插件,你只需在`pom.xml`里添加如下依赖,便可以在任意层级使用注解鉴权 8 | 9 | ``` xml 10 | 11 | 12 | cn.dev33 13 | sa-token-spring-aop 14 | 1.19.0 15 | 16 | ``` 17 | 18 | 19 | #### 注意点: 20 | - 使用拦截器模式,只能把注解写在`Controller层`,使用AOP模式,可以将注解写在任意层级
21 | - **拦截器模式和AOP模式不可同时集成**,否则会在`Controller层`发生一个注解校验两次的bug 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /sa-token-core/src/main/java/cn/dev33/satoken/stp/StpInterfaceDefaultImpl.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.stp; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | /** 7 | * 对StpInterface接口默认的实现类 8 | *

9 | * 如果开发者没有实现StpInterface接口,则使用此默认实现 10 | * 11 | * @author kong 12 | */ 13 | public class StpInterfaceDefaultImpl implements StpInterface { 14 | 15 | @Override 16 | public List getPermissionList(Object loginId, String loginKey) { 17 | return new ArrayList(); 18 | } 19 | 20 | @Override 21 | public List getRoleList(Object loginId, String loginKey) { 22 | return new ArrayList(); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /sa-token-core/src/main/java/cn/dev33/satoken/annotation/SaCheckLogin.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * 登录校验:标注在一个方法上,当前会话必须已经登录才能进入该方法 10 | *

可标注在类上,其效果等同于标注在此类的所有方法上 11 | * @author kong 12 | * 13 | */ 14 | @Retention(RetentionPolicy.RUNTIME) 15 | @Target({ ElementType.METHOD, ElementType.TYPE }) 16 | public @interface SaCheckLogin { 17 | 18 | /** 19 | * 多账号体系下所属的账号体系标识 20 | * @return see note 21 | */ 22 | String key() default ""; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /sa-token-demo/sa-token-demo-quick-login/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | # 端口 2 | server: 3 | port: 8081 4 | 5 | # Sa-Token-Quick-Login 配置 6 | sa: 7 | # 登录账号 8 | name: sa 9 | # 登录密码 10 | pwd: 123456 11 | # 是否自动随机生成账号密码 (此项为true时, name与pwd失效) 12 | auto: false 13 | # 是否开启全局认证(关闭后将不再强行拦截) 14 | auth: true 15 | # 登录页标题 16 | title: Sa-Token 登录 17 | # 是否显示底部版权信息 18 | copr: true 19 | # 将本地磁盘的某个路径作为静态资源开放 20 | # dir: file:E:\static 21 | 22 | 23 | # 静态文件路径映射 24 | spring: 25 | resources: 26 | static-locations: classpath:/META-INF/resources/,classpath:/resources/, classpath:/static/, classpath:/public/, ${sa.dir:} 27 | -------------------------------------------------------------------------------- /sa-token-doc/doc/use/token-prefix.md: -------------------------------------------------------------------------------- 1 | # Token前缀 2 | 3 | ### 需求场景 4 | 5 | 在某些系统中,前端提交token时会在前面加个固定的前缀,例如: 6 | 7 | ``` js 8 | { 9 | "satoken": "Bearer xxxx-xxxx-xxxx-xxxx" 10 | } 11 | ``` 12 | 13 | 此时后端如果不做任何特殊处理,框架将会把`Bearer `视为token的一部分,无法正常读取token信息,导致鉴权失败 14 | 15 | 为此,我们需要在yml中添加如下配置: 16 | ``` java 17 | spring: 18 | # sa-token配置 19 | sa-token: 20 | # token前缀 21 | tokenPrefix: Bearer 22 | ``` 23 | 24 | 此时 sa-token 便可在读取token时裁剪掉 `Bearer`,成功获取`xxxx-xxxx-xxxx-xxxx` 25 | 26 | 27 | ### 注意点 28 | 29 | 1. `token前缀` 与 `token值` 之间必须有一个空格 30 | 2. 一旦配置了`token前缀`,则前端提交token时,必须带有前缀,否则会导致框架无法读取token 31 | 3. 由于`Cookie`中无法存储空格字符,也就意味配置token前缀后,`Cookie`鉴权方式将会失效,此时只能将token提交到`header`里进行传输 32 | 33 | -------------------------------------------------------------------------------- /sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/util/SaOAuth2InsideUtil.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.oauth2.util; 2 | 3 | /** 4 | * sa-token-oauth2 模块内部算法util 5 | * @author kong 6 | * 7 | */ 8 | public class SaOAuth2InsideUtil { 9 | 10 | /** 11 | * 验证URL的正则表达式 12 | */ 13 | static final String URL_REGEX = "(https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]"; 14 | 15 | /** 16 | * 使用正则表达式判断一个字符串是否为URL 17 | * @param str 字符串 18 | * @return 拼接后的url字符串 19 | */ 20 | public static boolean isUrl(String str) { 21 | if(str == null) { 22 | return false; 23 | } 24 | return str.toLowerCase().matches(URL_REGEX); 25 | } 26 | 27 | 28 | } 29 | -------------------------------------------------------------------------------- /sa-token-core/src/main/java/cn/dev33/satoken/stp/StpInterface.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.stp; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * 权限认证接口,实现此接口即可集成权限认证功能 7 | * 8 | * @author kong 9 | */ 10 | public interface StpInterface { 11 | 12 | /** 13 | * 返回指定 LoginId 所拥有的权限码集合 14 | * 15 | * @param loginId 账号id 16 | * @param loginKey 账号体系标识 17 | * @return 该账号id具有的权限码集合 18 | */ 19 | public List getPermissionList(Object loginId, String loginKey); 20 | 21 | /** 22 | * 返回指定loginId所拥有的角色标识集合 23 | * 24 | * @param loginId 账号id 25 | * @param loginKey 账号体系标识 26 | * @return 该账号id具有的角色标识集合 27 | */ 28 | public List getRoleList(Object loginId, String loginKey); 29 | 30 | } 31 | -------------------------------------------------------------------------------- /sa-token-core/src/main/java/cn/dev33/satoken/context/model/SaStorage.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.context.model; 2 | 3 | /** 4 | * [存储器] 包装类 5 | *

在 Request作用域里: 存值、取值 6 | * @author kong 7 | * 8 | */ 9 | public interface SaStorage { 10 | 11 | /** 12 | * 获取底层源对象 13 | * @return see note 14 | */ 15 | public Object getSource(); 16 | 17 | /** 18 | * 在 [Request作用域] 里写入一个值 19 | * @param key 键 20 | * @param value 值 21 | */ 22 | public void set(String key, Object value); 23 | 24 | /** 25 | * 在 [Request作用域] 里获取一个值 26 | * @param key 键 27 | * @return 值 28 | */ 29 | public Object get(String key); 30 | 31 | /** 32 | * 在 [Request作用域] 里删除一个值 33 | * @param key 键 34 | */ 35 | public void delete(String key); 36 | 37 | } 38 | -------------------------------------------------------------------------------- /sa-token-doc/doc/more/link.md: -------------------------------------------------------------------------------- 1 | # 友情链接 2 | 3 | --- 4 | 5 | #### 集成sa-token的开源项目: 6 | 7 | [[sa-plus] 一个基于springboot架构的快速开发框架,内置代码生成器](https://gitee.com/click33/sa-plus) 8 | 9 | 10 |
11 | 12 | #### 推荐项目: 13 | 14 | [[OkHttps] - 一个轻量级http通信框架,API设计无比优雅,支持 WebSocket 以及 Stomp 协议](https://gitee.com/ejlchina-zhxu/okhttps) 15 | 16 | [[hasor] - 轻量级ioc/aop框架,采用"微内核+插件"的设计思想](https://gitee.com/zycgit/hasor) 17 | 18 | [[sa-admin] - 一个多窗口后台模板,流畅、易上手、提高生产力](https://gitee.com/ejlchina-zhxu/okhttps) 19 | 20 | [[vue-next-admin] - 一套为开发者快速开发准备的基于 vue2.x 越看越精彩的后台管理系统一站式平台模板](https://gitee.com/lyt-top/vue-admin-wonderful) 21 | 22 | [[小诺快速开发平台] - 基于SpringBoot2 + AntDesignVue全新快速开发平台,同时拥有三个版本](https://xiaonuo.vip/index#pricing) 23 | 24 |
25 | 虚位以待... 26 | 27 | -------------------------------------------------------------------------------- /sa-token-doc/doc/fun/token-info.md: -------------------------------------------------------------------------------- 1 | # SaTokenInfo 参数详解 2 | 3 | token信息Model: 用来描述一个token的常用参数 4 | 5 | ``` js 6 | { 7 | "code": 200, 8 | "msg": "ok", 9 | "data": { 10 | "tokenName": "satoken", // token名称 11 | "tokenValue": "e67b99f1-3d7a-4a8d-bb2f-e888a0805633", // token值 12 | "isLogin": true, // 此token是否已经登录 13 | "loginId": "10001", // 此token对应的LoginId,未登录时为null 14 | "loginKey": "login", // LoginKey账号体系标识 15 | "tokenTimeout": 2591977, // token剩余有效期 (单位: 秒) 16 | "sessionTimeout": 2591977, // User-Session剩余有效时间 (单位: 秒) 17 | "tokenSessionTimeout": -2, // Token-Session剩余有效时间 (单位: 秒) 18 | "tokenActivityTimeout": -1, // token剩余无操作有效时间 (单位: 秒) 19 | "loginDevice": "default-device" // 登录设备标识 20 | }, 21 | } 22 | ``` -------------------------------------------------------------------------------- /sa-token-core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | cn.dev33 9 | sa-token-parent 10 | 1.19.0 11 | 12 | jar 13 | 14 | sa-token-core 15 | sa-token-core 16 | A Java Web lightweight authority authentication framework, comprehensive function, easy to use 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /sa-token-demo/sa-token-demo-quick-login/src/main/java/com/pj/test/TestController.java: -------------------------------------------------------------------------------- 1 | package com.pj.test; 2 | 3 | import org.springframework.web.bind.annotation.RequestMapping; 4 | import org.springframework.web.bind.annotation.RestController; 5 | 6 | import cn.dev33.satoken.util.SaTokenConsts; 7 | 8 | /** 9 | * 测试专用Controller 10 | * @author kong 11 | * 12 | */ 13 | @RestController 14 | public class TestController { 15 | 16 | // 浏览器访问测试: http://localhost:8081 17 | @RequestMapping({"/"}) 18 | public String index() { 19 | String str = "
" 20 | // + "

Welcome to the system

" 21 | + "

资源页 (登录后才可进入本页面)

" 22 | + "
" 23 | + "

Sa-Token " + SaTokenConsts.VERSION_NO + "

"; 24 | return str; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /sa-token-demo/sa-token-demo-oauth2-server/src/main/java/com/pj/controller/ServerAccController.java: -------------------------------------------------------------------------------- 1 | package com.pj.controller; 2 | 3 | import org.springframework.web.bind.annotation.RequestMapping; 4 | import org.springframework.web.bind.annotation.RestController; 5 | 6 | import com.pj.utils.AjaxJson; 7 | 8 | import cn.dev33.satoken.stp.StpUtil; 9 | 10 | /** 11 | * 服务端登录Controller 12 | * @author kong 13 | */ 14 | @RestController 15 | public class ServerAccController { 16 | 17 | // 登录方法 18 | @RequestMapping("/doLogin") 19 | public AjaxJson test(String username, String password) { 20 | System.out.println("------------------ 成功进入请求 ------------------"); 21 | if("test".equals(username) && "test".equals(password)) { 22 | StpUtil.setLoginId(10001); 23 | return AjaxJson.getSuccess(); 24 | } 25 | return AjaxJson.getError(); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /sa-token-starter/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/spring/SaPathMatcherHolder.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.spring; 2 | 3 | import org.springframework.util.AntPathMatcher; 4 | import org.springframework.util.PathMatcher; 5 | 6 | /** 7 | * 8 | * @author kong 9 | * 10 | */ 11 | public class SaPathMatcherHolder { 12 | 13 | /** 14 | * 路由匹配器 15 | */ 16 | public static PathMatcher pathMatcher; 17 | 18 | /** 19 | * 获取路由匹配器 20 | * @return 路由匹配器 21 | */ 22 | public static PathMatcher getPathMatcher() { 23 | if(pathMatcher == null) { 24 | pathMatcher = new AntPathMatcher(); 25 | } 26 | return pathMatcher; 27 | } 28 | 29 | /** 30 | * 写入路由匹配器 31 | * @param pathMatcher 路由匹配器 32 | */ 33 | public static void setPathMatcher(PathMatcher pathMatcher) { 34 | SaPathMatcherHolder.pathMatcher = pathMatcher; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /sa-token-starter/sa-token-reactor-spring-boot-starter/src/main/java/cn/dev33/satoken/reactor/spring/SaPathMatcherHolder.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.reactor.spring; 2 | 3 | import org.springframework.util.AntPathMatcher; 4 | import org.springframework.util.PathMatcher; 5 | 6 | /** 7 | * 8 | * @author kong 9 | * 10 | */ 11 | public class SaPathMatcherHolder { 12 | 13 | /** 14 | * 路由匹配器 15 | */ 16 | public static PathMatcher pathMatcher; 17 | 18 | /** 19 | * 获取路由匹配器 20 | * @return 路由匹配器 21 | */ 22 | public static PathMatcher getPathMatcher() { 23 | if(pathMatcher == null) { 24 | pathMatcher = new AntPathMatcher(); 25 | } 26 | return pathMatcher; 27 | } 28 | 29 | /** 30 | * 写入路由匹配器 31 | * @param pathMatcher 路由匹配器 32 | */ 33 | public static void setPathMatcher(PathMatcher pathMatcher) { 34 | SaPathMatcherHolder.pathMatcher = pathMatcher; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /sa-token-core/src/main/java/cn/dev33/satoken/annotation/SaCheckRole.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * 角色校验:标注在一个方法上,当前会话必须具有指定角色标识才能进入该方法 10 | *

可标注在类上,其效果等同于标注在此类的所有方法上 11 | * @author kong 12 | * 13 | */ 14 | @Retention(RetentionPolicy.RUNTIME) 15 | @Target({ElementType.METHOD,ElementType.TYPE}) 16 | public @interface SaCheckRole { 17 | 18 | /** 19 | * 需要校验的角色标识 20 | * @return 需要校验的角色标识 21 | */ 22 | String [] value() default {}; 23 | 24 | /** 25 | * 验证模式:AND | OR,默认AND 26 | * @return 验证模式 27 | */ 28 | SaMode mode() default SaMode.AND; 29 | 30 | /** 31 | * 多账号体系下所属的账号体系标识 32 | * @return see note 33 | */ 34 | String key() default ""; 35 | 36 | } 37 | -------------------------------------------------------------------------------- /sa-token-core/src/main/java/cn/dev33/satoken/annotation/SaCheckPermission.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * 权限校验:标注在一个方法上,当前会话必须具有指定权限才能进入该方法 10 | *

可标注在类上,其效果等同于标注在此类的所有方法上 11 | * @author kong 12 | * 13 | */ 14 | @Retention(RetentionPolicy.RUNTIME) 15 | @Target({ElementType.METHOD,ElementType.TYPE}) 16 | public @interface SaCheckPermission { 17 | 18 | /** 19 | * 需要校验的权限码 20 | * @return 需要校验的权限码 21 | */ 22 | String [] value() default {}; 23 | 24 | /** 25 | * 验证模式:AND | OR,默认AND 26 | * @return 验证模式 27 | */ 28 | SaMode mode() default SaMode.AND; 29 | 30 | /** 31 | * 多账号体系下所属的账号体系标识 32 | * @return see note 33 | */ 34 | String key() default ""; 35 | 36 | } 37 | -------------------------------------------------------------------------------- /sa-token-starter/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | cn.dev33 9 | sa-token-parent 10 | 1.19.0 11 | 12 | pom 13 | 14 | sa-token-starter 15 | sa-token-starter 16 | sa-token starters 17 | 18 | 19 | 20 | sa-token-servlet 21 | sa-token-spring-boot-starter 22 | sa-token-reactor-spring-boot-starter 23 | 24 | 25 | -------------------------------------------------------------------------------- /sa-token-core/src/main/java/cn/dev33/satoken/exception/SaTokenException.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.exception; 2 | 3 | /** 4 | * sa-token框架内部逻辑发生错误抛出的异常 5 | * (自定义此异常可方便开发者在做全局异常处理时分辨异常类型) 6 | * 7 | * @author kong 8 | * 9 | */ 10 | public class SaTokenException extends RuntimeException { 11 | 12 | /** 13 | * 序列化版本号 14 | */ 15 | private static final long serialVersionUID = 6806129545290130132L; 16 | 17 | /** 18 | * 构建一个异常 19 | * 20 | * @param message 异常描述信息 21 | */ 22 | public SaTokenException(String message) { 23 | super(message); 24 | } 25 | 26 | /** 27 | * 构建一个异常 28 | * 29 | * @param cause 异常对象 30 | */ 31 | public SaTokenException(Throwable cause) { 32 | super(cause); 33 | } 34 | 35 | /** 36 | * 构建一个异常 37 | * 38 | * @param message 异常信息 39 | * @param cause 异常对象 40 | */ 41 | public SaTokenException(String message, Throwable cause) { 42 | super(message, cause); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /sa-token-core/src/main/java/cn/dev33/satoken/context/SaTokenContextForThreadLocal.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.context; 2 | 3 | import cn.dev33.satoken.context.model.SaRequest; 4 | import cn.dev33.satoken.context.model.SaResponse; 5 | import cn.dev33.satoken.context.model.SaStorage; 6 | 7 | /** 8 | * 上下文环境 [ThreadLocal版本] 9 | * @author kong 10 | * 11 | */ 12 | public class SaTokenContextForThreadLocal implements SaTokenContext { 13 | 14 | @Override 15 | public SaRequest getRequest() { 16 | return SaTokenContextForThreadLocalStorage.getRequest(); 17 | } 18 | 19 | @Override 20 | public SaResponse getResponse() { 21 | return SaTokenContextForThreadLocalStorage.getResponse(); 22 | } 23 | 24 | @Override 25 | public SaStorage getStorage() { 26 | return SaTokenContextForThreadLocalStorage.getStorage(); 27 | } 28 | 29 | @Override 30 | public boolean matchPath(String pattern, String path) { 31 | return false; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /sa-token-core/src/main/java/cn/dev33/satoken/fun/IsRunFunction.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.fun; 2 | 3 | /** 4 | * 根据boolean变量,决定是否执行一个函数 5 | * 6 | * @author kong 7 | * 8 | */ 9 | public class IsRunFunction { 10 | 11 | /** 12 | * 变量 13 | */ 14 | public final Boolean isRun; 15 | 16 | /** 17 | * 设定一个变量,如果为true,则执行exe函数 18 | * 19 | * @param isRun 变量 20 | */ 21 | public IsRunFunction(boolean isRun) { 22 | this.isRun = isRun; 23 | } 24 | 25 | /** 26 | * 当 isRun == true 时执行此函数 27 | * @param function 函数 28 | * @return 对象自身 29 | */ 30 | public IsRunFunction exe(SaFunction function) { 31 | if (isRun) { 32 | function.run(); 33 | } 34 | return this; 35 | } 36 | 37 | /** 38 | * 当 isRun == false 时执行此函数 39 | * @param function 函数 40 | * @return 对象自身 41 | */ 42 | public IsRunFunction noExe(SaFunction function) { 43 | if (!isRun) { 44 | function.run(); 45 | } 46 | return this; 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /sa-token-plugin/sa-token-quick-login/src/main/java/cn/dev33/satoken/quick/SaQuickManager.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.quick; 2 | 3 | import cn.dev33.satoken.quick.config.SaQuickConfig; 4 | import cn.dev33.satoken.util.SaFoxUtil; 5 | 6 | /** 7 | * SaQuickManager 8 | * @author kong 9 | * 10 | */ 11 | public class SaQuickManager { 12 | 13 | /** 14 | * 配置文件 Bean 15 | */ 16 | private static SaQuickConfig config; 17 | public static void setConfig(SaQuickConfig config) { 18 | SaQuickManager.config = config; 19 | // 如果配置了随机密码 20 | if(config.getAuto()) { 21 | config.setName(SaFoxUtil.getRandomString(8)); 22 | config.setPwd(SaFoxUtil.getRandomString(8)); 23 | } 24 | } 25 | public static SaQuickConfig getConfig() { 26 | if (config == null) { 27 | synchronized (SaQuickManager.class) { 28 | if (config == null) { 29 | setConfig(new SaQuickConfig()); 30 | } 31 | } 32 | } 33 | return config; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /sa-token-core/src/main/java/cn/dev33/satoken/context/model/SaRequest.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.context.model; 2 | 3 | /** 4 | * Request包装类 5 | * @author kong 6 | * 7 | */ 8 | public interface SaRequest { 9 | 10 | /** 11 | * 获取底层源对象 12 | * @return see note 13 | */ 14 | public Object getSource(); 15 | 16 | /** 17 | * 在 [请求体] 里获取一个值 18 | * @param name 键 19 | * @return 值 20 | */ 21 | public String getParameter(String name); 22 | 23 | /** 24 | * 在 [请求头] 里获取一个值 25 | * @param name 键 26 | * @return 值 27 | */ 28 | public String getHeader(String name); 29 | 30 | /** 31 | * 在 [Cookie作用域] 里获取一个值 32 | * @param name 键 33 | * @return 值 34 | */ 35 | public String getCookieValue(String name); 36 | 37 | /** 38 | * 返回当前请求path (不包括上下文名称) 39 | * @return see note 40 | */ 41 | public String getRequestPath(); 42 | 43 | /** 44 | * 返回当前请求的类型 45 | * @return see note 46 | */ 47 | public String getMethod(); 48 | 49 | } 50 | -------------------------------------------------------------------------------- /sa-token-core/src/main/java/cn/dev33/satoken/context/SaTokenContext.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.context; 2 | 3 | import cn.dev33.satoken.context.model.SaRequest; 4 | import cn.dev33.satoken.context.model.SaStorage; 5 | import cn.dev33.satoken.context.model.SaResponse; 6 | 7 | /** 8 | * Sa-Token 上下文处理器 9 | * @author kong 10 | * 11 | */ 12 | public interface SaTokenContext { 13 | 14 | /** 15 | * 获取当前请求的 [Request] 对象 16 | * 17 | * @return see note 18 | */ 19 | public SaRequest getRequest(); 20 | 21 | /** 22 | * 获取当前请求的 [Response] 对象 23 | * 24 | * @return see note 25 | */ 26 | public SaResponse getResponse(); 27 | 28 | /** 29 | * 获取当前请求的 [存储器] 对象 30 | * 31 | * @return see note 32 | */ 33 | public SaStorage getStorage(); 34 | 35 | /** 36 | * 校验指定路由匹配符是否可以匹配成功指定路径 37 | * 38 | * @param pattern 路由匹配符 39 | * @param path 需要匹配的路径 40 | * @return see note 41 | */ 42 | public boolean matchPath(String pattern, String path); 43 | 44 | } 45 | -------------------------------------------------------------------------------- /sa-token-plugin/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | cn.dev33 9 | sa-token-parent 10 | 1.19.0 11 | 12 | pom 13 | 14 | sa-token-plugin 15 | sa-token-plugin 16 | sa-token plugins 17 | 18 | 19 | 20 | sa-token-dao-redis 21 | sa-token-dao-redis-jackson 22 | sa-token-spring-aop 23 | 24 | sa-token-quick-login 25 | 26 | 27 | -------------------------------------------------------------------------------- /sa-token-core/src/main/java/cn/dev33/satoken/context/SaHolder.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.context; 2 | 3 | import cn.dev33.satoken.SaManager; 4 | import cn.dev33.satoken.context.model.SaRequest; 5 | import cn.dev33.satoken.context.model.SaResponse; 6 | import cn.dev33.satoken.context.model.SaStorage; 7 | 8 | /** 9 | * Sa-Token 上下文持有类 10 | * @author kong 11 | * 12 | */ 13 | public class SaHolder { 14 | 15 | /** 16 | * 获取当前请求的 [Request] 对象 17 | * 18 | * @return see note 19 | */ 20 | public static SaRequest getRequest() { 21 | return SaManager.getSaTokenContext().getRequest(); 22 | } 23 | 24 | /** 25 | * 获取当前请求的 [Response] 对象 26 | * 27 | * @return see note 28 | */ 29 | public static SaResponse getResponse() { 30 | return SaManager.getSaTokenContext().getResponse(); 31 | } 32 | 33 | /** 34 | * 获取当前请求的 [存储器] 对象 35 | * 36 | * @return see note 37 | */ 38 | public static SaStorage getStorage() { 39 | return SaManager.getSaTokenContext().getStorage(); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /sa-token-plugin/sa-token-oauth2/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | cn.dev33 9 | sa-token-plugin 10 | 1.19.0 11 | 12 | jar 13 | 14 | sa-token-dao-redis 15 | sa-token-oauth2 16 | 1.15.0-alpha 17 | sa-token realization oauth2.0 18 | 19 | 20 | 21 | 22 | cn.dev33 23 | sa-token-core 24 | ${sa-token-version} 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /sa-token-demo/sa-token-demo-springboot/src/main/java/com/pj/test/UserController.java: -------------------------------------------------------------------------------- 1 | package com.pj.test; 2 | 3 | import org.springframework.web.bind.annotation.RequestMapping; 4 | import org.springframework.web.bind.annotation.RestController; 5 | 6 | import cn.dev33.satoken.stp.StpUtil; 7 | 8 | /** 9 | * 登录测试 10 | * @author kong 11 | * 12 | */ 13 | @RestController 14 | @RequestMapping("/user/") 15 | public class UserController { 16 | 17 | // 测试登录,浏览器访问: http://localhost:8081/user/doLogin?username=zhang&password=123456 18 | @RequestMapping("doLogin") 19 | public String doLogin(String username, String password) { 20 | // 此处仅作模拟示例,真实项目需要从数据库中查询数据进行比对 21 | if("zhang".equals(username) && "123456".equals(password)) { 22 | StpUtil.setLoginId(10001); 23 | return "登录成功"; 24 | } 25 | return "登录失败"; 26 | } 27 | 28 | // 查询登录状态,浏览器访问: http://localhost:8081/user/isLogin 29 | @RequestMapping("isLogin") 30 | public String isLogin(String username, String password) { 31 | return "当前会话是否登录:" + StpUtil.isLogin(); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /sa-token-core/src/main/java/cn/dev33/satoken/exception/NotRoleException.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.exception; 2 | 3 | import cn.dev33.satoken.stp.StpUtil; 4 | 5 | /** 6 | * 没有指定角色标识,抛出的异常 7 | * 8 | * @author kong 9 | * 10 | */ 11 | public class NotRoleException extends SaTokenException { 12 | 13 | /** 14 | * 序列化版本号 15 | */ 16 | private static final long serialVersionUID = 8243974276159004739L; 17 | 18 | /** 角色标识 */ 19 | private String role; 20 | 21 | /** 22 | * @return 获得角色标识 23 | */ 24 | public String getRole() { 25 | return role; 26 | } 27 | 28 | /** 29 | * loginKey 30 | */ 31 | private String loginKey; 32 | 33 | /** 34 | * 获得loginKey 35 | * 36 | * @return loginKey 37 | */ 38 | public String getLoginKey() { 39 | return loginKey; 40 | } 41 | 42 | public NotRoleException(String role) { 43 | this(role, StpUtil.stpLogic.loginKey); 44 | } 45 | 46 | public NotRoleException(String role, String loginKey) { 47 | super("无此角色:" + role); 48 | this.role = role; 49 | this.loginKey = loginKey; 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /sa-token-demo/sa-token-demo-springboot/src/main/java/com/pj/test/SSOController.java: -------------------------------------------------------------------------------- 1 | package com.pj.test; 2 | 3 | import org.springframework.web.bind.annotation.RequestMapping; 4 | import org.springframework.web.bind.annotation.RequestParam; 5 | import org.springframework.web.bind.annotation.RestController; 6 | 7 | import com.pj.util.AjaxJson; 8 | 9 | import cn.dev33.satoken.stp.StpUtil; 10 | 11 | /** 12 | * 测试: 同域单点登录 13 | * @author kong 14 | */ 15 | @RestController 16 | @RequestMapping("/sso/") 17 | public class SSOController { 18 | 19 | // 测试:进行登录 20 | @RequestMapping("doLogin") 21 | public AjaxJson doLogin(@RequestParam(defaultValue = "10001") String id) { 22 | System.out.println("---------------- 进行登录 "); 23 | StpUtil.setLoginId(id); 24 | return AjaxJson.getSuccess("登录成功: " + id); 25 | } 26 | 27 | // 测试:是否登录 28 | @RequestMapping("isLogin") 29 | public AjaxJson isLogin() { 30 | System.out.println("---------------- 是否登录 "); 31 | boolean isLogin = StpUtil.isLogin(); 32 | return AjaxJson.getSuccess("是否登录: " + isLogin); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /sa-token-doc/doc/use/mutex-login.md: -------------------------------------------------------------------------------- 1 | # 同端互斥登录 2 | 3 | 如果你经常使用腾讯QQ,就会发现它的登录有如下特点:它可以手机电脑同时在线,但是不能在两个手机上同时登录一个账号
4 | 同端互斥登录,指的就是像腾讯QQ一样,在同一类型设备上只允许单地点登录,在不同类型设备上允许同时在线 5 | 6 | --- 7 | 8 | ## 具体API 9 | 10 | 在`sa-token`中如何做到同端互斥登录?
11 | 首先在配置文件中,将 `allowConcurrentLogin` 配置为false,然后调用登录等相关接口时声明设备标识即可: 12 | 13 | 14 | #### 指定设备标识登录 15 | ``` java 16 | // 指定`账号id`和`设备标识`进行登录 17 | StpUtil.setLoginId(10001, "PC"); 18 | ``` 19 | 调用此方法登录后,同设备的会被顶下线(不同设备不受影响),再次访问系统时会抛出 `NotLoginException` 异常,场景值=`-4` 20 | 21 | 22 | #### 指定设备标识强制注销 23 | ``` java 24 | // 指定`账号id`和`设备标识`进行强制注销 (踢人下线) 25 | StpUtil.logoutByLoginId(10001, "PC"); 26 | ``` 27 | 如果第二个参数填写null或不填,代表将这个账号id所有在线端踢下线,被踢出者再次访问系统时会抛出 `NotLoginException` 异常,场景值=`-5` 28 | 29 | 30 | #### 查询当前登录的设备标识 31 | ``` java 32 | // 返回当前token的登录设备 33 | StpUtil.getLoginDevice(); 34 | ``` 35 | 36 | 37 | #### id反查token 38 | ``` java 39 | // 获取指定loginId指定设备端的tokenValue 40 | StpUtil.getTokenValueByLoginId(10001, "APP"); 41 | ``` 42 | 43 | 44 | > 不同设备账号在登录时设置不同的token有效期等信息, 详见[登录时指定token有效期](/use/remember-me?id=登录时指定token有效期) -------------------------------------------------------------------------------- /sa-token-core/src/main/java/cn/dev33/satoken/exception/NotPermissionException.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.exception; 2 | 3 | import cn.dev33.satoken.stp.StpUtil; 4 | 5 | /** 6 | * 没有指定权限码,抛出的异常 7 | * 8 | * @author kong 9 | * 10 | */ 11 | public class NotPermissionException extends SaTokenException { 12 | 13 | /** 14 | * 序列化版本号 15 | */ 16 | private static final long serialVersionUID = 6806129545290130141L; 17 | 18 | /** 权限码 */ 19 | private String code; 20 | 21 | /** 22 | * @return 获得权限码 23 | */ 24 | public String getCode() { 25 | return code; 26 | } 27 | 28 | /** 29 | * loginKey 30 | */ 31 | private String loginKey; 32 | 33 | /** 34 | * 获得loginKey 35 | * 36 | * @return loginKey 37 | */ 38 | public String getLoginKey() { 39 | return loginKey; 40 | } 41 | 42 | public NotPermissionException(String code) { 43 | this(code, StpUtil.stpLogic.loginKey); 44 | } 45 | 46 | public NotPermissionException(String code, String loginKey) { 47 | super("无此权限:" + code); 48 | this.code = code; 49 | this.loginKey = loginKey; 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /sa-token-demo/sa-token-demo-springboot/src/main/java/com/pj/util/Ttime.java: -------------------------------------------------------------------------------- 1 | package com.pj.util; 2 | 3 | 4 | /** 5 | * 用于测试用时 6 | * @author kong 7 | * 8 | */ 9 | public class Ttime { 10 | 11 | private long start=0; //开始时间 12 | private long end=0; //结束时间 13 | 14 | public static Ttime t = new Ttime(); //static快捷使用 15 | 16 | /** 17 | * 开始计时 18 | * @return 19 | */ 20 | public Ttime start() { 21 | start=System.currentTimeMillis(); 22 | return this; 23 | } 24 | 25 | 26 | /** 27 | * 结束计时 28 | */ 29 | public Ttime end() { 30 | end=System.currentTimeMillis(); 31 | return this; 32 | } 33 | 34 | 35 | /** 36 | * 返回所用毫秒数 37 | */ 38 | public long returnMs() { 39 | return end-start; 40 | } 41 | 42 | /** 43 | * 格式化输出结果 44 | */ 45 | public void outTime() { 46 | System.out.println(this.toString()); 47 | } 48 | 49 | /** 50 | * 结束并格式化输出结果 51 | */ 52 | public void endOutTime() { 53 | this.end().outTime(); 54 | } 55 | 56 | @Override 57 | public String toString() { 58 | return (returnMs() + 0.0) / 1000 + "s"; // 格式化为:0.01s 59 | } 60 | 61 | 62 | 63 | } 64 | -------------------------------------------------------------------------------- /sa-token-core/src/main/java/cn/dev33/satoken/action/SaTokenAction.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.action; 2 | 3 | import java.lang.reflect.Method; 4 | import java.util.List; 5 | 6 | import cn.dev33.satoken.session.SaSession; 7 | 8 | /** 9 | * sa-token逻辑代理接口 10 | *

此接口将会代理框架内部的一些关键性逻辑,方便开发者进行按需重写

11 | * @author kong 12 | * 13 | */ 14 | public interface SaTokenAction { 15 | 16 | /** 17 | * 根据一定的算法生成一个token 18 | * @param loginId 账号id 19 | * @param loginKey 账号体系key 20 | * @return 一个token 21 | */ 22 | public String createToken(Object loginId, String loginKey); 23 | 24 | /** 25 | * 根据 SessionId 创建一个 Session 26 | * @param sessionId Session的Id 27 | * @return 创建后的Session 28 | */ 29 | public SaSession createSession(String sessionId); 30 | 31 | /** 32 | * 指定集合是否包含指定元素(模糊匹配) 33 | * @param list 集合 34 | * @param element 元素 35 | * @return 是否包含 36 | */ 37 | public boolean hasElement(List list, String element); 38 | 39 | /** 40 | * 对一个Method对象进行注解检查(注解鉴权内部实现) 41 | * @param method Method对象 42 | */ 43 | public void checkMethodAnnotation(Method method); 44 | 45 | } 46 | -------------------------------------------------------------------------------- /sa-token-doc/doc/use/search-session.md: -------------------------------------------------------------------------------- 1 | # 会话治理 2 | 3 | 尽管框架将大部分操作提供了简易的封装,但在一些特殊场景下,我们仍需要绕过框架,直达数据底层进行一些操作
4 | sa-token提供以下API助你直接操作会话列表 5 | 6 | 7 | --- 8 | 9 | ## 具体API 10 | 11 | ``` java 12 | // 查询所有token 13 | StpUtil.searchTokenValue(String keyword, int start, int size); 14 | 15 | // 查询所有账号Session会话 16 | StpUtil.searchSessionId(String keyword, int start, int size); 17 | 18 | // 查询所有令牌Session会话 19 | StpUtil.searchTokenSessionId(String keyword, int start, int size); 20 | ``` 21 | 22 | 23 | #### 参数详解: 24 | - `keyword`: 查询关键字,只有包括这个字符串的token值才会被查询出来 25 | - `start`: 数据开始处索引, 值为-1时代表一次性取出所有数据 26 | - `size`: 要获取的数据条数 27 | 28 | 使用示例: 29 | ``` java 30 | // 查询value包括1000的所有token,结果集从第0条开始,返回10条 31 | List tokenList = StpUtil.searchTokenValue("1000", 0, 10); 32 | for (String token : tokenList) { 33 | System.out.println(token); 34 | } 35 | ``` 36 | 37 | 38 |
39 | 40 | #### 注意事项: 41 | 由于会话查询底层采用了遍历方式获取数据,当数据量过大时此操作将会比较耗时,有多耗时呢?这里提供一份参考数据: 42 | - 单机模式下:百万会话取出10条token平均耗时 `0.255s` 43 | - Redis模式下:百万会话取出10条token平均耗时 `3.322s` 44 | 45 | 请根据业务实际水平合理调用API 46 | 47 | 48 | > 如果需要实时获取当前登录人数或者需要在用户退出后自动触发某事件等, 建议采用websocket技术 -------------------------------------------------------------------------------- /sa-token-core/src/main/java/cn/dev33/satoken/context/model/SaResponse.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.context.model; 2 | 3 | /** 4 | * Response 包装类 5 | * @author kong 6 | * 7 | */ 8 | public interface SaResponse { 9 | 10 | /** 11 | * 获取底层源对象 12 | * @return see note 13 | */ 14 | public Object getSource(); 15 | 16 | /** 17 | * 删除指定Cookie 18 | * @param name Cookie名称 19 | */ 20 | public void deleteCookie(String name); 21 | 22 | /** 23 | * 写入指定Cookie 24 | * @param name Cookie名称 25 | * @param value Cookie值 26 | * @param path Cookie路径 27 | * @param domain Cookie的作用域 28 | * @param timeout 过期时间 (秒) 29 | */ 30 | public void addCookie(String name, String value, String path, String domain, int timeout); 31 | 32 | /** 33 | * 在响应头里写入一个值 34 | * @param name 名字 35 | * @param value 值 36 | * @return 对象自身 37 | */ 38 | public SaResponse setHeader(String name, String value); 39 | 40 | /** 41 | * 在响应头写入 [Server] 服务器名称 42 | * @param value 服务器名称 43 | * @return 对象自身 44 | */ 45 | public default SaResponse setServer(String value) { 46 | return this.setHeader("Server", value); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /sa-token-doc/doc/senior/dcs.md: -------------------------------------------------------------------------------- 1 | # 集群、分布式 2 | Sa-Token 在集群、分布式下的解决方案 3 | 4 | --- 5 | 6 | 7 | ### 分布式会话 8 | 分布式架构下的第一个难题便是数据同步,单机版的`Session`在分布式环境下一般不能正常工作,为此我们需要对框架做一些特定的处理。 9 | 10 | 首先我们要明白,分布式环境下为什么`Session`会失效?因为用户在一个节点对会话做出的更改无法实时同步到其它的节点, 11 | 这就导致一个很严重的问题:如果用户在节点一上已经登录成功,那么当下一次的请求落在节点二上时,对节点二来讲,此用户仍然是未登录状态。 12 | 13 | 要怎么解决这个问题呢?目前的主流方案有四种: 14 | 1. **Session同步**:只要一个节点的数据发生了改变,就强制同步到其它所有节点 15 | 2. **Session粘滞**:通过一定的算法,保证一个用户的所有请求都稳定的落在一个节点之上,对这个用户来讲,就好像还是在访问一个单机版的服务 16 | 3. **建立会话中心**:将Session存储在专业的缓存中间件上,使每个节点都变成了无状态服务,例如:`Redis` 17 | 4. **颁发无状态token**:放弃Session机制,将用户数据直接写入到令牌本身上,使会话数据做到令牌自解释,例如:`jwt` 18 | 19 | 该如何选择一个合适的方案? 20 | - 方案一:性能消耗太大,不太考虑 21 | - 方案二:需要从网关处动手,与框架无关 22 | - 方案三:`sa-token`整合`Redis`非常简单,详见章节: [持久层扩展](use/dao-extend) 23 | - 方案四:详见官方仓库中`sa-token`整合`jwt`的示例 24 | 25 | 由于`jwt`模式不在服务端存储数据,对于比较复杂的业务可能会功能受限,因此更加推荐使用方案三 26 | 27 | 28 | ### 微服务网关鉴权 29 | 由于大多数常见网关组件基于`webflux`编写,从底层上脱离了"ServletAPI"模型(如`Gateway`、`Soul`等),这就导致很多底层依赖ServletAPI的权限认证框架无法在网关处使用。 30 | 31 | 为此`Sa-Token`自`v1.16.0`版本开始提供了`Reactor响应式模型`web框架的starter依赖包,你可以据此轻松完成网关鉴权需求, 32 | 详细请参考:[全局过滤器](/use/global-filter) 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /sa-token-core/src/main/java/cn/dev33/satoken/session/TokenSign.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.session; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * token签名 Model 7 | * 8 | * 挂在到SaSession上的token签名 9 | * 10 | * @author kong 11 | * 12 | */ 13 | public class TokenSign implements Serializable { 14 | 15 | /** 16 | * 17 | */ 18 | private static final long serialVersionUID = 1406115065849845073L; 19 | 20 | /** 21 | * token值 22 | */ 23 | private String value; 24 | 25 | /** 26 | * 所在设备标识 27 | */ 28 | private String device; 29 | 30 | /** 构建一个 */ 31 | public TokenSign() { 32 | } 33 | 34 | /** 35 | * 构建一个 36 | * 37 | * @param value token值 38 | * @param device 所在设备标识 39 | */ 40 | public TokenSign(String value, String device) { 41 | this.value = value; 42 | this.device = device; 43 | } 44 | 45 | /** 46 | * @return token value 47 | */ 48 | public String getValue() { 49 | return value; 50 | } 51 | 52 | /** 53 | * @return token登录设备 54 | */ 55 | public String getDevice() { 56 | return device; 57 | } 58 | 59 | @Override 60 | public String toString() { 61 | return "TokenSign [value=" + value + ", device=" + device + "]"; 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /sa-token-doc/doc/use/kick.md: -------------------------------------------------------------------------------- 1 | # 踢人下线 2 | 所谓踢人下线,核心操作就是找到其指定`loginId`对应的`token`,并设置其失效 3 | 4 | ![踢下线](../static/kickout.png) 5 | 6 | --- 7 | 8 | 9 | ### 根据账号id踢人 10 | 让指定账号id的会话注销登录,例如: 11 | 12 | ``` java 13 | // 使账号id为10001的会话注销登录(踢人下线),待到10001再次访问系统时会抛出`NotLoginException`异常,场景值为-5 14 | StpUtil.logoutByLoginId(10001); 15 | ``` 16 | 17 | ### 根据Token令牌踢人 18 | 你还可以让指定token的会话注销登录 19 | ``` java 20 | // 使账号id为10001的会话注销登录 21 | StpUtil.logoutByTokenValue("xxxx-xxxx-xxxx-xxxx-xxxx"); 22 | ``` 23 | 此方法直接删除了`token->uid`的映射关系,对方再次访问时提示:`token无效`,场景值为-2 24 | 25 | 26 | 27 | ### 账号封禁 28 | 对于违规账号,有时候我们仅仅将其踢下线还是远远不够的,我们还需要对其进行**账号封禁**防止其再次登录 29 | 30 | ``` java 31 | // 封禁指定账号 32 | // 参数一:账号id 33 | // 参数二:封禁时长,单位:秒 (86400秒=1天,此值为-1时,代表永久封禁) 34 | StpUtil.disable(10001, 86400); 35 | 36 | // 获取指定账号是否已被封禁 (true=已被封禁, false=未被封禁) 37 | StpUtil.isDisable(10001); 38 | 39 | // 获取指定账号剩余封禁时间,单位:秒 40 | StpUtil.getDisableTime(10001); 41 | 42 | // 解除封禁 43 | StpUtil.untieDisable(10001); 44 | ``` 45 | 46 | 47 | #### 注意点 48 | 对于正在登录的账号,对其账号封禁时并不会使其立刻注销
49 | 如果需要将其封禁后立即掉线,可采取先踢再封禁的策略,例如: 50 | ``` java 51 | // 先踢下线 52 | StpUtil.logoutByLoginId(10001); 53 | // 再封禁账号 54 | StpUtil.disableLoginId(10001, 86400); 55 | ``` 56 | 57 | -------------------------------------------------------------------------------- /sa-token-demo/sa-token-demo-springboot/src/main/java/com/pj/satoken/StpInterfaceImpl.java: -------------------------------------------------------------------------------- 1 | package com.pj.satoken; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import org.springframework.stereotype.Component; 7 | 8 | import cn.dev33.satoken.stp.StpInterface; 9 | 10 | /** 11 | * 自定义权限验证接口扩展 12 | */ 13 | @Component // 打开此注解,保证此类被springboot扫描,即可完成sa-token的自定义权限验证扩展 14 | public class StpInterfaceImpl implements StpInterface { 15 | 16 | /** 17 | * 返回一个账号所拥有的权限码集合 18 | */ 19 | @Override 20 | public List getPermissionList(Object loginId, String loginKey) { 21 | // 本list仅做模拟,实际项目中要根据具体业务逻辑来查询权限 22 | List list = new ArrayList(); 23 | list.add("101"); 24 | list.add("user-add"); 25 | list.add("user-delete"); 26 | list.add("user-update"); 27 | list.add("user-get"); 28 | list.add("article-get"); 29 | return list; 30 | } 31 | 32 | /** 33 | * 返回一个账号所拥有的角色标识集合 34 | */ 35 | @Override 36 | public List getRoleList(Object loginId, String loginKey) { 37 | // 本list仅做模拟,实际项目中要根据具体业务逻辑来查询角色 38 | List list = new ArrayList(); 39 | list.add("admin"); 40 | list.add("super-admin"); 41 | return list; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /sa-token-demo/sa-token-demo-webflux/src/main/java/com/pj/satoken/StpInterfaceImpl.java: -------------------------------------------------------------------------------- 1 | package com.pj.satoken; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import org.springframework.stereotype.Component; 7 | 8 | import cn.dev33.satoken.stp.StpInterface; 9 | 10 | /** 11 | * 自定义权限验证接口扩展 12 | */ 13 | @Component // 打开此注解,保证此类被springboot扫描,即可完成sa-token的自定义权限验证扩展 14 | public class StpInterfaceImpl implements StpInterface { 15 | 16 | /** 17 | * 返回一个账号所拥有的权限码集合 18 | */ 19 | @Override 20 | public List getPermissionList(Object loginId, String loginKey) { 21 | // 本list仅做模拟,实际项目中要根据具体业务逻辑来查询权限 22 | List list = new ArrayList(); 23 | list.add("101"); 24 | list.add("user-add"); 25 | list.add("user-delete"); 26 | list.add("user-update"); 27 | list.add("user-get"); 28 | list.add("article-get"); 29 | return list; 30 | } 31 | 32 | /** 33 | * 返回一个账号所拥有的角色标识集合 34 | */ 35 | @Override 36 | public List getRoleList(Object loginId, String loginKey) { 37 | // 本list仅做模拟,实际项目中要根据具体业务逻辑来查询角色 38 | List list = new ArrayList(); 39 | list.add("admin"); 40 | list.add("super-admin"); 41 | return list; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /sa-token-starter/sa-token-reactor-spring-boot-starter/src/main/java/cn/dev33/satoken/reactor/context/SaReactorHolder.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.reactor.context; 2 | 3 | import org.springframework.web.server.ServerWebExchange; 4 | 5 | import reactor.core.publisher.Mono; 6 | 7 | /** 8 | * Reactor上下文操作 [异步] 9 | * @author kong 10 | * 11 | */ 12 | public class SaReactorHolder { 13 | 14 | /** 15 | * key 16 | */ 17 | public static final Class CONTEXT_KEY = ServerWebExchange.class; 18 | 19 | /** 20 | * 获取上下文对象 21 | * @return see note 22 | */ 23 | public static Mono getContent() { 24 | // 从全局 Mono 获取 25 | return Mono.subscriberContext().map(ctx -> ctx.get(CONTEXT_KEY)); 26 | } 27 | 28 | /** 29 | * 获取上下文对象, 并设置到同步上下文中 30 | * @return see note 31 | */ 32 | public static Mono getContentAndSetSync() { 33 | // 从全局 Mono 获取 34 | return Mono.subscriberContext().map(ctx -> { 35 | // 设置到sync中 36 | SaReactorSyncHolder.setContent(ctx.get(CONTEXT_KEY)); 37 | return ctx.get(CONTEXT_KEY); 38 | }).doFinally(r->{ 39 | // 从sync中清除 40 | SaReactorSyncHolder.clearContent(); 41 | }); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /sa-token-demo/sa-token-demo-webflux/src/main/java/com/pj/test/DefineRoutes.java: -------------------------------------------------------------------------------- 1 | package com.pj.test; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.http.MediaType; 6 | import org.springframework.web.reactive.function.server.RequestPredicates; 7 | import org.springframework.web.reactive.function.server.RouterFunction; 8 | import org.springframework.web.reactive.function.server.RouterFunctions; 9 | import org.springframework.web.reactive.function.server.ServerResponse; 10 | 11 | import com.pj.util.AjaxJson; 12 | 13 | import cn.dev33.satoken.stp.StpUtil; 14 | 15 | @Configuration 16 | public class DefineRoutes { 17 | 18 | /** 19 | * 函数式编程,初始化路由表 20 | * @return 路由表 21 | */ 22 | @Bean 23 | public RouterFunction getRoutes() { 24 | return RouterFunctions.route(RequestPredicates.GET("/fun"), req -> { 25 | // 测试打印 26 | System.out.println("是否登录:" + StpUtil.isLogin()); 27 | 28 | // 返回结果 29 | AjaxJson aj = AjaxJson.getSuccessData(StpUtil.getTokenInfo()); 30 | return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON_UTF8).syncBody(aj); 31 | }); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /sa-token-starter/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/interceptor/SaAnnotationInterceptor.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.interceptor; 2 | 3 | import java.lang.reflect.Method; 4 | 5 | import javax.servlet.http.HttpServletRequest; 6 | import javax.servlet.http.HttpServletResponse; 7 | 8 | import org.springframework.web.method.HandlerMethod; 9 | import org.springframework.web.servlet.HandlerInterceptor; 10 | 11 | import cn.dev33.satoken.SaManager; 12 | 13 | /** 14 | * 注解式鉴权 - 拦截器 15 | * 16 | * @author kong 17 | */ 18 | public class SaAnnotationInterceptor implements HandlerInterceptor { 19 | 20 | /** 21 | * 构建: 注解式鉴权 - 拦截器 22 | */ 23 | public SaAnnotationInterceptor() { 24 | } 25 | 26 | /** 27 | * 每次请求之前触发的方法 28 | */ 29 | @Override 30 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) 31 | throws Exception { 32 | 33 | // 获取处理method 34 | if (handler instanceof HandlerMethod == false) { 35 | return true; 36 | } 37 | Method method = ((HandlerMethod) handler).getMethod(); 38 | 39 | // 进行验证 40 | SaManager.getSaTokenAction().checkMethodAnnotation(method); 41 | 42 | // 通过验证 43 | return true; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /sa-token-starter/sa-token-servlet/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | cn.dev33 9 | sa-token-starter 10 | 1.19.0 11 | 12 | jar 13 | 14 | sa-token-servlet 15 | sa-token-servlet 16 | sa-token authentication by Sservlet API 17 | 18 | 19 | 20 | 21 | cn.dev33 22 | sa-token-core 23 | ${sa-token-version} 24 | 25 | 26 | 27 | javax.servlet 28 | javax.servlet-api 29 | 3.1.0 30 | true 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /sa-token-demo/sa-token-demo-webflux/src/main/java/com/pj/satoken/SaTokenConfigure.java: -------------------------------------------------------------------------------- 1 | package com.pj.satoken; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | 6 | import com.pj.util.AjaxJson; 7 | 8 | import cn.dev33.satoken.reactor.filter.SaReactorFilter; 9 | 10 | /** 11 | * [Sa-Token 权限认证] 配置类 12 | * @author kong 13 | * 14 | */ 15 | @Configuration 16 | public class SaTokenConfigure { 17 | 18 | /** 19 | * 注册 [sa-token全局过滤器] 20 | */ 21 | @Bean 22 | public SaReactorFilter getSaReactorFilter() { 23 | return new SaReactorFilter() 24 | // 指定 [拦截路由] 25 | .addInclude("/**") 26 | // 指定 [放行路由] 27 | .addExclude("/favicon.ico") 28 | // 指定[认证函数]: 每次请求执行 29 | .setAuth(r -> { 30 | System.out.println("---------- sa全局认证"); 31 | // SaRouterUtil.match("/test/test", () -> StpUtil.checkLogin()); 32 | }) 33 | // 指定[异常处理函数]:每次[认证函数]发生异常时执行此函数 34 | .setError(e -> { 35 | System.out.println("---------- sa全局异常 "); 36 | return AjaxJson.getError(e.getMessage()); 37 | }) 38 | ; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /sa-token-demo/sa-token-demo-oauth2-server/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8001 3 | 4 | spring: 5 | # 静态文件路径映射 6 | resources: 7 | static-locations: classpath:/META-INF/resources/,classpath:/resources/, classpath:/static/, classpath:/public/ 8 | # static-locations: file:E:\work\project-yun\sa-token\sa-token-demo-oauth2\sa-token-demo-oauth2-server\src\main\resources\static\ 9 | 10 | # sa-token配置 11 | sa-token: 12 | # token名称 (同时也是cookie名称) 13 | token-name: satoken-server 14 | 15 | 16 | # redis配置 17 | redis: 18 | # Redis数据库索引(默认为0) 19 | database: 1 20 | # Redis服务器地址 21 | host: 127.0.0.1 22 | # Redis服务器连接端口 23 | port: 6379 24 | # Redis服务器连接密码(默认为空) 25 | # password: 26 | # 连接超时时间(毫秒) 27 | timeout: 1000ms 28 | lettuce: 29 | pool: 30 | # 连接池最大连接数 31 | max-active: 200 32 | # 连接池最大阻塞等待时间(使用负值表示没有限制) 33 | max-wait: -1ms 34 | # 连接池中的最大空闲连接 35 | max-idle: 10 36 | # 连接池中的最小空闲连接 37 | min-idle: 0 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/model/ScopeModel.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.oauth2.model; 2 | 3 | /** 4 | * 权限Model 5 | * @author kong 6 | * 7 | */ 8 | public class ScopeModel { 9 | 10 | /** 11 | * 权限名称 12 | */ 13 | private String name; 14 | 15 | /** 16 | * 详细介绍 17 | */ 18 | private String introduce; 19 | 20 | 21 | /** 22 | * 构造一个 23 | */ 24 | public ScopeModel() { 25 | super(); 26 | } 27 | /** 28 | * 构造一个 29 | * @param name 权限名称 30 | * @param introduce 权限详细介绍 31 | */ 32 | public ScopeModel(String name, String introduce) { 33 | super(); 34 | this.name = name; 35 | this.introduce = introduce; 36 | } 37 | 38 | /** 39 | * @return name 40 | */ 41 | public String getName() { 42 | return name; 43 | } 44 | 45 | /** 46 | * @param name 要设置的 name 47 | */ 48 | public void setName(String name) { 49 | this.name = name; 50 | } 51 | 52 | /** 53 | * @return introduce 54 | */ 55 | public String getIntroduce() { 56 | return introduce; 57 | } 58 | 59 | /** 60 | * @param introduce 要设置的 introduce 61 | */ 62 | public void setIntroduce(String introduce) { 63 | this.introduce = introduce; 64 | } 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | } 74 | -------------------------------------------------------------------------------- /sa-token-starter/sa-token-servlet/src/main/java/cn/dev33/satoken/servlet/model/SaStorageForServlet.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.servlet.model; 2 | 3 | import javax.servlet.http.HttpServletRequest; 4 | 5 | import cn.dev33.satoken.context.model.SaStorage; 6 | 7 | /** 8 | * Storage for Servlet 9 | * @author kong 10 | * 11 | */ 12 | public class SaStorageForServlet implements SaStorage { 13 | 14 | /** 15 | * 底层Request对象 16 | */ 17 | HttpServletRequest request; 18 | 19 | /** 20 | * 实例化 21 | * @param request request对象 22 | */ 23 | public SaStorageForServlet(HttpServletRequest request) { 24 | this.request = request; 25 | } 26 | 27 | /** 28 | * 获取底层源对象 29 | */ 30 | @Override 31 | public Object getSource() { 32 | return request; 33 | } 34 | 35 | /** 36 | * 在 [Request作用域] 里写入一个值 37 | */ 38 | @Override 39 | public void set(String key, Object value) { 40 | request.setAttribute(key, value); 41 | } 42 | 43 | /** 44 | * 在 [Request作用域] 里获取一个值 45 | */ 46 | @Override 47 | public Object get(String key) { 48 | return request.getAttribute(key); 49 | } 50 | 51 | /** 52 | * 在 [Request作用域] 里删除一个值 53 | */ 54 | @Override 55 | public void delete(String key) { 56 | request.removeAttribute(key); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /sa-token-plugin/sa-token-spring-aop/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | cn.dev33 9 | sa-token-plugin 10 | 1.19.0 11 | 12 | jar 13 | 14 | sa-token-spring-aop 15 | sa-token-spring-aop 16 | sa-token authentication by spring-aop 17 | 18 | 19 | 20 | 21 | cn.dev33 22 | sa-token-spring-boot-starter 23 | ${sa-token-version} 24 | 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-aop 29 | 2.0.0.RELEASE 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /sa-token-plugin/sa-token-dao-redis/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | cn.dev33 9 | sa-token-plugin 10 | 1.19.0 11 | 12 | jar 13 | 14 | sa-token-dao-redis 15 | sa-token-dao-redis 16 | sa-token integrate redis 17 | 18 | 19 | 20 | 21 | cn.dev33 22 | sa-token-core 23 | ${sa-token-version} 24 | 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-data-redis 29 | 2.3.3.RELEASE 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /sa-token-demo/sa-token-demo-webflux/src/main/java/com/pj/SaTokenWebfluxDemoApplication.java: -------------------------------------------------------------------------------- 1 | package com.pj; 2 | 3 | //import org.springframework.boot.SpringApplication; 4 | //import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | //import org.springframework.context.annotation.Bean; 6 | //import org.springframework.http.MediaType; 7 | //import org.springframework.web.reactive.function.server.RequestPredicates; 8 | //import org.springframework.web.reactive.function.server.RouterFunction; 9 | //import org.springframework.web.reactive.function.server.RouterFunctions; 10 | //import org.springframework.web.reactive.function.server.ServerRequest; 11 | //import org.springframework.web.reactive.function.server.ServerResponse; 12 | 13 | 14 | import org.springframework.boot.SpringApplication; 15 | import org.springframework.boot.autoconfigure.SpringBootApplication; 16 | 17 | import cn.dev33.satoken.SaManager; 18 | 19 | /** 20 | * sa-token整合webflux 示例 21 | * @author kong 22 | * 23 | */ 24 | @SpringBootApplication 25 | public class SaTokenWebfluxDemoApplication { 26 | 27 | public static void main(String[] args) { 28 | SpringApplication.run(SaTokenWebfluxDemoApplication.class, args); 29 | System.out.println("\n启动成功:sa-token配置如下:" + SaManager.getConfig()); 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /sa-token-demo/sa-token-demo-jwt/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | # 端口 2 | server: 3 | port: 8081 4 | 5 | spring: 6 | # sa-token配置 7 | sa-token: 8 | # token名称 (同时也是cookie名称) 9 | token-name: satoken 10 | # token有效期,单位s 默认30天, -1代表永不过期 11 | timeout: 2592000 12 | # token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒 13 | activity-timeout: -1 14 | # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录) 15 | allow-concurrent-login: true 16 | # 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token) 17 | is-share: true 18 | # token风格 19 | token-style: uuid 20 | # redis配置 21 | redis: 22 | # Redis数据库索引(默认为0) 23 | database: 0 24 | # Redis服务器地址 25 | host: 127.0.0.1 26 | # Redis服务器连接端口 27 | port: 6379 28 | # Redis服务器连接密码(默认为空) 29 | password: 30 | # 连接超时时间(毫秒) 31 | timeout: 10000ms 32 | lettuce: 33 | pool: 34 | # 连接池最大连接数 35 | max-active: 200 36 | # 连接池最大阻塞等待时间(使用负值表示没有限制) 37 | max-wait: -1ms 38 | # 连接池中的最大空闲连接 39 | max-idle: 10 40 | # 连接池中的最小空闲连接 41 | min-idle: 0 42 | 43 | -------------------------------------------------------------------------------- /sa-token-core/src/main/java/cn/dev33/satoken/context/SaTokenContextDefaultImpl.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.context; 2 | 3 | import cn.dev33.satoken.context.model.SaRequest; 4 | import cn.dev33.satoken.context.model.SaStorage; 5 | import cn.dev33.satoken.context.model.SaResponse; 6 | import cn.dev33.satoken.exception.SaTokenException; 7 | 8 | /** 9 | * Sa-Token 上下文处理器 [默认实现类] 10 | * @author kong 11 | * 12 | */ 13 | public class SaTokenContextDefaultImpl implements SaTokenContext { 14 | 15 | /** 16 | * 默认的错误提示语 17 | */ 18 | public static final String ERROR_MESSAGE = "未初始化任何有效上下文处理器"; 19 | 20 | /** 21 | * 获取当前请求的 [Request] 对象 22 | */ 23 | @Override 24 | public SaRequest getRequest() { 25 | throw new SaTokenException(ERROR_MESSAGE); 26 | } 27 | 28 | /** 29 | * 获取当前请求的 [Response] 对象 30 | */ 31 | @Override 32 | public SaResponse getResponse() { 33 | throw new SaTokenException(ERROR_MESSAGE); 34 | } 35 | 36 | /** 37 | * 获取当前请求的 [存储器] 对象 38 | */ 39 | @Override 40 | public SaStorage getStorage() { 41 | throw new SaTokenException(ERROR_MESSAGE); 42 | } 43 | 44 | /** 45 | * 校验指定路由匹配符是否可以匹配成功指定路径 46 | */ 47 | @Override 48 | public boolean matchPath(String pattern, String path) { 49 | throw new SaTokenException(ERROR_MESSAGE); 50 | } 51 | 52 | 53 | 54 | } 55 | -------------------------------------------------------------------------------- /sa-token-starter/sa-token-reactor-spring-boot-starter/src/main/java/cn/dev33/satoken/reactor/model/SaStorageForReactor.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.reactor.model; 2 | 3 | import org.springframework.web.server.ServerWebExchange; 4 | 5 | import cn.dev33.satoken.context.model.SaStorage; 6 | 7 | /** 8 | * Storage for Reactor 9 | * @author kong 10 | * 11 | */ 12 | public class SaStorageForReactor implements SaStorage { 13 | 14 | /** 15 | * 底层Request对象 16 | */ 17 | ServerWebExchange exchange; 18 | 19 | /** 20 | * 实例化 21 | * @param exchange exchange对象 22 | */ 23 | public SaStorageForReactor(ServerWebExchange exchange) { 24 | this.exchange = exchange; 25 | } 26 | 27 | /** 28 | * 获取底层源对象 29 | */ 30 | @Override 31 | public Object getSource() { 32 | return exchange; 33 | } 34 | 35 | /** 36 | * 在 [Request作用域] 里写入一个值 37 | */ 38 | @Override 39 | public void set(String key, Object value) { 40 | exchange.getAttributes().put(key, value); 41 | } 42 | 43 | /** 44 | * 在 [Request作用域] 里获取一个值 45 | */ 46 | @Override 47 | public Object get(String key) { 48 | return exchange.getAttributes().get(key); 49 | } 50 | 51 | /** 52 | * 在 [Request作用域] 里删除一个值 53 | */ 54 | @Override 55 | public void delete(String key) { 56 | exchange.getAttributes().remove(key); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /sa-token-demo/sa-token-demo-webflux/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | # 端口 2 | server: 3 | port: 8081 4 | 5 | spring: 6 | # sa-token配置 7 | sa-token: 8 | # token名称 (同时也是cookie名称) 9 | token-name: satoken 10 | # token有效期,单位s 默认30天, -1代表永不过期 11 | timeout: 2592000 12 | # token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒 13 | activity-timeout: -1 14 | # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录) 15 | allow-concurrent-login: true 16 | # 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token) 17 | is-share: true 18 | # token风格 19 | token-style: uuid 20 | 21 | 22 | # redis配置 23 | redis: 24 | # Redis数据库索引(默认为0) 25 | database: 0 26 | # Redis服务器地址 27 | host: 127.0.0.1 28 | # Redis服务器连接端口 29 | port: 6379 30 | # Redis服务器连接密码(默认为空) 31 | password: 32 | # 连接超时时间(毫秒) 33 | timeout: 10000ms 34 | lettuce: 35 | pool: 36 | # 连接池最大连接数 37 | max-active: 200 38 | # 连接池最大阻塞等待时间(使用负值表示没有限制) 39 | max-wait: -1ms 40 | # 连接池中的最大空闲连接 41 | max-idle: 10 42 | # 连接池中的最小空闲连接 43 | min-idle: 0 44 | 45 | -------------------------------------------------------------------------------- /sa-token-starter/sa-token-spring-boot-starter/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | cn.dev33 9 | sa-token-starter 10 | 1.19.0 11 | 12 | jar 13 | 14 | sa-token-spring-boot-starter 15 | sa-token-spring-boot-starter 16 | springboot integrate sa-token 17 | 18 | 19 | 20 | cn.dev33 21 | sa-token-servlet 22 | ${sa-token-version} 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-web 27 | 2.0.0.RELEASE 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-configuration-processor 32 | 2.0.0.RELEASE 33 | true 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /sa-token-demo/sa-token-demo-springboot/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | # 端口 2 | server: 3 | port: 8081 4 | 5 | spring: 6 | # sa-token配置 7 | sa-token: 8 | # token名称 (同时也是cookie名称) 9 | token-name: satoken 10 | # token有效期,单位s 默认30天, -1代表永不过期 11 | timeout: 2592000 12 | # token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒 13 | activity-timeout: -1 14 | # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录) 15 | allow-concurrent-login: true 16 | # 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token) 17 | is-share: true 18 | # token风格 19 | token-style: uuid 20 | # 是否输出操作日志 21 | is-log: false 22 | 23 | # redis配置 24 | redis: 25 | # Redis数据库索引(默认为0) 26 | database: 0 27 | # Redis服务器地址 28 | host: 127.0.0.1 29 | # Redis服务器连接端口 30 | port: 6379 31 | # Redis服务器连接密码(默认为空) 32 | password: 33 | # 连接超时时间(毫秒) 34 | timeout: 10000ms 35 | lettuce: 36 | pool: 37 | # 连接池最大连接数 38 | max-active: 200 39 | # 连接池最大阻塞等待时间(使用负值表示没有限制) 40 | max-wait: -1ms 41 | # 连接池中的最大空闲连接 42 | max-idle: 10 43 | # 连接池中的最小空闲连接 44 | min-idle: 0 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /sa-token-starter/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/spring/SpringMVCUtil.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.spring; 2 | 3 | import javax.servlet.http.HttpServletRequest; 4 | import javax.servlet.http.HttpServletResponse; 5 | 6 | import org.springframework.web.context.request.RequestContextHolder; 7 | import org.springframework.web.context.request.ServletRequestAttributes; 8 | 9 | import cn.dev33.satoken.exception.SaTokenException; 10 | 11 | /** 12 | * SpringMVC相关操作 13 | * @author kong 14 | * 15 | */ 16 | public class SpringMVCUtil { 17 | 18 | /** 19 | * 获取当前会话的 request 20 | * @return request 21 | */ 22 | public static HttpServletRequest getRequest() { 23 | ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); 24 | if(servletRequestAttributes == null) { 25 | throw new SaTokenException("非Web上下文无法获取Request"); 26 | } 27 | return servletRequestAttributes.getRequest(); 28 | } 29 | 30 | /** 31 | * 获取当前会话的 response 32 | * @return response 33 | */ 34 | public static HttpServletResponse getResponse() { 35 | ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); 36 | if(servletRequestAttributes == null) { 37 | throw new SaTokenException("非Web上下文无法获取Response"); 38 | } 39 | return servletRequestAttributes.getResponse(); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /sa-token-demo/sa-token-demo-oauth2-server/src/main/resources/static/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 服务提供方-登录页 6 | 10 | 11 | 12 | 20 | 21 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /sa-token-doc/doc/use/mock-person.md: -------------------------------------------------------------------------------- 1 | # 模拟他人 2 | --- 3 | 4 | 5 | 以上介绍的api都是操作当前账号,对当前账号进行各种鉴权操作,你可能会问,我能不能对别的账号进行一些操作?
6 | 比如:查看账号`10001`有无某个权限码、获取id账号为`10002`的用户`User-Session`,等等... 7 | 8 | sa-token在api设计时充分考虑了这一点,暴露出多个api进行此类操作 9 | 10 | 11 | ## 有关操作其它账号的api 12 | 13 | ``` java 14 | // 获取指定账号10001的`tokenValue`值 15 | StpUtil.getTokenValueByLoginId(10001); 16 | 17 | // 将账号10001的会话注销登录(踢人下线) 18 | StpUtil.logoutByLoginId(10001); 19 | 20 | // 获取账号10001的Session对象, 如果session尚未创建, 则新建并返回 21 | StpUtil.getSessionByLoginId(10001); 22 | 23 | // 获取账号10001的Session对象, 如果session尚未创建, 则返回null 24 | StpUtil.getSessionByLoginId(10001, false); 25 | 26 | // 获取账号10001是否含有指定角色标识 27 | StpUtil.hasRole(10001, "super-admin"); 28 | 29 | // 获取账号10001是否含有指定权限码 30 | StpUtil.hasPermission(10001, "user:add"); 31 | ``` 32 | 33 | 34 | 35 | ## 临时身份切换 36 | 37 | 有时候,我们需要直接将当前会话的身份切换为其它账号,比如: 38 | ``` java 39 | // 将当前会话[身份临时切换]为其它账号 40 | StpUtil.switchTo(10044); 41 | 42 | // 此时再调用此方法会返回 10044 (我们临时切换到的账号id) 43 | StpUtil.getLoginId(); 44 | 45 | // 结束 [身份临时切换] 46 | StpUtil.endSwitch(); 47 | ``` 48 | 49 | 你还可以: 直接在一个代码段里方法内,临时切换身份为指定loginId (此方式无需手动调用`StpUtil.endSwitch()`关闭身份切换) 50 | ``` java 51 | System.out.println("------- [身份临时切换]调用开始..."); 52 | StpUtil.switchTo(10044, () -> { 53 | System.out.println("是否正在身份临时切换中: " + StpUtil.isSwitch()); 54 | System.out.println("获取当前登录账号id: " + StpUtil.getLoginId()); 55 | }); 56 | System.out.println("------- [身份临时切换]调用结束..."); 57 | ``` 58 | 59 | 60 | -------------------------------------------------------------------------------- /sa-token-demo/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2SpringAutowired.java: -------------------------------------------------------------------------------- 1 | package com.pj.oauth2; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.boot.context.properties.ConfigurationProperties; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.stereotype.Component; 7 | 8 | import cn.dev33.satoken.oauth2.SaOAuth2Manager; 9 | import cn.dev33.satoken.oauth2.config.SaOAuth2Config; 10 | import cn.dev33.satoken.oauth2.logic.SaOAuth2Interface; 11 | 12 | /** 13 | * 利用Spring完成自动装配 14 | * 15 | * @author kong 16 | * 17 | */ 18 | @Component 19 | public class SaOAuth2SpringAutowired { 20 | 21 | /** 22 | * 获取OAuth2配置Bean 23 | * 24 | * @return 配置对象 25 | */ 26 | @Bean 27 | @ConfigurationProperties(prefix = "spring.sa-token.oauth2") 28 | public SaOAuth2Config getSaOAuth2Config() { 29 | return new SaOAuth2Config(); 30 | } 31 | 32 | /** 33 | * 注入OAuth2配置Bean 34 | * 35 | * @param saOAuth2Config 配置对象 36 | */ 37 | @Autowired 38 | public void setSaOAuth2Config(SaOAuth2Config saOAuth2Config) { 39 | SaOAuth2Manager.setConfig(saOAuth2Config); 40 | } 41 | 42 | /** 43 | * 注入OAuth2接口Bean 44 | * 45 | * @param saOAuth2Interface OAuth2接口Bean 46 | */ 47 | @Autowired(required = false) 48 | public void setSaOAuth2Interface(SaOAuth2Interface saOAuth2Interface) { 49 | SaOAuth2Manager.setInterface(saOAuth2Interface); 50 | } 51 | 52 | 53 | } 54 | -------------------------------------------------------------------------------- /sa-token-starter/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/spring/SaTokenContextForSpring.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.spring; 2 | 3 | import cn.dev33.satoken.context.SaTokenContext; 4 | import cn.dev33.satoken.context.model.SaRequest; 5 | import cn.dev33.satoken.context.model.SaResponse; 6 | import cn.dev33.satoken.context.model.SaStorage; 7 | import cn.dev33.satoken.servlet.model.SaRequestForServlet; 8 | import cn.dev33.satoken.servlet.model.SaResponseForServlet; 9 | import cn.dev33.satoken.servlet.model.SaStorageForServlet; 10 | 11 | /** 12 | * sa-token 对Cookie的相关操作 接口实现类 13 | * 14 | * @author kong 15 | * 16 | */ 17 | public class SaTokenContextForSpring implements SaTokenContext { 18 | 19 | /** 20 | * 获取当前请求的Request对象 21 | */ 22 | @Override 23 | public SaRequest getRequest() { 24 | return new SaRequestForServlet(SpringMVCUtil.getRequest()); 25 | } 26 | 27 | /** 28 | * 获取当前请求的Response对象 29 | */ 30 | @Override 31 | public SaResponse getResponse() { 32 | return new SaResponseForServlet(SpringMVCUtil.getResponse()); 33 | } 34 | 35 | /** 36 | * 获取当前请求的 [存储器] 对象 37 | */ 38 | @Override 39 | public SaStorage getStorage() { 40 | return new SaStorageForServlet(SpringMVCUtil.getRequest()); 41 | } 42 | 43 | /** 44 | * 校验指定路由匹配符是否可以匹配成功指定路径 45 | */ 46 | @Override 47 | public boolean matchPath(String pattern, String path) { 48 | return SaPathMatcherHolder.getPathMatcher().match(pattern, path); 49 | } 50 | 51 | 52 | 53 | } 54 | -------------------------------------------------------------------------------- /sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/SaOAuth2Manager.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.oauth2; 2 | 3 | import cn.dev33.satoken.oauth2.config.SaOAuth2Config; 4 | import cn.dev33.satoken.oauth2.logic.SaOAuth2Interface; 5 | import cn.dev33.satoken.oauth2.logic.SaOAuth2InterfaceDefaultImpl; 6 | 7 | /** 8 | * sa-token oauth2 模块 总控类 9 | * 10 | * @author kong 11 | * 12 | */ 13 | public class SaOAuth2Manager { 14 | 15 | /** 16 | * OAuth2 配置 Bean 17 | */ 18 | private static SaOAuth2Config config; 19 | public static SaOAuth2Config getConfig() { 20 | if (config == null) { 21 | // 初始化默认值 22 | synchronized (SaOAuth2Manager.class) { 23 | if (config == null) { 24 | setConfig(new SaOAuth2Config()); 25 | } 26 | } 27 | } 28 | return config; 29 | } 30 | public static void setConfig(SaOAuth2Config config) { 31 | SaOAuth2Manager.config = config; 32 | } 33 | 34 | /** 35 | * sa-token-oauth2 逻辑 Bean 36 | */ 37 | private static SaOAuth2Interface saOAuth2Interface; 38 | public static SaOAuth2Interface getInterface() { 39 | if (saOAuth2Interface == null) { 40 | // 初始化默认值 41 | synchronized (SaOAuth2Manager.class) { 42 | if (saOAuth2Interface == null) { 43 | setInterface(new SaOAuth2InterfaceDefaultImpl()); 44 | } 45 | } 46 | } 47 | return saOAuth2Interface; 48 | } 49 | public static void setInterface(SaOAuth2Interface interfaceObj) { 50 | SaOAuth2Manager.saOAuth2Interface = interfaceObj; 51 | } 52 | 53 | 54 | } 55 | -------------------------------------------------------------------------------- /sa-token-plugin/sa-token-quick-login/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | cn.dev33 9 | sa-token-plugin 10 | 1.19.0 11 | 12 | jar 13 | 14 | sa-token-quick-login 15 | sa-token-quick-login 16 | sa-token-quick-login 17 | 18 | 19 | 20 | 21 | cn.dev33 22 | sa-token-spring-boot-starter 23 | ${sa-token-version} 24 | 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-thymeleaf 29 | 2.0.0.RELEASE 30 | 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-configuration-processor 35 | 2.0.0.RELEASE 36 | true 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /sa-token-doc/doc/fun/token-timeout.md: -------------------------------------------------------------------------------- 1 | # token有效期详解 2 | 3 | 4 | 5 | `sa-token` 提供两种token自动过期策略,分别是`timeout`与`activity-timeout`,其详细用法如下: 6 | 7 | 8 | ### timeout 9 | 1. `timeout`代表token的长久有效期,单位/秒,例如将其配置为`2592000`(30天),代表在30天后,token必定过期,无法继续使用 10 | 2. `timeout`无法续签,想要继续使用必须重新登录 11 | 3. `timeout`的值配置为-1后,代表永久有效,不会过期 12 | 13 | 14 | ### activity-timeout 15 | 1. `activity-timeout`代表临时有效期,单位/秒,例如将其配置为`1800`(30分钟),代表用户如果30分钟无操作,则此token会立即过期 16 | 2. 如果在30分钟内用户有操作,则会再次续签30分钟,用户如果一直操作则会一直续签,直到连续30分钟无操作,token才会过期 17 | 3. `activity-timeout`的值配置为-1后,代表永久有效,不会过期,此时也无需频繁续签 18 | 19 | 20 | ### 关于activity-timeout的续签 21 | 如果`activity-timeout`配置了大于零的值,`sa-token`会在登录时开始计时,在每次直接或间接调用`getLoginId()`时进行一次过期检查与续签操作。 22 | 此时会有两种情况: 23 | 1. 一种是会话无操作时间太长,token已经过期,此时框架会抛出`NotLoginException`异常(场景值=-3), 24 | 2. 另一种则是会话在`activity-timeout`有效期内通过检查,此时token可以成功续签 25 | 26 | 27 | ### 我可以手动续签吗? 28 | **可以!** 29 | 如果框架的自动续签算法无法满足您的业务需求,你可以进行手动续签,`sa-token`提供两个API供你操作: 30 | 1. `StpUtil.checkActivityTimeout()`: 检查当前token 是否已经[临时过期],如果已经过期则抛出异常 31 | 2. `StpUtil.updateLastActivityToNow()`: 续签当前token:(将 [最后操作时间] 更新为当前时间戳) 32 | 33 | 注意:在手动续签时,即时token已经 [临时过期] 也可续签成功,如果此场景下需要提示续签失败,可采用先检查再续签的形式保证token有效性 34 | 35 | 例如以下代码: 36 | ``` java 37 | // 先检查是否已过期 38 | StpUtil.checkActivityTimeout(); 39 | // 检查通过后继续续签 40 | StpUtil.updateLastActivityToNow(); 41 | ``` 42 | 43 | 同时,你还可以关闭框架的自动续签(在配置文件中配置 `autoRenew=false` ),此时续签操作完全由开发者控制,框架不再自动进行任何续签操作 44 | 45 | 46 | ### timeout与activity-timeout可以同时使用吗? 47 | **可以同时使用!** 48 | 两者的认证逻辑彼此独立,互不干扰,可以同时使用。 49 | 50 | -------------------------------------------------------------------------------- /sa-token-core/src/main/java/cn/dev33/satoken/secure/SaBase64Util.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.secure; 2 | 3 | import java.io.UnsupportedEncodingException; 4 | import java.util.Base64; 5 | 6 | /** 7 | * Base64工具类 8 | * @author kong 9 | * 10 | */ 11 | public class SaBase64Util { 12 | 13 | private static Base64.Encoder encoder = Base64.getEncoder(); 14 | private static Base64.Decoder decoder = Base64.getDecoder(); 15 | 16 | /** 17 | * Base64编码,byte[] 转 String 18 | * @param bytes byte[] 19 | * @return 字符串 20 | */ 21 | public static String encodeBytesToString(byte[] bytes){ 22 | return encoder.encodeToString(bytes); 23 | } 24 | 25 | /** 26 | * Base64解码,String 转 byte[] 27 | * @param text 字符串 28 | * @return byte[] 29 | */ 30 | public static byte[] decodeStringToBytes(String text){ 31 | return decoder.decode(text); 32 | } 33 | 34 | /** 35 | * Base64编码,String 转 String 36 | * @param text 字符串 37 | * @return Base64格式字符串 38 | */ 39 | public static String encode(String text){ 40 | try { 41 | return encoder.encodeToString(text.getBytes("UTF-8")); 42 | } catch (UnsupportedEncodingException e) { 43 | throw new RuntimeException(e); 44 | } 45 | } 46 | 47 | /** 48 | * Base64解码,String 转 String 49 | * @param base64Text Base64格式字符串 50 | * @return 字符串 51 | */ 52 | public static String decode(String base64Text){ 53 | try { 54 | return new String(decoder.decode(base64Text), "UTF-8"); 55 | } catch (UnsupportedEncodingException e) { 56 | throw new RuntimeException(e); 57 | } 58 | } 59 | 60 | 61 | } 62 | -------------------------------------------------------------------------------- /sa-token-plugin/sa-token-dao-redis-jackson/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | cn.dev33 9 | sa-token-plugin 10 | 1.19.0 11 | 12 | jar 13 | 14 | sa-token-dao-redis-jackson 15 | sa-token-dao-redis-jackson 16 | sa-token integrate redis (to jackson) 17 | 18 | 19 | 20 | 21 | cn.dev33 22 | sa-token-core 23 | ${sa-token-version} 24 | 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-data-redis 29 | 2.3.3.RELEASE 30 | 31 | 32 | 33 | com.fasterxml.jackson.core 34 | jackson-databind 35 | 2.11.2 36 | true 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /sa-token-core/src/main/java/cn/dev33/satoken/exception/DisableLoginException.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.exception; 2 | 3 | /** 4 | * 一个异常:代表账号已被封禁 5 | * 6 | * @author kong 7 | */ 8 | public class DisableLoginException extends SaTokenException { 9 | 10 | /** 11 | * 序列化版本号 12 | */ 13 | private static final long serialVersionUID = 6806129545290130143L; 14 | 15 | /** 异常标记值 */ 16 | public static final String BE_VALUE = "disable"; 17 | 18 | /** 异常提示语 */ 19 | public static final String BE_MESSAGE = "此账号已被封禁"; 20 | 21 | /** 22 | * LoginKey 23 | */ 24 | private String loginKey; 25 | 26 | /** 27 | * 被封禁的账号id 28 | */ 29 | private Object loginId; 30 | 31 | /** 32 | * 封禁剩余时间,单位:秒 33 | */ 34 | private long disableTime; 35 | 36 | /** 37 | * 获得LoginKey 38 | * 39 | * @return See above 40 | */ 41 | public String getLoginKey() { 42 | return loginKey; 43 | } 44 | 45 | /** 46 | * 获取: 被封禁的账号id 47 | * 48 | * @return See above 49 | */ 50 | public Object getLoginId() { 51 | return loginId; 52 | } 53 | 54 | /** 55 | * 获取: 封禁剩余时间,单位:秒 56 | * @return See above 57 | */ 58 | public long getDisableTime() { 59 | return disableTime; 60 | } 61 | 62 | /** 63 | * 构造方法创建一个 64 | * 65 | * @param loginKey loginKey 66 | * @param loginId 被封禁的账号id 67 | * @param disableTime 封禁剩余时间,单位:秒 68 | */ 69 | public DisableLoginException(String loginKey, Object loginId, long disableTime) { 70 | super(BE_MESSAGE); 71 | this.loginId = loginId; 72 | this.loginKey = loginKey; 73 | this.disableTime = disableTime; 74 | } 75 | 76 | 77 | 78 | } 79 | -------------------------------------------------------------------------------- /sa-token-demo/sa-token-demo-quick-login/src/main/java/com/pj/SaQuicikStartup.java: -------------------------------------------------------------------------------- 1 | package com.pj; 2 | 3 | import java.text.SimpleDateFormat; 4 | import java.util.Date; 5 | 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.boot.CommandLineRunner; 8 | import org.springframework.stereotype.Component; 9 | 10 | import cn.dev33.satoken.quick.SaQuickManager; 11 | 12 | /** 13 | * springboot启动之后 14 | * @author kong 15 | * 16 | */ 17 | @Component 18 | public class SaQuicikStartup implements CommandLineRunner { 19 | 20 | @Value("${spring.application.name:sa-quick}") 21 | private String applicationName; 22 | 23 | @Value("${server.port:8080}") 24 | private String port; 25 | 26 | @Value("${server.servlet.context-path:}") 27 | private String path; 28 | 29 | // @Value("${spring.profiles.active:}") 30 | // private String active; 31 | 32 | @Override 33 | public void run(String... args) throws Exception { 34 | String str = "\n------------- " + applicationName + " 启动成功 (" + getNow() + ") -------------\n" + 35 | " - home: " + "http://localhost:" + port + path + "\n" + 36 | " - name: " + SaQuickManager.getConfig().getName() + "\n"+ 37 | " - pwd : " + SaQuickManager.getConfig().getPwd() + "\n"; 38 | System.out.println(str); 39 | } 40 | 41 | 42 | 43 | /** 44 | * 返回系统当前时间的YYYY-MM-dd hh:mm:ss 字符串格式 45 | */ 46 | private static String getNow(){ 47 | return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()); 48 | } 49 | 50 | 51 | } 52 | 53 | -------------------------------------------------------------------------------- /sa-token-plugin/sa-token-quick-login/src/main/resources/templates/sa-login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 登录 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 |
14 |
15 | 32 |
33 | 34 |
35 | This page is provided by Sa-Token 36 |
37 |
38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /sa-token-doc/doc/use/global-listener.md: -------------------------------------------------------------------------------- 1 | # 全局侦听器 2 | 3 | 接口`SaTokenListener`是Sa-Token的全局侦听器,通过实现此接口,你可以在用户登陆、退出、被踢下线等关键性操作时进行一些AOP操作 4 | 5 | 框架对此侦听器的默认实现是log日志输出,你可以通过配置`spring.sa-token.is-log=true`开启 6 | 7 | 下面我们演示一下如何自定义侦听器的实现: 8 | 9 | --- 10 | 11 | 12 | ### 自定义侦听器实现 13 | 14 | 新建`MySaTokenListener.java`,继承`SaTokenListener`接口, 并添加上注解`@Component`,保证此类被`SpringBoot`扫描到 15 | ``` java 16 | /** 17 | * 自定义侦听器的实现 18 | */ 19 | @Component 20 | public class MySaTokenListener implements SaTokenListener { 21 | 22 | /** 每次登录时触发 */ 23 | @Override 24 | public void doLogin(String loginKey, Object loginId, SaLoginModel loginModel) { 25 | // ... 26 | } 27 | 28 | /** 每次注销时触发 */ 29 | @Override 30 | public void doLogout(String loginKey, Object loginId, String tokenValue) { 31 | // ... 32 | } 33 | 34 | /** 每次被踢下线时触发 */ 35 | @Override 36 | public void doLogoutByLoginId(String loginKey, Object loginId, String tokenValue, String device) { 37 | // ... 38 | } 39 | 40 | /** 每次被顶下线时触发 */ 41 | @Override 42 | public void doReplaced(String loginKey, Object loginId, String tokenValue, String device) { 43 | // ... 44 | } 45 | 46 | /** 每次被封禁时触发 */ 47 | @Override 48 | public void doDisable(String loginKey, Object loginId, long disableTime) { 49 | // ... 50 | } 51 | 52 | /** 每次被解封时触发 */ 53 | @Override 54 | public void doUntieDisable(String loginKey, Object loginId) { 55 | // ... 56 | } 57 | 58 | /** 每次创建Session时触发 */ 59 | @Override 60 | public void doCreateSession(String id) { 61 | // ... 62 | } 63 | 64 | /** 每次注销Session时触发 */ 65 | @Override 66 | public void doLogoutSession(String id) { 67 | // ... 68 | } 69 | 70 | } 71 | ``` 72 | 73 | -------------------------------------------------------------------------------- /sa-token-starter/sa-token-servlet/src/main/java/cn/dev33/satoken/servlet/model/SaResponseForServlet.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.servlet.model; 2 | 3 | import javax.servlet.http.Cookie; 4 | import javax.servlet.http.HttpServletResponse; 5 | 6 | import cn.dev33.satoken.context.model.SaResponse; 7 | import cn.dev33.satoken.util.SaFoxUtil; 8 | 9 | /** 10 | * Response for Servlet 11 | * @author kong 12 | * 13 | */ 14 | public class SaResponseForServlet implements SaResponse { 15 | 16 | /** 17 | * 底层Request对象 18 | */ 19 | HttpServletResponse response; 20 | 21 | /** 22 | * 实例化 23 | * @param response response对象 24 | */ 25 | public SaResponseForServlet(HttpServletResponse response) { 26 | this.response = response; 27 | } 28 | 29 | /** 30 | * 获取底层源对象 31 | */ 32 | @Override 33 | public Object getSource() { 34 | return response; 35 | } 36 | 37 | /** 38 | * 删除指定Cookie 39 | */ 40 | @Override 41 | public void deleteCookie(String name) { 42 | addCookie(name, null, null, null, 0); 43 | } 44 | 45 | /** 46 | * 写入指定Cookie 47 | */ 48 | @Override 49 | public void addCookie(String name, String value, String path, String domain, int timeout) { 50 | Cookie cookie = new Cookie(name, value); 51 | if(SaFoxUtil.isEmpty(path) == true) { 52 | path = "/"; 53 | } 54 | if(SaFoxUtil.isEmpty(domain) == false) { 55 | cookie.setDomain(domain); 56 | } 57 | cookie.setPath(path); 58 | cookie.setMaxAge(timeout); 59 | response.addCookie(cookie); 60 | } 61 | 62 | 63 | /** 64 | * 在响应头里写入一个值 65 | */ 66 | @Override 67 | public SaResponse setHeader(String name, String value) { 68 | response.setHeader(name, value); 69 | return this; 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /sa-token-demo/sa-token-demo-oauth2-client/src/main/java/com/pj/controller/ExceptionHandle.java: -------------------------------------------------------------------------------- 1 | package com.pj.controller; 2 | 3 | import org.springframework.web.bind.annotation.ControllerAdvice; 4 | import org.springframework.web.bind.annotation.ExceptionHandler; 5 | import org.springframework.web.bind.annotation.ResponseBody; 6 | 7 | import com.pj.utils.AjaxJson; 8 | 9 | import cn.dev33.satoken.exception.NotLoginException; 10 | import cn.dev33.satoken.exception.NotPermissionException; 11 | import cn.dev33.satoken.exception.NotRoleException; 12 | 13 | /** 14 | * 全局异常拦截 15 | *

@ControllerAdvice 可指定包前缀,例如:(basePackages = "com.pj.controller.admin") 16 | * @author kong 17 | * 18 | */ 19 | @ControllerAdvice 20 | public class ExceptionHandle { 21 | 22 | 23 | /** 全局异常拦截 */ 24 | @ResponseBody 25 | @ExceptionHandler 26 | public AjaxJson handlerException(Exception e) { 27 | 28 | // 打印堆栈,以供调试 29 | e.printStackTrace(); 30 | 31 | // 记录日志信息 32 | AjaxJson aj = null; 33 | 34 | // ------------- 判断异常类型,提供个性化提示信息 35 | 36 | // 如果是未登录异常 37 | if(e instanceof NotLoginException){ 38 | aj = AjaxJson.getNotLogin(); 39 | } 40 | // 如果是角色异常 41 | else if(e instanceof NotRoleException) { 42 | NotPermissionException ee = (NotPermissionException) e; 43 | aj = AjaxJson.getNotJur("无此角色:" + ee.getCode()); 44 | } 45 | // 如果是权限异常 46 | else if(e instanceof NotPermissionException) { 47 | NotPermissionException ee = (NotPermissionException) e; 48 | aj = AjaxJson.getNotJur("无此权限:" + ee.getCode()); 49 | } 50 | // 普通异常输出:500 + 异常信息 51 | else { 52 | aj = AjaxJson.getError(e.getMessage()); 53 | } 54 | 55 | // 返回到前台 56 | return aj; 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /sa-token-demo/sa-token-demo-oauth2-server/src/main/java/com/pj/controller/ExceptionHandle.java: -------------------------------------------------------------------------------- 1 | package com.pj.controller; 2 | 3 | import org.springframework.web.bind.annotation.ControllerAdvice; 4 | import org.springframework.web.bind.annotation.ExceptionHandler; 5 | import org.springframework.web.bind.annotation.ResponseBody; 6 | 7 | import com.pj.utils.AjaxJson; 8 | 9 | import cn.dev33.satoken.exception.NotLoginException; 10 | import cn.dev33.satoken.exception.NotPermissionException; 11 | import cn.dev33.satoken.exception.NotRoleException; 12 | 13 | /** 14 | * 全局异常拦截 15 | *

@ControllerAdvice 可指定包前缀,例如:(basePackages = "com.pj.controller.admin") 16 | * @author kong 17 | * 18 | */ 19 | @ControllerAdvice 20 | public class ExceptionHandle { 21 | 22 | 23 | /** 全局异常拦截 */ 24 | @ResponseBody 25 | @ExceptionHandler 26 | public AjaxJson handlerException(Exception e) { 27 | 28 | // 打印堆栈,以供调试 29 | e.printStackTrace(); 30 | 31 | // 记录日志信息 32 | AjaxJson aj = null; 33 | 34 | // ------------- 判断异常类型,提供个性化提示信息 35 | 36 | // 如果是未登录异常 37 | if(e instanceof NotLoginException){ 38 | aj = AjaxJson.getNotLogin(); 39 | } 40 | // 如果是角色异常 41 | else if(e instanceof NotRoleException) { 42 | NotPermissionException ee = (NotPermissionException) e; 43 | aj = AjaxJson.getNotJur("无此角色:" + ee.getCode()); 44 | } 45 | // 如果是权限异常 46 | else if(e instanceof NotPermissionException) { 47 | NotPermissionException ee = (NotPermissionException) e; 48 | aj = AjaxJson.getNotJur("无此权限:" + ee.getCode()); 49 | } 50 | // 普通异常输出:500 + 异常信息 51 | else { 52 | aj = AjaxJson.getError(e.getMessage()); 53 | } 54 | 55 | // 返回到前台 56 | return aj; 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /sa-token-doc/doc/_sidebar.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | - **开始** 4 | - [介绍](/README) 5 | - [集成](/start/download) 6 | - [在SpringBoot环境集成](/start/example) 7 | - [在WebFlux环境集成](/start/webflux-example) 8 | 9 | - **基础** 10 | - [登录认证](/use/login-auth) 11 | - [权限认证](/use/jur-auth) 12 | - [Session会话](/use/session) 13 | - [踢人下线](/use/kick) 14 | - [注解式鉴权](/use/at-check) 15 | - [路由拦截式鉴权](/use/route-check) 16 | - [框架配置](/use/config) 17 | 18 | - **深入** 19 | - [持久层扩展(集成Redis)](/use/dao-extend) 20 | - [无Cookie模式(前后台分离)](/use/not-cookie) 21 | - [花式token](/use/token-style) 22 | - [Token前缀](/use/token-prefix) 23 | - [记住我模式](/use/remember-me) 24 | - [模拟他人 & 身份切换](/use/mock-person) 25 | - [同端互斥登录](/use/mutex-login) 26 | - [密码加密](/use/password-secure) 27 | - [会话治理](/use/search-session) 28 | - [全局侦听器](/use/global-listener) 29 | 30 | - **进阶** 31 | - [全局过滤器](/use/global-filter) 32 | - [集群、分布式](/senior/dcs) 33 | - [单点登录](/senior/sso) 34 | - [多账号验证](/use/many-account) 35 | 36 | - **插件** 37 | - [AOP注解鉴权](/plugin/aop-at) 38 | - [Quick-Login快速登录插件](/plugin/quick-login) 39 | 40 | - **其它** 41 | - [常见问题](/more/common-questions) 42 | - [更新日志](/more/update-log) 43 | - [友情链接](/more/link) 44 | - [推荐公众号](/more/tj-gzh) 45 | 46 | - **附录** 47 | - [未登录场景值](/fun/not-login-scene) 48 | - [token有效期详解](/fun/token-timeout) 49 | - [Session模型详解](/fun/session-model) 50 | - [TokenInfo参数详解](/fun/token-info) 51 | - [框架源码所有技术栈](/fun/tech-stack) 52 | - [为Sa-Token贡献代码](/fun/git-pr) 53 | 54 | 55 | 56 | 57 | 58 |













59 |

----- 到底线了 -----

-------------------------------------------------------------------------------- /sa-token-plugin/sa-token-quick-login/src/main/resources/static/sa-res/login.js: -------------------------------------------------------------------------------- 1 | // sa 2 | var sa = {}; 3 | 4 | // 打开loading 5 | sa.loading = function(msg) { 6 | layer.closeAll(); // 开始前先把所有弹窗关了 7 | return layer.msg(msg, {icon: 16, shade: 0.3, time: 1000 * 20, skin: 'ajax-layer-load' }); 8 | }; 9 | 10 | // 隐藏loading 11 | sa.hideLoading = function() { 12 | layer.closeAll(); 13 | }; 14 | 15 | 16 | // ----------------------------------- 登录事件 ----------------------------------- 17 | 18 | $('.login-btn').click(function(){ 19 | sa.loading("正在登录..."); 20 | // 开始登录 21 | setTimeout(function() { 22 | $.ajax({ 23 | url: "doLogin", 24 | type: "post", 25 | data: { 26 | name: $('[name=name]').val(), 27 | pwd: $('[name=pwd]').val() 28 | }, 29 | dataType: 'json', 30 | success: function(res){ 31 | console.log('返回数据:', res); 32 | sa.hideLoading(); 33 | if(res.code == 200) { 34 | layer.msg('登录成功', {anim: 0, icon: 6 }); 35 | setTimeout(function() { 36 | location.reload(); 37 | }, 800) 38 | } else { 39 | layer.msg(res.msg, {anim: 6, icon: 2 }); 40 | } 41 | }, 42 | error: function(xhr, type, errorThrown){ 43 | sa.hideLoading(); 44 | if(xhr.status == 0){ 45 | return layer.alert('无法连接到服务器,请检查网络'); 46 | } 47 | return layer.alert("异常:" + JSON.stringify(xhr)); 48 | } 49 | }); 50 | }, 400); 51 | }); 52 | 53 | // 绑定回车事件 54 | $('[name=name],[name=pwd]').bind('keypress', function(event){ 55 | if(event.keyCode == "13") { 56 | $('.login-btn').click(); 57 | } 58 | }); 59 | 60 | // 输入框获取焦点 61 | $("[name=name]").focus(); 62 | 63 | // 打印信息 64 | var str = "This page is provided by Sa-Token, Please refer to: " + "http://sa-token.dev33.cn/"; 65 | console.log(str); 66 | -------------------------------------------------------------------------------- /sa-token-doc/doc/use/login-auth.md: -------------------------------------------------------------------------------- 1 | # 登录认证 2 | --- 3 | 4 | 5 | ### 核心思想 6 | 7 | 所谓登录认证,说白了就是限制某些接口只有登录后才能访问(如:查询我的账号资料)
8 | 那么判断一个会话是否登录的依据是什么?当然是登录成功后框架给你做个标记!然后在需要鉴权的接口里检查此标记,有标记者视为已登录,无标记者视为未登录! 9 | 10 | 11 | ### 登录与注销 12 | 根据以上思路,我们很容易想到以下api: 13 | 14 | ``` java 15 | // 标记当前会话登录的账号id 16 | // 建议的参数类型:long | int | String, 不可以传入复杂类型,如:User、Admin等等 17 | StpUtil.setLoginId(Object loginId); 18 | 19 | // 当前会话注销登录 20 | StpUtil.logout(); 21 | 22 | // 获取当前会话是否已经登录,返回true=已登录,false=未登录 23 | StpUtil.isLogin(); 24 | 25 | // 检验当前会话是否已经登录, 如果未登录,则抛出异常:`NotLoginException` 26 | StpUtil.checkLogin() 27 | ``` 28 | 29 | 扩展:`NotLoginException` 对象可通过 `getLoginKey()` 方法获取具体是哪个 `StpLogic` 抛出的异常
30 | 扩展:`NotLoginException` 对象可通过 `getType()` 方法获取具体的场景值,详细参考章节:[未登录场景值](/fun/not-login-scene) 31 | 32 | 33 | ### 会话查询 34 | ``` java 35 | // 获取当前会话登录id, 如果未登录,则抛出异常:`NotLoginException` 36 | StpUtil.getLoginId(); 37 | 38 | // 类似查询API还有: 39 | StpUtil.getLoginIdAsString(); // 获取当前会话登录id, 并转化为`String`类型 40 | StpUtil.getLoginIdAsInt(); // 获取当前会话登录id, 并转化为`int`类型 41 | StpUtil.getLoginIdAsLong(); // 获取当前会话登录id, 并转化为`long`类型 42 | 43 | // ---------- 指定未登录情形下返回的默认值 ---------- 44 | 45 | // 获取当前会话登录id, 如果未登录,则返回null 46 | StpUtil.getLoginIdDefaultNull(); 47 | 48 | // 获取当前会话登录id, 如果未登录,则返回默认值 (`defaultValue`可以为任意类型) 49 | StpUtil.getLoginId(T defaultValue); 50 | ``` 51 | 52 | 53 | ### 其它API 54 | ``` java 55 | // 获取指定token对应的登录id,如果未登录,则返回 null 56 | StpUtil.getLoginIdByToken(String tokenValue); 57 | 58 | // 获取当前`StpLogic`的token名称 59 | StpUtil.getTokenName(); 60 | 61 | // 获取当前会话的token值 62 | StpUtil.getTokenValue(); 63 | 64 | // 获取当前会话的token信息参数 65 | StpUtil.getTokenInfo(); 66 | ``` 67 | 68 | ?> 有关TokenInfo参数详解,请参考:[参考:TokenInfo参数详解](/fun/token-info) 69 | 70 | 71 | -------------------------------------------------------------------------------- /sa-token-demo/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2InterfaceImpl.java: -------------------------------------------------------------------------------- 1 | package com.pj.oauth2; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | 6 | import org.springframework.stereotype.Component; 7 | 8 | import cn.dev33.satoken.oauth2.logic.SaOAuth2Interface; 9 | 10 | /** 11 | * 使用oauth2.0 所必须的一些自定义实现 12 | * @author kong 13 | */ 14 | @Component 15 | public class SaOAuth2InterfaceImpl implements SaOAuth2Interface { 16 | 17 | 18 | /* 19 | * ------ 注意: 以下代码均为示例,真实环境需要根据数据库查询相关信息 20 | */ 21 | 22 | // 返回此平台所有权限集合 23 | @Override 24 | public List getAppScopeList() { 25 | return Arrays.asList("userinfo"); 26 | } 27 | 28 | // 返回指定Client签约的所有Scope集合 29 | @Override 30 | public List getClientScopeList(String clientId) { 31 | return Arrays.asList("userinfo"); 32 | } 33 | 34 | // 获取指定 LoginId 对指定 Client 已经授权过的所有 Scope 35 | @Override 36 | public List getGrantScopeList(Object loginId, String clientId) { 37 | return Arrays.asList(); 38 | } 39 | 40 | // 返回指定Client允许的回调域名, 多个用逗号隔开, *代表不限制 41 | @Override 42 | public String getClientDomain(String clientId) { 43 | return "*"; 44 | } 45 | 46 | // 返回指定ClientId的ClientSecret 47 | @Override 48 | public String getClientSecret(String clientId) { 49 | return "aaaa-bbbb-cccc-dddd-eeee"; 50 | } 51 | 52 | // 根据ClientId和LoginId返回openid 53 | @Override 54 | public String getOpenid(String clientId, Object loginId) { 55 | return "gr_SwoIN0MC1ewxHX_vfCW3BothWDZMMtx__"; 56 | } 57 | 58 | // 根据ClientId和openid返回LoginId 59 | @Override 60 | public Object getLoginId(String clientId, String openid) { 61 | return 10001; 62 | } 63 | 64 | 65 | 66 | /* 67 | * 以上函数为开发时必须重写实现,其余函数可以按需重写 68 | */ 69 | 70 | 71 | 72 | } 73 | -------------------------------------------------------------------------------- /sa-token-plugin/sa-token-quick-login/src/main/java/cn/dev33/satoken/quick/config/SaQuickConfig.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.quick.config; 2 | 3 | /** 4 | * sa-quick 配置类 Model 5 | * 6 | * @author kong 7 | * 8 | */ 9 | public class SaQuickConfig { 10 | 11 | /** 是否开启全局认证 */ 12 | private Boolean auth = true; 13 | 14 | /** 用户名 */ 15 | private String name = "sa"; 16 | 17 | /** 密码 */ 18 | private String pwd = "123456"; 19 | 20 | /** 是否自动生成一个账号和密码 */ 21 | private Boolean auto = false; 22 | 23 | /** 登录页面的标题 */ 24 | private String title = "Sa-Token 登录"; 25 | 26 | /** 是否显示底部版权信息 */ 27 | private Boolean copr = true; 28 | 29 | 30 | public Boolean getAuth() { 31 | return auth; 32 | } 33 | 34 | public void setAuth(Boolean auth) { 35 | this.auth = auth; 36 | } 37 | 38 | public String getName() { 39 | return name; 40 | } 41 | 42 | public void setName(String name) { 43 | this.name = name; 44 | } 45 | 46 | public String getPwd() { 47 | return pwd; 48 | } 49 | 50 | public void setPwd(String pwd) { 51 | this.pwd = pwd; 52 | } 53 | 54 | public Boolean getAuto() { 55 | return auto; 56 | } 57 | 58 | public void setAuto(Boolean auto) { 59 | this.auto = auto; 60 | } 61 | 62 | public String getTitle() { 63 | return title; 64 | } 65 | 66 | public void setTitle(String title) { 67 | this.title = title; 68 | } 69 | 70 | public Boolean getCopr() { 71 | return copr; 72 | } 73 | 74 | public void setCopr(Boolean copr) { 75 | this.copr = copr; 76 | } 77 | 78 | 79 | @Override 80 | public String toString() { 81 | return "SaQuickConfig [auth=" + auth + ", name=" + name + ", pwd=" + pwd + ", auto=" + auto + ", title=" + title 82 | + ", copr=" + copr + "]"; 83 | } 84 | 85 | 86 | 87 | 88 | 89 | 90 | } 91 | -------------------------------------------------------------------------------- /sa-token-starter/sa-token-reactor-spring-boot-starter/src/main/java/cn/dev33/satoken/reactor/model/SaRequestForReactor.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.reactor.model; 2 | 3 | 4 | import org.springframework.http.HttpCookie; 5 | import org.springframework.http.server.reactive.ServerHttpRequest; 6 | 7 | import cn.dev33.satoken.context.model.SaRequest; 8 | 9 | /** 10 | * Request for Reactor 11 | * @author kong 12 | * 13 | */ 14 | public class SaRequestForReactor implements SaRequest { 15 | 16 | /** 17 | * 底层Request对象 18 | */ 19 | ServerHttpRequest request; 20 | 21 | /** 22 | * 实例化 23 | * @param request request对象 24 | */ 25 | public SaRequestForReactor(ServerHttpRequest request) { 26 | this.request = request; 27 | } 28 | 29 | /** 30 | * 获取底层源对象 31 | */ 32 | @Override 33 | public Object getSource() { 34 | return request; 35 | } 36 | 37 | /** 38 | * 在 [请求体] 里获取一个值 39 | */ 40 | @Override 41 | public String getParameter(String name) { 42 | return request.getQueryParams().getFirst(name); 43 | } 44 | 45 | /** 46 | * 在 [请求头] 里获取一个值 47 | */ 48 | @Override 49 | public String getHeader(String name) { 50 | return request.getHeaders().getFirst(name); 51 | } 52 | 53 | /** 54 | * 在 [Cookie作用域] 里获取一个值 55 | */ 56 | @Override 57 | public String getCookieValue(String name) { 58 | HttpCookie cookie = request.getCookies().getFirst(name); 59 | if(cookie == null) { 60 | return null; 61 | } 62 | return cookie.getValue(); 63 | } 64 | 65 | /** 66 | * 返回当前请求path (不包括上下文名称) 67 | */ 68 | @Override 69 | public String getRequestPath() { 70 | return request.getURI().getPath(); 71 | } 72 | 73 | /** 74 | * 返回当前请求的类型 75 | */ 76 | @Override 77 | public String getMethod() { 78 | return request.getMethodValue(); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/config/SaOAuth2Config.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.oauth2.config; 2 | 3 | /** 4 | * sa-token oauth2 配置类 Model 5 | * @author kong 6 | * 7 | */ 8 | public class SaOAuth2Config { 9 | 10 | /** 11 | * 授权码默认保存的时间(单位秒) 默认五分钟 12 | */ 13 | private long codeTimeout = 60 * 5; 14 | 15 | /** 16 | * access_token默认保存的时间(单位秒) 默认两个小时 17 | */ 18 | private long accessTokenTimeout = 60 * 60 * 2; 19 | 20 | /** 21 | * refresh_token默认保存的时间(单位秒) 默认30 天 22 | */ 23 | private long refreshTokenTimeout = 60 * 60 * 24 * 30; 24 | 25 | 26 | /** 27 | * @return codeTimeout 28 | */ 29 | public long getCodeTimeout() { 30 | return codeTimeout; 31 | } 32 | 33 | /** 34 | * @param codeTimeout 要设置的 codeTimeout 35 | * @return 对象自身 36 | */ 37 | public SaOAuth2Config setCodeTimeout(long codeTimeout) { 38 | this.codeTimeout = codeTimeout; 39 | return this; 40 | } 41 | 42 | /** 43 | * @return accessTokenTimeout 44 | */ 45 | public long getAccessTokenTimeout() { 46 | return accessTokenTimeout; 47 | } 48 | 49 | /** 50 | * @param accessTokenTimeout 要设置的 accessTokenTimeout 51 | * @return 对象自身 52 | */ 53 | public SaOAuth2Config setAccessTokenTimeout(long accessTokenTimeout) { 54 | this.accessTokenTimeout = accessTokenTimeout; 55 | return this; 56 | } 57 | 58 | /** 59 | * @return refreshTokenTimeout 60 | */ 61 | public long getRefreshTokenTimeout() { 62 | return refreshTokenTimeout; 63 | } 64 | 65 | /** 66 | * @param refreshTokenTimeout 要设置的 refreshTokenTimeout 67 | * @return 对象自身 68 | */ 69 | public SaOAuth2Config setRefreshTokenTimeout(long refreshTokenTimeout) { 70 | this.refreshTokenTimeout = refreshTokenTimeout; 71 | return this; 72 | } 73 | 74 | 75 | 76 | 77 | 78 | 79 | } 80 | -------------------------------------------------------------------------------- /sa-token-starter/sa-token-servlet/src/main/java/cn/dev33/satoken/servlet/model/SaRequestForServlet.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.servlet.model; 2 | 3 | import javax.servlet.http.Cookie; 4 | import javax.servlet.http.HttpServletRequest; 5 | 6 | import cn.dev33.satoken.context.model.SaRequest; 7 | 8 | /** 9 | * Request for Servlet 10 | * @author kong 11 | * 12 | */ 13 | public class SaRequestForServlet implements SaRequest { 14 | 15 | /** 16 | * 底层Request对象 17 | */ 18 | HttpServletRequest request; 19 | 20 | /** 21 | * 实例化 22 | * @param request request对象 23 | */ 24 | public SaRequestForServlet(HttpServletRequest request) { 25 | this.request = request; 26 | } 27 | 28 | /** 29 | * 获取底层源对象 30 | */ 31 | @Override 32 | public Object getSource() { 33 | return request; 34 | } 35 | 36 | /** 37 | * 在 [请求体] 里获取一个值 38 | */ 39 | @Override 40 | public String getParameter(String name) { 41 | return request.getParameter(name); 42 | } 43 | 44 | /** 45 | * 在 [请求头] 里获取一个值 46 | */ 47 | @Override 48 | public String getHeader(String name) { 49 | return request.getHeader(name); 50 | } 51 | 52 | /** 53 | * 在 [Cookie作用域] 里获取一个值 54 | */ 55 | @Override 56 | public String getCookieValue(String name) { 57 | Cookie[] cookies = request.getCookies(); 58 | if (cookies != null) { 59 | for (Cookie cookie : cookies) { 60 | if (cookie != null && name.equals(cookie.getName())) { 61 | return cookie.getValue(); 62 | } 63 | } 64 | } 65 | return null; 66 | } 67 | 68 | /** 69 | * 返回当前请求path (不包括上下文名称) 70 | */ 71 | @Override 72 | public String getRequestPath() { 73 | return request.getServletPath(); 74 | } 75 | 76 | /** 77 | * 返回当前请求的类型 78 | */ 79 | @Override 80 | public String getMethod() { 81 | return request.getMethod(); 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /sa-token-starter/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/interceptor/SaRouteInterceptor.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.interceptor; 2 | 3 | import javax.servlet.http.HttpServletRequest; 4 | import javax.servlet.http.HttpServletResponse; 5 | 6 | import org.springframework.web.servlet.HandlerInterceptor; 7 | 8 | import cn.dev33.satoken.router.SaRouteFunction; 9 | import cn.dev33.satoken.servlet.model.SaRequestForServlet; 10 | import cn.dev33.satoken.servlet.model.SaResponseForServlet; 11 | import cn.dev33.satoken.stp.StpUtil; 12 | 13 | /** 14 | * sa-token基于路由的拦截式鉴权 15 | * @author kong 16 | */ 17 | public class SaRouteInterceptor implements HandlerInterceptor { 18 | 19 | /** 20 | * 每次进入拦截器的[执行函数] 21 | */ 22 | public SaRouteFunction function; 23 | 24 | /** 25 | * 创建一个路由拦截器 26 | */ 27 | public SaRouteInterceptor() { 28 | } 29 | 30 | /** 31 | * 创建, 并指定[执行函数] 32 | * @param function [执行函数] 33 | */ 34 | public SaRouteInterceptor(SaRouteFunction function) { 35 | this.function = function; 36 | } 37 | 38 | /** 39 | * 静态方法快速构建一个 40 | * @param function 自定义模式下的执行函数 41 | * @return sa路由拦截器 42 | */ 43 | public static SaRouteInterceptor newInstance(SaRouteFunction function) { 44 | return new SaRouteInterceptor(function); 45 | } 46 | 47 | 48 | // ----------------- 验证方法 ----------------- 49 | 50 | /** 51 | * 每次请求之前触发的方法 52 | */ 53 | @Override 54 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) 55 | throws Exception { 56 | 57 | // 如果未提供function,默认进行登录验证 58 | if(function == null) { 59 | StpUtil.checkLogin(); 60 | } else { 61 | // 否则执行函数 62 | function.run(new SaRequestForServlet(request), new SaResponseForServlet(response), handler); 63 | } 64 | 65 | // 通过验证 66 | return true; 67 | } 68 | 69 | 70 | } 71 | -------------------------------------------------------------------------------- /sa-token-doc/doc/fun/not-login-scene.md: -------------------------------------------------------------------------------- 1 | # NotLoginException 场景值 2 | 3 | 本篇介绍如何根据`NotLoginException`异常的场景值,来定制化处理未登录的逻辑
4 | 应用场景举例:未登录、被顶下线、被踢下线等场景需要不同方式来处理 5 | 6 | 7 | ## 何为场景值 8 | 在前面的章节中,我们了解到,在会话未登录的情况下尝试获取`loginId`会使框架抛出`NotLoginException`异常,而同为未登录异常却有五种抛出场景的区分 9 | 10 | | 场景值 | 对应常量 | 含义说明 | 11 | |--- |--- |--- | 12 | | -1 | NotLoginException.NOT_TOKEN | 未能从请求中读取到token | 13 | | -2 | NotLoginException.INVALID_TOKEN| 已读取到token,但是token无效 | 14 | | -3 | NotLoginException.TOKEN_TIMEOUT| 已读取到token,但是token已经过期 | 15 | | -4 | NotLoginException.BE_REPLACED| 已读取到token,但是token已被顶下线 | 16 | | -5 | NotLoginException.KICK_OUT| 已读取到token,但是token已被踢下线 | 17 | 18 | 19 | 20 | 那么,如何获取场景值呢?废话少说直接上代码: 21 | 22 | 23 | ``` java 24 | // 全局异常拦截(拦截项目中的NotLoginException异常) 25 | @ExceptionHandler(NotLoginException.class) 26 | public AjaxJson handlerNotLoginException(NotLoginException nle, HttpServletRequest request, HttpServletResponse response) 27 | throws Exception { 28 | 29 | // 打印堆栈,以供调试 30 | nle.printStackTrace(); 31 | 32 | // 判断场景值,定制化异常信息 33 | String message = ""; 34 | if(nle.getType().equals(NotLoginException.NOT_TOKEN)) { 35 | message = "未提供token"; 36 | } 37 | else if(nle.getType().equals(NotLoginException.INVALID_TOKEN)) { 38 | message = "token无效"; 39 | } 40 | else if(nle.getType().equals(NotLoginException.TOKEN_TIMEOUT)) { 41 | message = "token已过期"; 42 | } 43 | else if(nle.getType().equals(NotLoginException.BE_REPLACED)) { 44 | message = "token已被顶下线"; 45 | } 46 | else if(nle.getType().equals(NotLoginException.KICK_OUT)) { 47 | message = "token已被踢下线"; 48 | } 49 | else { 50 | message = "当前会话未登录"; 51 | } 52 | 53 | // 返回给前端 54 | return AjaxJson.getError(message); 55 | } 56 | ``` 57 | 58 |
59 | 注意:以上代码并非处理逻辑的最佳方式,只为以最简单的代码演示出场景值的获取与应用,大家可以根据自己的项目需求来定制化处理 60 | 61 | -------------------------------------------------------------------------------- /sa-token-demo/sa-token-demo-webflux/src/main/java/com/pj/test/GlobalException.java: -------------------------------------------------------------------------------- 1 | package com.pj.test; 2 | 3 | import org.springframework.web.bind.annotation.ControllerAdvice; 4 | import org.springframework.web.bind.annotation.ExceptionHandler; 5 | import org.springframework.web.bind.annotation.ResponseBody; 6 | 7 | import com.pj.util.AjaxJson; 8 | 9 | import cn.dev33.satoken.exception.DisableLoginException; 10 | import cn.dev33.satoken.exception.NotLoginException; 11 | import cn.dev33.satoken.exception.NotPermissionException; 12 | import cn.dev33.satoken.exception.NotRoleException; 13 | 14 | /** 15 | * 全局异常处理 16 | */ 17 | @ControllerAdvice // 可指定包前缀,比如:(basePackages = "com.pj.admin") 18 | public class GlobalException { 19 | 20 | // 全局异常拦截(拦截项目中的所有异常) 21 | @ResponseBody 22 | @ExceptionHandler 23 | public AjaxJson handlerException(Exception e) 24 | throws Exception { 25 | 26 | // 打印堆栈,以供调试 27 | System.out.println("全局异常---------------"); 28 | e.printStackTrace(); 29 | 30 | // 不同异常返回不同状态码 31 | AjaxJson aj = null; 32 | if (e instanceof NotLoginException) { // 如果是未登录异常 33 | NotLoginException ee = (NotLoginException) e; 34 | aj = AjaxJson.getNotLogin().setMsg(ee.getMessage()); 35 | } else if(e instanceof NotRoleException) { // 如果是角色异常 36 | NotRoleException ee = (NotRoleException) e; 37 | aj = AjaxJson.getNotJur("无此角色:" + ee.getRole()); 38 | } else if(e instanceof NotPermissionException) { // 如果是权限异常 39 | NotPermissionException ee = (NotPermissionException) e; 40 | aj = AjaxJson.getNotJur("无此权限:" + ee.getCode()); 41 | } else if(e instanceof DisableLoginException) { // 如果是被封禁异常 42 | DisableLoginException ee = (DisableLoginException) e; 43 | aj = AjaxJson.getNotJur("账号被封禁:" + ee.getDisableTime() + "秒后解封"); 44 | } else { // 普通异常, 输出:500 + 异常信息 45 | aj = AjaxJson.getError(e.getMessage()); 46 | } 47 | 48 | // 返回给前端 49 | return aj; 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /sa-token-core/src/main/java/cn/dev33/satoken/listener/SaTokenListener.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.listener; 2 | 3 | import cn.dev33.satoken.stp.SaLoginModel; 4 | 5 | /** 6 | * Sa-Token的侦听器 7 | *

你可以通过实现此接口在用户登陆、退出等关键性操作时进行一些AOP操作 8 | * @author kong 9 | * 10 | */ 11 | public interface SaTokenListener { 12 | 13 | /** 14 | * 每次登录时触发 15 | * @param loginKey 账号类别 16 | * @param loginId 账号id 17 | * @param loginModel 登录参数 18 | */ 19 | public void doLogin(String loginKey, Object loginId, SaLoginModel loginModel); 20 | 21 | /** 22 | * 每次注销时触发 23 | * @param loginKey 账号类别 24 | * @param loginId 账号id 25 | * @param tokenValue token值 26 | */ 27 | public void doLogout(String loginKey, Object loginId, String tokenValue); 28 | 29 | /** 30 | * 每次被踢下线时触发 31 | * @param loginKey 账号类别 32 | * @param loginId 账号id 33 | * @param tokenValue token值 34 | * @param device 设备标识 35 | */ 36 | public void doLogoutByLoginId(String loginKey, Object loginId, String tokenValue, String device); 37 | 38 | /** 39 | * 每次被顶下线时触发 40 | * @param loginKey 账号类别 41 | * @param loginId 账号id 42 | * @param tokenValue token值 43 | * @param device 设备标识 44 | */ 45 | public void doReplaced(String loginKey, Object loginId, String tokenValue, String device); 46 | 47 | /** 48 | * 每次被封禁时触发 49 | * @param loginKey 账号类别 50 | * @param loginId 账号id 51 | * @param disableTime 封禁时长,单位: 秒 52 | */ 53 | public void doDisable(String loginKey, Object loginId, long disableTime); 54 | 55 | /** 56 | * 每次被解封时触发 57 | * @param loginKey 账号类别 58 | * @param loginId 账号id 59 | */ 60 | public void doUntieDisable(String loginKey, Object loginId); 61 | 62 | /** 63 | * 每次创建Session时触发 64 | * @param id SessionId 65 | */ 66 | public void doCreateSession(String id); 67 | 68 | /** 69 | * 每次注销Session时触发 70 | * @param id SessionId 71 | */ 72 | public void doLogoutSession(String id); 73 | 74 | } 75 | -------------------------------------------------------------------------------- /sa-token-demo/sa-token-demo-oauth2-client/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | com.pj 5 | sa-token-demo-oauth2-client 6 | 0.0.1-SNAPSHOT 7 | 8 | 9 | 10 | org.springframework.boot 11 | spring-boot-starter-parent 12 | 2.3.3.RELEASE 13 | 14 | 15 | 16 | 17 | 1.8 18 | 3.1.1 19 | 20 | 1.15.0.RELEASE 21 | 22 | 23 | 24 | 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-web 29 | 30 | 31 | 32 | 33 | cn.dev33 34 | sa-token-spring-boot-starter 35 | ${sa-token-version} 36 | 37 | 38 | 39 | 40 | com.ejlchina 41 | okhttps 42 | 2.4.5 43 | 44 | 45 | 46 | 47 | org.springframework.boot 48 | spring-boot-configuration-processor 49 | true 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /sa-token-demo/sa-token-demo-springboot/src/main/java/com/pj/test/StressTestController.java: -------------------------------------------------------------------------------- 1 | package com.pj.test; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.bind.annotation.RestController; 8 | 9 | import com.pj.util.AjaxJson; 10 | import com.pj.util.Ttime; 11 | 12 | import cn.dev33.satoken.stp.StpUtil; 13 | 14 | /** 15 | * 压力测试 16 | * @author kong 17 | * 18 | */ 19 | @RestController 20 | @RequestMapping("/s-test/") 21 | public class StressTestController { 22 | 23 | 24 | // 测试 浏览器访问: http://localhost:8081/s-test/login 25 | // 测试前,请先将 is-read-cookie 配置为 false 26 | @RequestMapping("login") 27 | public AjaxJson login() { 28 | // StpUtil.getTokenSession().logout(); 29 | // StpUtil.logoutByLoginId(10001); 30 | 31 | int count = 10; // 循环多少轮 32 | int loginCount = 10000; // 每轮循环多少次 33 | 34 | // 循环10次 取平均时间 35 | List list = new ArrayList<>(); 36 | for (int i = 1; i <= count; i++) { 37 | System.out.println("\n---------------------第" + i + "轮---------------------"); 38 | Ttime t = new Ttime().start(); 39 | // 每次登录的次数 40 | for (int j = 1; j <= loginCount; j++) { 41 | StpUtil.setLoginId("1000" + j, "PC-" + j); 42 | if(j % 1000 == 0) { 43 | System.out.println("已登录:" + j); 44 | } 45 | } 46 | t.end(); 47 | list.add((t.returnMs() + 0.0) / 1000); 48 | System.out.println("第" + i + "轮" + "用时:" + t.toString()); 49 | } 50 | // System.out.println(((SaTokenDaoDefaultImpl)SaTokenManager.getSaTokenDao()).dataMap.size()); 51 | 52 | System.out.println("\n---------------------测试结果---------------------"); 53 | System.out.println(list.size() + "次测试: " + list); 54 | double ss = 0; 55 | for (int i = 0; i < list.size(); i++) { 56 | ss += list.get(i); 57 | } 58 | System.out.println("平均用时: " + ss / list.size()); 59 | return AjaxJson.getSuccess(); 60 | } 61 | 62 | 63 | 64 | 65 | 66 | } 67 | -------------------------------------------------------------------------------- /sa-token-plugin/sa-token-spring-aop/src/main/java/cn/dev33/satoken/aop/SaCheckAspect.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.aop; 2 | 3 | import org.aspectj.lang.ProceedingJoinPoint; 4 | import org.aspectj.lang.annotation.Around; 5 | import org.aspectj.lang.annotation.Aspect; 6 | import org.aspectj.lang.annotation.Pointcut; 7 | import org.aspectj.lang.reflect.MethodSignature; 8 | import org.springframework.core.annotation.Order; 9 | import org.springframework.stereotype.Component; 10 | 11 | import cn.dev33.satoken.SaManager; 12 | import cn.dev33.satoken.util.SaTokenConsts; 13 | 14 | /** 15 | * sa-token 基于 Spring Aop 的注解鉴权 16 | * 17 | * @author kong 18 | */ 19 | @Aspect 20 | @Component 21 | @Order(SaTokenConsts.ASSEMBLY_ORDER) 22 | public class SaCheckAspect { 23 | 24 | /** 25 | * 构建 26 | */ 27 | public SaCheckAspect() { 28 | } 29 | 30 | /** 31 | * 定义AOP签名 (切入所有使用sa-token鉴权注解的方法) 32 | */ 33 | public static final String POINTCUT_SIGN = 34 | "@within(cn.dev33.satoken.annotation.SaCheckLogin) || @annotation(cn.dev33.satoken.annotation.SaCheckLogin) || " 35 | + "@within(cn.dev33.satoken.annotation.SaCheckRole) || @annotation(cn.dev33.satoken.annotation.SaCheckRole) || " 36 | + "@within(cn.dev33.satoken.annotation.SaCheckPermission) || @annotation(cn.dev33.satoken.annotation.SaCheckPermission)"; 37 | 38 | /** 39 | * 声明AOP签名 40 | */ 41 | @Pointcut(POINTCUT_SIGN) 42 | public void pointcut() { 43 | } 44 | 45 | /** 46 | * 环绕切入 47 | * 48 | * @param joinPoint 切面对象 49 | * @return 底层方法执行后的返回值 50 | * @throws Throwable 底层方法抛出的异常 51 | */ 52 | @Around("pointcut()") 53 | public Object around(ProceedingJoinPoint joinPoint) throws Throwable { 54 | 55 | // 注解鉴权 56 | MethodSignature signature = (MethodSignature) joinPoint.getSignature(); 57 | SaManager.getSaTokenAction().checkMethodAnnotation(signature.getMethod()); 58 | 59 | try { 60 | // 执行原有逻辑 61 | Object obj = joinPoint.proceed(); 62 | return obj; 63 | } catch (Throwable e) { 64 | throw e; 65 | } 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /sa-token-starter/sa-token-reactor-spring-boot-starter/src/main/java/cn/dev33/satoken/reactor/model/SaResponseForReactor.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.reactor.model; 2 | 3 | import org.springframework.http.ResponseCookie; 4 | import org.springframework.http.ResponseCookie.ResponseCookieBuilder; 5 | import org.springframework.http.server.reactive.ServerHttpResponse; 6 | 7 | import cn.dev33.satoken.context.model.SaResponse; 8 | import cn.dev33.satoken.util.SaFoxUtil; 9 | 10 | /** 11 | * Response for Reactor 12 | * @author kong 13 | * 14 | */ 15 | public class SaResponseForReactor implements SaResponse { 16 | 17 | /** 18 | * 底层Response对象 19 | */ 20 | ServerHttpResponse response; 21 | 22 | /** 23 | * 实例化 24 | * @param response response对象 25 | */ 26 | public SaResponseForReactor(ServerHttpResponse response) { 27 | this.response = response; 28 | } 29 | 30 | /** 31 | * 获取底层源对象 32 | */ 33 | @Override 34 | public Object getSource() { 35 | return response; 36 | } 37 | 38 | /** 39 | * 删除指定Cookie 40 | */ 41 | @Override 42 | public void deleteCookie(String name) { 43 | addCookie(name, null, null, null, 0); 44 | } 45 | 46 | /** 47 | * 写入指定Cookie 48 | */ 49 | @Override 50 | public void addCookie(String name, String value, String path, String domain, int timeout) { 51 | 52 | // 构建CookieBuilder 53 | ResponseCookieBuilder builder = ResponseCookie.from(name, value) 54 | .domain(domain) 55 | .path(path) 56 | .maxAge(timeout) 57 | ; 58 | 59 | // set path 60 | if(SaFoxUtil.isEmpty(path) == true) { 61 | path = "/"; 62 | } 63 | builder.path(path); 64 | 65 | // set domain 66 | if(SaFoxUtil.isEmpty(domain) == false) { 67 | builder.domain(domain); 68 | } 69 | 70 | // 写入Cookie 71 | response.addCookie(builder.build()); 72 | } 73 | 74 | /** 75 | * 在响应头里写入一个值 76 | */ 77 | @Override 78 | public SaResponse setHeader(String name, String value) { 79 | response.getHeaders().set(name, value); 80 | return this; 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /sa-token-core/src/main/java/cn/dev33/satoken/session/SaSessionCustomUtil.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.session; 2 | 3 | import cn.dev33.satoken.SaManager; 4 | 5 | /** 6 | * 自定义Session工具类 7 | * 8 | * @author kong 9 | * 10 | */ 11 | public class SaSessionCustomUtil { 12 | 13 | /** 14 | * 添加上指定前缀,防止恶意伪造session 15 | */ 16 | public static String sessionKey = "custom"; 17 | 18 | /** 19 | * 组织一下自定义Session的id 20 | * 21 | * @param sessionId 会话id 22 | * @return sessionId 23 | */ 24 | public static String splicingSessionKey(String sessionId) { 25 | return SaManager.getConfig().getTokenName() + ":" + sessionKey + ":session:" + sessionId; 26 | } 27 | 28 | /** 29 | * 验证指定key的Session是否存在 30 | * 31 | * @param sessionId session的id 32 | * @return 是否存在 33 | */ 34 | public static boolean isExists(String sessionId) { 35 | return SaManager.getSaTokenDao().getSession(splicingSessionKey(sessionId)) != null; 36 | } 37 | 38 | /** 39 | * 获取指定key的Session 40 | * 41 | * @param sessionId key 42 | * @param isCreate 如果此Session尚未在DB创建,是否新建并返回 43 | * @return SaSession 44 | */ 45 | public static SaSession getSessionById(String sessionId, boolean isCreate) { 46 | SaSession session = SaManager.getSaTokenDao().getSession(splicingSessionKey(sessionId)); 47 | if (session == null && isCreate) { 48 | session = SaManager.getSaTokenAction().createSession(splicingSessionKey(sessionId)); 49 | SaManager.getSaTokenDao().setSession(session, SaManager.getConfig().getTimeout()); 50 | } 51 | return session; 52 | } 53 | 54 | /** 55 | * 获取指定key的Session, 如果此Session尚未在DB创建,则新建并返回 56 | * 57 | * @param sessionId key 58 | * @return session对象 59 | */ 60 | public static SaSession getSessionById(String sessionId) { 61 | return getSessionById(sessionId, true); 62 | } 63 | 64 | /** 65 | * 删除指定key的session 66 | * 67 | * @param sessionId 指定key 68 | */ 69 | public static void deleteSessionById(String sessionId) { 70 | SaManager.getSaTokenDao().deleteSession(splicingSessionKey(sessionId)); 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /sa-token-starter/sa-token-reactor-spring-boot-starter/src/main/java/cn/dev33/satoken/reactor/context/SaReactorSyncHolder.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.reactor.context; 2 | 3 | import org.springframework.web.server.ServerWebExchange; 4 | 5 | import cn.dev33.satoken.context.SaTokenContextForThreadLocalStorage; 6 | import cn.dev33.satoken.context.SaTokenContextForThreadLocalStorage.Box; 7 | import cn.dev33.satoken.context.model.SaRequest; 8 | import cn.dev33.satoken.context.model.SaResponse; 9 | import cn.dev33.satoken.context.model.SaStorage; 10 | import cn.dev33.satoken.fun.SaFunction; 11 | import cn.dev33.satoken.reactor.model.SaRequestForReactor; 12 | import cn.dev33.satoken.reactor.model.SaResponseForReactor; 13 | import cn.dev33.satoken.reactor.model.SaStorageForReactor; 14 | 15 | /** 16 | * Reactor上下文操作 [同步] 17 | * @author kong 18 | * 19 | */ 20 | public class SaReactorSyncHolder { 21 | 22 | /** 23 | * 写入上下文对象 24 | * @param exchange see note 25 | */ 26 | public static void setContent(ServerWebExchange exchange) { 27 | SaRequest request = new SaRequestForReactor(exchange.getRequest()); 28 | SaResponse response = new SaResponseForReactor(exchange.getResponse()); 29 | SaStorage storage = new SaStorageForReactor(exchange); 30 | SaTokenContextForThreadLocalStorage.setBox(request, response, storage); 31 | } 32 | 33 | /** 34 | * 获取上下文对象 35 | * @return see note 36 | */ 37 | public static ServerWebExchange getContent() { 38 | Box box = SaTokenContextForThreadLocalStorage.getBoxNotNull(); 39 | return (ServerWebExchange)box.getStorage().getSource(); 40 | } 41 | 42 | /** 43 | * 清除上下文对象 44 | */ 45 | public static void clearContent() { 46 | SaTokenContextForThreadLocalStorage.clearBox(); 47 | } 48 | 49 | /** 50 | * 写入上下文对象, 并在执行函数后将其清除 51 | * @param exchange see note 52 | * @param fun see note 53 | */ 54 | public static void setContent(ServerWebExchange exchange, SaFunction fun) { 55 | try { 56 | setContent(exchange); 57 | fun.run(); 58 | } finally { 59 | clearContent(); 60 | } 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /sa-token-plugin/sa-token-quick-login/src/main/java/cn/dev33/satoken/quick/web/SaQuickController.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.quick.web; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import javax.servlet.http.HttpServletRequest; 7 | 8 | import org.springframework.stereotype.Controller; 9 | import org.springframework.web.bind.annotation.GetMapping; 10 | import org.springframework.web.bind.annotation.PostMapping; 11 | import org.springframework.web.bind.annotation.ResponseBody; 12 | 13 | import cn.dev33.satoken.quick.SaQuickManager; 14 | import cn.dev33.satoken.quick.config.SaQuickConfig; 15 | import cn.dev33.satoken.stp.StpUtil; 16 | import cn.dev33.satoken.util.SaFoxUtil; 17 | 18 | /** 19 | * 登录Controller 20 | * @author kong 21 | * 22 | */ 23 | @Controller 24 | public class SaQuickController { 25 | 26 | /** 27 | * 进入登录页面 28 | * @param request see note 29 | * @return see note 30 | */ 31 | @GetMapping("/saLogin") 32 | public String saLogin(HttpServletRequest request) { 33 | request.setAttribute("cfg", SaQuickManager.getConfig()); 34 | return "sa-login.html"; 35 | } 36 | 37 | 38 | /** 39 | * 登录接口 40 | * @param name 账号 41 | * @param pwd 密码 42 | * @return 是否登录成功 43 | */ 44 | @PostMapping("/doLogin") 45 | @ResponseBody 46 | public Map doLogin(String name, String pwd) { 47 | 48 | // 参数完整性校验 49 | if(SaFoxUtil.isEmpty(name) || SaFoxUtil.isEmpty(pwd)) { 50 | return getResult(500, "请输入账号和密码", null); 51 | } 52 | 53 | // 密码校验 54 | SaQuickConfig config = SaQuickManager.getConfig(); 55 | if(name.equals(config.getName()) && pwd.equals(config.getPwd())) { 56 | StpUtil.setLoginId(config.getName()); 57 | return getResult(200, "ok", StpUtil.getTokenInfo()); 58 | } else { 59 | // 校验失败 60 | return getResult(500, "账号或密码输入错误", null); 61 | } 62 | } 63 | 64 | 65 | private Map getResult(int code, String msg, Object data) { 66 | Map map = new HashMap(); 67 | map.put("code", code); 68 | map.put("msg", msg); 69 | map.put("data", data); 70 | return map; 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /sa-token-doc/doc/use/dao-extend.md: -------------------------------------------------------------------------------- 1 | # 持久层扩展 2 | --- 3 | 4 | Sa-token默认将会话数据保存在内存中,此模式读写速度最快,且避免了序列化与反序列化带来的性能消耗,但是此模式也有一些缺点,比如:重启后数据会丢失,无法在集群模式下共享数据 5 | 6 | 为此,Sa-Token将数据持久操作全部抽象到 `SaTokenDao` 接口中,保证大家对框架进行灵活扩展,比如我们可以将会话数据存储在 `Redis`、`Memcached`等专业的缓存中间件中,做到重启数据不丢失,而且保证分布式环境下多节点的会话一致性 7 | 8 | 除了框架内部对`SaTokenDao`提供的基于内存的默认实现,官方仓库还提供了以下扩展方案:
9 | 10 | 11 | ### 1. Sa-Token 整合 Redis (使用jdk默认序列化方式) 12 | ``` xml 13 | 14 | 15 | cn.dev33 16 | sa-token-dao-redis 17 | 1.19.0 18 | 19 | ``` 20 | 优点:兼容性好,缺点:Session序列化后基本不可读,对开发者来讲等同于乱码 21 | 22 | 23 | ### 2. Sa-Token 整合 Redis (使用jackson序列化方式) 24 | ``` xml 25 | 26 | 27 | cn.dev33 28 | sa-token-dao-redis-jackson 29 | 1.19.0 30 | 31 | ``` 32 | 优点:Session序列化后可读性强,可灵活手动修改,缺点:兼容性稍差 33 | 34 | 35 |
36 | 37 | ### 集成Redis请注意: 38 | 39 | 40 | **1. 无论使用哪种序列化方式,你都必须为项目提供一个Redis实例化方案,例如:** 41 | ``` xml 42 | 43 | 44 | org.apache.commons 45 | commons-pool2 46 | 47 | ``` 48 | 49 | **2. 引入了依赖,我还需要为Redis配置连接信息吗?**
50 | 需要!只有项目初始化了正确的Redis实例,`sa-token`才可以使用Redis进行数据持久化,参考以下`yml配置`: 51 | ``` java 52 | # 端口 53 | spring: 54 | # redis配置 55 | redis: 56 | # Redis数据库索引(默认为0) 57 | database: 1 58 | # Redis服务器地址 59 | host: 127.0.0.1 60 | # Redis服务器连接端口 61 | port: 6379 62 | # Redis服务器连接密码(默认为空) 63 | # password: 64 | # 连接超时时间(毫秒) 65 | timeout: 1000ms 66 | lettuce: 67 | pool: 68 | # 连接池最大连接数 69 | max-active: 200 70 | # 连接池最大阻塞等待时间(使用负值表示没有限制) 71 | max-wait: -1ms 72 | # 连接池中的最大空闲连接 73 | max-idle: 10 74 | # 连接池中的最小空闲连接 75 | min-idle: 0 76 | ``` 77 | 78 | 79 | **3. 集成Redis后,是我额外手动保存数据,还是框架自动保存?**
80 | 框架自动保存。集成`Redis`只需要引入对应的`pom依赖`即可,框架所有上层API保持不变 81 | 82 | 83 |

84 | 更多框架的集成方案正在更新中... (欢迎大家提交pr) 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /sa-token-demo/sa-token-demo-oauth2-client/src/main/java/com/pj/controller/ClientAccController.java: -------------------------------------------------------------------------------- 1 | package com.pj.controller; 2 | 3 | import org.springframework.web.bind.annotation.RequestMapping; 4 | import org.springframework.web.bind.annotation.RestController; 5 | 6 | import com.ejlchina.okhttps.OkHttps; 7 | import com.pj.utils.AjaxJson; 8 | import com.pj.utils.SoMap; 9 | 10 | import cn.dev33.satoken.stp.StpUtil; 11 | 12 | /** 13 | * 登录注册Controller 14 | * @author kong 15 | */ 16 | @RestController 17 | public class ClientAccController { 18 | 19 | 20 | // 返回当前登录者的账号id, 如果未登录, 返回null 21 | @RequestMapping("/getLoginInfo") 22 | public AjaxJson getLoginInfo() { 23 | Object loginId = StpUtil.getLoginIdDefaultNull(); 24 | return AjaxJson.getSuccessData(loginId); 25 | } 26 | 27 | // 注销登录 28 | @RequestMapping("/logout") 29 | public AjaxJson logout() { 30 | StpUtil.logout(); 31 | return AjaxJson.getSuccess(); 32 | } 33 | 34 | // 根据code码进行登录 35 | @RequestMapping("/doCodeLogin") 36 | public AjaxJson doCodeLogin(String code) { 37 | System.out.println("------------------ 成功进入请求 ------------------"); 38 | 39 | // 请求服务提供方接口地址,获取 access_token 以及其他信息 40 | // 携带三个关键参数: code、client_id、client_secret 41 | String str = OkHttps.sync("http://localhost:8001/oauth2/getAccessToken") 42 | .addBodyPara("code", code) 43 | .addBodyPara("client_id", "123123123") 44 | .addBodyPara("client_secret", "aaaa-bbbb-cccc-dddd-eeee") 45 | .post() 46 | .getBody() 47 | .toString(); 48 | SoMap so = SoMap.getSoMap().setJsonString(str); 49 | System.out.println("返回结果: " + so); 50 | 51 | // code不等于200 代表请求失败 52 | if(so.getInt("code") != 200) { 53 | return AjaxJson.getError(so.getString("msg")); 54 | } 55 | 56 | // 根据openid获取其对应的userId 57 | String openid = so.getString("openid"); 58 | long userId = getUserIdByOpenid(openid); 59 | 60 | // 登录并返回账号信息 61 | StpUtil.setLoginId(userId); 62 | return AjaxJson.getSuccessData(userId).set("openid", openid); 63 | } 64 | 65 | 66 | 67 | // ------------ 模拟方法 ------------------ 68 | 69 | // 模拟方法:根据openid获取userId 70 | private long getUserIdByOpenid(String openid) { 71 | // 此方法仅做模拟,实际开发要根据具体业务逻辑来获取userId 72 | return 10001; 73 | } 74 | 75 | 76 | } 77 | -------------------------------------------------------------------------------- /sa-token-demo/sa-token-demo-quick-login/pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | cn.dev33 6 | sa-token-demo-quick-login 7 | 0.0.1-SNAPSHOT 8 | 9 | 10 | 11 | org.springframework.boot 12 | spring-boot-starter-parent 13 | 2.0.0.RELEASE 14 | 15 | 16 | 17 | 18 | 1.19.0 19 | 20 | 21 | 22 | 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-web 27 | 28 | 29 | 30 | 31 | cn.dev33 32 | sa-token-spring-boot-starter 33 | ${sa-token-version} 34 | 35 | 36 | 37 | 38 | cn.dev33 39 | sa-token-quick-login 40 | ${sa-token-version} 41 | 42 | 43 | 44 | 45 | org.springframework.boot 46 | spring-boot-configuration-processor 47 | true 48 | 49 | 50 | 51 | 52 | org.springframework.boot 53 | spring-boot-devtools 54 | true 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | org.springframework.boot 63 | spring-boot-maven-plugin 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /sa-token-doc/doc/use/at-check.md: -------------------------------------------------------------------------------- 1 | # 注解式鉴权 2 | --- 3 | 4 | 有同学表示:尽管使用代码鉴权非常方便,但是我仍希望把鉴权逻辑和业务逻辑分离开来,我可以使用注解鉴权吗?当然可以!
5 | 6 | 注解鉴权 —— 优雅的将鉴权与业务代码分离! 7 | 8 | - `@SaCheckLogin`: 标注在方法或类上,当前会话必须处于登录状态才可通过校验 9 | - `@SaCheckRole("admin")`: 标注在方法或类上,当前会话必须具有指定角色标识才能通过校验 10 | - `@SaCheckPermission("user:add")`: 标注在方法或类上,当前会话必须具有指定权限才能通过校验 11 | 12 | Sa-Token使用全局拦截器完成注解鉴权功能,为了不为项目带来不必要的性能负担,拦截器默认处于关闭状态
13 | 因此,为了使用注解鉴权,你必须手动将sa-token的全局拦截器注册到你项目中 14 | 15 | 17 | 18 | 19 | ### 1、注册拦截器 20 | 以`SpringBoot2.0`为例, 新建配置类`SaTokenConfigure.java` 21 | 22 | ``` java 23 | @Configuration 24 | public class SaTokenConfigure implements WebMvcConfigurer { 25 | // 注册sa-token的注解拦截器,打开注解式鉴权功能 26 | @Override 27 | public void addInterceptors(InterceptorRegistry registry) { 28 | // 注册注解拦截器,并排除不需要注解鉴权的接口地址 (与登录拦截器无关) 29 | registry.addInterceptor(new SaAnnotationInterceptor()).addPathPatterns("/**"); 30 | } 31 | } 32 | ``` 33 | 保证此类被`springboot`启动类扫描到即可 34 | 35 | 36 | ### 2、使用注解鉴权 37 | 然后我们就可以愉快的使用注解鉴权: 38 | 39 | ``` java 40 | // 登录认证:当前会话必须登录才能通过 41 | @SaCheckLogin 42 | @RequestMapping("info") 43 | public String info() { 44 | return "查询用户信息"; 45 | } 46 | 47 | // 角色认证:当前会话必须具有指定角色标识才能通过 48 | @SaCheckRole("super-admin") 49 | @RequestMapping("add") 50 | public String add() { 51 | return "用户增加"; 52 | } 53 | 54 | // 权限认证:当前会话必须具有指定权限才能通过 55 | @SaCheckPermission("user-add") 56 | @RequestMapping("add") 57 | public String add() { 58 | return "用户增加"; 59 | } 60 | ``` 61 | 62 | 注:以上注解都可以加在类上,代表为这个类所有方法进行鉴权 63 | 64 | 65 | ### 3、设定校验模式 66 | `@SaCheckRole`与`@SaCheckPermission`注解可设置校验模式,例如: 67 | ``` java 68 | // 注解式鉴权:只要具有其中一个权限即可通过校验 69 | @RequestMapping("atJurOr") 70 | @SaCheckPermission(value = {"user-add", "user-all", "user-delete"}, mode = SaMode.OR) 71 | public AjaxJson atJurOr() { 72 | return AjaxJson.getSuccessData("用户信息"); 73 | } 74 | ``` 75 | 76 | 77 | mode有两种取值: 78 | - `SaMode.AND`, 标注一组权限,会话必须全部具有才可通过校验 79 | - `SaMode.OR`, 标注一组权限,会话只要具有其一即可通过校验 80 | 81 | 82 | 83 | ### 4、在业务逻辑层使用注解鉴权 84 | 疑问:我能否将注解写在其它架构层呢,比如业务逻辑层? 85 | 86 | 使用拦截器模式,只能在`Controller层`进行注解鉴权,如需在任意层级使用注解鉴权,请参考:[AOP注解鉴权](/plugin/aop-at) 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /sa-token-starter/sa-token-reactor-spring-boot-starter/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | cn.dev33 9 | sa-token-starter 10 | 1.19.0 11 | 12 | jar 13 | 14 | sa-token-reactor-spring-boot-starter 15 | sa-token-reactor-spring-boot-starter 16 | springboot reactor integrate sa-token 17 | 18 | 19 | 20 | 21 | cn.dev33 22 | sa-token-core 23 | ${sa-token-version} 24 | 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter 29 | 2.0.0.RELEASE 30 | compile 31 | 32 | 33 | 34 | org.springframework 35 | spring-web 36 | 5.0.4.RELEASE 37 | compile 38 | 39 | 40 | 41 | io.projectreactor 42 | reactor-core 43 | 3.1.4.RELEASE 44 | compile 45 | 46 | 47 | 48 | org.springframework.boot 49 | spring-boot-configuration-processor 50 | 2.0.0.RELEASE 51 | true 52 | 53 | 54 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /sa-token-demo/sa-token-demo-oauth2-server/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | com.pj 5 | sa-token-demo-oauth2-server 6 | 0.0.1-SNAPSHOT 7 | 8 | 9 | 10 | org.springframework.boot 11 | spring-boot-starter-parent 12 | 2.3.3.RELEASE 13 | 14 | 15 | 16 | 17 | 1.8 18 | 3.1.1 19 | 20 | 1.15.0.RELEASE 21 | 22 | 23 | 24 | 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-web 29 | 30 | 31 | 32 | 33 | cn.dev33 34 | sa-token-spring-boot-starter 35 | ${sa-token-version} 36 | 37 | 38 | 39 | 40 | cn.dev33 41 | sa-token-oauth2 42 | 1.15.0-alpha 43 | 44 | 45 | 46 | 55 | 56 | 57 | 58 | org.springframework.boot 59 | spring-boot-configuration-processor 60 | true 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /sa-token-core/src/main/java/cn/dev33/satoken/listener/SaTokenListenerDefaultImpl.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.listener; 2 | 3 | import java.util.Date; 4 | 5 | import cn.dev33.satoken.SaManager; 6 | import cn.dev33.satoken.stp.SaLoginModel; 7 | import cn.dev33.satoken.util.SaFoxUtil; 8 | 9 | /** 10 | * Sa-Token 侦听器的默认实现:log打印 11 | * @author kong 12 | * 13 | */ 14 | public class SaTokenListenerDefaultImpl implements SaTokenListener { 15 | 16 | /** 17 | * 每次登录时触发 18 | */ 19 | @Override 20 | public void doLogin(String loginKey, Object loginId, SaLoginModel loginModel) { 21 | println("账号[" + loginId + "]登录成功"); 22 | } 23 | 24 | /** 25 | * 每次注销时触发 26 | */ 27 | @Override 28 | public void doLogout(String loginKey, Object loginId, String tokenValue) { 29 | println("账号[" + loginId + "]注销成功"); 30 | } 31 | 32 | /** 33 | * 每次被踢下线时触发 34 | */ 35 | @Override 36 | public void doLogoutByLoginId(String loginKey, Object loginId, String tokenValue, String device) { 37 | println("账号[" + loginId + "]被踢下线 (终端: " + device + ")"); 38 | } 39 | 40 | /** 41 | * 每次被顶下线时触发 42 | */ 43 | @Override 44 | public void doReplaced(String loginKey, Object loginId, String tokenValue, String device) { 45 | println("账号[" + loginId + "]被顶下线 (终端: " + device + ")"); 46 | } 47 | 48 | /** 49 | * 每次被封禁时触发 50 | */ 51 | @Override 52 | public void doDisable(String loginKey, Object loginId, long disableTime) { 53 | Date date = new Date(System.currentTimeMillis() + disableTime * 1000); 54 | println("账号[" + loginId + "]被封禁 (解封时间: " + SaFoxUtil.formatDate(date) + ")"); 55 | } 56 | 57 | /** 58 | * 每次被解封时触发 59 | */ 60 | @Override 61 | public void doUntieDisable(String loginKey, Object loginId) { 62 | println("账号[" + loginId + "]被解除封禁"); 63 | } 64 | 65 | /** 66 | * 每次创建Session时触发 67 | */ 68 | @Override 69 | public void doCreateSession(String id) { 70 | println("Session[" + id + "]创建成功"); 71 | } 72 | 73 | /** 74 | * 每次注销Session时触发 75 | */ 76 | @Override 77 | public void doLogoutSession(String id) { 78 | println("Session[" + id + "]注销成功"); 79 | } 80 | 81 | /** 82 | * 日志输出的前缀 83 | */ 84 | public static final String LOG_PREFIX = "SaLog -->: "; 85 | 86 | /** 87 | * 打印指定字符串 88 | * @param str 字符串 89 | */ 90 | public void println(String str) { 91 | if(SaManager.getConfig().getIsLog()) { 92 | System.out.println(LOG_PREFIX + str); 93 | } 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /sa-token-core/src/main/java/cn/dev33/satoken/util/SaTokenConsts.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.util; 2 | 3 | /** 4 | * sa-token常量类 5 | * @author kong 6 | * 7 | */ 8 | public class SaTokenConsts { 9 | 10 | 11 | // =================== sa-token版本信息 =================== 12 | 13 | /** 14 | * sa-token 当前版本号 15 | */ 16 | public static final String VERSION_NO = "v1.19.0"; 17 | 18 | /** 19 | * sa-token 开源地址 20 | */ 21 | public static final String GITHUB_URL = "https://github.com/dromara/sa-token"; 22 | 23 | /** 24 | * sa-token 开发文档地址 25 | */ 26 | public static final String DEV_DOC_URL = "http://sa-token.dev33.cn"; 27 | 28 | // =================== 常量key标记 =================== 29 | 30 | /** 31 | * 常量key标记: 如果token为本次请求新创建的,则以此字符串为key存储在当前request中 32 | */ 33 | public static final String JUST_CREATED_SAVE_KEY = "JUST_CREATED_SAVE_KEY_"; 34 | 35 | /** 36 | * 常量key标记: 如果本次请求已经验证过[无操作过期], 则以此值存储在当前request中 37 | */ 38 | public static final String TOKEN_ACTIVITY_TIMEOUT_CHECKED_KEY = "TOKEN_ACTIVITY_TIMEOUT_CHECKED_KEY_"; 39 | 40 | /** 41 | * 常量key标记: 在登录时,默认使用的设备名称 42 | */ 43 | public static final String DEFAULT_LOGIN_DEVICE = "default-device"; 44 | 45 | /** 46 | * 常量key标记: 在进行临时身份切换时使用的key 47 | */ 48 | public static final String SWITCH_TO_SAVE_KEY = "SWITCH_TO_SAVE_KEY_"; 49 | 50 | 51 | // =================== token-style 相关 =================== 52 | 53 | /** 54 | * token风格: uuid 55 | */ 56 | public static final String TOKEN_STYLE_UUID = "uuid"; 57 | 58 | /** 59 | * token风格: 简单uuid (不带下划线) 60 | */ 61 | public static final String TOKEN_STYLE_SIMPLE_UUID = "simple-uuid"; 62 | 63 | /** 64 | * token风格: 32位随机字符串 65 | */ 66 | public static final String TOKEN_STYLE_RANDOM_32 = "random-32"; 67 | 68 | /** 69 | * token风格: 64位随机字符串 70 | */ 71 | public static final String TOKEN_STYLE_RANDOM_64 = "random-64"; 72 | 73 | /** 74 | * token风格: 128位随机字符串 75 | */ 76 | public static final String TOKEN_STYLE_RANDOM_128 = "random-128"; 77 | 78 | /** 79 | * token风格: tik风格 (2_14_16) 80 | */ 81 | public static final String TOKEN_STYLE_TIK = "tik"; 82 | 83 | 84 | // =================== 其它 =================== 85 | 86 | /** 87 | * 连接token前缀和token值的字符 88 | */ 89 | public static final String TOKEN_CONNECTOR_CHAT = " "; 90 | 91 | /** 92 | * 切面、拦截器、过滤器等各种组件的注册优先级顺序 93 | */ 94 | public static final int ASSEMBLY_ORDER = -100; 95 | 96 | } 97 | -------------------------------------------------------------------------------- /sa-token-demo/sa-token-demo-webflux/pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | cn.dev33 6 | sa-token-demo-webflux 7 | 0.0.1-SNAPSHOT 8 | 9 | 10 | 11 | org.springframework.boot 12 | spring-boot-starter-parent 13 | 2.0.0.RELEASE 14 | 15 | 16 | 17 | 18 | 19 | 1.19.0 20 | 21 | 22 | 23 | 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-webflux 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-aop 32 | 33 | 34 | 35 | 36 | cn.dev33 37 | sa-token-reactor-spring-boot-starter 38 | ${sa-token-version} 39 | 40 | 41 | 42 | 47 | 48 | 49 | 54 | 55 | 56 | 60 | 61 | 62 | 63 | org.springframework.boot 64 | spring-boot-configuration-processor 65 | true 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /sa-token-demo/sa-token-demo-springboot/src/main/java/com/pj/satoken/SaTokenConfigure.java: -------------------------------------------------------------------------------- 1 | package com.pj.satoken; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 6 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 7 | 8 | import com.pj.util.AjaxJson; 9 | 10 | import cn.dev33.satoken.context.SaHolder; 11 | import cn.dev33.satoken.filter.SaServletFilter; 12 | import cn.dev33.satoken.interceptor.SaAnnotationInterceptor; 13 | 14 | 15 | /** 16 | * [Sa-Token 权限认证] 配置类 17 | * @author kong 18 | * 19 | */ 20 | @Configuration 21 | public class SaTokenConfigure implements WebMvcConfigurer { 22 | 23 | /** 24 | * 注册sa-token的拦截器,打开注解式鉴权功能 25 | */ 26 | @Override 27 | public void addInterceptors(InterceptorRegistry registry) { 28 | // 注册注解拦截器 29 | registry.addInterceptor(new SaAnnotationInterceptor()).addPathPatterns("/**").excludePathPatterns(""); 30 | } 31 | 32 | /** 33 | * 注册 [sa-token全局过滤器] 34 | */ 35 | @Bean 36 | public SaServletFilter getSaServletFilter() { 37 | return new SaServletFilter() 38 | 39 | // 指定 [拦截路由] 与 [放行路由] 40 | .addInclude("/**").addExclude("/favicon.ico") 41 | 42 | // 认证函数: 每次请求执行 43 | .setAuth(r -> { 44 | // System.out.println("---------- sa全局认证"); 45 | 46 | // SaRouterUtil.match("/test/test", () -> new Object()); 47 | }) 48 | 49 | // 异常处理函数:每次认证函数发生异常时执行此函数 50 | .setError(e -> { 51 | System.out.println("---------- sa全局异常 "); 52 | return AjaxJson.getError(e.getMessage()); 53 | }) 54 | 55 | // 前置函数:在每次认证函数之前执行 56 | .setBeforeAuth(r -> { 57 | // ---------- 设置一些安全响应头 ---------- 58 | SaHolder.getResponse() 59 | // 服务器名称 60 | .setServer("sa-server") 61 | // 是否可以在iframe显示视图: DENY=不可以 | SAMEORIGIN=同域下可以 | ALLOW-FROM uri=指定域名下可以 62 | .setHeader("X-Frame-Options", "SAMEORIGIN") 63 | // 是否启用浏览器默认XSS防护: 0=禁用 | 1=启用 | 1; mode=block 启用, 并在检查到XSS攻击时,停止渲染页面 64 | .setHeader("X-Frame-Options", "1; mode=block") 65 | // 禁用浏览器内容嗅探 66 | .setHeader("X-Content-Type-Options", "nosniff") 67 | ; 68 | }) 69 | ; 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /sa-token-doc/doc/use/remember-me.md: -------------------------------------------------------------------------------- 1 | # [记住我]模式 2 | --- 3 | 4 | 如下图所示,一般网站的登录界面都会有一个 [ 记住我 ] 按钮,当你勾选它后,即时你关闭浏览器再次打开网站,也依然会处于登录状态,无须重复验证密码 5 | 6 | ![../static/login-view.png](../static/login-view.png) 7 | 8 | 那么在sa-token中,如何做到 [ 记住我 ] 功能呢? 9 | 10 | 11 | ### 在sa-token中实现记住我功能 12 | 13 | sa-token的登录授权,**默认就是`[记住我]`模式**,为了实现`[非记住我]`模式, 你需要在登录时如下设置: 14 | 15 | ``` java 16 | // 设置登录账号id为10001,第二个参数指定是否为[记住我],当此值为false后,关闭浏览器后再次打开需要重新登录 17 | StpUtil.setLoginId(10001, false); 18 | ``` 19 | 20 | 那么,sa-token实现`[记住我]`的具体原理是? 21 | 22 | 23 | ### 实现原理 24 | Cookie作为浏览器提供的默认会话跟踪机制,其生命周期有两种形式,分别是: 25 | - 临时Cookie:有效期为本次会话,只要关闭浏览器窗口,Cookie就会消失 26 | - 永久Cookie:有效期为一个具体的时间,在时间未到期之前,即使用户关闭了浏览器Cookie也不会消失 27 | 28 | 利用Cookie的此特性,我们便可以轻松实现 [记住我] 模式: 29 | - 勾选[记住我]按钮时:调用`StpUtil.setLoginId(10001, true)`,在浏览器写入一个`永久Cookie`储存token,此时用户即使重启浏览器token依然有效 30 | - 不勾选[记住我]按钮时:调用`StpUtil.setLoginId(10001, false)`,在浏览器写入一个`临时Cookie`储存token,此时用户在重启浏览器后token便会消失,导致会话失效 31 | 32 | 33 | ### 前后台分离模式下如何实现[记住我]? 34 | 35 | 此时机智的你😏很快发现一个问题,Cookie虽好,却无法在前后端分离环境下使用,那是不是代表上述方案在APP、小程序等环境中无效? 36 | 37 | 准确的讲,答案是肯定的,任何基于Cookie的认证方案在前后台分离环境下都会失效(原因在于这些客户端默认没有实现Cookie功能),不过好在,这些客户端一般都提供了替代方案, 38 | 唯一遗憾的是,此场景中token的生命周期需要我们在前端手动控制 39 | 40 | 以经典跨端框架 [uni-app](https://uniapp.dcloud.io/) 为例,我们可以使用如下方式达到同样的效果: 41 | ``` js 42 | // 使用本地存储保存token,达到 [永久Cookie] 的效果 43 | uni.setStorageSync("satoken", "xxxx-xxxx-xxxx-xxxx-xxx"); 44 | 45 | // 使用globalData保存token,达到 [临时Cookie] 的效果 46 | getApp().globalData.satoken = "xxxx-xxxx-xxxx-xxxx-xxx"; 47 | ``` 48 | 49 | 如果你决定在PC浏览器环境下进行前后台分离模式开发,那么更加简单: 50 | ``` js 51 | // 使用 localStorage 保存token,达到 [永久Cookie] 的效果 52 | localStorage.setItem("satoken", "xxxx-xxxx-xxxx-xxxx-xxx"); 53 | 54 | // 使用 sessionStorage 保存token,达到 [临时Cookie] 的效果 55 | sessionStorage.setItem("satoken", "xxxx-xxxx-xxxx-xxxx-xxx"); 56 | ``` 57 | 58 | Remember me, it's too easy! 59 | 60 | 61 | 62 | ### 登录时指定token有效期 63 | 登录时不仅可以指定是否为`[记住我]`模式,还可以指定一个特定的时间作为token有效时长,如下示例: 64 | ``` java 65 | // 示例1: 66 | // 指定token有效期(单位: 秒),如下所示token七天有效 67 | StpUtil.setLoginId(10001, new SaLoginModel().setTimeout(60 * 60 * 24 * 7)); 68 | 69 | // ----------------------- 示例2:所有参数 70 | // `SaLoginModel`为登录参数Model,其有诸多参数决定登录时的各种逻辑,例如: 71 | StpUtil.setLoginId(10001, new SaLoginModel() 72 | .setDevice("PC") // 此次登录的客户端设备标识, 用于[同端互斥登录]时指定此次登录的设备名称 73 | .setIsLastingCookie(true) // 是否为持久Cookie(临时Cookie在浏览器关闭时会自动删除,持久Cookie在重新打开后依然存在) 74 | .setTimeout(60 * 60 * 24 * 7) // 指定此次登录token的有效期, 单位:秒 (如未指定,自动取全局配置的timeout值) 75 | ); 76 | ``` 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /sa-token-plugin/sa-token-quick-login/src/main/resources/static/sa-res/login.css: -------------------------------------------------------------------------------- 1 | *{margin: 0; padding: 0;} 2 | body{font-family: Helvetica Neue,Helvetica,PingFang SC,Tahoma,Arial,sans-serif;} 3 | ::-webkit-input-placeholder{color: #ccc;} 4 | 5 | /* 视图盒子 */ 6 | .view-box{position: relative; width: 100vw; height: 100vh; overflow: hidden;} 7 | /* 背景 EAEFF3 */ 8 | .bg-1{height: 50%; background: linear-gradient(to bottom right, #0466c5, #3496F5);} 9 | .bg-2{height: 50%; background-color: #EAEFF3;} 10 | 11 | /* 渐变背景 */ 12 | .bg-1{ 13 | background-size: 500%; 14 | background-image: linear-gradient(125deg,#0466c5,#3496F5,#0466c5,#3496F5,#0466c5,#2496F5); 15 | animation: bganimation 30s infinite; 16 | } 17 | @keyframes bganimation{ 18 | 0%{background-position: 0% 50%;} 19 | 50%{background-position: 100% 50%;} 20 | 100%{background-position: 0% 50%;} 21 | } 22 | 23 | /* 内容盒子 */ 24 | .content-box{position: absolute; width: 100vw; height: 100vh; top: 0px;} 25 | 26 | /* 登录盒子 */ 27 | /* .login-box{width: 400px; height: 400px; position: absolute; left: calc(50% - 200px); top: calc(50% - 200px); max-width: 90%; } */ 28 | .login-box{width: 400px; margin: auto; max-width: 90%; height: 100%;} 29 | .login-box{display: flex; align-items: center; text-align: center;} 30 | 31 | /* 表单 */ 32 | .from-box{flex: 1; padding: 20px 50px; background-color: #FFF;} 33 | .from-box{border-radius: 1px; box-shadow: 1px 1px 20px #666;} 34 | .from-title{margin-top: 20px; margin-bottom: 30px; text-align: center;} 35 | 36 | /* 输入框 */ 37 | .from-item{border: 0px #000 solid; margin-bottom: 15px;} 38 | .s-input{width: 100%; line-height: 32px; height: 32px; text-indent: 1em; outline: 0; border: 1px #ccc solid; border-radius: 3px; transition: all 0.2s;} 39 | .s-input{font-size: 12px;} 40 | .s-input:focus{border-color: #409eff} 41 | 42 | /* 登录按钮 */ 43 | .s-btn{ text-indent: 0; cursor: pointer; background-color: #409EFF; border-color: #409EFF; color: #FFF;} 44 | .s-btn:hover{background-color: #50aEFF;} 45 | 46 | /* 重置按钮 */ 47 | .reset-box{text-align: left; font-size: 12px;} 48 | .reset-box a{text-decoration: none;} 49 | .reset-box a:hover{text-decoration: underline;} 50 | 51 | /* loading框样式 */ 52 | .ajax-layer-load.layui-layer-dialog{min-width: 0px !important; background-color: rgba(0,0,0,0.85);} 53 | .ajax-layer-load.layui-layer-dialog .layui-layer-content{padding: 10px 20px 10px 40px; color: #FFF;} 54 | .ajax-layer-load.layui-layer-dialog .layui-layer-content .layui-layer-ico{width: 20px; height: 20px; background-size: 20px 20px; top: 12px; } -------------------------------------------------------------------------------- /sa-token-demo/sa-token-demo-oauth2-server/src/main/resources/static/auth.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 服务提供方-确认授权页 6 | 10 | 11 | 12 |

25 | 26 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /sa-token-doc/doc/use/not-cookie.md: -------------------------------------------------------------------------------- 1 | # 无Cookie模式 2 | --- 3 | 4 | ### 何为无Cookie模式? 5 | 6 | 无Cookie:特指不支持Cookie功能的终端,通俗来讲就是我们常说的 —— **前后台分离模式** 7 | 8 | 常规PC端鉴权方法,一般由`Cookie模式`完成,而`Cookie`有两个特性: 9 | 1. 可由后端控制写入 10 | 2. 每次请求自动提交 11 | 12 | 这就使得我们在前端代码中,无需任何特殊操作,就能完成鉴权的全部流程(因为整个流程都是后端控制完成的)
13 | 而在app、小程序等前后台分离场景中,一般是没有`Cookie`这一功能的,此时大多数人都会一脸懵逼,咋进行鉴权啊? 14 | 15 | 见招拆招,其实答案很简单: 16 | - 不能后端控制写入了,就前端自己写入(难点在**后端如何将token传递到前端**) 17 | - 每次请求不能自动提交了,那就手动提交(难点在**前端如何将token传递到后端**,同时**后端将其读取出来**) 18 | 19 | 20 | 21 | ### 1、后端将 token 返回到前端 22 | 23 | 1. 首先调用 `StpUtil.setLoginId(Object loginId)` 进行登录 24 | 2. 调用 `StpUtil.getTokenInfo()` 返回当前会话的token详细参数 25 | - 此方法返回一个对象,其有两个关键属性:`tokenName`和`tokenValue`(`token`的名称和`token`的值) 26 | - 将此对象传递到前台,让前端人员将这两个值保存到本地 27 | 28 | ### 2、前端将 token 提交到后端 29 | 1. 无论是app还是小程序,其传递方式都大同小异 30 | 2. 那就是,将`token`塞到请求`header`里 ,格式为:`{tokenName: tokenValue}` 31 | 3. 以经典跨端框架 [uni-app](https://uniapp.dcloud.io/) 为例: 32 | 33 | **方式1,简单粗暴** 34 | 35 | ``` js 36 | // 1、首先在登录时,将 tokenValue 存储在本地,例如: 37 | uni.setStorageSync('tokenValue', tokenValue); 38 | 39 | // 2、在发起ajax请求的地方,获取这个值,并塞到header里 40 | uni.request({ 41 | url: 'https://www.example.com/request', // 仅为示例,并非真实接口地址。 42 | header: { 43 | "content-type": "application/x-www-form-urlencoded", 44 | "satoken": uni.getStorageSync('tokenValue') // 关键代码, 注意参数名字是 satoken 45 | }, 46 | success: (res) => { 47 | console.log(res.data); 48 | } 49 | }); 50 | ``` 51 | 52 | **方式2,更加灵活** 53 | 54 | ``` js 55 | // 1、首先在登录时,将tokenName和tokenValue一起存储在本地,例如: 56 | uni.setStorageSync('tokenName', tokenName); 57 | uni.setStorageSync('tokenValue', tokenValue); 58 | 59 | // 2、在发起ajax的地方,获取这两个值, 并组织到head里 60 | var tokenName = uni.getStorageSync('tokenName'); // 从本地缓存读取tokenName值 61 | var tokenValue = uni.getStorageSync('tokenValue'); // 从本地缓存读取tokenValue值 62 | var header = { 63 | "content-type": "application/x-www-form-urlencoded" // 防止后台拿不到参数 64 | }; 65 | if (tokenName != undefined && tokenName != '') { 66 | header[tokenName] = tokenValue; 67 | } 68 | 69 | // 3、后续在发起请求时将 header 对象塞到请求头部 70 | uni.request({ 71 | url: 'https://www.example.com/request', // 仅为示例,并非真实接口地址。 72 | header: header, 73 | success: (res) => { 74 | console.log(res.data); 75 | } 76 | }); 77 | ``` 78 | 79 | 4. 只要按照如此方法将`token`值传递到后端,`sa-token`就能像传统PC端一样自动读取到`token`值,进行鉴权 80 | 5. 你可能会有疑问,难道我每个`ajax`都要写这么一坨?岂不是麻烦死了 81 | - 你当然不能每个`ajax`都写这么一坨,因为这种重复代码都是要封装在一个函数里统一调用的 82 | 83 | 84 | ### 其它解决方案? 85 | 如果你对`Cookie`非常了解,那你就会明白,所谓`Cookie`,本质上就是一个特殊的`header`参数而已
86 | 而既然它只是一个`header`参数,我们就能就能手动模拟实现它,从而完成鉴权操作 87 | 88 | 这其实是对`无Cookie模式`的另一种解决方案,有兴趣的同学可以百度了解一下,在此暂不赘述 89 | 90 | -------------------------------------------------------------------------------- /sa-token-doc/doc/use/many-account.md: -------------------------------------------------------------------------------- 1 | # 多账号验证 2 | --- 3 | 4 | ### 需求场景 5 | 有的时候,我们会在一个项目中设计两套账号体系,比如一个电商系统的 `user表` 和 `admin表`
6 | 在这种场景下,如果两套账号我们都使用 `StpUtil` 类的API进行登录鉴权,那么势必会发生逻辑冲突 7 | 8 | 在sa-token中,这个问题的模型叫做:多账号体系验证
9 | 要解决这个问题,我们必须有一个合理的机制将这两套账号的授权给区分开,让它们互不干扰才行 10 | 11 | 12 | ### 解决方案 13 | 14 | 以上几篇介绍的api调用,都是经过 `StpUtil` 类的各种静态方法进行授权验证, 15 | 而如果我们深入它的源码,[点此阅览](https://gitee.com/dromara/sa-token/blob/master/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpUtil.java)
16 | 就会发现,此类并没有任何代码逻辑,唯一做的事就是对成员变量`stpLogic`的各个API包装一下进行转发 17 | 18 | 这样做有两个优点: 19 | - `StpLogic`类的所有函数都可以被重写,按需扩展 20 | - 在构造方法时随意传入一个不同的 `loginKey`,就可以再造一套账号登录体系 21 | 22 | 23 | ### 操作示例 24 | 25 | 比如说,对于原生`StpUtil`类,我们只做`admin账号`权限验证,而对于`user账号`,我们则: 26 | 1. 新建一个新的权限验证类,比如: `StpUserUtil.java` 27 | 2. 将`StpUtil.java`类的全部代码复制粘贴到 `StpUserUtil.java`里 28 | 3. 更改一下其 `LoginKey`, 比如: 29 | 30 | ``` java 31 | public class StpUserUtil { 32 | 33 | /** 34 | * 账号体系标识 35 | */ 36 | public static final String KEY = "user"; // 将 LoginKey 从`login`改为`user` 37 | 38 | // 其它代码 ... 39 | 40 | } 41 | ``` 42 | 4. 接下来就可以像调用`StpUtil.java`一样调用 `StpUserUtil.java`了,这两套账号认证的逻辑是完全隔离的 43 | 44 | > 成品样例参考:[码云 StpUserUtil.java](https://gitee.com/click33/sa-plus/blob/master/sp-server/src/main/java/com/pj/current/satoken/StpUserUtil.java) 45 | 46 | 47 | ### 在多账号模式下使用注解鉴权 48 | 框架默认的注解鉴权 如`@SaCheckLogin` 只针对原生`StpUtil`进行鉴权 49 | 50 | 例如,我们在一个方法上加上`@SaCheckLogin`注解,这个注解只会放行通过`StpUtil.setLoginId(id)`进行登录的会话, 51 | 而对于通过`StpUserUtil.setLoginId(id)`进行登录的都会话,则始终不会通过校验 52 | 53 | 那么如何告诉`@SaCheckLogin`要鉴别的是哪套账号的登录会话呢?很简单,你只需要指定一下注解的key属性即可: 54 | 55 | ``` java 56 | // 通过key属性指定此注解校验的是我们自定义的`StpUserUtil`,而不是原生`StpUtil` 57 | @SaCheckLogin(key = StpUserUtil.KEY) 58 | @RequestMapping("info") 59 | public String info() { 60 | return "查询用户信息"; 61 | } 62 | ``` 63 | 64 | 注:`@SaCheckRole("xxx")`、`@SaCheckPermission("xxx")`同理,亦可根据key属性指定其校验的账号体系,此属性默认为`""`,代表使用原生`StpUtil`账号体系 65 | 66 | 67 | 68 | 69 | 70 | ### 进阶 71 | 假设我们不仅需要在后台同时集成两套账号,我们还需要在一个客户端同时登陆两套账号(业务场景举例:一个APP中可以同时登陆商家账号和用户账号) 72 | 73 | 如果我们不做任何特殊处理的话,在客户端会发生`token覆盖`,新登录的token会覆盖掉旧登录的token从而导致旧登录失效 74 | 75 | 那么如何解决这个问题?
76 | 很简单,我们只要更改一下 `StpUserUtil` 的 `TokenName` 即可,参考示例如下: 77 | 78 | ``` java 79 | // 底层的 StpLogic 对象 80 | public static StpLogic stpLogic = new StpLogic("user") { 81 | // 重写 `splicingKeyTokenName` 函数,返回一个与 `StpUtil` 不同的token名称, 防止冲突 82 | @Override 83 | public String splicingKeyTokenName() { 84 | return super.splicingKeyTokenName() + "-user"; 85 | } 86 | }; 87 | ``` 88 | 89 | 再次调用 `StpUserUtil.setLoginId(10001)` 进行登录授权时,token的名称将不再是 `satoken`,而是我们重写后的 `satoken-user` 90 | 91 | 92 | 93 | > 不同体系账号在登录时设置不同的token有效期等信息, 详见[登录时指定token有效期](/use/remember-me?id=登录时指定token有效期) -------------------------------------------------------------------------------- /sa-token-doc/doc/more/common-questions.md: -------------------------------------------------------------------------------- 1 | # 常见问题 2 | 本篇整理大家在群聊里经常提问的一些问题,如有补充,欢迎提交pr 3 | 4 | --- 5 | 6 | ### 加了注解进行鉴权认证,不生效? 7 | 注解鉴权功能默认关闭,两种方式任选其一进行打开:注册注解拦截器、集成`AOP模块`, 8 | 如果已经打开仍然没有效果,加群说明一下复现步骤 9 | 10 | 11 | ### 整合Redis时,除了引入pom依赖,还需要做其它的吗? 12 | 引入pom依赖后,在框架层面你无须做其它事情,但是你需要为项目指定一下`Redis`的连接信息,参考此文件:[application-dev](https://gitee.com/click33/sa-plus/blob/master/sp-server/src/main/resources/application-dev.yml) 13 | 14 | 15 | ### 登录方法需要我自己实现吗? 16 | 是的,不同于`shiro`等框架,`sa-token`不会在登录流程中强插一脚,开发者比对完用户的账号和密码之后,只需要调用`StpUtil.setLogin(id)`通知一下框架即可 17 | 18 | 19 | ### 一个User对象存进Session后,再取出来时报错:无法从User类型转换成User类型? 20 | 群员亲测,当你打开热部署模式后,先存进去的对象,再热刷新后再取出,会报错,关闭热刷新即可解决 21 | 22 | 23 | ### 框架抛出的权限不足异常,我想根据自定义提示信息,可以吗? 24 | 可以,在全局异常拦截器里捕获`NotPermissionException`,可以通过`getCode()`获取没有通过认证的权限码,可以据此自定义返回信息 25 | 26 | 27 | ### 我的项目权限模型不是RBAC模型,很复杂,可以集成吗? 28 | 无论什么模型,只要能把一个用户具有的所有权限塞到一个List里返回给框架,就能集成 29 | 30 | 31 | ### SaRouterUtil.match 有多个路径需要排除怎么办? 32 | 可以点进去源码看一下,`SaRouterUtil.match`方法有多个重载,可以放一个集合, 例如:
33 | `SaRouterUtil.match(Arrays.asList("/**"), Arrays.asList("/login", "/reg"), () -> StpUtil.checkLogin());` 34 | 35 | 36 | ### 为什么StpUtil.setLoginId() 不能直接写入一个User对象? 37 | `StpUtil.setLoginId()`只是为了给当前会话做个唯一标记,通常写入`UserId`即可,如果要存储User对象,可以使用`StpUtil.getSession()`获取Session对象进行存储 38 | 39 | 40 | ### 前后台分离模式下和普通模式有何不同? 41 | 主要是失去了`Cookie`无法自动化保存和提交`token秘钥`,可以参考章节:[前后台分离](/use/not-cookie) 42 | 43 | 44 | ### 前后台分离时,前端提交的header参数是叫token还是satoken还是tokenName? 45 | 默认是satoken,如果想换一个名字,更改一下配置文件的`tokenName`即可 46 | 47 | 48 | ### Springboot环境下采用自定义拦截器排除了某个路径仍然被拦截了? 49 | 可能是404了,SpringBoot环境下如果访问接口404后,会被重定向到`/error`,然后被再次拦截,如果是其它原因,欢迎加群反馈 50 | 51 | 52 | ### 权限可以做成动态的吗? 53 | 权限本来就是动态的,只有jwt那种模式才是非动态的 54 | 55 | 56 | ### 集成jwt后为什么在 getSession 时提示 jwt has not session ? 57 | `jwt`的招牌便是无须借助服务端完成会话管理,如果集成`jwt`后再次使用`Session`功能,那将又回到了传统`Session`模式,属于自断招牌,此种技术组合没有任何意义,因此jwt集成模式不提供`Session`功能,如果需要`Session`功能,就不要集成`jwt` 58 | 59 | 60 | ### 怎么关闭默认的Cookie模式呢? 61 | 在配置文件将`isReadCookie`值配置为`false` 62 | 63 | 64 | ### 怎么关掉每次启动时的字符画打印? 65 | 在配置文件将`isV`值配置为`false` 66 | 67 | 68 | ### StpUtil.getSession()必须登录后才能调用吗?如果我想在用户未登录之前存储一些数据应该怎么办? 69 | `StpUtil.getSession()`获取的是`User-Session`,必须登录后才能使用,如果需要在未登录状态下也使用Session功能,请使用`Token-Session`
70 | 步骤:先在配置文件里将`tokenSessionCheckLogin`配置为`false`,然后通过`StpUtil.getTokenSession()`获取Session 71 | 72 | 73 | ### 我只使用header来传输token,还需要打开Cookie模式吗? 74 | 不需要,如果只使用header来传输token,可以在配置文件关闭Cookie模式,例:`isReadCookie=false` 75 | 76 | 77 | ### 我想让用户修改密码后立即掉线重新登录,应该怎么做? 78 | 框架内置 [强制指定账号下线] 的APi,在执行修改密码逻辑之后调用此API即可: `StpUtil.logout()` 79 | 80 | 81 | ### 还是有不明白到的地方? 82 | 请在`github`提交`issues`,或者加入qq群交流(群链接在[首页](README?id=交流群)) 83 | 84 | 85 | ### 我能为这个框架贡献代码吗? 86 | **可以**,请参照首页的提交pr步骤 ,[贡献代码](README?id=贡献代码) 87 | 88 | -------------------------------------------------------------------------------- /sa-token-demo/sa-token-demo-jwt/pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | cn.dev33 6 | sa-token-demo-jwt 7 | 0.0.1-SNAPSHOT 8 | 9 | 10 | 11 | org.springframework.boot 12 | spring-boot-starter-parent 13 | 2.0.0.RELEASE 14 | 15 | 16 | 17 | 18 | 19 | 1.19.0 20 | 21 | 22 | 23 | 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-web 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-aop 32 | 33 | 34 | 35 | 36 | cn.dev33 37 | sa-token-spring-boot-starter 38 | ${sa-token-version} 39 | 40 | 41 | 42 | 43 | io.jsonwebtoken 44 | jjwt 45 | 0.9.1 46 | 47 | 48 | 49 | 54 | 55 | 56 | 61 | 62 | 63 | 67 | 68 | 69 | 74 | 75 | 76 | 77 | org.springframework.boot 78 | spring-boot-configuration-processor 79 | true 80 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /sa-token-demo/sa-token-demo-webflux/src/main/java/com/pj/test/TestController.java: -------------------------------------------------------------------------------- 1 | package com.pj.test; 2 | 3 | import java.time.Duration; 4 | 5 | import org.springframework.web.bind.annotation.RequestMapping; 6 | import org.springframework.web.bind.annotation.RequestParam; 7 | import org.springframework.web.bind.annotation.RestController; 8 | 9 | import com.pj.util.AjaxJson; 10 | 11 | import cn.dev33.satoken.reactor.context.SaReactorHolder; 12 | import cn.dev33.satoken.stp.StpUtil; 13 | import reactor.core.publisher.Mono; 14 | 15 | /** 16 | * 测试专用Controller 17 | * @author kong 18 | * 19 | */ 20 | @RestController 21 | @RequestMapping("/test/") 22 | public class TestController { 23 | 24 | // 测试登录接口 [同步模式], 浏览器访问: http://localhost:8081/test/login 25 | @RequestMapping("login") 26 | public AjaxJson login(@RequestParam(defaultValue="10001") String id) { 27 | StpUtil.setLoginId(id); 28 | return AjaxJson.getSuccess("登录成功"); 29 | } 30 | 31 | // API测试 [同步模式], 浏览器访问: http://localhost:8081/test/isLogin 32 | @RequestMapping("isLogin") 33 | public AjaxJson isLogin() { 34 | System.out.println("当前会话是否登录:" + StpUtil.isLogin()); 35 | return AjaxJson.getSuccessData(StpUtil.getTokenInfo()); 36 | } 37 | 38 | // API测试 [异步模式], 浏览器访问: http://localhost:8081/test/isLogin2 39 | @RequestMapping("isLogin2") 40 | public Mono isLogin2() { 41 | System.out.println("当前会话是否登录:" + StpUtil.isLogin()); 42 | AjaxJson aj = AjaxJson.getSuccessData(StpUtil.getTokenInfo()); 43 | return Mono.just(aj); 44 | } 45 | 46 | // API测试 [异步模式, 同一线程], 浏览器访问: http://localhost:8081/test/isLogin3 47 | @RequestMapping("isLogin3") 48 | public Mono isLogin3() { 49 | System.out.println("当前会话是否登录:" + StpUtil.isLogin()); 50 | // 异步方式 51 | return SaReactorHolder.getContent().map(e -> { 52 | System.out.println("当前会话是否登录2:" + StpUtil.isLogin()); 53 | return AjaxJson.getSuccessData(StpUtil.getTokenInfo()); 54 | }); 55 | } 56 | 57 | // API测试 [异步模式, 不同线程], 浏览器访问: http://localhost:8081/test/isLogin4 58 | @RequestMapping("isLogin4") 59 | public Mono isLogin4() { 60 | System.out.println("当前会话是否登录:" + StpUtil.isLogin()); 61 | System.out.println("线程id-----" + Thread.currentThread().getId()); 62 | return Mono.delay(Duration.ofSeconds(1)).flatMap(r->{ 63 | return SaReactorHolder.getContent().map(rr->{ 64 | System.out.println("线程id---内--" + Thread.currentThread().getId()); 65 | System.out.println("当前会话是否登录2:" + StpUtil.isLogin()); 66 | return AjaxJson.getSuccessData(StpUtil.getTokenInfo()); 67 | }); 68 | }); 69 | } 70 | 71 | // 测试 浏览器访问: http://localhost:8081/test/test 72 | @RequestMapping("test") 73 | public AjaxJson test() { 74 | System.out.println("线程id-----------Controller--" + Thread.currentThread().getId() + "\t\t"); 75 | System.out.println("当前会话是否登录:" + StpUtil.isLogin()); 76 | return AjaxJson.getSuccessData(StpUtil.getTokenInfo()); 77 | } 78 | 79 | 80 | } 81 | -------------------------------------------------------------------------------- /sa-token-plugin/sa-token-quick-login/src/main/java/cn/dev33/satoken/quick/SaQuickBean.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.quick; 2 | 3 | import javax.servlet.http.HttpServletRequest; 4 | import javax.servlet.http.HttpServletResponse; 5 | 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.context.properties.ConfigurationProperties; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | import org.springframework.context.annotation.Import; 11 | import org.springframework.core.annotation.Order; 12 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 13 | 14 | import cn.dev33.satoken.exception.NotLoginException; 15 | import cn.dev33.satoken.filter.SaServletFilter; 16 | import cn.dev33.satoken.quick.config.SaQuickConfig; 17 | import cn.dev33.satoken.quick.web.SaQuickController; 18 | import cn.dev33.satoken.spring.SpringMVCUtil; 19 | import cn.dev33.satoken.stp.StpUtil; 20 | import cn.dev33.satoken.util.SaTokenConsts; 21 | 22 | /** 23 | * 自动注入 24 | * 25 | * @author kong 26 | * 27 | */ 28 | @Configuration 29 | @Import({ SaQuickController.class }) 30 | public class SaQuickBean implements WebMvcConfigurer { 31 | 32 | /** 33 | * quick-login 配置 34 | * 35 | * @return see note 36 | */ 37 | @Bean 38 | @ConfigurationProperties(prefix = "sa") 39 | public SaQuickConfig getSaQuickConfig() { 40 | return new SaQuickConfig(); 41 | } 42 | 43 | /** 44 | * 注入quick-login 配置 45 | * 46 | * @param saQuickConfig 配置对象 47 | */ 48 | @Autowired 49 | public void setSaQuickConfig(SaQuickConfig saQuickConfig) { 50 | SaQuickManager.setConfig(saQuickConfig); 51 | } 52 | 53 | /** 54 | * 注册 [sa-token全局过滤器] 55 | * 56 | * @return see note 57 | */ 58 | @Bean 59 | @Order(SaTokenConsts.ASSEMBLY_ORDER - 1) 60 | public SaServletFilter getSaServletFilter() { 61 | return new SaServletFilter(). 62 | 63 | // 拦截路由 & 放行路由 64 | addInclude("/**").addExclude("/favicon.ico", "/saLogin", "/doLogin", "/sa-res/**"). 65 | 66 | // 认证函数: 每次请求执行 67 | setAuth(r -> { 68 | // System.out.println("---------- 进入sa-token全局认证 -----------"); 69 | 70 | // 未登录时直接转发到login.html页面 71 | if (SaQuickManager.getConfig().getAuth() && StpUtil.isLogin() == false) { 72 | try { 73 | HttpServletRequest request = SpringMVCUtil.getRequest(); 74 | HttpServletResponse response = SpringMVCUtil.getResponse(); 75 | request.getRequestDispatcher("/saLogin").forward(request, response); 76 | } catch (Exception e) { 77 | e.printStackTrace(); 78 | } 79 | // 抛出异常,不再继续执行 80 | throw NotLoginException.newInstance(StpUtil.getLoginKey(), ""); 81 | } 82 | 83 | }). 84 | 85 | // 异常处理函数:每次认证函数发生异常时执行此函数 86 | setError(e -> { 87 | // System.out.println("---------- 进入sa-token异常处理 -----------"); 88 | return e.getMessage(); 89 | }); 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /sa-token-demo/sa-token-demo-springboot/pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | cn.dev33 6 | sa-token-demo-springboot 7 | 0.0.1-SNAPSHOT 8 | 9 | 10 | 11 | org.springframework.boot 12 | spring-boot-starter-parent 13 | 2.0.0.RELEASE 14 | 15 | 16 | 17 | 18 | 19 | 1.19.0 20 | 21 | 22 | 23 | 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-web 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-aop 32 | 33 | 34 | 35 | 36 | cn.dev33 37 | sa-token-spring-boot-starter 38 | ${sa-token-version} 39 | 40 | 41 | 42 | 47 | 48 | 49 | 54 | 55 | 56 | 60 | 61 | 62 | 67 | 68 | 69 | 70 | org.springframework.boot 71 | spring-boot-configuration-processor 72 | true 73 | 74 | 75 | 76 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /sa-token-doc/doc/use/password-secure.md: -------------------------------------------------------------------------------- 1 | # 密码加密 2 | 3 | 严格来讲,密码加密不属于 [权限认证] 的范畴,但是对于大多数系统来讲,密码加密又是安全认证不可或缺的部分, 4 | 所以,应大家要求,`sa-token`在`v1.14版本`添加密码加密模块,该模块非常简单,仅仅封装了一些常见的加密算法 5 | 6 | 7 | 8 | ### 摘要加密 9 | md5、sha1、sha256 10 | ``` java 11 | // md5加密 12 | SaSecureUtil.md5("123456"); 13 | 14 | // sha1加密 15 | SaSecureUtil.sha1("123456"); 16 | 17 | // sha256加密 18 | SaSecureUtil.sha256("123456"); 19 | 20 | // md5加盐加密: md5(md5(str) + md5(salt)) 21 | SaSecureUtil.md5BySalt("123456", "salt"); 22 | ``` 23 | 24 | 25 | ### 对称加密 26 | AES加密 27 | ``` java 28 | // 定义秘钥和明文 29 | String key = "123456"; 30 | String text = "sa-token 一个轻量级java权限认证框架"; 31 | 32 | // 加密 33 | String ciphertext = SaSecureUtil.aesEncrypt(key, text); 34 | System.out.println("AES加密后:" + ciphertext); 35 | 36 | // 解密 37 | String text2 = SaSecureUtil.aesDecrypt(key, ciphertext); 38 | System.out.println("AES解密后:" + text2); 39 | ``` 40 | 41 | 42 | ### 非对称加密 43 | RSA加密 44 | ``` java 45 | // 定义私钥和公钥 46 | 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=="; 47 | String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDvsJrdNacJvZRzHauwPIJBIoJNFyjH47/uCIwBYVgkok3j+3bZZ3HhSW5Fsb23O0KN3wjqcJn3LJMFHN1UoMoiZDSQ7PYrz275bfCFNm2fjwQ8WMUgiRa4KLlTqQK/Gq/2+oDbmE4tb7dxZBrUowAhW9NNv+vESlE8nvP7XXFYpQIDAQAB"; 48 | 49 | // 文本 50 | String text = "sa-token 一个轻量级java权限认证框架"; 51 | 52 | // 使用公钥加密 53 | String ciphertext = SaSecureUtil.rsaEncryptByPublic(publicKey, text); 54 | System.out.println("公钥加密后:" + ciphertext); 55 | 56 | // 使用私钥解密 57 | String text2 = SaSecureUtil.rsaDecryptByPrivate(privateKey, ciphertext); 58 | System.out.println("私钥解密后:" + text2); 59 | ``` 60 | 61 | 你可能会有疑问,私钥和公钥这么长的一大串,我怎么弄出来,手写吗?当然不是,调用以下方法生成即可 62 | ``` java 63 | // 生成一对公钥和私钥,其中Map对象 (private=私钥, public=公钥) 64 | System.out.println(SaSecureUtil.rsaGenerateKeyPair()); 65 | ``` 66 | 67 | 68 | ### Base64编码与解码 69 | ``` java 70 | // 文本 71 | String text = "sa-token 一个轻量级java权限认证框架"; 72 | 73 | // 使用Base64编码 74 | String base64Text = SaBase64Util.encode(text); 75 | System.out.println("Base64编码后:" + base64Text); 76 | 77 | // 使用Base64解码 78 | String text2 = SaBase64Util.decode(base64Text); 79 | System.out.println("Base64解码后:" + text2); 80 | ``` 81 | 82 |
83 | 如需更多加密算法请提交pr 84 | -------------------------------------------------------------------------------- /sa-token-core/src/main/java/cn/dev33/satoken/stp/SaLoginModel.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.stp; 2 | 3 | import cn.dev33.satoken.SaManager; 4 | import cn.dev33.satoken.config.SaTokenConfig; 5 | import cn.dev33.satoken.dao.SaTokenDao; 6 | import cn.dev33.satoken.util.SaTokenConsts; 7 | 8 | /** 9 | * 调用 `StpUtil.setLogin()` 时的 [配置参数 Model ] 10 | * @author kong 11 | * 12 | */ 13 | public class SaLoginModel { 14 | 15 | 16 | /** 17 | * 此次登录的客户端设备标识 18 | */ 19 | public String device; 20 | 21 | /** 22 | * 是否为持久Cookie(临时Cookie在浏览器关闭时会自动删除,持久Cookie在重新打开后依然存在) 23 | */ 24 | public Boolean isLastingCookie; 25 | 26 | /** 27 | * 指定此次登录token的有效期, 单位:秒 (如未指定,自动取全局配置的timeout值) 28 | */ 29 | public Long timeout; 30 | 31 | 32 | /** 33 | * @return device 34 | */ 35 | public String getDevice() { 36 | return device; 37 | } 38 | 39 | /** 40 | * @param device 要设置的 device 41 | * @return 对象自身 42 | */ 43 | public SaLoginModel setDevice(String device) { 44 | this.device = device; 45 | return this; 46 | } 47 | 48 | /** 49 | * @return isLastingCookie 50 | */ 51 | public Boolean getIsLastingCookie() { 52 | return isLastingCookie; 53 | } 54 | 55 | /** 56 | * @param isLastingCookie 要设置的 isLastingCookie 57 | * @return 对象自身 58 | */ 59 | public SaLoginModel setIsLastingCookie(Boolean isLastingCookie) { 60 | this.isLastingCookie = isLastingCookie; 61 | return this; 62 | } 63 | 64 | /** 65 | * @return timeout 66 | */ 67 | public Long getTimeout() { 68 | return timeout; 69 | } 70 | 71 | /** 72 | * @param timeout 要设置的 timeout 73 | * @return 对象自身 74 | */ 75 | public SaLoginModel setTimeout(long timeout) { 76 | this.timeout = timeout; 77 | return this; 78 | } 79 | 80 | 81 | /** 82 | * @return cookie时长 83 | */ 84 | public int getCookieTimeout() { 85 | if(isLastingCookie == false) { 86 | return -1; 87 | } 88 | if(timeout == SaTokenDao.NEVER_EXPIRE) { 89 | return Integer.MAX_VALUE; 90 | } 91 | return (int)(long)timeout; 92 | } 93 | 94 | 95 | /** 96 | * 构建对象,初始化默认值 97 | * @return 对象自身 98 | */ 99 | public SaLoginModel build() { 100 | return build(SaManager.getConfig()); 101 | } 102 | 103 | /** 104 | * 构建对象,初始化默认值 105 | * @param config 配置对象 106 | * @return 对象自身 107 | */ 108 | public SaLoginModel build(SaTokenConfig config) { 109 | if(device == null) { 110 | device = SaTokenConsts.DEFAULT_LOGIN_DEVICE; 111 | } 112 | if(isLastingCookie == null) { 113 | isLastingCookie = true; 114 | } 115 | if(timeout == null) { 116 | timeout = config.getTimeout(); 117 | } 118 | return this; 119 | } 120 | 121 | 122 | /** 123 | * 静态方法获取一个 SaLoginModel 对象 124 | * @return SaLoginModel 对象 125 | */ 126 | public static SaLoginModel create() { 127 | return new SaLoginModel(); 128 | } 129 | 130 | 131 | /** 132 | * toString 133 | */ 134 | @Override 135 | public String toString() { 136 | return "SaLoginModel [device=" + device + ", isLastingCookie=" + isLastingCookie + ", timeout=" + timeout + "]"; 137 | } 138 | 139 | 140 | 141 | 142 | } 143 | -------------------------------------------------------------------------------- /sa-token-doc/doc/use/token-style.md: -------------------------------------------------------------------------------- 1 | # 花式token 2 | 3 | 本篇介绍token生成的各种风格,以及自定义token生成策略 4 | 5 | --- 6 | 7 | 8 | ## 内置风格 9 | 10 | Sa-Token默认的token生成策略是uuid风格, 其模样类似于:`623368f0-ae5e-4475-a53f-93e4225f16ae`
11 | 如果你对这种风格不太感冒,还可以将token生成设置为其他风格 12 | 13 | 怎么设置呢?只需要在yml配置文件里设置 `spring.sa-token.token-style=风格类型` 即可,其有多种取值: 14 | 15 | ``` java 16 | // 1. token-style=uuid —— uuid风格 (默认风格) 17 | "623368f0-ae5e-4475-a53f-93e4225f16ae" 18 | 19 | // 2. token-style=simple-uuid —— 同上,uuid风格, 只不过去掉了中划线 20 | "6fd4221395024b5f87edd34bc3258ee8" 21 | 22 | // 3. token-style=random-32 —— 随机32位字符串 23 | "qEjyPsEA1Bkc9dr8YP6okFr5umCZNR6W" 24 | 25 | // 4. token-style=random-64 —— 随机64位字符串 26 | "v4ueNLEpPwMtmOPMBtOOeIQsvP8z9gkMgIVibTUVjkrNrlfra5CGwQkViDjO8jcc" 27 | 28 | // 5. token-style=random-128 —— 随机128位字符串 29 | "nojYPmcEtrFEaN0Otpssa8I8jpk8FO53UcMZkCP9qyoHaDbKS6dxoRPky9c6QlftQ0pdzxRGXsKZmUSrPeZBOD6kJFfmfgiRyUmYWcj4WU4SSP2ilakWN1HYnIuX0Olj" 30 | 31 | // 6. token-style=tik —— tik风格 32 | "gr_SwoIN0MC1ewxHX_vfCW3BothWDZMMtx__" 33 | ``` 34 | 35 | 36 | ## 自定义token生成策略 37 | 38 | 如果你觉着以上风格都不是你喜欢的类型,那么你还可以**自定义token生成策略**,来定制化token生成风格
39 | 40 | 怎么做呢?只需要重写`SaTokenAction`接口的`createToken`方法即可 41 | 42 | 43 | #### 参考步骤如下: 44 | 1、新建文件`MySaTokenAction.java`,继承`SaTokenActionDefaultImpl`默认实现类, 并添加上注解`@Component`,保证此类被`springboot`扫描到 45 | ``` java 46 | package com.pj.satoken; 47 | 48 | import org.springframework.stereotype.Component; 49 | import cn.dev33.satoken.action.SaTokenActionDefaultImpl; 50 | 51 | /** 52 | * 继承sa-token行为Bean默认实现, 重写部分逻辑 53 | */ 54 | @Component 55 | public class MySaTokenAction extends SaTokenActionDefaultImpl { 56 | // 重写token生成策略 57 | @Override 58 | public String createToken(Object loginId, String loginKey) { 59 | return SaTokenInsideUtil.getRandomString(60); // 随机60位字符串 60 | } 61 | } 62 | ``` 63 | 64 | 2、再次调用 `StpUtil.setLoginId(10001)`方法进行登录,观察其生成的token样式: 65 | ``` html 66 | gfuPSwZsnUhwgz08GTCH4wOgasWtc3odP4HLwXJ7NDGOximTvT4OlW19zeLH 67 | ``` 68 | 69 | 70 | 71 | ## 以雪花算法生成token 72 | 在此再举一个例子,以`自定义token生成策略`的方式集成`雪花算法`来生成token 73 | 74 | 1、首先我们需要找一个合适的类库,帮助我们生成雪花算法唯一id,在此推荐 [Hutool](https://hutool.cn/docs/#/) ,在`pom.xml`里添加依赖: 75 | ``` xml 76 | 77 | 78 | cn.hutool 79 | hutool-all 80 | 5.5.4 81 | 82 | ``` 83 | 84 | 2、同上,我们需要新建文件`MySaTokenAction.java`,继承`SaTokenActionDefaultImpl`默认实现类, 并添加上注解`@Component`,保证此类被`springboot`扫描到 85 | ``` java 86 | package com.pj.satoken; 87 | 88 | import org.springframework.stereotype.Component; 89 | import cn.dev33.satoken.action.SaTokenActionDefaultImpl; 90 | import cn.hutool.core.util.IdUtil; 91 | 92 | /** 93 | * 继承sa-token行为Bean默认实现, 重写部分逻辑 94 | */ 95 | @Component 96 | public class MySaTokenAction extends SaTokenActionDefaultImpl { 97 | // 重写token生成策略 98 | @Override 99 | public String createToken(Object loginId, String loginKey) { 100 | return IdUtil.getSnowflake(1, 1).nextIdStr(); // 以雪花算法生成token 101 | } 102 | } 103 | ``` 104 | 105 | 3、再次调用 `StpUtil.setLoginId(10001)`方法进行登录,观察其生成的token样式: 106 | ``` html 107 | 1339604338175250432 108 | ``` -------------------------------------------------------------------------------- /sa-token-doc/doc/use/global-filter.md: -------------------------------------------------------------------------------- 1 | # 全局过滤器 2 | --- 3 | 4 | ### 组件简述 5 | 6 | 之前的章节中,我们学习了“根据拦截器实现路由拦截鉴权”,其实在大多数web框架中,使用过滤器可以实现同样的功能,本章我们就利用sa-token全局过滤器来实现路由拦截器鉴权。 7 | 8 | 首先我们先梳理清楚一个问题,既然拦截器已经可以实现路由鉴权,为什么还要用过滤器再实现一遍呢?简而言之: 9 | 1. 相比于拦截器,过滤器更加底层,执行时机更靠前,有利于防渗透扫描 10 | 2. 过滤器可以拦截静态资源,方便我们做一些权限控制 11 | 3. 部分Web框架根本就没有提供拦截器功能,但几乎所有的Web框架都会提供过滤器机制 12 | 13 | 但是过滤器也有一些缺点,比如: 14 | 1. 由于太过底层,导致无法率先拿到`HandlerMethod`对象,无法据此添加一些额外功能 15 | 2. 由于拦截的太全面了,导致我们需要对很多特殊路由(如`/favicon.ico`)做一些额外处理 16 | 3. 在Spring中,过滤器中抛出的异常无法进入全局`@ExceptionHandler`,我们必须额外编写代码进行异常处理 17 | 18 | Sa-Token同时提供过滤器和拦截器机制,不是为了让谁替代谁,而是为了让大家根据自己的实际业务合理选择,拥有更多的发挥空间。 19 | 20 | 21 | ### 注册过滤器 22 | 同拦截器一样,为了避免不必要的性能浪费,sa-token全局过滤器默认处于关闭状态,若要使用过滤器组件,首先你需要注册它到项目中: 23 | ``` java 24 | /** 25 | * [Sa-Token 权限认证] 配置类 26 | * @author kong 27 | */ 28 | @Configuration 29 | public class SaTokenConfigure { 30 | 31 | /** 32 | * 注册 [sa-token全局过滤器] 33 | */ 34 | @Bean 35 | public SaServletFilter getSaServletFilter() { 36 | return new SaServletFilter() 37 | 38 | // 指定 拦截路由 与 放行路由 39 | .addInclude("/**").addExclude("/favicon.ico") 40 | 41 | // 认证函数: 每次请求执行 42 | .setAuth(r -> { 43 | System.out.println("---------- 进入sa-token全局认证 -----------"); 44 | 45 | // 登录验证 -- 拦截所有路由,并排除/user/doLogin 用于开放登录 46 | SaRouterUtil.match("/**", "/user/doLogin", () -> StpUtil.checkLogin()); 47 | 48 | // 更多拦截处理方式,请参考“路由拦截式鉴权”章节 49 | }) 50 | 51 | // 异常处理函数:每次认证函数发生异常时执行此函数 52 | .setError(e -> { 53 | System.out.println("---------- 进入sa-token异常处理 -----------"); 54 | return AjaxJson.getError(e.getMessage()); 55 | }) 56 | 57 | // 前置函数:在每次认证函数之前执行 58 | .setBeforeAuth(r -> { 59 | // ---------- 设置一些安全响应头 ---------- 60 | SaHolder.getResponse() 61 | // 服务器名称 62 | .setServer("sa-server") 63 | // 是否可以在iframe显示视图: DENY=不可以 | SAMEORIGIN=同域下可以 | ALLOW-FROM uri=指定域名下可以 64 | .setHeader("X-Frame-Options", "SAMEORIGIN") 65 | // 是否启用浏览器默认XSS防护: 0=禁用 | 1=启用 | 1; mode=block 启用, 并在检查到XSS攻击时,停止渲染页面 66 | .setHeader("X-Frame-Options", "1; mode=block") 67 | // 禁用浏览器内容嗅探 68 | .setHeader("X-Content-Type-Options", "nosniff") 69 | ; 70 | }) 71 | ; 72 | } 73 | 74 | } 75 | ``` 76 | 77 | ### 注意事项 78 | - 在`[认证函数]`里,你可以写和拦截器里一致的代码,进行路由匹配鉴权,参考:[路由拦截式鉴权](/use/route-check) 79 | - 由于过滤器中抛出的异常不进入全局异常处理,所以你必须提供`[异常处理函数]`来处理`[认证函数]`里抛出的异常 80 | - 在`[异常处理函数]`里的返回值,将作为字符串输出到前端,如果需要定制化返回数据,请注意其中的格式转换 81 | 82 | 83 | ### 在WebFlux中使用过滤器 84 | `Spring WebFlux`中不提供拦截器机制,因此若你的项目需要路由鉴权功能,过滤器是你唯一的选择,在`Spring WebFlux`注册过滤器的流程与上述流程几乎完全一致, 85 | 除了您需要将过滤器名称由`SaServletFilter`更换为`SaReactorFilter`以外,其它所有步骤均可参考以上示例 86 | ``` java 87 | /** 88 | * [Sa-Token 权限认证] 配置类 89 | * @author kong 90 | */ 91 | @Configuration 92 | public class SaTokenConfigure { 93 | 94 | /** 95 | * 注册 [sa-token全局过滤器] 96 | */ 97 | @Bean 98 | public SaReactorFilter getSaReactorFilter() { 99 | return new SaReactorFilter() 100 | // 其它代码... 101 | ; 102 | } 103 | 104 | } 105 | ``` 106 | -------------------------------------------------------------------------------- /sa-token-starter/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/spring/SaTokenSpringAutowired.java: -------------------------------------------------------------------------------- 1 | package cn.dev33.satoken.spring; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.boot.context.properties.ConfigurationProperties; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.stereotype.Component; 7 | import org.springframework.util.PathMatcher; 8 | 9 | import cn.dev33.satoken.SaManager; 10 | import cn.dev33.satoken.action.SaTokenAction; 11 | import cn.dev33.satoken.config.SaTokenConfig; 12 | import cn.dev33.satoken.context.SaTokenContext; 13 | import cn.dev33.satoken.dao.SaTokenDao; 14 | import cn.dev33.satoken.listener.SaTokenListener; 15 | import cn.dev33.satoken.stp.StpInterface; 16 | 17 | /** 18 | * 利用spring的自动装配来加载开发者重写的Bean 19 | * 20 | * @author kong 21 | * 22 | */ 23 | @Component 24 | public class SaTokenSpringAutowired { 25 | 26 | /** 27 | * 获取配置Bean 28 | * 29 | * @return 配置对象 30 | */ 31 | @Bean 32 | @ConfigurationProperties(prefix = "spring.sa-token") 33 | public SaTokenConfig getSaTokenConfig() { 34 | return new SaTokenConfig(); 35 | } 36 | 37 | /** 38 | * 注入配置Bean 39 | * 40 | * @param saTokenConfig 配置对象 41 | */ 42 | @Autowired 43 | public void setConfig(SaTokenConfig saTokenConfig) { 44 | SaManager.setConfig(saTokenConfig); 45 | } 46 | 47 | /** 48 | * 注入持久化Bean 49 | * 50 | * @param saTokenDao SaTokenDao对象 51 | */ 52 | @Autowired(required = false) 53 | public void setSaTokenDao(SaTokenDao saTokenDao) { 54 | SaManager.setSaTokenDao(saTokenDao); 55 | } 56 | 57 | /** 58 | * 注入权限认证Bean 59 | * 60 | * @param stpInterface StpInterface对象 61 | */ 62 | @Autowired(required = false) 63 | public void setStpInterface(StpInterface stpInterface) { 64 | SaManager.setStpInterface(stpInterface); 65 | } 66 | 67 | /** 68 | * 注入框架行为Bean 69 | * 70 | * @param saTokenAction SaTokenAction对象 71 | */ 72 | @Autowired(required = false) 73 | public void setSaTokenAction(SaTokenAction saTokenAction) { 74 | SaManager.setSaTokenAction(saTokenAction); 75 | } 76 | 77 | /** 78 | * 获取容器交互Bean (Spring版) 79 | * 80 | * @return 容器交互Bean (Spring版) 81 | */ 82 | @Bean 83 | public SaTokenContext getSaTokenContext() { 84 | return new SaTokenContextForSpring(); 85 | } 86 | 87 | /** 88 | * 注入容器交互Bean 89 | * 90 | * @param saTokenContext SaTokenContext对象 91 | */ 92 | @Autowired 93 | public void setSaTokenContext(SaTokenContext saTokenContext) { 94 | SaManager.setSaTokenContext(saTokenContext); 95 | } 96 | 97 | /** 98 | * 注入侦听器Bean 99 | * 100 | * @param saTokenListener saTokenListener对象 101 | */ 102 | @Autowired(required = false) 103 | public void setSaTokenListener(SaTokenListener saTokenListener) { 104 | SaManager.setSaTokenListener(saTokenListener); 105 | } 106 | 107 | /** 108 | * 利用自动匹配特性,获取SpringMVC框架内部使用的路由匹配器 109 | * 110 | * @param pathMatcher 要设置的 pathMatcher 111 | */ 112 | @Autowired(required = false) 113 | public void setPathMatcher(PathMatcher pathMatcher) { 114 | SaPathMatcherHolder.setPathMatcher(pathMatcher); 115 | } 116 | 117 | 118 | } 119 | -------------------------------------------------------------------------------- /sa-token-demo/sa-token-demo-jwt/src/main/java/com/pj/test/TestJwtController.java: -------------------------------------------------------------------------------- 1 | package com.pj.test; 2 | 3 | import java.util.Date; 4 | 5 | import org.springframework.web.bind.annotation.RequestMapping; 6 | import org.springframework.web.bind.annotation.RequestParam; 7 | import org.springframework.web.bind.annotation.RestController; 8 | 9 | import com.fasterxml.jackson.core.JsonProcessingException; 10 | import com.fasterxml.jackson.databind.ObjectMapper; 11 | import com.pj.util.AjaxJson; 12 | 13 | import cn.dev33.satoken.stp.SaTokenInfo; 14 | import cn.dev33.satoken.stp.StpUtil; 15 | 16 | /** 17 | * 测试专用Controller 18 | * @author kong 19 | * 20 | */ 21 | @RestController 22 | @RequestMapping("/test/") 23 | public class TestJwtController { 24 | 25 | 26 | 27 | // 测试登录接口, 浏览器访问: http://localhost:8081/test/login 28 | @RequestMapping("login") 29 | public AjaxJson login(@RequestParam(defaultValue="10001") String id) { 30 | System.out.println("======================= 进入方法,测试登录接口 ========================= "); 31 | System.out.println("当前会话的token:" + StpUtil.getTokenValue()); 32 | System.out.println("当前是否登录:" + StpUtil.isLogin()); 33 | System.out.println("当前登录账号:" + StpUtil.getLoginIdDefaultNull()); 34 | 35 | StpUtil.setLoginId(id); // 在当前会话登录此账号 36 | System.out.println("登录成功"); 37 | System.out.println("当前是否登录:" + StpUtil.isLogin()); 38 | System.out.println("当前登录账号:" + StpUtil.getLoginId()); 39 | // System.out.println("当前登录账号并转为int:" + StpUtil.getLoginIdAsInt()); 40 | System.out.println("当前登录设备:" + StpUtil.getLoginDevice()); 41 | // System.out.println("当前token信息:" + StpUtil.getTokenInfo()); 42 | 43 | return AjaxJson.getSuccess(); 44 | } 45 | 46 | // 打印当前token信息, 浏览器访问: http://localhost:8081/test/tokenInfo 47 | @RequestMapping("tokenInfo") 48 | public AjaxJson tokenInfo() { 49 | System.out.println("======================= 进入方法,打印当前token信息 ========================= "); 50 | SaTokenInfo tokenInfo = StpUtil.getTokenInfo(); 51 | System.out.println(tokenInfo); 52 | return AjaxJson.getSuccessData(tokenInfo); 53 | } 54 | 55 | 56 | // 测试会话session接口, 浏览器访问: http://localhost:8081/test/session 57 | @RequestMapping("session") 58 | public AjaxJson session() throws JsonProcessingException { 59 | System.out.println("======================= 进入方法,测试会话session接口 ========================= "); 60 | System.out.println("当前是否登录:" + StpUtil.isLogin()); 61 | System.out.println("当前登录账号session的id" + StpUtil.getSession().getId()); 62 | System.out.println("当前登录账号session的id" + StpUtil.getSession().getId()); 63 | System.out.println("测试取值name:" + StpUtil.getSession().getAttribute("name")); 64 | StpUtil.getSession().setAttribute("name", new Date()); // 写入一个值 65 | System.out.println("测试取值name:" + StpUtil.getSession().getAttribute("name")); 66 | System.out.println( new ObjectMapper().writeValueAsString(StpUtil.getSession())); 67 | return AjaxJson.getSuccess(); 68 | } 69 | 70 | 71 | // 测试 浏览器访问: http://localhost:8081/test/test 72 | @RequestMapping("test") 73 | public AjaxJson test() { 74 | System.out.println(); 75 | System.out.println("--------------进入请求--------------"); 76 | StpUtil.setLoginId(10001); 77 | System.out.println(StpUtil.getTokenInfo().getTokenValue()); 78 | return AjaxJson.getSuccess(); 79 | } 80 | 81 | 82 | } 83 | --------------------------------------------------------------------------------