Compare commits
151 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 004a1b7ed9 | |||
| bd92fbbed9 | |||
| 35cb81085c | |||
| 8f467bbc9c | |||
| 43c9452fb2 | |||
| 81709bcef8 | |||
| fcab8b876e | |||
| 1a62c391ea | |||
| 1c05958d51 | |||
| 1850130e11 | |||
| 4be4ee54c2 | |||
| dd533fba8e | |||
| 54114ebac6 | |||
| 26bf2bd478 | |||
| e024457405 | |||
| 1ab5bbe3d6 | |||
| 89a69e5624 | |||
| c3bd2018a2 | |||
| 535c8e972f | |||
| b4baa4229f | |||
| bdac07d7a0 | |||
| b2416ae43d | |||
| c5c3253171 | |||
| 6d4496897d | |||
| ea546c7adb | |||
| f612c87e3a | |||
| adb600d643 | |||
| c508e99281 | |||
| d6f2be4a2e | |||
| c169721ebf | |||
| f35a6f6ad8 | |||
| aa47362cfa | |||
| 55821d09c6 | |||
| b23aa55ffa | |||
| e781664840 | |||
| 9261a2687c | |||
| 7556796eaa | |||
| 278dd42695 | |||
| 1bbf127b95 | |||
| 73843741af | |||
| 60fdc4ca2a | |||
| ce40fe2dad | |||
| cc9fe4c08b | |||
| b926bad13d | |||
| 83ac203631 | |||
| f4dda6eb6c | |||
| 78fd26e9db | |||
| 9e37896bb0 | |||
| c1bc858f14 | |||
| 42d94827c1 | |||
| 1ba8d6f8d4 | |||
| 9680d6cb9f | |||
| ce4d12b0ca | |||
| b92ba4de87 | |||
| 7f5ae11961 | |||
| c0a993a0f5 | |||
| 2eacec9539 | |||
| 21948fbf7e | |||
| c850874e49 | |||
| 64abd69715 | |||
| 5a7463dc91 | |||
| cf1f255a4a | |||
| 4c216069aa | |||
| 7ee27add84 | |||
| 27618484dc | |||
| 8d6b648d4b | |||
| c52fd9c86f | |||
| 818c1cb4eb | |||
| 14a97a1fcb | |||
| 7652a51592 | |||
| 08659f1fa8 | |||
| 32157cc389 | |||
| e4af180ab8 | |||
| 4f26427bd5 | |||
| f90bd2cd6a | |||
| bd313849fc | |||
| 21a4f66595 | |||
| 2c87e47b2b | |||
| c8a5b922a2 | |||
| 2db7ab6f4b | |||
| 7b45385ffd | |||
| 50f9e00576 | |||
| a180330215 | |||
| c8bcfa19d6 | |||
| 200f9fe642 | |||
| 1277992b5d | |||
| ecb18f5b45 | |||
| d238eea60d | |||
| 2a85f81dbc | |||
| 46771bcb3b | |||
| 5e354f3ae2 | |||
| 5d733e4f4a | |||
| 76e3f7ccc1 | |||
| 56101f8526 | |||
| eaf43d0e5f | |||
| 543613b5dd | |||
| ccb79f6494 | |||
| 18bce0877e | |||
| 3ff0bbdeaf | |||
| 03e3925c4d | |||
| f764f33b5d | |||
| 7f6b495578 | |||
| f2d3222b5d | |||
| 7ad13a1c9d | |||
| 579aee164a | |||
| 217721cae6 | |||
| 8a2a4f0372 | |||
| 45ecb14c72 | |||
| 4a2751cc4b | |||
| b896daf9c7 | |||
| 8b815a6621 | |||
| 003e844254 | |||
| f5dc8b11bf | |||
| 801e29632f | |||
| 07e36f52c5 | |||
| 6de66bb9e6 | |||
| 0bd89831ab | |||
| b47ef019aa | |||
| 4069dbc1f3 | |||
| be161174aa | |||
| 0679036d7b | |||
| 60d88bc5e5 | |||
| da0622d30c | |||
| 3f59792487 | |||
| cb15d79029 | |||
| e2603295c7 | |||
| 3ce3160f05 | |||
| 0fb7b86f24 | |||
| 9ae45da4a8 | |||
| b86fc06030 | |||
| dea944a141 | |||
| 413fce53ab | |||
| 3b18d90f7f | |||
| 0c4e3d6741 | |||
| 6777953f17 | |||
| 45965f6b21 | |||
| 3070e06ffc | |||
| bd08d840d8 | |||
| 03bcf61fb5 | |||
| dbf3cff2bb | |||
| 786928f5e5 | |||
| 636b1bc874 | |||
| 075e22650b | |||
| 3d1deba562 | |||
| 275a68a82f | |||
| 9087aea6dd | |||
| 562c369cf2 | |||
| 87c4dfefbd | |||
| d4d213806b | |||
| 49e51a2114 | |||
| 8f75dd067c |
@@ -1,7 +1,7 @@
|
||||
<p align="center">
|
||||
<img alt="logo" src="https://sa-token.cc/logo.png" width="150" height="150">
|
||||
</p>
|
||||
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">Sa-Token v1.37.0</h1>
|
||||
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">Sa-Token v1.38.0</h1>
|
||||
<h4 align="center">一个轻量级 Java 权限认证框架,让鉴权变得简单、优雅!</h4>
|
||||
<p align="center">
|
||||
<a href="https://gitee.com/dromara/sa-token/stargazers"><img src="https://gitee.com/dromara/sa-token/badge/star.svg?theme=gvp"></a>
|
||||
@@ -12,20 +12,20 @@
|
||||
<a href="https://github.com/dromara/sa-token/issues"><img src="https://img.shields.io/github/issues/dromara/sa-token.svg?style=flat-square&logo=GitHub"></a>
|
||||
<a href="https://github.com/dromara/sa-token/blob/master/LICENSE"><img src="https://img.shields.io/github/license/dromara/sa-token.svg?style=flat-square"></a>
|
||||
</p>
|
||||
<!-- <p align="center">学习测试请拉取 master 分支,dev 是在开发分支 (在根目录执行 `git checkout master`)</p> -->
|
||||
<p align="center"><a href="https://sa-token.cc" target="_blank">在线文档:https://sa-token.cc</a></p>
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 前言:
|
||||
- [在线文档:https://sa-token.cc](https://sa-token.cc)
|
||||
### Sa-Token 介绍
|
||||
|
||||
- 注:学习测试请拉取 master 分支,dev 是开发分支,有很多特性并不稳定(在项目根目录执行 `git checkout master`)。
|
||||
|
||||
- 开源不易,点个 star 鼓励一下吧!
|
||||
Sa-Token 是一个轻量级 Java 权限认证框架,目前拥有五大核心模块:登录认证、权限认证、单点登录、OAuth2.0、微服务鉴权。
|
||||
|
||||
|
||||
## Sa-Token 介绍
|
||||
|
||||
**Sa-Token** 是一个轻量级 Java 权限认证框架,主要解决:登录认证、权限认证、单点登录、OAuth2.0、分布式Session会话、微服务网关鉴权 等一系列权限相关问题。
|
||||
<details>
|
||||
<summary><b>简单示例展示:(点击展开 / 折叠)</b></summary>
|
||||
|
||||
Sa-Token 旨在以简单、优雅的方式完成系统的权限认证部分,以登录认证为例,你只需要:
|
||||
|
||||
@@ -79,62 +79,81 @@ registry.addInterceptor(new SaInterceptor(handler -> {
|
||||
|
||||
当你受够 Shiro、SpringSecurity 等框架的三拜九叩之后,你就会明白,相对于这些传统老牌框架,Sa-Token 的 API 设计是多么的简单、优雅!
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
## Sa-Token 功能模块一览
|
||||
<details>
|
||||
<summary> <b>核心模块一览:(点击展开 / 折叠)</b> </summary>
|
||||
|
||||
Sa-Token 目前主要五大功能模块:登录认证、权限认证、单点登录、OAuth2.0、微服务鉴权。
|
||||
- **登录认证** —— 单端登录、多端登录、同端互斥登录、七天内免登录。
|
||||
- **权限认证** —— 权限认证、角色认证、会话二级认证。
|
||||
- **踢人下线** —— 根据账号id踢人下线、根据Token值踢人下线。
|
||||
- **注解式鉴权** —— 优雅的将鉴权与业务代码分离。
|
||||
- **路由拦截式鉴权** —— 根据路由拦截鉴权,可适配 restful 模式。
|
||||
- **Session会话** —— 全端共享Session,单端独享Session,自定义Session,方便的存取值。
|
||||
- **持久层扩展** —— 可集成 Redis,重启数据不丢失。
|
||||
- **前后台分离** —— APP、小程序等不支持 Cookie 的终端也可以轻松鉴权。
|
||||
- **Token风格定制** —— 内置六种 Token 风格,还可:自定义 Token 生成策略。
|
||||
- **记住我模式** —— 适配 [记住我] 模式,重启浏览器免验证。
|
||||
- **二级认证** —— 在已登录的基础上再次认证,保证安全性。
|
||||
- **模拟他人账号** —— 实时操作任意用户状态数据。
|
||||
- **临时身份切换** —— 将会话身份临时切换为其它账号。
|
||||
- **同端互斥登录** —— 像QQ一样手机电脑同时在线,但是两个手机上互斥登录。
|
||||
- **账号封禁** —— 登录封禁、按照业务分类封禁、按照处罚阶梯封禁。
|
||||
- **密码加密** —— 提供基础加密算法,可快速 MD5、SHA1、SHA256、AES 加密。
|
||||
- **会话查询** —— 提供方便灵活的会话查询接口。
|
||||
- **Http Basic认证** —— 一行代码接入 Http Basic、Digest 认证。
|
||||
- **全局侦听器** —— 在用户登陆、注销、被踢下线等关键性操作时进行一些AOP操作。
|
||||
- **全局过滤器** —— 方便的处理跨域,全局设置安全响应头等操作。
|
||||
- **多账号体系认证** —— 一个系统多套账号分开鉴权(比如商城的 User 表和 Admin 表)
|
||||
- **单点登录** —— 内置三种单点登录模式:同域、跨域、同Redis、跨Redis、前后端分离等架构都可以搞定。
|
||||
- **单点注销** —— 任意子系统内发起注销,即可全端下线。
|
||||
- **OAuth2.0认证** —— 轻松搭建 OAuth2.0 服务,支持openid模式 。
|
||||
- **分布式会话** —— 提供共享数据中心分布式会话方案。
|
||||
- **微服务网关鉴权** —— 适配Gateway、ShenYu、Zuul等常见网关的路由拦截认证。
|
||||
- **RPC调用鉴权** —— 网关转发鉴权,RPC调用鉴权,让服务调用不再裸奔
|
||||
- **临时Token认证** —— 解决短时间的 Token 授权问题。
|
||||
- **独立Redis** —— 将权限缓存与业务缓存分离。
|
||||
- **Quick快速登录认证** —— 为项目零代码注入一个登录页面。
|
||||
- **标签方言** —— 提供 Thymeleaf 标签方言集成包,提供 beetl 集成示例。
|
||||
- **jwt集成** —— 提供三种模式的 jwt 集成方案,提供 token 扩展参数能力。
|
||||
- **RPC调用状态传递** —— 提供 dubbo、grpc 等集成包,在RPC调用时登录状态不丢失。
|
||||
- **参数签名** —— 提供跨系统API调用签名校验模块,防参数篡改,防请求重放。
|
||||
- **自动续签** —— 提供两种Token过期策略,灵活搭配使用,还可自动续签。
|
||||
- **开箱即用** —— 提供SpringMVC、WebFlux、Solon 等常见框架集成包,开箱即用。
|
||||
- **最新技术栈** —— 适配最新技术栈:支持 SpringBoot 3.x,jdk 17。
|
||||
|
||||
- **登录认证** —— 单端登录、多端登录、同端互斥登录、七天内免登录
|
||||
- **权限认证** —— 权限认证、角色认证、会话二级认证
|
||||
- **Session会话** —— 全端共享Session、单端独享Session、自定义Session
|
||||
- **踢人下线** —— 根据账号id踢人下线、根据Token值踢人下线
|
||||
- **账号封禁** —— 登录封禁、按照业务分类封禁、按照处罚阶梯封禁
|
||||
- **持久层扩展** —— 可集成Redis、Memcached等专业缓存中间件,重启数据不丢失
|
||||
- **分布式会话** —— 提供jwt集成、共享数据中心两种分布式会话方案
|
||||
- **微服务网关鉴权** —— 适配Gateway、ShenYu、Zuul等常见网关的路由拦截认证
|
||||
- **单点登录** —— 内置三种单点登录模式:无论是否跨域、是否共享Redis,都可以搞定
|
||||
- **OAuth2.0认证** —— 轻松搭建 OAuth2.0 服务,支持openid模式
|
||||
- **二级认证** —— 在已登录的基础上再次认证,保证安全性
|
||||
- **Basic认证** —— 一行代码接入 Http Basic 认证
|
||||
- **独立Redis** —— 将权限缓存与业务缓存分离
|
||||
- **临时Token认证** —— 解决短时间的Token授权问题
|
||||
- **模拟他人账号** —— 实时操作任意用户状态数据
|
||||
- **临时身份切换** —— 将会话身份临时切换为其它账号
|
||||
- **前后台分离** —— APP、小程序等不支持Cookie的终端
|
||||
- **同端互斥登录** —— 像QQ一样手机电脑同时在线,但是两个手机上互斥登录
|
||||
- **多账号认证体系** —— 比如一个商城项目的user表和admin表分开鉴权
|
||||
- **Token风格定制** —— 内置六种Token风格,还可:自定义Token生成策略、自定义Token前缀
|
||||
- **注解式鉴权** —— 优雅的将鉴权与业务代码分离
|
||||
- **路由拦截式鉴权** —— 根据路由拦截鉴权,可适配restful模式
|
||||
- **自动续签** —— 提供两种Token过期策略,灵活搭配使用,还可自动续签
|
||||
- **会话治理** —— 提供方便灵活的会话查询接口
|
||||
- **记住我模式** —— 适配[记住我]模式,重启浏览器免验证
|
||||
- **密码加密** —— 提供密码加密模块,可快速MD5、SHA1、SHA256、AES、RSA加密
|
||||
- **全局侦听器** —— 在用户登陆、注销、被踢下线等关键性操作时进行一些AOP操作
|
||||
- **开箱即用** —— 提供SpringMVC、WebFlux等常见web框架starter集成包,真正的开箱即用
|
||||
</details>
|
||||
|
||||
功能结构图:
|
||||
|
||||

|
||||
|
||||
|
||||
## Sa-Token-SSO 单点登录
|
||||
Sa-Token-SSO 由简入难划分为三种模式,解决不同架构下的 SSO 接入问题:
|
||||
### SSO 单点登录
|
||||
Sa-Token SSO 分为三种模式,解决同域、跨域、共享Redis、跨Redis、前后端一体、前后端分离……等不同架构下的 SSO 接入问题:
|
||||
|
||||
| 系统架构 | 采用模式 | 简介 | 文档链接 |
|
||||
| :-------- | :-------- | :-------- | :-------- |
|
||||
| 前端同域 + 后端同 Redis | 模式一 | 共享Cookie同步会话 | [文档](https://sa-token.cc/doc.html#/sso/sso-type1)、[示例](https://gitee.com/dromara/sa-token/blob/master/sa-token-demo/sa-token-demo-sso1-client) |
|
||||
| 前端同域 + 后端同 Redis | 模式一 | 共享Cookie同步会话 | [文档](https://sa-token.cc/doc.html#/sso/sso-type1)、[示例](https://gitee.com/dromara/sa-token/blob/master/sa-token-demo/sa-token-demo-sso1-client) |
|
||||
| 前端不同域 + 后端同 Redis | 模式二 | URL重定向传播会话 | [文档](https://sa-token.cc/doc.html#/sso/sso-type2)、[示例](https://gitee.com/dromara/sa-token/blob/master/sa-token-demo/sa-token-demo-sso2-client) |
|
||||
| 前端不同域 + 后端 不同Redis | 模式三 | Http请求获取会话 | [文档](https://sa-token.cc/doc.html#/sso/sso-type3)、[示例](https://gitee.com/dromara/sa-token/blob/master/sa-token-demo/sa-token-demo-sso3-client) |
|
||||
| 前端不同域 + 后端 不同Redis | 模式三 | Http请求获取会话 | [文档](https://sa-token.cc/doc.html#/sso/sso-type3)、[示例](https://gitee.com/dromara/sa-token/blob/master/sa-token-demo/sa-token-demo-sso3-client) |
|
||||
|
||||
|
||||
1. 前端同域:就是指多个系统可以部署在同一个主域名之下,比如:`c1.domain.com`、`c2.domain.com`、`c3.domain.com`
|
||||
2. 后端同Redis:就是指多个系统可以连接同一个Redis。PS:这里并不需要把所有项目的数据都放在同一个Redis中,Sa-Token提供了 **`[权限缓存与业务缓存分离]`** 的解决方案,详情戳:[Alone独立Redis插件](https://sa-token.cc/doc.html#/plugin/alone-redis)
|
||||
3. 如果既无法做到前端同域,也无法做到后端同Redis,那么只能走模式三,Http请求获取会话(Sa-Token对SSO提供了完整的封装,你只需要按照示例从文档上复制几段代码便可以轻松集成)
|
||||
2. 后端同Redis:就是指多个系统可以连接同一个Redis。(此处并非要所有项目数据都放在一个Redis中,Sa-Token提供 **`[权限缓存与业务缓存分离]`** 的解决方案)
|
||||
3. 如果既无法做到前端同域,也无法做到后端同Redis,可以走模式三,Http请求校验 ticket 获取会话。
|
||||
4. 提供 NoSdk 模式示例,不使用 Sa-Token 的系统也可以对接。
|
||||
5. 提供 sso-server 接口文档,不使用 java 语言的系统也可以对接。
|
||||
6. 提供前后端分离整合方案:无论是 sso-server 还是 sso-client 的前后端分离都可以整合。
|
||||
7. 提供安全校验:域名校验、ticket校验、参数签名校验,有效防 ticket 劫持,防请求重放等攻击。
|
||||
8. 参数防丢:笔者曾试验多个SSO框架,均有参数丢失情况,比如登录前是:`http://a.com?id=1&name=2`,登录成功后就变成了:`http://a.com?id=1`,Sa-Token-SSO 内有专门算法保证了参数不丢失,登录成功后精准原路返回。
|
||||
9. 提供用户数据同步/迁移方案的建议:开发前统一迁移、运行时实时数据同步、根据关联字段匹配、根据 center_id 字段匹配等。
|
||||
10. 提供直接可运行的 demo 示例,帮助你快速熟悉 SSO 大致登录流程。
|
||||
|
||||
## Sa-Token-OAuth2 授权认证
|
||||
Sa-OAuth2 模块分为四种授权模式,解决不同场景下的授权需求
|
||||
|
||||
|
||||
### OAuth2 授权认证
|
||||
Sa-Token-OAuth2 模块分为四种授权模式,解决不同场景下的授权需求
|
||||
|
||||
| 授权模式 | 简介 |
|
||||
| :-------- | :-------- |
|
||||
@@ -146,50 +165,34 @@ Sa-OAuth2 模块分为四种授权模式,解决不同场景下的授权需求
|
||||
详细参考文档:[https://sa-token.cc/doc.html#/oauth2/readme](https://sa-token.cc/doc.html#/oauth2/readme)
|
||||
|
||||
|
||||
## 使用 Sa-Token 的开源项目
|
||||
### 开源集成案例
|
||||
|
||||
- [[ Snowy ]](https://gitee.com/xiaonuobase/snowy):国内首个国密前后分离快速开发平台,采用 Vue3 + AntDesignVue3 + Vite + SpringBoot + Mp + HuTool + SaToken。
|
||||
|
||||
- [[ RuoYi-Vue-Plus ]](https://gitee.com/dromara/RuoYi-Vue-Plus):重写RuoYi-Vue所有功能 集成 Sa-Token+Mybatis-Plus+Jackson+Xxl-Job+knife4j+Hutool+OSS 定期同步
|
||||
|
||||
- [[ RuoYi-Cloud-Plus ]](https://gitee.com/dromara/RuoYi-Cloud-Plus):重写RuoYi-Cloud所有功能 整合 SpringCloudAlibaba Dubbo3.0 Sa-Token Mybatis-Plus MQ OSS ES Xxl-Job Docker 全方位升级 定期同步
|
||||
|
||||
- [[ EasyAdmin ]](https://gitee.com/lakernote/easy-admin):一个基于SpringBoot2 + Sa-Token + Mybatis-Plus + Snakerflow + Layui 的后台管理系统,灵活多变可前后端分离,也可单体,内置代码生成器、权限管理、工作流引擎等
|
||||
|
||||
- [[ YC-Framework ]](http://framework.youcongtech.com/):致力于打造一款优秀的分布式微服务解决方案。
|
||||
|
||||
- [[ Pig-Satoken ]](https://gitee.com/wchenyang/cloud-satoken):重写 Pig 授权方式为 Sa-Token,其他代码不变。
|
||||
|
||||
更多开源案例可参考:[Awesome-Sa-Token](https://gitee.com/sa-token/awesome-sa-token)
|
||||
还有更多优秀开源案例无法逐一展示,请参考:[Awesome-Sa-Token](https://gitee.com/sa-token/awesome-sa-token)
|
||||
|
||||
|
||||
## 友情链接
|
||||
### 友情链接
|
||||
- [[ OkHttps ]](https://gitee.com/ejlchina-zhxu/okhttps):轻量级 http 通信框架,API无比优雅,支持 WebSocket、Stomp 协议
|
||||
|
||||
- [[ Bean Searcher ]](https://github.com/ejlchina/bean-searcher):专注高级查询的只读 ORM,使一行代码实现复杂列表检索!
|
||||
|
||||
- [[ Jpom ]](https://gitee.com/dromara/Jpom):简而轻的低侵入式在线构建、自动部署、日常运维、项目监控软件。
|
||||
|
||||
- [[ TLog ]](https://gitee.com/dromara/TLog):一个轻量级的分布式日志标记追踪神器。
|
||||
|
||||
- [[ hippo4j ]](https://gitee.com/agentart/hippo4j):强大的动态线程池框架,附带监控报警功能。
|
||||
|
||||
- [[ hertzbeat ]](https://gitee.com/dromara/hertzbeat):易用友好的开源实时监控告警系统,无需Agent,高性能集群,强大自定义监控能力。
|
||||
|
||||
- [[ Solon ]](https://gitee.com/noear/solon):一个更现代感的应用开发框架:更快、更小、更自由。
|
||||
|
||||
|
||||
## 知识星球
|
||||
<img src="https://oss.dev33.cn/sa-token/dromara-xingqiu--sa-token.jpg" width="300px" />
|
||||
|
||||
|
||||
## 交流群
|
||||
QQ交流群:837325627 [点击加入](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=98NOhX0Q3a2hcv3eURcnYMBuZUZrlHUH&authKey=td3pmX3BnYNr%2FCRkEDwE5FgGARk29D9HAMwL0bAfK7tqN8XN93jccnEanyZl18mM&noverify=0&group_code=837325627)
|
||||
### 交流群
|
||||
QQ交流群:685792424 [点击加入](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=Y05Ld4125W92YSwZ0gA8e3RhG9Q4Vsfx&authKey=IomXuIuhP9g8G7l%2ByfkrRsS7i%2Fna0lIBpkTXxx%2BQEaz0NNEyJq00kgeiC4dUyNLS&noverify=0&group_code=685792424)
|
||||
|
||||
微信交流群:
|
||||
|
||||
<img src="https://oss.dev33.cn/sa-token/qr/wx-qr-m-400k.png" width="230px" title="微信群" />
|
||||
<!--  -->
|
||||
|
||||
(扫码添加微信,备注:sa-token,邀您加入群聊)
|
||||
|
||||
|
||||
+23
-26
@@ -2,33 +2,55 @@
|
||||
:: 整体clean
|
||||
call mvn clean
|
||||
|
||||
|
||||
:: demo模块clean
|
||||
cd sa-token-demo
|
||||
|
||||
cd sa-token-demo-alone-redis & call mvn clean & cd ..
|
||||
cd sa-token-demo-alone-redis-cluster & call mvn clean & cd ..
|
||||
cd sa-token-demo-beetl & call mvn clean & cd ..
|
||||
cd sa-token-demo-bom-import & call mvn clean & cd ..
|
||||
cd sa-token-demo-case & call mvn clean & cd ..
|
||||
cd sa-token-demo-grpc & call mvn clean & cd ..
|
||||
cd sa-token-demo-hutool-timed-cache & call mvn clean & cd ..
|
||||
cd sa-token-demo-jwt & call mvn clean & cd ..
|
||||
cd sa-token-demo-quick-login & call mvn clean & cd ..
|
||||
cd sa-token-demo-solon & call mvn clean & cd ..
|
||||
cd sa-token-demo-solon-redisson & call mvn clean & cd ..
|
||||
cd sa-token-demo-springboot & call mvn clean & cd ..
|
||||
cd sa-token-demo-springboot3-redis & call mvn clean & cd ..
|
||||
cd sa-token-demo-springboot-redis & call mvn clean & cd ..
|
||||
cd sa-token-demo-springboot-redisson & call mvn clean & cd ..
|
||||
cd sa-token-demo-ssm & call mvn clean & cd ..
|
||||
cd sa-token-demo-test & call mvn clean & cd ..
|
||||
cd sa-token-demo-thymeleaf & call mvn clean & cd ..
|
||||
cd sa-token-demo-webflux & call mvn clean & cd ..
|
||||
cd sa-token-demo-webflux-springboot3 & call mvn clean & cd ..
|
||||
cd sa-token-demo-websocket & call mvn clean & cd ..
|
||||
cd sa-token-demo-websocket-spring & call mvn clean & cd ..
|
||||
cd sa-token-demo-bom-import & call mvn clean & cd ..
|
||||
|
||||
cd sa-token-demo-dubbo
|
||||
cd sa-token-demo-dubbo-consumer & call mvn clean & cd ..
|
||||
cd sa-token-demo-dubbo-provider & call mvn clean & cd ..
|
||||
cd sa-token-demo-dubbo3-consumer & call mvn clean & cd ..
|
||||
cd sa-token-demo-dubbo3-provider & call mvn clean & cd ..
|
||||
cd ..
|
||||
|
||||
cd sa-token-demo-oauth2
|
||||
cd sa-token-demo-oauth2-client & call mvn clean & cd ..
|
||||
cd sa-token-demo-oauth2-server & call mvn clean & cd ..
|
||||
cd ..
|
||||
|
||||
cd sa-token-demo-remember-me
|
||||
cd sa-token-demo-remember-me-server & call mvn clean & cd ..
|
||||
cd ..
|
||||
|
||||
cd sa-token-demo-sso
|
||||
cd sa-token-demo-sso-server & call mvn clean & cd ..
|
||||
cd sa-token-demo-sso1-client & call mvn clean & cd ..
|
||||
cd sa-token-demo-sso2-client & call mvn clean & cd ..
|
||||
cd sa-token-demo-sso3-client & call mvn clean & cd ..
|
||||
cd sa-token-demo-sso3-client-test2 & call mvn clean & cd ..
|
||||
cd sa-token-demo-sso3-client-nosdk & call mvn clean & cd ..
|
||||
cd ..
|
||||
|
||||
@@ -39,31 +61,6 @@ cd sa-token-demo-sso3-client-solon & call mvn clean & cd ..
|
||||
cd sa-token-demo-sso-server-solon & call mvn clean & cd ..
|
||||
cd ..
|
||||
|
||||
cd sa-token-demo-oauth2
|
||||
cd sa-token-demo-oauth2-client & call mvn clean & cd ..
|
||||
cd sa-token-demo-oauth2-server & call mvn clean & cd ..
|
||||
cd ..
|
||||
|
||||
cd sa-token-demo-cross
|
||||
cd sa-token-demo-cross-header-server & call mvn clean & cd ..
|
||||
cd sa-token-demo-cross-cookie-server & call mvn clean & cd ..
|
||||
cd ..
|
||||
|
||||
cd sa-token-demo-dubbo
|
||||
cd sa-token-demo-dubbo-consumer & call mvn clean & cd ..
|
||||
cd sa-token-demo-dubbo-provider & call mvn clean & cd ..
|
||||
cd sa-token-demo-dubbo3-consumer & call mvn clean & cd ..
|
||||
cd sa-token-demo-dubbo3-provider & call mvn clean & cd ..
|
||||
cd ..
|
||||
|
||||
cd sa-token-demo-remember-me
|
||||
cd sa-token-demo-remember-me-server & call mvn clean & cd ..
|
||||
cd ..
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
cd ..
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
|
||||
<!-- 一些属性 -->
|
||||
<properties>
|
||||
<revision>1.37.0</revision>
|
||||
<revision>1.38.0</revision>
|
||||
<jdk.version>1.8</jdk.version>
|
||||
<project.build.sourceEncoding>utf-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>utf-8</project.reporting.outputEncoding>
|
||||
|
||||
+11
-1
@@ -13,7 +13,7 @@
|
||||
<url>https://github.com/dromara/sa-token</url>
|
||||
|
||||
<properties>
|
||||
<revision>1.37.0</revision>
|
||||
<revision>1.38.0</revision>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
@@ -99,6 +99,11 @@
|
||||
<artifactId>sa-token-dubbo</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-dubbo3</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-grpc</artifactId>
|
||||
@@ -139,6 +144,11 @@
|
||||
<artifactId>sa-token-redisx</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-hutool-timed-cache</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-dialect-thymeleaf</artifactId>
|
||||
|
||||
+3
-3
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
package cn.dev33.satoken.annotation;
|
||||
|
||||
import cn.dev33.satoken.basic.SaBasicTemplate;
|
||||
import cn.dev33.satoken.httpauth.basic.SaHttpBasicTemplate;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
@@ -32,13 +32,13 @@ import java.lang.annotation.Target;
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.METHOD, ElementType.TYPE })
|
||||
public @interface SaCheckBasic {
|
||||
public @interface SaCheckHttpBasic {
|
||||
|
||||
/**
|
||||
* 领域
|
||||
* @return /
|
||||
*/
|
||||
String realm() default SaBasicTemplate.DEFAULT_REALM;
|
||||
String realm() default SaHttpBasicTemplate.DEFAULT_REALM;
|
||||
|
||||
/**
|
||||
* 需要校验的账号密码,格式形如 sa:123456
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2020-2099 sa-token.cc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package cn.dev33.satoken.annotation;
|
||||
|
||||
import cn.dev33.satoken.httpauth.digest.SaHttpDigestModel;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Http Digest 认证校验:只有通过 Http Digest 认证后才能进入该方法,否则抛出异常。
|
||||
*
|
||||
* <p> 可标注在方法、类上(效果等同于标注在此类的所有方法上)
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.38.0
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.METHOD, ElementType.TYPE })
|
||||
public @interface SaCheckHttpDigest {
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
* @return /
|
||||
*/
|
||||
String username() default "";
|
||||
|
||||
/**
|
||||
* 密码
|
||||
* @return /
|
||||
*/
|
||||
String password() default "";
|
||||
|
||||
/**
|
||||
* 领域
|
||||
* @return /
|
||||
*/
|
||||
String realm() default SaHttpDigestModel.DEFAULT_REALM;
|
||||
|
||||
/**
|
||||
* 需要校验的用户名和密码,格式形如 sa:123456
|
||||
* @return /
|
||||
*/
|
||||
String value() default "";
|
||||
|
||||
}
|
||||
@@ -61,11 +61,18 @@ public @interface SaCheckOr {
|
||||
SaCheckSafe[] safe() default {};
|
||||
|
||||
/**
|
||||
* 设定 @SaCheckBasic,参考 {@link SaCheckBasic}
|
||||
* 设定 @SaCheckHttpBasic,参考 {@link SaCheckHttpBasic}
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
SaCheckBasic[] basic() default {};
|
||||
SaCheckHttpBasic[] httpBasic() default {};
|
||||
|
||||
/**
|
||||
* 设定 @SaCheckBasic,参考 {@link SaCheckHttpDigest}
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
SaCheckHttpDigest[] httpDigest() default {};
|
||||
|
||||
/**
|
||||
* 设定 @SaCheckDisable,参考 {@link SaCheckDisable}
|
||||
|
||||
@@ -23,11 +23,15 @@ import cn.dev33.satoken.secure.SaBase64Util;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
|
||||
/**
|
||||
* <h2> 已更换至包:cn.dev33.satoken.httpauth.basic </h2>
|
||||
* <h2> 已更换名称:SaHttpBasicTemplate </h2>
|
||||
*
|
||||
* Sa-Token Http Basic 认证模块
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.26.0
|
||||
*/
|
||||
@Deprecated
|
||||
public class SaBasicTemplate {
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,11 +16,15 @@
|
||||
package cn.dev33.satoken.basic;
|
||||
|
||||
/**
|
||||
* <h2> 已更换至包:cn.dev33.satoken.httpauth.basic </h2>
|
||||
* <h2> 已更换名称:SaHttpBasicUtil </h2>
|
||||
*
|
||||
* Sa-Token Http Basic 认证模块,Util 工具类
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.26.0
|
||||
*/
|
||||
@Deprecated
|
||||
public class SaBasicUtil {
|
||||
|
||||
private SaBasicUtil() {
|
||||
|
||||
@@ -36,11 +36,17 @@ public class SaSignConfig {
|
||||
*/
|
||||
private long timestampDisparity = 1000 * 60 * 15;
|
||||
|
||||
/**
|
||||
* 是否校验 nonce 随机字符串
|
||||
*/
|
||||
private Boolean isCheckNonce = true;
|
||||
|
||||
public SaSignConfig() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param secretKey 秘钥
|
||||
*/
|
||||
public SaSignConfig(String secretKey) {
|
||||
this.secretKey = secretKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 API 调用签名秘钥
|
||||
@@ -88,26 +94,6 @@ public class SaSignConfig {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 是否校验 nonce 随机字符串
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
public Boolean getIsCheckNonce() {
|
||||
return this.isCheckNonce;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 是否校验 nonce 随机字符串
|
||||
*
|
||||
* @param isCheckNonce /
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaSignConfig setIsCheckNonce(Boolean isCheckNonce) {
|
||||
this.isCheckNonce = isCheckNonce;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算保存 nonce 时应该使用的 ttl,单位:秒
|
||||
* @return /
|
||||
@@ -128,8 +114,18 @@ public class SaSignConfig {
|
||||
return "SaSignConfig ["
|
||||
+ "secretKey=" + secretKey
|
||||
+ ", timestampDisparity=" + timestampDisparity
|
||||
+ ", isCheckNonce=" + isCheckNonce
|
||||
+ "]";
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置:是否校验 nonce 随机字符串
|
||||
* <h2> isCheckNonce 方案已废弃,不再提供此配置项 </h2>
|
||||
*
|
||||
* @param isCheckNonce /
|
||||
*/
|
||||
@Deprecated
|
||||
public void setIsCheckNonce(Boolean isCheckNonce) {
|
||||
System.err.println("--------- isCheckNonce 方案已废弃,不再提供此配置项 ---------");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
package cn.dev33.satoken.config;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
|
||||
import java.io.Serializable;
|
||||
@@ -148,9 +147,14 @@ public class SaTokenConfig implements Serializable {
|
||||
private String jwtSecretKey;
|
||||
|
||||
/**
|
||||
* Http Basic 认证的默认账号和密码
|
||||
* Http Basic 认证的默认账号和密码,冒号隔开,例如:sa:123456
|
||||
*/
|
||||
private String basic = "";
|
||||
private String httpBasic = "";
|
||||
|
||||
/**
|
||||
* Http Digest 认证的默认账号和密码,冒号隔开,例如:sa:123456
|
||||
*/
|
||||
private String httpDigest = "";
|
||||
|
||||
/**
|
||||
* 配置当前项目的网络访问地址
|
||||
@@ -555,18 +559,34 @@ public class SaTokenConfig implements Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Http Basic 认证的默认账号和密码
|
||||
* @return Http Basic 认证的默认账号和密码,冒号隔开,例如:sa:123456
|
||||
*/
|
||||
public String getBasic() {
|
||||
return basic;
|
||||
public String getHttpBasic() {
|
||||
return httpBasic;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param basic Http Basic 认证的默认账号和密码
|
||||
* @param httpBasic Http Basic 认证的默认账号和密码,冒号隔开,例如:sa:123456
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaTokenConfig setBasic(String basic) {
|
||||
this.basic = basic;
|
||||
public SaTokenConfig setHttpBasic(String httpBasic) {
|
||||
this.httpBasic = httpBasic;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Http Digest 认证的默认账号和密码,冒号隔开,例如:sa:123456
|
||||
*/
|
||||
public String getHttpDigest() {
|
||||
return httpDigest;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param httpDigest Http Digest 认证的默认账号和密码,冒号隔开,例如:sa:123456
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaTokenConfig setHttpDigest(String httpDigest) {
|
||||
this.httpDigest = httpDigest;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -676,7 +696,8 @@ public class SaTokenConfig implements Serializable {
|
||||
+ ", logLevelInt=" + logLevelInt
|
||||
+ ", isColorLog=" + isColorLog
|
||||
+ ", jwtSecretKey=" + jwtSecretKey
|
||||
+ ", basic=" + basic
|
||||
+ ", httpBasic=" + httpBasic
|
||||
+ ", httpDigest=" + httpDigest
|
||||
+ ", currDomain=" + currDomain
|
||||
+ ", sameTokenTimeout=" + sameTokenTimeout
|
||||
+ ", checkSameToken=" + checkSameToken
|
||||
@@ -685,8 +706,12 @@ public class SaTokenConfig implements Serializable {
|
||||
+ "]";
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ------------------- 过期方法 -------------------
|
||||
|
||||
/**
|
||||
* 请更改为 getActiveTimeout()
|
||||
* <h2> 请更改为 getActiveTimeout() </h2>
|
||||
* @return token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
|
||||
* (例如可以设置为 1800 代表 30 分钟内无操作就冻结)
|
||||
*/
|
||||
@@ -697,7 +722,7 @@ public class SaTokenConfig implements Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* 请更改为 setActiveTimeout()
|
||||
* <h2> 请更改为 setActiveTimeout() </h2>
|
||||
* @param activityTimeout token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
|
||||
* (例如可以设置为 1800 代表 30 分钟内无操作就冻结)
|
||||
* @return 对象自身
|
||||
@@ -709,5 +734,24 @@ public class SaTokenConfig implements Serializable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <h2> 请更改为 getHttpBasic() </h2>
|
||||
* @return Http Basic 认证的默认账号和密码
|
||||
*/
|
||||
@Deprecated
|
||||
public String getBasic() {
|
||||
return httpBasic;
|
||||
}
|
||||
|
||||
/**
|
||||
* <h2> 请更改为 setHttpBasic() </h2>
|
||||
* @param basic Http Basic 认证的默认账号和密码
|
||||
* @return 对象自身
|
||||
*/
|
||||
@Deprecated
|
||||
public SaTokenConfig setBasic(String basic) {
|
||||
this.httpBasic = basic;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import cn.dev33.satoken.context.model.SaRequest;
|
||||
import cn.dev33.satoken.context.model.SaResponse;
|
||||
import cn.dev33.satoken.context.model.SaStorage;
|
||||
import cn.dev33.satoken.error.SaErrorCode;
|
||||
import cn.dev33.satoken.exception.InvalidContextException;
|
||||
import cn.dev33.satoken.exception.SaTokenContextException;
|
||||
|
||||
/**
|
||||
* Sa-Token 上下文处理器 [ 默认实现类 ]
|
||||
@@ -46,22 +46,22 @@ public class SaTokenContextDefaultImpl implements SaTokenContext {
|
||||
|
||||
@Override
|
||||
public SaRequest getRequest() {
|
||||
throw new InvalidContextException(ERROR_MESSAGE).setCode(SaErrorCode.CODE_10001);
|
||||
throw new SaTokenContextException(ERROR_MESSAGE).setCode(SaErrorCode.CODE_10001);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SaResponse getResponse() {
|
||||
throw new InvalidContextException(ERROR_MESSAGE).setCode(SaErrorCode.CODE_10001);
|
||||
throw new SaTokenContextException(ERROR_MESSAGE).setCode(SaErrorCode.CODE_10001);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SaStorage getStorage() {
|
||||
throw new InvalidContextException(ERROR_MESSAGE).setCode(SaErrorCode.CODE_10001);
|
||||
throw new SaTokenContextException(ERROR_MESSAGE).setCode(SaErrorCode.CODE_10001);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matchPath(String pattern, String path) {
|
||||
throw new InvalidContextException(ERROR_MESSAGE).setCode(SaErrorCode.CODE_10001);
|
||||
throw new SaTokenContextException(ERROR_MESSAGE).setCode(SaErrorCode.CODE_10001);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-2
@@ -19,7 +19,7 @@ import cn.dev33.satoken.context.model.SaRequest;
|
||||
import cn.dev33.satoken.context.model.SaResponse;
|
||||
import cn.dev33.satoken.context.model.SaStorage;
|
||||
import cn.dev33.satoken.error.SaErrorCode;
|
||||
import cn.dev33.satoken.exception.InvalidContextException;
|
||||
import cn.dev33.satoken.exception.SaTokenContextException;
|
||||
|
||||
/**
|
||||
* Sa-Token 上下文处理器 [ThreadLocal 版本] ---- 对象存储器
|
||||
@@ -69,7 +69,7 @@ public class SaTokenContextForThreadLocalStorage {
|
||||
public static Box getBoxNotNull() {
|
||||
Box box = boxThreadLocal.get();
|
||||
if(box == null) {
|
||||
throw new InvalidContextException("未能获取有效的上下文").setCode(SaErrorCode.CODE_10002);
|
||||
throw new SaTokenContextException("未能获取有效的上下文").setCode(SaErrorCode.CODE_10002);
|
||||
}
|
||||
return box;
|
||||
}
|
||||
|
||||
@@ -60,6 +60,9 @@ public interface SaErrorCode {
|
||||
/** 表示未能通过 Http Basic 认证校验 */
|
||||
int CODE_10311 = 10311;
|
||||
|
||||
/** 表示未能通过 Http Digest 认证校验 */
|
||||
int CODE_10312 = 10312;
|
||||
|
||||
/** 提供的 HttpMethod 是无效的 */
|
||||
int CODE_10321 = 10321;
|
||||
|
||||
@@ -118,7 +121,13 @@ public interface SaErrorCode {
|
||||
|
||||
/** 二级认证校验未通过 */
|
||||
int CODE_11071 = 11071;
|
||||
|
||||
|
||||
/** 获取 SaSession 时提供的 SessionId 为空 */
|
||||
int CODE_11072 = 11072;
|
||||
|
||||
/** 获取 Token-Session 时提供的 token 为空 */
|
||||
int CODE_11073 = 11073;
|
||||
|
||||
|
||||
// ------------
|
||||
|
||||
|
||||
@@ -17,10 +17,12 @@ package cn.dev33.satoken.exception;
|
||||
|
||||
/**
|
||||
* 一个异常:代表框架未能获取有效的上下文
|
||||
* <h1>已过期:请更名为 SaTokenContextException 用法不变,未来版本将彻底删除此类</h1>
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.33.0
|
||||
*/
|
||||
@Deprecated
|
||||
public class InvalidContextException extends SaTokenException {
|
||||
|
||||
/**
|
||||
@@ -30,7 +32,7 @@ public class InvalidContextException extends SaTokenException {
|
||||
|
||||
/**
|
||||
* 一个异常:代表框架未能获取有效的上下文
|
||||
* @param message 异常描述
|
||||
* @param message 异常描述
|
||||
*/
|
||||
public InvalidContextException(String message) {
|
||||
super(message);
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2020-2099 sa-token.cc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package cn.dev33.satoken.exception;
|
||||
|
||||
/**
|
||||
* 一个异常:代表会话未能通过 Http Digest 认证校验
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.38.0
|
||||
*/
|
||||
public class NotHttpDigestAuthException extends SaTokenException {
|
||||
|
||||
/**
|
||||
* 序列化版本号
|
||||
*/
|
||||
private static final long serialVersionUID = 6806129545290130144L;
|
||||
|
||||
/** 异常提示语 */
|
||||
public static final String BE_MESSAGE = "no http digest auth";
|
||||
|
||||
/**
|
||||
* 一个异常:代表会话未通过 Http Digest 认证
|
||||
*/
|
||||
public NotHttpDigestAuthException() {
|
||||
super(BE_MESSAGE);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -64,13 +64,4 @@ public class NotPermissionException extends SaTokenException {
|
||||
this.loginType = loginType;
|
||||
}
|
||||
|
||||
/**
|
||||
* <h1> 警告:自 v1.30+ 版本起,获取异常权限码由 getCode() 更改为 getPermission(),请及时更换! </h1>
|
||||
* @return 获得权限码
|
||||
*/
|
||||
@Deprecated
|
||||
public int getCode() {
|
||||
return super.getCode();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -38,11 +38,40 @@ public class SaSignException extends SaTokenException {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 断言 flag 不为 true,否则抛出 message 异常
|
||||
* @param flag 表达式
|
||||
* @param message 异常信息
|
||||
*/
|
||||
public static void notTrue(boolean flag, String message) {
|
||||
// notTrue
|
||||
if(flag) {
|
||||
throw new SaSignException(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 断言 value 不为空,否则抛出 message 异常
|
||||
* @param value 值
|
||||
* @param message 异常信息
|
||||
*/
|
||||
public static void notEmpty(Object value, String message) {
|
||||
if(SaFoxUtil.isEmpty(value)) {
|
||||
throw new SaSignException(message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ------------------- 已过期 -------------------
|
||||
|
||||
/**
|
||||
* 如果flag==true,则抛出message异常
|
||||
* <h2>已过期:请使用 notTrue 代替,用法不变</h2>
|
||||
*
|
||||
* @param flag 标记
|
||||
* @param message 异常信息
|
||||
*/
|
||||
@Deprecated
|
||||
public static void throwBy(boolean flag, String message) {
|
||||
if(flag) {
|
||||
throw new SaSignException(message);
|
||||
@@ -51,13 +80,17 @@ public class SaSignException extends SaTokenException {
|
||||
|
||||
/**
|
||||
* 如果 value isEmpty,则抛出 message 异常
|
||||
* <h2>已过期:请使用 notEmpty 代替,用法不变</h2>
|
||||
*
|
||||
* @param value 值
|
||||
* @param message 异常信息
|
||||
*/
|
||||
@Deprecated
|
||||
public static void throwByNull(Object value, String message) {
|
||||
if(SaFoxUtil.isEmpty(value)) {
|
||||
throw new SaSignException(message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2020-2099 sa-token.cc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package cn.dev33.satoken.exception;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 一个异常:代表框架未能获取有效的上下文
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.33.0
|
||||
*/
|
||||
public class SaTokenContextException extends InvalidContextException implements Serializable {
|
||||
|
||||
/**
|
||||
* 序列化版本号
|
||||
*/
|
||||
private static final long serialVersionUID = 6806129545290130144L;
|
||||
|
||||
/**
|
||||
* 一个异常:代表框架未能获取有效的上下文
|
||||
* @param message 异常描述
|
||||
*/
|
||||
public SaTokenContextException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -105,13 +105,60 @@ public class SaTokenException extends RuntimeException {
|
||||
this.code = code;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 如果flag==true,则抛出message异常
|
||||
* 断言 flag 不为 true,否则抛出 message 异常
|
||||
* @param flag 标记
|
||||
* @param message 异常信息
|
||||
*/
|
||||
public static void notTrue(boolean flag, String message) {
|
||||
notTrue(flag, message, SaErrorCode.CODE_UNDEFINED);
|
||||
}
|
||||
|
||||
/**
|
||||
* 断言 flag 不为 true,否则抛出 message 异常
|
||||
* @param flag 标记
|
||||
* @param message 异常信息
|
||||
* @param code 异常细分状态码
|
||||
*/
|
||||
public static void notTrue(boolean flag, String message, int code) {
|
||||
if(flag) {
|
||||
throw new SaTokenException(message).setCode(code);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 断言 value 不为空,否则抛出 message 异常
|
||||
* @param value 值
|
||||
* @param message 异常信息
|
||||
*/
|
||||
public static void notEmpty(Object value, String message) {
|
||||
notEmpty(value, message, SaErrorCode.CODE_UNDEFINED);
|
||||
}
|
||||
|
||||
/**
|
||||
* 断言 value 不为空,否则抛出 message 异常
|
||||
* @param value 值
|
||||
* @param message 异常信息
|
||||
* @param code 异常细分状态码
|
||||
*/
|
||||
public static void notEmpty(Object value, String message, int code) {
|
||||
if(SaFoxUtil.isEmpty(value)) {
|
||||
throw new SaTokenException(message).setCode(code);
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------- 已过期 -------------------
|
||||
|
||||
/**
|
||||
* 如果flag==true,则抛出message异常
|
||||
* <h2>已过期:请使用 notTrue 代替,用法不变</h2>
|
||||
*
|
||||
* @param flag 标记
|
||||
* @param message 异常信息
|
||||
* @param code 异常细分状态码
|
||||
*/
|
||||
@Deprecated
|
||||
public static void throwBy(boolean flag, String message, int code) {
|
||||
if(flag) {
|
||||
throw new SaTokenException(message).setCode(code);
|
||||
@@ -119,15 +166,18 @@ public class SaTokenException extends RuntimeException {
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果value==null或者isEmpty,则抛出message异常
|
||||
* @param value 值
|
||||
* @param message 异常信息
|
||||
* @param code 异常细分状态码
|
||||
* 如果value==null或者isEmpty,则抛出message异常
|
||||
* <h2>已过期:请使用 notEmpty 代替,用法不变</h2>
|
||||
*
|
||||
* @param value 值
|
||||
* @param message 异常信息
|
||||
* @param code 异常细分状态码
|
||||
*/
|
||||
@Deprecated
|
||||
public static void throwByNull(Object value, String message, int code) {
|
||||
if(SaFoxUtil.isEmpty(value)) {
|
||||
throw new SaTokenException(message).setCode(code);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright 2020-2099 sa-token.cc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package cn.dev33.satoken.httpauth.basic;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.context.SaHolder;
|
||||
import cn.dev33.satoken.error.SaErrorCode;
|
||||
import cn.dev33.satoken.exception.NotBasicAuthException;
|
||||
import cn.dev33.satoken.secure.SaBase64Util;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
|
||||
/**
|
||||
* Sa-Token Http Basic 认证模块
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.26.0
|
||||
*/
|
||||
public class SaHttpBasicTemplate {
|
||||
|
||||
/**
|
||||
* 默认的 Realm 领域名称
|
||||
*/
|
||||
public static final String DEFAULT_REALM = "Sa-Token";
|
||||
|
||||
/**
|
||||
* 在校验失败时,设置响应头,并抛出异常
|
||||
* @param realm 领域
|
||||
*/
|
||||
public void throwNotBasicAuthException(String realm) {
|
||||
SaHolder.getResponse().setStatus(401).setHeader("WWW-Authenticate", "Basic Realm=" + realm);
|
||||
throw new NotBasicAuthException().setCode(SaErrorCode.CODE_10311);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取浏览器提交的 Basic 参数 (裁剪掉前缀并解码)
|
||||
* @return 值
|
||||
*/
|
||||
public String getAuthorizationValue() {
|
||||
|
||||
// 获取前端提交的请求头 Authorization 参数
|
||||
String authorization = SaHolder.getRequest().getHeader("Authorization");
|
||||
|
||||
// 如果不是以 Basic 作为前缀,则视为无效
|
||||
if(authorization == null || ! authorization.startsWith("Basic ")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 裁剪前缀并解码
|
||||
return SaBase64Util.decode(authorization.substring(6));
|
||||
}
|
||||
|
||||
/**
|
||||
* 对当前会话进行 Basic 校验(使用全局配置的账号密码),校验不通过则抛出异常
|
||||
*/
|
||||
public void check() {
|
||||
check(DEFAULT_REALM, SaManager.getConfig().getHttpBasic());
|
||||
}
|
||||
|
||||
/**
|
||||
* 对当前会话进行 Basic 校验(手动设置账号密码),校验不通过则抛出异常
|
||||
* @param account 账号(格式为 user:password)
|
||||
*/
|
||||
public void check(String account) {
|
||||
check(DEFAULT_REALM, account);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对当前会话进行 Basic 校验(手动设置 Realm 和 账号密码),校验不通过则抛出异常
|
||||
* @param realm 领域
|
||||
* @param account 账号(格式为 user:password)
|
||||
*/
|
||||
public void check(String realm, String account) {
|
||||
if(SaFoxUtil.isEmpty(account)) {
|
||||
account = SaManager.getConfig().getHttpBasic();
|
||||
}
|
||||
String authorization = getAuthorizationValue();
|
||||
if(SaFoxUtil.isEmpty(authorization) || ! authorization.equals(account)) {
|
||||
throwNotBasicAuthException(realm);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2020-2099 sa-token.cc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package cn.dev33.satoken.httpauth.basic;
|
||||
|
||||
/**
|
||||
* Sa-Token Http Basic 认证模块,Util 工具类
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.26.0
|
||||
*/
|
||||
public class SaHttpBasicUtil {
|
||||
|
||||
private SaHttpBasicUtil() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 底层使用的 SaBasicTemplate 对象
|
||||
*/
|
||||
public static SaHttpBasicTemplate saHttpBasicTemplate = new SaHttpBasicTemplate();
|
||||
|
||||
/**
|
||||
* 获取浏览器提交的 Basic 参数 (裁剪掉前缀并解码)
|
||||
* @return 值
|
||||
*/
|
||||
public static String getAuthorizationValue() {
|
||||
return saHttpBasicTemplate.getAuthorizationValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* 对当前会话进行 Basic 校验(使用全局配置的账号密码),校验不通过则抛出异常
|
||||
*/
|
||||
public static void check() {
|
||||
saHttpBasicTemplate.check();
|
||||
}
|
||||
|
||||
/**
|
||||
* 对当前会话进行 Basic 校验(手动设置账号密码),校验不通过则抛出异常
|
||||
* @param account 账号(格式为 user:password)
|
||||
*/
|
||||
public static void check(String account) {
|
||||
saHttpBasicTemplate.check(account);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对当前会话进行 Basic 校验(手动设置 Realm 和 账号密码),校验不通过则抛出异常
|
||||
* @param realm 领域
|
||||
* @param account 账号(格式为 user:password)
|
||||
*/
|
||||
public static void check(String realm, String account) {
|
||||
saHttpBasicTemplate.check(realm, account);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,346 @@
|
||||
/*
|
||||
* Copyright 2020-2099 sa-token.cc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package cn.dev33.satoken.httpauth.digest;
|
||||
|
||||
/**
|
||||
* Sa-Token Http Digest 认证 - 参数实体类
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.38.0
|
||||
*/
|
||||
public class SaHttpDigestModel {
|
||||
|
||||
/**
|
||||
* 默认的 Realm 领域名称
|
||||
*/
|
||||
public static final String DEFAULT_REALM = "Sa-Token";
|
||||
|
||||
/**
|
||||
* 默认的 qop 值
|
||||
*/
|
||||
public static final String DEFAULT_QOP = "auth";
|
||||
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
public String username;
|
||||
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
public String password;
|
||||
|
||||
/**
|
||||
* 领域
|
||||
*/
|
||||
public String realm = DEFAULT_REALM;
|
||||
|
||||
/**
|
||||
* 随机数
|
||||
*/
|
||||
public String nonce;
|
||||
|
||||
/**
|
||||
* 请求 uri
|
||||
*/
|
||||
public String uri;
|
||||
|
||||
/**
|
||||
* 请求方法
|
||||
*/
|
||||
public String method;
|
||||
|
||||
/**
|
||||
* 保护质量(auth=默认的,auth-int=增加报文完整性检测),可以为空,但不推荐
|
||||
*/
|
||||
public String qop;
|
||||
|
||||
/**
|
||||
* nonce计数器,是一个16进制的数值,表示同一nonce下客户端发送出请求的数量
|
||||
*/
|
||||
public String nc;
|
||||
|
||||
/**
|
||||
* 客户端随机数,由客户端提供
|
||||
*/
|
||||
public String cnonce;
|
||||
|
||||
/**
|
||||
* opaque
|
||||
*/
|
||||
public String opaque;
|
||||
|
||||
/**
|
||||
* 请求摘要,最终计算的摘要结果
|
||||
*/
|
||||
public String response;
|
||||
|
||||
// ------------------- 构造函数 -------------------
|
||||
|
||||
public SaHttpDigestModel() {
|
||||
}
|
||||
public SaHttpDigestModel(String username, String password) {
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
}
|
||||
public SaHttpDigestModel(String username, String password, String realm) {
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.realm = realm;
|
||||
}
|
||||
|
||||
|
||||
// ------------------- get/set -------------------
|
||||
|
||||
/**
|
||||
* 获取 用户名
|
||||
*
|
||||
* @return username 用户名
|
||||
*/
|
||||
public String getUsername() {
|
||||
return this.username;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 用户名
|
||||
*
|
||||
* @param username 用户名
|
||||
* @return /
|
||||
*/
|
||||
public SaHttpDigestModel setUsername(String username) {
|
||||
this.username = username;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 领域
|
||||
*
|
||||
* @return realm 领域
|
||||
*/
|
||||
public String getRealm() {
|
||||
return this.realm;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 领域
|
||||
*
|
||||
* @param realm 领域
|
||||
* @return /
|
||||
*/
|
||||
public SaHttpDigestModel setRealm(String realm) {
|
||||
this.realm = realm;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 密码
|
||||
*
|
||||
* @return password 密码
|
||||
*/
|
||||
public String getPassword() {
|
||||
return this.password;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 密码
|
||||
*
|
||||
* @param password 密码
|
||||
* @return /
|
||||
*/
|
||||
public SaHttpDigestModel setPassword(String password) {
|
||||
this.password = password;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 随机数
|
||||
*
|
||||
* @return nonce 随机数
|
||||
*/
|
||||
public String getNonce() {
|
||||
return this.nonce;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 随机数
|
||||
*
|
||||
* @param nonce 随机数
|
||||
* @return /
|
||||
*/
|
||||
public SaHttpDigestModel setNonce(String nonce) {
|
||||
this.nonce = nonce;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 请求 uri
|
||||
*
|
||||
* @return uri 请求 uri
|
||||
*/
|
||||
public String getUri() {
|
||||
return this.uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 请求 uri
|
||||
*
|
||||
* @param uri 请求 uri
|
||||
* @return /
|
||||
*/
|
||||
public SaHttpDigestModel setUri(String uri) {
|
||||
this.uri = uri;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 请求方法
|
||||
*
|
||||
* @return method 请求方法
|
||||
*/
|
||||
public String getMethod() {
|
||||
return this.method;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 请求方法
|
||||
*
|
||||
* @param method 请求方法
|
||||
* @return /
|
||||
*/
|
||||
public SaHttpDigestModel setMethod(String method) {
|
||||
this.method = method;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 保护质量(auth=默认的,auth-int=增加报文完整性检测),可以为空,但不推荐
|
||||
*
|
||||
* @return qop 保护质量(auth=默认的,auth-int=增加报文完整性检测),可以为空,但不推荐
|
||||
*/
|
||||
public String getQop() {
|
||||
return this.qop;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 保护质量(auth=默认的,auth-int=增加报文完整性检测),可以为空,但不推荐
|
||||
*
|
||||
* @param qop 保护质量(auth=默认的,auth-int=增加报文完整性检测),可以为空,但不推荐
|
||||
* @return /
|
||||
*/
|
||||
public SaHttpDigestModel setQop(String qop) {
|
||||
this.qop = qop;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 nonce计数器,是一个16进制的数值,表示同一nonce下客户端发送出请求的数量
|
||||
*
|
||||
* @return nc nonce计数器,是一个16进制的数值,表示同一nonce下客户端发送出请求的数量
|
||||
*/
|
||||
public String getNc() {
|
||||
return this.nc;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 nonce计数器,是一个16进制的数值,表示同一nonce下客户端发送出请求的数量
|
||||
*
|
||||
* @param nc nonce计数器,是一个16进制的数值,表示同一nonce下客户端发送出请求的数量
|
||||
* @return /
|
||||
*/
|
||||
public SaHttpDigestModel setNc(String nc) {
|
||||
this.nc = nc;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 客户端随机数,由客户端提供
|
||||
*
|
||||
* @return cnonce 客户端随机数,由客户端提供
|
||||
*/
|
||||
public String getCnonce() {
|
||||
return this.cnonce;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 客户端随机数,由客户端提供
|
||||
*
|
||||
* @param cnonce 客户端随机数,由客户端提供
|
||||
* @return /
|
||||
*/
|
||||
public SaHttpDigestModel setCnonce(String cnonce) {
|
||||
this.cnonce = cnonce;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 opaque
|
||||
*
|
||||
* @return opaque opaque
|
||||
*/
|
||||
public String getOpaque() {
|
||||
return this.opaque;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 opaque
|
||||
*
|
||||
* @param opaque opaque
|
||||
* @return /
|
||||
*/
|
||||
public SaHttpDigestModel setOpaque(String opaque) {
|
||||
this.opaque = opaque;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 请求摘要,最终计算的摘要结果
|
||||
*
|
||||
* @return response 请求摘要,最终计算的摘要结果
|
||||
*/
|
||||
public String getResponse() {
|
||||
return this.response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 请求摘要,最终计算的摘要结果
|
||||
*
|
||||
* @param response 请求摘要,最终计算的摘要结果
|
||||
* @return /
|
||||
*/
|
||||
public SaHttpDigestModel setResponse(String response) {
|
||||
this.response = response;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SaHttpDigestModel[" +
|
||||
"username=" + username +
|
||||
", password=" + password +
|
||||
", realm=" + realm +
|
||||
", nonce=" + nonce +
|
||||
", uri=" + uri +
|
||||
", method=" + method +
|
||||
", qop=" + qop +
|
||||
", nc=" + nc +
|
||||
", cnonce=" + cnonce +
|
||||
", opaque=" + opaque +
|
||||
", response=" + response +
|
||||
"]";
|
||||
}
|
||||
|
||||
}
|
||||
+295
@@ -0,0 +1,295 @@
|
||||
/*
|
||||
* Copyright 2020-2099 sa-token.cc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package cn.dev33.satoken.httpauth.digest;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.annotation.SaCheckHttpDigest;
|
||||
import cn.dev33.satoken.context.SaHolder;
|
||||
import cn.dev33.satoken.error.SaErrorCode;
|
||||
import cn.dev33.satoken.exception.NotHttpDigestAuthException;
|
||||
import cn.dev33.satoken.exception.SaTokenException;
|
||||
import cn.dev33.satoken.secure.SaSecureUtil;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Sa-Token Http Digest 认证模块 - 模板方法类
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.38.0
|
||||
*/
|
||||
public class SaHttpDigestTemplate {
|
||||
|
||||
/*
|
||||
这里只是 Http Digest 认证的一个简单实现,待实现功能还有:
|
||||
1、nonce 防重放攻击
|
||||
2、nc 计数器
|
||||
3、qop 保护质量=auth-int
|
||||
4、opaque 透明值
|
||||
5、algorithm 更多摘要算法
|
||||
等等
|
||||
*/
|
||||
|
||||
/**
|
||||
* 构建认证失败的响应头参数
|
||||
* @param model 参数对象
|
||||
* @return 响应头值
|
||||
*/
|
||||
public String buildResponseHeaderValue(SaHttpDigestModel model) {
|
||||
// 抛异常
|
||||
String headerValue = "Digest " +
|
||||
"realm=\"" + model.realm + "\", " +
|
||||
"qop=\"" + model.qop + "\", " +
|
||||
"nonce=\"" + model.nonce + "\", " +
|
||||
"nc=" + model.nc + ", " +
|
||||
"opaque=\"" + model.opaque + "\"";
|
||||
return headerValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在校验失败时,设置响应头,并抛出异常
|
||||
* @param model Digest 参数对象
|
||||
*/
|
||||
public void throwNotHttpDigestAuthException(SaHttpDigestModel model) {
|
||||
// 补全一些必须的参数
|
||||
model.realm = (model.realm != null) ? model.realm : SaHttpDigestModel.DEFAULT_REALM;
|
||||
model.qop = (model.qop != null) ? model.qop : SaHttpDigestModel.DEFAULT_QOP;
|
||||
model.nonce = (model.nonce != null) ? model.nonce : SaFoxUtil.getRandomString(32);
|
||||
model.opaque = (model.opaque != null) ? model.opaque : SaFoxUtil.getRandomString(32);
|
||||
model.nc = (model.nc != null) ? model.nc : "00000001";
|
||||
|
||||
// 设置响应头
|
||||
SaHolder.getResponse()
|
||||
.setStatus(401)
|
||||
.setHeader("WWW-Authenticate", buildResponseHeaderValue(model));
|
||||
|
||||
// 抛异常
|
||||
throw new NotHttpDigestAuthException().setCode(SaErrorCode.CODE_10312);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取浏览器提交的 Digest 参数 (裁剪掉前缀)
|
||||
* @return 值
|
||||
*/
|
||||
public String getAuthorizationValue() {
|
||||
|
||||
// 获取前端提交的请求头 Authorization 参数
|
||||
String authorization = SaHolder.getRequest().getHeader("Authorization");
|
||||
|
||||
// 如果不是以 Digest 作为前缀,则视为无效
|
||||
if(authorization == null || ! authorization.startsWith("Digest ")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 裁剪前缀并解码
|
||||
return authorization.substring(7);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取浏览器提交的 Digest 参数,并转化为 Map
|
||||
* @return /
|
||||
*/
|
||||
public SaHttpDigestModel getAuthorizationValueToModel() {
|
||||
|
||||
// 先获取字符串值
|
||||
String authorization = getAuthorizationValue();
|
||||
if(authorization == null) {
|
||||
// throw new SaTokenException("请求头中未携带 Digest 认证参数");
|
||||
return null;
|
||||
}
|
||||
|
||||
// 根据逗号分割,解析为 Map
|
||||
Map<String, String> map = new LinkedHashMap<>();
|
||||
String[] arr = authorization.split(",");
|
||||
for (String s : arr) {
|
||||
String[] kv = s.split("=");
|
||||
if (kv.length == 2) {
|
||||
map.put(kv[0].trim(), kv[1].trim().replace("\"", ""));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
参考样例:
|
||||
username=sa,
|
||||
realm=Sa-Token,
|
||||
nonce=dcd98b7102dd2f0e8b11d0f600bfb0c093,
|
||||
uri=/test/testDigest,
|
||||
response=a32023c128e142163dd4856a2f511c70,
|
||||
opaque=5ccc069c403ebaf9f0171e9517f40e41,
|
||||
qop=auth,
|
||||
nc=00000002,
|
||||
cnonce=f3ca6bfc0b2f59c4
|
||||
*/
|
||||
|
||||
// 转化为 Model
|
||||
SaHttpDigestModel model = new SaHttpDigestModel();
|
||||
model.username = map.get("username");
|
||||
model.realm = map.get("realm");
|
||||
model.nonce = map.get("nonce");
|
||||
model.uri = map.get("uri");
|
||||
model.method = SaHolder.getRequest().getMethod();
|
||||
model.qop = map.get("qop");
|
||||
model.nc = map.get("nc");
|
||||
model.cnonce = map.get("cnonce");
|
||||
model.opaque = map.get("opaque");
|
||||
model.response = map.get("response");
|
||||
|
||||
//
|
||||
return model;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算:根据 Digest 参数计算 response
|
||||
*
|
||||
* @param model Digest 参数对象
|
||||
* @return 计算出的 response
|
||||
*/
|
||||
public String calcResponse(SaHttpDigestModel model) {
|
||||
|
||||
// frag1 = md5(username:realm:password)
|
||||
String frag1 = SaSecureUtil.md5(model.username + ":" + model.realm + ":" + model.password);
|
||||
|
||||
// frag2 = nonce:nc:cnonce:qop
|
||||
String frag2 = model.nonce + ":" + model.nc + ":" + model.cnonce + ":" + model.qop;
|
||||
|
||||
// frag3 = md5(method:uri)
|
||||
String frag3 = SaSecureUtil.md5(model.method + ":" + model.uri);
|
||||
|
||||
// 最终结果 = md5(frag1:frag2:frag3)
|
||||
String response = SaSecureUtil.md5(frag1 + ":" + frag2 + ":" + frag3);
|
||||
|
||||
//
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 把 hopeModel 有的值都 copy 到 reqModel 中
|
||||
*/
|
||||
public void copyHopeToReq(SaHttpDigestModel hopeModel, SaHttpDigestModel reqModel){
|
||||
reqModel.username = hopeModel.username;
|
||||
reqModel.password = hopeModel.password;
|
||||
reqModel.realm = hopeModel.realm != null ? hopeModel.realm : reqModel.realm;
|
||||
reqModel.nonce = hopeModel.nonce != null ? hopeModel.nonce : reqModel.nonce;
|
||||
reqModel.uri = hopeModel.uri != null ? hopeModel.uri : reqModel.uri;
|
||||
reqModel.method = hopeModel.method != null ? hopeModel.method : reqModel.method;
|
||||
reqModel.qop = hopeModel.qop != null ? hopeModel.qop : reqModel.qop;
|
||||
reqModel.nc = hopeModel.nc != null ? hopeModel.nc : reqModel.nc;
|
||||
reqModel.opaque = hopeModel.opaque != null ? hopeModel.opaque : reqModel.opaque;
|
||||
// reqModel.cnonce = hopeModel.cnonce != null ? hopeModel.cnonce : reqModel.cnonce;
|
||||
// reqModel.response = hopeModel.response != null ? hopeModel.response : reqModel.response;
|
||||
}
|
||||
|
||||
// ---------- 校验 ----------
|
||||
|
||||
/**
|
||||
* 校验:根据提供 Digest 参数计算 res,与 request 请求中的 Digest 参数进行校验,校验不通过则抛出异常
|
||||
* @param hopeModel 提供的 Digest 参数对象
|
||||
*/
|
||||
public void check(SaHttpDigestModel hopeModel) {
|
||||
|
||||
// 先进行一些必须的希望参数校验
|
||||
SaTokenException.notEmpty(hopeModel, "Digest参数对象不能为空");
|
||||
SaTokenException.notEmpty(hopeModel.username, "必须提供希望的 username 参数");
|
||||
SaTokenException.notEmpty(hopeModel.password, "必须提供希望的 password 参数");
|
||||
|
||||
// 获取 web 请求中的 Digest 参数
|
||||
SaHttpDigestModel reqModel = getAuthorizationValueToModel();
|
||||
|
||||
// 为空代表前端根本没有提交 Digest 参数,直接抛异常
|
||||
if(reqModel == null) {
|
||||
throwNotHttpDigestAuthException(hopeModel);
|
||||
}
|
||||
|
||||
// 把 hopeModel 有的值都 copy 到 reqModel 中
|
||||
copyHopeToReq(hopeModel, reqModel);
|
||||
|
||||
// 计算
|
||||
String cResponse = calcResponse(reqModel);
|
||||
|
||||
// 比对,不一致就抛异常
|
||||
if(! cResponse.equals(reqModel.response)) {
|
||||
throwNotHttpDigestAuthException(hopeModel);
|
||||
}
|
||||
|
||||
// 认证通过
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验:根据提供的参数,校验不通过抛出异常
|
||||
* @param username 用户名
|
||||
* @param password 密码
|
||||
*/
|
||||
public void check(String username, String password) {
|
||||
check(new SaHttpDigestModel(username, password));
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验:根据提供的参数,校验不通过抛出异常
|
||||
* @param username 用户名
|
||||
* @param password 密码
|
||||
* @param realm 领域
|
||||
*/
|
||||
public void check(String username, String password, String realm) {
|
||||
check(new SaHttpDigestModel(username, password, realm));
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验:根据全局配置参数,校验不通过抛出异常
|
||||
*/
|
||||
public void check() {
|
||||
String httpDigest = SaManager.getConfig().getHttpDigest();
|
||||
if(SaFoxUtil.isEmpty(httpDigest)){
|
||||
throw new SaTokenException("未配置全局 Http Digest 认证参数");
|
||||
}
|
||||
String[] arr = httpDigest.split(":");
|
||||
if(arr.length != 2){
|
||||
throw new SaTokenException("全局 Http Digest 认证参数配置错误,格式应如:username:password");
|
||||
}
|
||||
check(arr[0], arr[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据注解 ( @SaCheckHttpDigest ) 鉴权
|
||||
*
|
||||
* @param at 注解对象
|
||||
*/
|
||||
public void checkByAnnotation(SaCheckHttpDigest at) {
|
||||
|
||||
// 如果配置了 value,则以 value 优先
|
||||
String value = at.value();
|
||||
if(SaFoxUtil.isNotEmpty(value)){
|
||||
String[] arr = value.split(":");
|
||||
if(arr.length != 2){
|
||||
throw new SaTokenException("注解参数配置错误,格式应如:username:password");
|
||||
}
|
||||
check(arr[0], arr[1]);
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果配置了 username,则分别获取参数
|
||||
String username = at.username();
|
||||
if(SaFoxUtil.isNotEmpty(username)){
|
||||
check(username, at.password(), at.realm());
|
||||
return;
|
||||
}
|
||||
|
||||
// 都没有配置,则根据全局配置参数进行校验
|
||||
check();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright 2020-2099 sa-token.cc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package cn.dev33.satoken.httpauth.digest;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckHttpDigest;
|
||||
import cn.dev33.satoken.context.SaHolder;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Sa-Token Http Digest 认证模块,Util 工具类
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.38.0
|
||||
*/
|
||||
public class SaHttpDigestUtil {
|
||||
|
||||
private SaHttpDigestUtil() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 底层使用的 SaHttpDigestTemplate 对象
|
||||
*/
|
||||
public static SaHttpDigestTemplate saHttpDigestTemplate = new SaHttpDigestTemplate();
|
||||
|
||||
|
||||
/**
|
||||
* 获取浏览器提交的 Digest 参数 (裁剪掉前缀)
|
||||
* @return 值
|
||||
*/
|
||||
public static String getAuthorizationValue() {
|
||||
return saHttpDigestTemplate.getAuthorizationValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取浏览器提交的 Digest 参数,并转化为 Map
|
||||
* @return /
|
||||
*/
|
||||
public static SaHttpDigestModel getAuthorizationValueToModel() {
|
||||
return saHttpDigestTemplate.getAuthorizationValueToModel();
|
||||
}
|
||||
|
||||
// ---------- 校验 ----------
|
||||
|
||||
/**
|
||||
* 校验:根据提供 Digest 参数计算 res,与 request 请求中的 Digest 参数进行校验,校验不通过则抛出异常
|
||||
* @param hopeModel 提供的 Digest 参数对象
|
||||
*/
|
||||
public static void check(SaHttpDigestModel hopeModel) {
|
||||
saHttpDigestTemplate.check(hopeModel);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验:根据提供的参数,校验不通过抛出异常
|
||||
* @param username 用户名
|
||||
* @param password 密码
|
||||
*/
|
||||
public static void check(String username, String password) {
|
||||
saHttpDigestTemplate.check(username, password);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验:根据提供的参数,校验不通过抛出异常
|
||||
* @param username 用户名
|
||||
* @param password 密码
|
||||
* @param realm 领域
|
||||
*/
|
||||
public static void check(String username, String password, String realm) {
|
||||
saHttpDigestTemplate.check(username, password, realm);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验:根据全局配置参数,校验不通过抛出异常
|
||||
*/
|
||||
public static void check() {
|
||||
saHttpDigestTemplate.check();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据注解 ( @SaCheckHttpDigest ) 鉴权
|
||||
*
|
||||
* @param at 注解对象
|
||||
*/
|
||||
public static void checkByAnnotation(SaCheckHttpDigest at) {
|
||||
saHttpDigestTemplate.checkByAnnotation(at);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -175,6 +175,7 @@ public class SaSecureUtil {
|
||||
* @param salt 盐
|
||||
* @return 加密后的字符串
|
||||
*/
|
||||
@Deprecated
|
||||
public static String md5BySalt(String str, String salt) {
|
||||
return md5(md5(str) + md5(salt));
|
||||
}
|
||||
@@ -185,6 +186,7 @@ public class SaSecureUtil {
|
||||
* @param salt 盐
|
||||
* @return 加密后的字符串
|
||||
*/
|
||||
@Deprecated
|
||||
public static String sha256BySalt(String str, String salt) {
|
||||
return sha256(sha256(str) + sha256(salt));
|
||||
}
|
||||
@@ -261,6 +263,7 @@ public class SaSecureUtil {
|
||||
* @return Map对象 (private=私钥, public=公钥)
|
||||
* @throws Exception 异常
|
||||
*/
|
||||
@Deprecated
|
||||
public static HashMap<String, String> rsaGenerateKeyPair() throws Exception {
|
||||
|
||||
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM);
|
||||
@@ -285,6 +288,7 @@ public class SaSecureUtil {
|
||||
* @param content 内容
|
||||
* @return 加密后内容
|
||||
*/
|
||||
@Deprecated
|
||||
public static String rsaEncryptByPublic(String publicKeyString, String content) {
|
||||
try {
|
||||
// 获得公钥对象
|
||||
@@ -311,6 +315,7 @@ public class SaSecureUtil {
|
||||
* @param content 内容
|
||||
* @return 加密后内容
|
||||
*/
|
||||
@Deprecated
|
||||
public static String rsaEncryptByPrivate(String privateKeyString, String content) {
|
||||
try {
|
||||
PrivateKey privateKey = getPrivateKeyFromString(privateKeyString);
|
||||
@@ -336,6 +341,7 @@ public class SaSecureUtil {
|
||||
* @param content 已加密内容
|
||||
* @return 解密后内容
|
||||
*/
|
||||
@Deprecated
|
||||
public static String rsaDecryptByPublic(String publicKeyString, String content) {
|
||||
|
||||
try {
|
||||
@@ -363,6 +369,7 @@ public class SaSecureUtil {
|
||||
* @param content 已加密内容
|
||||
* @return 解密后内容
|
||||
*/
|
||||
@Deprecated
|
||||
public static String rsaDecryptByPrivate(String privateKeyString, String content) {
|
||||
try {
|
||||
PrivateKey privateKey = getPrivateKeyFromString(privateKeyString);
|
||||
|
||||
@@ -26,6 +26,8 @@ import cn.dev33.satoken.util.SaFoxUtil;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import static cn.dev33.satoken.SaManager.log;
|
||||
|
||||
/**
|
||||
* API 参数签名算法,在跨系统接口调用时防参数篡改、防重放攻击。
|
||||
*
|
||||
@@ -42,6 +44,17 @@ import java.util.TreeMap;
|
||||
*/
|
||||
public class SaSignTemplate {
|
||||
|
||||
public SaSignTemplate() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param signConfig 签名参数配置对象
|
||||
*/
|
||||
public SaSignTemplate(SaSignConfig signConfig) {
|
||||
this.signConfig = signConfig;
|
||||
}
|
||||
|
||||
// ----------- 签名配置
|
||||
|
||||
SaSignConfig signConfig;
|
||||
@@ -148,7 +161,7 @@ public class SaSignTemplate {
|
||||
*/
|
||||
public String createSign(Map<String, ?> paramsMap) {
|
||||
String secretKey = getSecretKey();
|
||||
SaSignException.throwByNull(secretKey, "参与参数签名的秘钥不可为空", SaErrorCode.CODE_12201);
|
||||
SaSignException.notEmpty(secretKey, "参与参数签名的秘钥不可为空", SaErrorCode.CODE_12201);
|
||||
|
||||
// 如果调用者不小心传入了 sign 参数,则此处需要将 sign 参数排除在外
|
||||
if(paramsMap.containsKey(sign)) {
|
||||
@@ -160,7 +173,14 @@ public class SaSignTemplate {
|
||||
// 计算签名
|
||||
String paramsStr = joinParamsDictSort(paramsMap);
|
||||
String fullStr = paramsStr + "&" + key + "=" + secretKey;
|
||||
return abstractStr(fullStr);
|
||||
String signStr = abstractStr(fullStr);
|
||||
|
||||
// 输入日志,方便调试
|
||||
log.debug("fullStr:{}", fullStr);
|
||||
log.debug("signStr:{}", signStr);
|
||||
|
||||
// 返回
|
||||
return signStr;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -295,13 +315,14 @@ public class SaSignTemplate {
|
||||
String signValue = paramMap.get(sign);
|
||||
|
||||
// 参数非空校验
|
||||
SaSignException.throwByNull(timestampValue, "缺少 timestamp 字段");
|
||||
// SaSignException.throwByNull(nonceValue, "缺少 nonce 字段"); // 配置isCheckNonce=false时,可以不传 nonce
|
||||
SaSignException.throwByNull(signValue, "缺少 sign 字段");
|
||||
// 配置isCheckNonce=false时,可以不传 nonce
|
||||
if(SaFoxUtil.isEmpty(timestampValue) || SaFoxUtil.isEmpty(signValue)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 三个值的校验必须全部通过
|
||||
return isValidTimestamp(Long.parseLong(timestampValue))
|
||||
&& (getSignConfigOrGlobal().getIsCheckNonce() ? isValidNonce(nonceValue) : true)
|
||||
&& isValidNonce(nonceValue)
|
||||
&& isValidSign(paramMap, signValue);
|
||||
}
|
||||
|
||||
@@ -316,15 +337,13 @@ public class SaSignTemplate {
|
||||
String signValue = paramMap.get(sign);
|
||||
|
||||
// 参数非空校验
|
||||
SaSignException.throwByNull(timestampValue, "缺少 timestamp 字段");
|
||||
// SaSignException.throwByNull(nonceValue, "缺少 nonce 字段"); // 配置isCheckNonce=false时,可以不传 nonce
|
||||
SaSignException.throwByNull(signValue, "缺少 sign 字段");
|
||||
SaSignException.notEmpty(timestampValue, "缺少 timestamp 字段");
|
||||
SaSignException.notEmpty(nonceValue, "缺少 nonce 字段");
|
||||
SaSignException.notEmpty(signValue, "缺少 sign 字段");
|
||||
|
||||
// 依次校验三个参数
|
||||
checkTimestamp(Long.parseLong(timestampValue));
|
||||
if(getSignConfigOrGlobal().getIsCheckNonce()) {
|
||||
checkNonce(nonceValue);
|
||||
}
|
||||
checkNonce(nonceValue);
|
||||
checkSign(paramMap, signValue);
|
||||
|
||||
// 通过 √
|
||||
@@ -333,20 +352,52 @@ public class SaSignTemplate {
|
||||
/**
|
||||
* 判断:一个请求中的 nonce、timestamp、sign 是否均为合法的
|
||||
* @param request 待校验的请求对象
|
||||
* @param paramNames 指定参与签名的参数有哪些,如果不填写则默认为全部参数
|
||||
* @return 是否合法
|
||||
*/
|
||||
public boolean isValidRequest(SaRequest request) {
|
||||
return isValidParamMap(request.getParamMap());
|
||||
public boolean isValidRequest(SaRequest request, String... paramNames) {
|
||||
if(paramNames.length == 0) {
|
||||
return isValidParamMap(request.getParamMap());
|
||||
} else {
|
||||
return isValidParamMap(takeRequestParam(request, paramNames));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验:一个请求的 nonce、timestamp、sign 是否均为合法的,如果不合法,则抛出对应的异常
|
||||
* @param paramNames 指定参与签名的参数有哪些,如果不填写则默认为全部参数
|
||||
* @param request 待校验的请求对象
|
||||
*/
|
||||
public void checkRequest(SaRequest request) {
|
||||
checkParamMap(request.getParamMap());
|
||||
public void checkRequest(SaRequest request, String... paramNames) {
|
||||
if (paramNames.length == 0) {
|
||||
checkParamMap(request.getParamMap());
|
||||
} else {
|
||||
checkParamMap(takeRequestParam(request, paramNames));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从请求中提取指定的参数
|
||||
* @param request 请求对象
|
||||
* @param paramNames 指定的参数名称,不可为空,如果传入空数组则代表只拿 timestamp、nonce、sign 三个参数
|
||||
* @return 提取出的参数
|
||||
*/
|
||||
public Map<String, String> takeRequestParam(SaRequest request, String [] paramNames) {
|
||||
Map<String, String> paramMap = new TreeMap<>();
|
||||
|
||||
// 此三个参数是必须获取的
|
||||
paramMap.put(timestamp, request.getParam(timestamp));
|
||||
paramMap.put(nonce, request.getParam(nonce));
|
||||
paramMap.put(sign, request.getParam(sign));
|
||||
|
||||
// 获取指定的参数
|
||||
for (String paramName : paramNames) {
|
||||
paramMap.put(paramName, request.getParam(paramName));
|
||||
}
|
||||
|
||||
// 返回
|
||||
return paramMap;
|
||||
}
|
||||
|
||||
// ------------------- 返回相应key -------------------
|
||||
|
||||
|
||||
@@ -334,15 +334,15 @@ public class StpLogic {
|
||||
tokenValue = String.valueOf(storage.get(splicingKeyJustCreatedSave()));
|
||||
}
|
||||
// 2. 再尝试从 请求体 里面读取
|
||||
if(tokenValue == null && config.getIsReadBody()){
|
||||
if(SaFoxUtil.isEmpty(tokenValue) && config.getIsReadBody()){
|
||||
tokenValue = request.getParam(keyTokenName);
|
||||
}
|
||||
// 3. 再尝试从 header 头里读取
|
||||
if(tokenValue == null && config.getIsReadHeader()){
|
||||
if(SaFoxUtil.isEmpty(tokenValue) && config.getIsReadHeader()){
|
||||
tokenValue = request.getHeader(keyTokenName);
|
||||
}
|
||||
// 4. 最后尝试从 cookie 里读取
|
||||
if(tokenValue == null && config.getIsReadCookie()){
|
||||
if(SaFoxUtil.isEmpty(tokenValue) && config.getIsReadCookie()){
|
||||
tokenValue = request.getCookieValue(keyTokenName);
|
||||
}
|
||||
|
||||
@@ -471,7 +471,7 @@ public class StpLogic {
|
||||
String tokenValue = distUsableToken(id, loginModel);
|
||||
|
||||
// 4、获取此账号的 Account-Session , 续期
|
||||
SaSession session = getSessionByLoginId(id, true);
|
||||
SaSession session = getSessionByLoginId(id, true, loginModel.getTimeoutOrGlobalConfig());
|
||||
session.updateMinTimeout(loginModel.getTimeout());
|
||||
|
||||
// 5、在 Account-Session 上记录本次登录的 token 签名
|
||||
@@ -994,12 +994,12 @@ public class StpLogic {
|
||||
if(loginId == null) {
|
||||
return defaultValue;
|
||||
}
|
||||
// 3、loginId 不为 null,则开始尝试类型转换
|
||||
if (defaultValue == null) {
|
||||
return null;
|
||||
}
|
||||
// 3、loginId 不为 null,则开始尝试类型转换
|
||||
if(defaultValue == null) {
|
||||
return (T) loginId;
|
||||
}
|
||||
return (T) SaFoxUtil.getValueByType(loginId, defaultValue.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前会话账号id, 如果未登录,则返回null
|
||||
@@ -1147,7 +1147,7 @@ public class StpLogic {
|
||||
*/
|
||||
public void updateTokenToIdMapping(String tokenValue, Object loginId) {
|
||||
// 先判断一下,是否传入了空值
|
||||
SaTokenException.throwBy(SaFoxUtil.isEmpty(loginId), "loginId 不能为空", SaErrorCode.CODE_11003);
|
||||
SaTokenException.notTrue(SaFoxUtil.isEmpty(loginId), "loginId 不能为空", SaErrorCode.CODE_11003);
|
||||
|
||||
// 更新缓存中的 token 指向
|
||||
getSaTokenDao().update(splicingKeyTokenValue(tokenValue), loginId.toString());
|
||||
@@ -1170,14 +1170,15 @@ public class StpLogic {
|
||||
*
|
||||
* @param sessionId SessionId
|
||||
* @param isCreate 是否新建
|
||||
* @param appendOperation 如果这个 SaSession 是新建的,则要追加执行的动作
|
||||
* @param timeout 如果这个 SaSession 是新建的,则使用此值作为过期值(单位:秒),可填 null,代表使用全局 timeout 值
|
||||
* @param appendOperation 如果这个 SaSession 是新建的,则要追加执行的动作,可填 null,代表无追加动作
|
||||
* @return Session对象
|
||||
*/
|
||||
public SaSession getSessionBySessionId(String sessionId, boolean isCreate, Consumer<SaSession> appendOperation) {
|
||||
public SaSession getSessionBySessionId(String sessionId, boolean isCreate, Long timeout, Consumer<SaSession> appendOperation) {
|
||||
|
||||
// 如果提供的 sessionId 为 null,则直接返回 null
|
||||
if(SaFoxUtil.isEmpty(sessionId)) {
|
||||
return null;
|
||||
throw new SaTokenException("SessionId 不能为空").setCode(SaErrorCode.CODE_11072);
|
||||
}
|
||||
|
||||
// 先检查这个 SaSession 是否已经存在,如果不存在且 isCreate=true,则新建并返回
|
||||
@@ -1192,8 +1193,22 @@ public class StpLogic {
|
||||
appendOperation.accept(session);
|
||||
}
|
||||
|
||||
// 如果未提供 timeout,则根据相应规则设定默认的 timeout
|
||||
if(timeout == null) {
|
||||
// 如果是 Token-Session,则使用对用 token 的有效期,使 token 和 token-session 保持相同ttl,同步失效
|
||||
if(SaTokenConsts.SESSION_TYPE__TOKEN.equals(session.getType())) {
|
||||
timeout = getTokenTimeout(session.getToken());
|
||||
if(timeout == SaTokenDao.NOT_VALUE_EXPIRE) {
|
||||
timeout = getConfigOrGlobal().getTimeout();
|
||||
}
|
||||
} else {
|
||||
// 否则使用全局配置的 timeout
|
||||
timeout = getConfigOrGlobal().getTimeout();
|
||||
}
|
||||
}
|
||||
|
||||
// 将这个 SaSession 入库
|
||||
getSaTokenDao().setSession(session, getConfigOrGlobal().getTimeout());
|
||||
getSaTokenDao().setSession(session, timeout);
|
||||
}
|
||||
return session;
|
||||
}
|
||||
@@ -1205,7 +1220,28 @@ public class StpLogic {
|
||||
* @return Session对象
|
||||
*/
|
||||
public SaSession getSessionBySessionId(String sessionId) {
|
||||
return getSessionBySessionId(sessionId, false, null);
|
||||
return getSessionBySessionId(sessionId, false, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定账号 id 的 Account-Session, 如果该 SaSession 尚未创建,isCreate=是否新建并返回
|
||||
*
|
||||
* @param loginId 账号id
|
||||
* @param isCreate 是否新建
|
||||
* @param timeout 如果这个 SaSession 是新建的,则使用此值作为过期值(单位:秒),可填 null,代表使用全局 timeout 值
|
||||
* @return SaSession 对象
|
||||
*/
|
||||
public SaSession getSessionByLoginId(Object loginId, boolean isCreate, Long timeout) {
|
||||
if(SaFoxUtil.isEmpty(loginId)) {
|
||||
throw new SaTokenException("Account-Session 获取失败:loginId 不能为空");
|
||||
}
|
||||
return getSessionBySessionId(splicingKeySession(loginId), isCreate, timeout, session -> {
|
||||
// 这里是该 Account-Session 首次创建时才会被执行的方法:
|
||||
// 设定这个 SaSession 的各种基础信息:类型、账号体系、账号id
|
||||
session.setType(SaTokenConsts.SESSION_TYPE__ACCOUNT);
|
||||
session.setLoginType(getLoginType());
|
||||
session.setLoginId(loginId);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1216,13 +1252,7 @@ public class StpLogic {
|
||||
* @return SaSession 对象
|
||||
*/
|
||||
public SaSession getSessionByLoginId(Object loginId, boolean isCreate) {
|
||||
return getSessionBySessionId(splicingKeySession(loginId), isCreate, session -> {
|
||||
// 这里是该 Account-Session 首次创建时才会被执行的方法:
|
||||
// 设定这个 SaSession 的各种基础信息:类型、账号体系、账号id
|
||||
session.setType(SaTokenConsts.SESSION_TYPE__ACCOUNT);
|
||||
session.setLoginType(getLoginType());
|
||||
session.setLoginId(loginId);
|
||||
});
|
||||
return getSessionByLoginId(loginId, isCreate, null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1232,7 +1262,7 @@ public class StpLogic {
|
||||
* @return SaSession 对象
|
||||
*/
|
||||
public SaSession getSessionByLoginId(Object loginId) {
|
||||
return getSessionByLoginId(loginId, true);
|
||||
return getSessionByLoginId(loginId, true, null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1265,7 +1295,10 @@ public class StpLogic {
|
||||
* @return session对象
|
||||
*/
|
||||
public SaSession getTokenSessionByToken(String tokenValue, boolean isCreate) {
|
||||
return getSessionBySessionId(splicingKeyTokenSession(tokenValue), isCreate, session -> {
|
||||
if(SaFoxUtil.isEmpty(tokenValue)) {
|
||||
throw new SaTokenException("Token-Session 获取失败:token 为空").setCode(SaErrorCode.CODE_11073);
|
||||
}
|
||||
return getSessionBySessionId(splicingKeyTokenSession(tokenValue), isCreate, null, session -> {
|
||||
// 这里是该 Token-Session 首次创建时才会被执行的方法:
|
||||
// 设定这个 SaSession 的各种基础信息:类型、账号体系、Token 值
|
||||
session.setType(SaTokenConsts.SESSION_TYPE__TOKEN);
|
||||
@@ -1300,7 +1333,7 @@ public class StpLogic {
|
||||
// 2、如果前端根本没有提供 Token ,则直接返回 null
|
||||
String tokenValue = getTokenValue();
|
||||
if(SaFoxUtil.isEmpty(tokenValue)) {
|
||||
return null;
|
||||
throw new SaTokenException("Token-Session 获取失败:token 为空").setCode(SaErrorCode.CODE_11073);
|
||||
}
|
||||
|
||||
// 3、代码至此:tokenSessionCheckLogin 校验通过、且 Token 有值
|
||||
@@ -1377,7 +1410,14 @@ public class StpLogic {
|
||||
setTokenValue(tokenValue);
|
||||
|
||||
// 返回其 Token-Session 对象
|
||||
return getTokenSessionByToken(tokenValue, isCreate);
|
||||
final String finalTokenValue = tokenValue;
|
||||
return getSessionBySessionId(splicingKeyTokenSession(tokenValue), isCreate, getConfigOrGlobal().getTimeout(), session -> {
|
||||
// 这里是该 Anon-Token-Session 首次创建时才会被执行的方法:
|
||||
// 设定这个 SaSession 的各种基础信息:类型、账号体系、Token 值
|
||||
session.setType(SaTokenConsts.SESSION_TYPE__TOKEN);
|
||||
session.setLoginType(getLoginType());
|
||||
session.setToken(finalTokenValue);
|
||||
});
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
@@ -1532,6 +1572,40 @@ public class StpLogic {
|
||||
return activeTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定 token 的最后活跃时间(13位时间戳),如果不存在则返回 -2
|
||||
*
|
||||
* @param tokenValue 指定token
|
||||
* @return /
|
||||
*/
|
||||
public long getTokenLastActiveTime(String tokenValue) {
|
||||
// 1、如果提供的 token 为 null,则返回 -2
|
||||
if(SaFoxUtil.isEmpty(tokenValue)) {
|
||||
return SaTokenDao.NOT_VALUE_EXPIRE;
|
||||
}
|
||||
|
||||
// 2、获取这个 token 的最后活跃时间,13位时间戳
|
||||
String key = splicingKeyLastActiveTime(tokenValue);
|
||||
String lastActiveTimeString = getSaTokenDao().get(key);
|
||||
|
||||
// 3、查不到,返回-2
|
||||
if(lastActiveTimeString == null) {
|
||||
return SaTokenDao.NOT_VALUE_EXPIRE;
|
||||
}
|
||||
|
||||
// 4、根据逗号切割字符串
|
||||
return new SaValue2Box(lastActiveTimeString).getValue1AsLong();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前 token 的最后活跃时间(13位时间戳),如果不存在则返回 -2
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
public long getTokenLastActiveTime() {
|
||||
return getTokenLastActiveTime(getTokenValue());
|
||||
}
|
||||
|
||||
|
||||
// ------------------- 过期时间相关 -------------------
|
||||
|
||||
@@ -1624,26 +1698,14 @@ public class StpLogic {
|
||||
return SaTokenDao.NEVER_EXPIRE;
|
||||
}
|
||||
|
||||
// 如果提供的 token 为 null,则返回 -2
|
||||
if(SaFoxUtil.isEmpty(tokenValue)) {
|
||||
return SaTokenDao.NOT_VALUE_EXPIRE;
|
||||
}
|
||||
|
||||
// ------ 开始查询
|
||||
|
||||
// 1、先获取这个 token 的最后活跃时间,13位时间戳
|
||||
String key = splicingKeyLastActiveTime(tokenValue);
|
||||
String lastActiveTimeString = getSaTokenDao().get(key);
|
||||
// 先获取这个 token 的最后活跃时间,13位时间戳
|
||||
long lastActiveTime = getTokenLastActiveTime(tokenValue);
|
||||
if(lastActiveTime == SaTokenDao.NOT_VALUE_EXPIRE) {
|
||||
return SaTokenDao.NOT_VALUE_EXPIRE;
|
||||
}
|
||||
|
||||
// 2、如果查不到,返回-2
|
||||
if(lastActiveTimeString == null) {
|
||||
return SaTokenDao.NOT_VALUE_EXPIRE;
|
||||
}
|
||||
|
||||
// 3、计算最后活跃时间 距离 此时此刻 的时间差
|
||||
// 计算公式为: (当前时间 - 最后活跃时间) / 1000
|
||||
SaValue2Box box = new SaValue2Box(lastActiveTimeString);
|
||||
long lastActiveTime = box.getValue1AsLong();
|
||||
// 实际时间差
|
||||
long timeDiff = (System.currentTimeMillis() - lastActiveTime) / 1000;
|
||||
// 该 token 允许的时间差
|
||||
@@ -1653,7 +1715,7 @@ public class StpLogic {
|
||||
return SaTokenDao.NEVER_EXPIRE;
|
||||
}
|
||||
|
||||
// 4、校验这个时间差是否超过了允许的值
|
||||
// 校验这个时间差是否超过了允许的值
|
||||
// 计算公式为: 允许的最大时间差 - 实际时间差,判断是否 < 0, 如果是则代表已经被冻结 ,返回-2
|
||||
long activeTimeout = allowTimeDiff - timeDiff;
|
||||
if(activeTimeout < 0) {
|
||||
@@ -1750,7 +1812,11 @@ public class StpLogic {
|
||||
* @return /
|
||||
*/
|
||||
public boolean hasRole(String role) {
|
||||
return hasElement(getRoleList(), role);
|
||||
try {
|
||||
return hasRole(getLoginId(), role);
|
||||
} catch (NotLoginException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1774,7 +1840,7 @@ public class StpLogic {
|
||||
try {
|
||||
checkRoleAnd(roleArray);
|
||||
return true;
|
||||
} catch (NotRoleException e) {
|
||||
} catch (NotLoginException | NotRoleException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1789,7 +1855,7 @@ public class StpLogic {
|
||||
try {
|
||||
checkRoleOr(roleArray);
|
||||
return true;
|
||||
} catch (NotRoleException e) {
|
||||
} catch (NotLoginException | NotRoleException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1800,7 +1866,7 @@ public class StpLogic {
|
||||
* @param role 角色标识
|
||||
*/
|
||||
public void checkRole(String role) {
|
||||
if( ! hasRole(role)) {
|
||||
if( ! hasRole(getLoginId(), role)) {
|
||||
throw new NotRoleException(role, this.loginType).setCode(SaErrorCode.CODE_11041);
|
||||
}
|
||||
}
|
||||
@@ -1884,7 +1950,11 @@ public class StpLogic {
|
||||
* @return 是否含有指定权限
|
||||
*/
|
||||
public boolean hasPermission(String permission) {
|
||||
return hasElement(getPermissionList(), permission);
|
||||
try {
|
||||
return hasPermission(getLoginId(), permission);
|
||||
} catch (NotLoginException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1908,7 +1978,7 @@ public class StpLogic {
|
||||
try {
|
||||
checkPermissionAnd(permissionArray);
|
||||
return true;
|
||||
} catch (NotPermissionException e) {
|
||||
} catch (NotLoginException | NotPermissionException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1923,7 +1993,7 @@ public class StpLogic {
|
||||
try {
|
||||
checkPermissionOr(permissionArray);
|
||||
return true;
|
||||
} catch (NotPermissionException e) {
|
||||
} catch (NotLoginException | NotPermissionException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1934,7 +2004,7 @@ public class StpLogic {
|
||||
* @param permission 权限码
|
||||
*/
|
||||
public void checkPermission(String permission) {
|
||||
if( ! hasPermission(permission)) {
|
||||
if( ! hasPermission(getLoginId(), permission)) {
|
||||
throw new NotPermissionException(permission, this.loginType).setCode(SaErrorCode.CODE_11051);
|
||||
}
|
||||
}
|
||||
@@ -2075,19 +2145,32 @@ public class StpLogic {
|
||||
* @return 当前令牌的登录设备类型
|
||||
*/
|
||||
public String getLoginDevice() {
|
||||
// 1、如果前端没有提交 token,直接返回 null
|
||||
String tokenValue = getTokenValue();
|
||||
if(tokenValue == null) {
|
||||
return getLoginDeviceByToken(getTokenValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回指定 token 会话的登录设备类型
|
||||
*
|
||||
* @param tokenValue 指定token
|
||||
* @return 当前令牌的登录设备类型
|
||||
*/
|
||||
public String getLoginDeviceByToken(String tokenValue) {
|
||||
// 1、如果 token 为 null,直接提前返回
|
||||
if(SaFoxUtil.isEmpty(tokenValue)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 2、如果当前会话还未登录,直接返回 null
|
||||
if(!isLogin()) {
|
||||
// 2、获取此 token 对应的 loginId,如果为null,或者此token已被冻结,直接返回null
|
||||
Object loginId = getLoginIdNotHandle(tokenValue);
|
||||
if( ! isValidLoginId(loginId)) {
|
||||
return null;
|
||||
}
|
||||
if(getTokenActiveTimeoutByToken(tokenValue) == SaTokenDao.NOT_VALUE_EXPIRE ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 3、获取当前账号的 Account-Session
|
||||
SaSession session = getSessionByLoginId(getLoginIdDefaultNull(), false);
|
||||
// 3、获取这个账号的 Account-Session
|
||||
SaSession session = getSessionByLoginId(loginId, false);
|
||||
|
||||
// 4、为 null 说明尚未登录,当然也就不存在什么设备类型,直接返回 null
|
||||
if(session == null) {
|
||||
@@ -2105,7 +2188,7 @@ public class StpLogic {
|
||||
// 6、没有找到,还是返回 null
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ------------------- 会话管理 -------------------
|
||||
|
||||
@@ -2598,7 +2681,13 @@ public class StpLogic {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2、如果缓存中可以查询出指定的键值,则代表已认证,否则视为未认证
|
||||
// 2、如果此 token 不处于登录状态,也将其视为未认证
|
||||
Object loginId = getLoginIdNotHandle(tokenValue);
|
||||
if( ! isValidLoginId(loginId) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3、如果缓存中可以查询出指定的键值,则代表已认证,否则视为未认证
|
||||
String value = getSaTokenDao().get(splicingKeySafe(tokenValue, service));
|
||||
return !(SaFoxUtil.isEmpty(value));
|
||||
}
|
||||
@@ -2616,8 +2705,14 @@ public class StpLogic {
|
||||
* @param service 业务标识
|
||||
*/
|
||||
public void checkSafe(String service) {
|
||||
// 1、必须先通过登录校验
|
||||
checkLogin();
|
||||
|
||||
// 2、再进行二级认证校验
|
||||
// 如果缓存中可以查询出指定的键值,则代表已认证,否则视为未认证
|
||||
String tokenValue = getTokenValue();
|
||||
if ( ! isSafe(tokenValue, service)) {
|
||||
String value = getSaTokenDao().get(splicingKeySafe(tokenValue, service));
|
||||
if(SaFoxUtil.isEmpty(value)) {
|
||||
throw new NotSafeException(loginType, tokenValue, service).setCode(SaErrorCode.CODE_11071);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -844,7 +844,27 @@ public class StpUtil {
|
||||
return stpLogic.getLoginDevice();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 返回指定 token 会话的登录设备类型
|
||||
*
|
||||
* @param tokenValue 指定token
|
||||
* @return 当前令牌的登录设备类型
|
||||
*/
|
||||
public static String getLoginDeviceByToken(String tokenValue) {
|
||||
return stpLogic.getLoginDeviceByToken(tokenValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前 token 的最后活跃时间(13位时间戳),如果不存在则返回 -2
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
public static long getTokenLastActiveTime() {
|
||||
return stpLogic.getTokenLastActiveTime();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ------------------- 会话管理 -------------------
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,10 +17,11 @@ package cn.dev33.satoken.strategy;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.annotation.*;
|
||||
import cn.dev33.satoken.basic.SaBasicUtil;
|
||||
import cn.dev33.satoken.exception.RequestPathInvalidException;
|
||||
import cn.dev33.satoken.exception.SaTokenException;
|
||||
import cn.dev33.satoken.fun.strategy.*;
|
||||
import cn.dev33.satoken.httpauth.basic.SaHttpBasicUtil;
|
||||
import cn.dev33.satoken.httpauth.digest.SaHttpDigestUtil;
|
||||
import cn.dev33.satoken.session.SaSession;
|
||||
import cn.dev33.satoken.stp.StpLogic;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
@@ -179,10 +180,16 @@ public final class SaStrategy {
|
||||
SaManager.getStpLogic(checkDisable.type(), false).checkByAnnotation(checkDisable);
|
||||
}
|
||||
|
||||
// 校验 @SaCheckBasic 注解
|
||||
SaCheckBasic checkBasic = (SaCheckBasic) SaStrategy.instance.getAnnotation.apply(element, SaCheckBasic.class);
|
||||
if(checkBasic != null) {
|
||||
SaBasicUtil.check(checkBasic.realm(), checkBasic.account());
|
||||
// 校验 @SaCheckHttpBasic 注解
|
||||
SaCheckHttpBasic checkHttpBasic = (SaCheckHttpBasic) SaStrategy.instance.getAnnotation.apply(element, SaCheckHttpBasic.class);
|
||||
if(checkHttpBasic != null) {
|
||||
SaHttpBasicUtil.check(checkHttpBasic.realm(), checkHttpBasic.account());
|
||||
}
|
||||
|
||||
// 校验 @SaCheckHttpDigest 注解
|
||||
SaCheckHttpDigest checkHttpDigest = (SaCheckHttpDigest) SaStrategy.instance.getAnnotation.apply(element, SaCheckHttpDigest.class);
|
||||
if(checkHttpDigest != null) {
|
||||
SaHttpDigestUtil.checkByAnnotation(checkHttpDigest);
|
||||
}
|
||||
|
||||
// 校验 @SaCheckOr 注解
|
||||
@@ -258,10 +265,21 @@ public final class SaStrategy {
|
||||
}
|
||||
|
||||
// 6、校验注解:@SaCheckBasic
|
||||
SaCheckBasic[] checkBasicArray = at.basic();
|
||||
for (SaCheckBasic item : checkBasicArray) {
|
||||
SaCheckHttpBasic[] checkHttpBasicArray = at.httpBasic();
|
||||
for (SaCheckHttpBasic item : checkHttpBasicArray) {
|
||||
try {
|
||||
SaBasicUtil.check(item.realm(), item.account());
|
||||
SaHttpBasicUtil.check(item.realm(), item.account());
|
||||
return;
|
||||
} catch (SaTokenException e) {
|
||||
errorList.add(e);
|
||||
}
|
||||
}
|
||||
|
||||
// 7、校验注解:@SaCheckDigest
|
||||
SaCheckHttpDigest[] checkHttpDigestArray = at.httpDigest();
|
||||
for (SaCheckHttpDigest item : checkHttpDigestArray) {
|
||||
try {
|
||||
SaHttpDigestUtil.checkByAnnotation(item);
|
||||
return;
|
||||
} catch (SaTokenException e) {
|
||||
errorList.add(e);
|
||||
|
||||
@@ -36,7 +36,7 @@ public class SaTokenConsts {
|
||||
/**
|
||||
* Sa-Token 当前版本号
|
||||
*/
|
||||
public static final String VERSION_NO = "v1.37.0";
|
||||
public static final String VERSION_NO = "v1.38.0";
|
||||
|
||||
/**
|
||||
* Sa-Token 开源地址 Gitee
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.37.0</sa-token.version>
|
||||
<sa-token.version>1.38.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.37.0</sa-token.version>
|
||||
<sa-token.version>1.38.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
+28
-9
@@ -3,31 +3,37 @@
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-demo-cross-header-server</artifactId>
|
||||
<artifactId>sa-token-demo-beetl</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
|
||||
<!-- SpringBoot -->
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.5.15</version>
|
||||
<version>2.5.14</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.37.0</sa-token.version>
|
||||
<java.run.main.class>com.pj.SaTokenCrossHeaderApplication</java.run.main.class>
|
||||
<sa-token.version>1.38.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- SpringBoot依赖 -->
|
||||
<!-- springboot依赖 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- Beetl 视图引擎 -->
|
||||
<dependency>
|
||||
<groupId>com.ibeetl</groupId>
|
||||
<artifactId>beetl-framework-starter</artifactId>
|
||||
<version>1.2.40.Beetl.RELEASE</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Sa-Token 权限认证, 在线文档:https://sa-token.cc/ -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
@@ -35,8 +41,21 @@
|
||||
<version>${sa-token.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 热刷新 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-devtools</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- @ConfigurationProperties -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
||||
|
||||
</project>
|
||||
|
||||
</project>
|
||||
+7
-11
@@ -1,21 +1,17 @@
|
||||
package com.pj;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
|
||||
/**
|
||||
* Sa-Token 跨域测试(header参数版)
|
||||
* @author click33
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class SaTokenCrossHeaderApplication {
|
||||
|
||||
public class SaTokenBeetlDemoApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SaTokenCrossHeaderApplication.class, args);
|
||||
SpringApplication.run(SaTokenBeetlDemoApplication.class, args);
|
||||
System.out.println("\n启动成功,Sa-Token 配置如下:" + SaManager.getConfig());
|
||||
System.out.println("\n后端地址使用 http://localhost 访问,前端页面用 http://127.0.0.1 访问");
|
||||
System.out.println("\n测试访问:http://localhost:8081/");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.pj.satoken;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import com.ibeetl.starter.BeetlTemplateCustomize;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
|
||||
/**
|
||||
* [Sa-Token 权限认证] 配置类
|
||||
* @author click33
|
||||
*
|
||||
*/
|
||||
@Configuration
|
||||
public class SaTokenConfigure implements WebMvcConfigurer {
|
||||
|
||||
// 为 Beetl 视图引擎注册自定义函数:
|
||||
// 通过 stp.xxx() 调用 StpUtil.stpLogic 对象上所有 public 方法
|
||||
@Bean
|
||||
public BeetlTemplateCustomize beetlTemplateCustomize(){
|
||||
return groupTemplate -> groupTemplate.registerFunctionPackage("stp", StpUtil.stpLogic);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.pj.satoken;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import cn.dev33.satoken.stp.StpInterface;
|
||||
|
||||
/**
|
||||
* 自定义权限验证接口扩展
|
||||
*/
|
||||
@Component // 打开此注解,保证此类被springboot扫描,即可完成sa-token的自定义权限验证扩展
|
||||
public class StpInterfaceImpl implements StpInterface {
|
||||
|
||||
/**
|
||||
* 返回一个账号所拥有的权限码集合
|
||||
*/
|
||||
@Override
|
||||
public List<String> getPermissionList(Object loginId, String loginType) {
|
||||
// 本list仅做模拟,实际项目中要根据具体业务逻辑来查询权限
|
||||
List<String> list = new ArrayList<String>();
|
||||
list.add("101");
|
||||
list.add("user-add");
|
||||
list.add("user-delete");
|
||||
list.add("user-update");
|
||||
list.add("user-get");
|
||||
list.add("article-get");
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回一个账号所拥有的角色标识集合
|
||||
*/
|
||||
@Override
|
||||
public List<String> getRoleList(Object loginId, String loginType) {
|
||||
// 本list仅做模拟,实际项目中要根据具体业务逻辑来查询角色
|
||||
List<String> list = new ArrayList<String>();
|
||||
list.add("admin");
|
||||
list.add("super-admin");
|
||||
return list;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.pj.test;
|
||||
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* 全局异常处理
|
||||
*/
|
||||
@RestControllerAdvice
|
||||
public class GlobalException {
|
||||
|
||||
// 全局异常拦截(拦截项目中的所有异常)
|
||||
@ExceptionHandler
|
||||
public SaResult handlerException(Exception e, HttpServletRequest request, HttpServletResponse response) {
|
||||
e.printStackTrace();
|
||||
return SaResult.error(e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.pj.test;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
/**
|
||||
* 测试 Controller
|
||||
* @author click33
|
||||
*
|
||||
*/
|
||||
@RestController
|
||||
public class TestController {
|
||||
|
||||
// 首页
|
||||
@RequestMapping("/")
|
||||
public Object index() {
|
||||
return new ModelAndView("index.btl");
|
||||
}
|
||||
|
||||
// 登录
|
||||
@RequestMapping("login")
|
||||
public SaResult login(@RequestParam(defaultValue="10001") String id) {
|
||||
StpUtil.login(id);
|
||||
StpUtil.getSession().set("name", "zhangsan");
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
// 注销
|
||||
@RequestMapping("logout")
|
||||
public SaResult logout() {
|
||||
StpUtil.logout();
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
# 端口
|
||||
server:
|
||||
port: 8081
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<title>Sa-Token 集成 Beetl 示例</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
</head>
|
||||
<body>
|
||||
<div class="view-box" style="padding: 30px;">
|
||||
<h2>Sa-Token 集成 Beetl —— 测试页面</h2>
|
||||
<p>当前是否登录:${stp.isLogin()}</p>
|
||||
<p>
|
||||
<span>操作:</span>
|
||||
<a href="login" target="_blank">登录</a>
|
||||
<a href="logout" target="_blank">注销</a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
登录之后才能显示:
|
||||
<% if(stp.isLogin()){ %>
|
||||
value
|
||||
<%}%>
|
||||
</p>
|
||||
<p>
|
||||
不登录才能显示:
|
||||
<% if(!stp.isLogin()){ %>
|
||||
value
|
||||
<%}%>
|
||||
</p>
|
||||
|
||||
<p>具有角色 admin 才能显示:<% if(stp.hasRole("admin")){ %> value <% } %></p>
|
||||
<p>同时具备多个角色才能显示:<% if(stp.hasRoleAnd("admin", "ceo", "cto")){ %> value <% } %></p>
|
||||
<p>只要具有其中一个角色就能显示:<% if(stp.hasRoleOr("admin", "ceo", "cto")){ %> value <% } %></p>
|
||||
<p>不具有角色 admin 才能显示:<% if(!stp.hasRole("admin")){ %> value <% } %></p>
|
||||
|
||||
<p>具有权限 user-add 才能显示:<% if(stp.hasPermission("user-add")){ %> value <% } %></p>
|
||||
<p>同时具备多个权限才能显示:<% if(stp.hasPermissionAnd("user-add", "user-delete", "user-get")){ %> value <% } %></p>
|
||||
<p>只要具有其中一个权限就能显示:<% if(stp.hasPermissionOr("user-add", "user-delete", "user-get")){ %> value <% } %></p>
|
||||
<p>不具有权限 user-add 才能显示:<% if(!stp.hasPermission("user-add")){ %> value <% } %></p>
|
||||
|
||||
<% if(stp.isLogin()){ %>
|
||||
<p>
|
||||
从SaSession中取值:${stp.getSession()["name"]} <br>
|
||||
或:${stp.getSession().name}
|
||||
</p>
|
||||
<%}%>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.37.0</sa-token.version>
|
||||
<sa-token.version>1.38.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
@@ -73,7 +73,7 @@
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-bom</artifactId>
|
||||
<version>1.37.0</version>
|
||||
<version>1.38.0</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.37.0</sa-token.version>
|
||||
<sa-token.version>1.38.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
+3
-4
@@ -1,11 +1,10 @@
|
||||
package com.pj.cases.up;
|
||||
|
||||
import cn.dev33.satoken.httpauth.basic.SaHttpBasicUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import cn.dev33.satoken.basic.SaBasicUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
|
||||
/**
|
||||
* Sa-Token Http Basic 认证
|
||||
*
|
||||
@@ -28,7 +27,7 @@ public class HttpBasicController {
|
||||
@RequestMapping("getInfo")
|
||||
public SaResult login() {
|
||||
// 1、Http Basic 认证校验,账号=sa,密码=123456
|
||||
SaBasicUtil.check("sa:123456");
|
||||
SaHttpBasicUtil.check("sa:123456");
|
||||
|
||||
// 2、返回数据
|
||||
String data = "这是通过 Http Basic 校验后才返回的数据";
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
package com.pj.satoken;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
import cn.dev33.satoken.context.SaHolder;
|
||||
import cn.dev33.satoken.filter.SaServletFilter;
|
||||
import cn.dev33.satoken.interceptor.SaInterceptor;
|
||||
@@ -14,6 +7,13 @@ import cn.dev33.satoken.router.SaRouter;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.strategy.SaStrategy;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
|
||||
/**
|
||||
@@ -114,11 +114,11 @@ public class SaTokenConfigure implements WebMvcConfigurer {
|
||||
/**
|
||||
* 重写 Sa-Token 框架内部算法策略
|
||||
*/
|
||||
@Autowired
|
||||
@PostConstruct
|
||||
public void rewriteSaStrategy() {
|
||||
// 重写Sa-Token的注解处理器,增加注解合并功能
|
||||
SaStrategy.instance.getAnnotation = (element, annotationClass) -> {
|
||||
return AnnotatedElementUtils.getMergedAnnotation(element, annotationClass);
|
||||
return AnnotatedElementUtils.getMergedAnnotation(element, annotationClass);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -4,18 +4,21 @@ import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.fun.SaFunction;
|
||||
import cn.dev33.satoken.listener.SaTokenEventCenter;
|
||||
import cn.dev33.satoken.session.SaSession;
|
||||
import cn.dev33.satoken.session.TokenSign;
|
||||
import cn.dev33.satoken.stp.SaLoginModel;
|
||||
import cn.dev33.satoken.stp.SaTokenInfo;
|
||||
import cn.dev33.satoken.stp.StpLogic;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Sa-Token 权限认证工具类 (User 版)
|
||||
* Sa-Token 权限认证工具类(User版)
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Component
|
||||
public class StpUserUtil {
|
||||
|
||||
private StpUserUtil() {}
|
||||
@@ -302,6 +305,15 @@ public class StpUserUtil {
|
||||
return stpLogic.isLogin();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断指定账号是否已经登录
|
||||
*
|
||||
* @return 已登录返回 true,未登录返回 false
|
||||
*/
|
||||
public static boolean isLogin(Object loginId) {
|
||||
return stpLogic.isLogin(loginId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检验当前会话是否已经登录,如未登录,则抛出异常
|
||||
*/
|
||||
@@ -802,6 +814,17 @@ public class StpUserUtil {
|
||||
return stpLogic.getTokenValueListByLoginId(loginId, device);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定账号 id 指定设备类型端的 tokenSign 集合
|
||||
*
|
||||
* @param loginId 账号id
|
||||
* @param device 设备类型,填 null 代表不限设备类型
|
||||
* @return 此 loginId 的所有登录 tokenSign
|
||||
*/
|
||||
public static List<TokenSign> getTokenSignListByLoginId(Object loginId, String device) {
|
||||
return stpLogic.getTokenSignListByLoginId(loginId, device);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回当前会话的登录设备类型
|
||||
*
|
||||
@@ -811,6 +834,26 @@ public class StpUserUtil {
|
||||
return stpLogic.getLoginDevice();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回指定 token 会话的登录设备类型
|
||||
*
|
||||
* @param tokenValue 指定token
|
||||
* @return 当前令牌的登录设备类型
|
||||
*/
|
||||
public static String getLoginDeviceByToken(String tokenValue) {
|
||||
return stpLogic.getLoginDeviceByToken(tokenValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前 token 的最后活跃时间(13位时间戳),如果不存在则返回 -2
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
public static long getTokenLastActiveTime() {
|
||||
return stpLogic.getTokenLastActiveTime();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ------------------- 会话管理 -------------------
|
||||
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title> Sa-Token 跨域测试 - Cookie 版,h5 页面 </title>
|
||||
</head>
|
||||
<body>
|
||||
<div style="text-align: center; padding-top: 200px;">
|
||||
<h2> Sa-Token 跨域测试 - Cookie 版,h5 页面 </h2>
|
||||
<p>当前是否登录:<b class="is-login"></b></p>
|
||||
<p>
|
||||
<a href="javascript: doLogin();">登录</a>
|
||||
<a href="javascript: doLogout();">注销</a>
|
||||
</p>
|
||||
</div>
|
||||
<script src="https://unpkg.zhimg.com/jquery@3.4.1/dist/jquery.min.js"></script>
|
||||
<script src="./method-util.js"></script>
|
||||
<script type="text/javascript">
|
||||
|
||||
// 查询当前会话是否登录
|
||||
function isLogin() {
|
||||
ajax('/acc/isLogin', {}, function (res) {
|
||||
$('.is-login').html(res.data + '');
|
||||
})
|
||||
}
|
||||
isLogin();
|
||||
|
||||
// 去登录
|
||||
function doLogin() {
|
||||
const param = {
|
||||
name: "zhang",
|
||||
pwd: "123456"
|
||||
}
|
||||
ajax('/acc/doLogin', param, function (res) {
|
||||
if(res.code === 200) {
|
||||
// 浏览器会自动在 cookie 中保存 token
|
||||
localStorage.satoken = res.token;
|
||||
$('.is-login').html('true');
|
||||
alert('登录成功');
|
||||
} else {
|
||||
alert(res.msg);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 去注销
|
||||
function doLogout() {
|
||||
ajax('/acc/logout', {}, function (res) {
|
||||
if(res.code === 200) {
|
||||
// 浏览器会自动清除 cookie 中的 token
|
||||
$('.is-login').html('false');
|
||||
alert('注销成功');
|
||||
} else {
|
||||
alert(res.msg);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,28 +0,0 @@
|
||||
|
||||
// 后端服务地址 (在 Cookie 版跨域模式中,此处应该是一个 https 地址)
|
||||
// var baseUrl = "http://localhost:8081";
|
||||
var baseUrl = "https://20e331r221.yicp.fun";
|
||||
|
||||
// 封装一下 Ajax 方法
|
||||
var ajax = function(path, data, successFn) {
|
||||
$.ajax({
|
||||
url: baseUrl + path,
|
||||
type: "post",
|
||||
data: data,
|
||||
dataType: 'json',
|
||||
// 指定是跨域模式,需要提交第三方 Cookie
|
||||
crossDomain: true,
|
||||
xhrFields:{
|
||||
withCredentials: true
|
||||
},
|
||||
headers: {
|
||||
"X-Requested-With": "XMLHttpRequest"
|
||||
},
|
||||
success: function(res){
|
||||
successFn(res);
|
||||
},
|
||||
error: function(xhr, type, errorThrown){
|
||||
return alert("异常:" + JSON.stringify(xhr));
|
||||
}
|
||||
});
|
||||
}
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
package com.pj;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
|
||||
/**
|
||||
* Sa-Token 跨域测试(Cookie 版)
|
||||
* @author click33
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class SaTokenCrossCookieApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SaTokenCrossCookieApplication.class, args);
|
||||
System.out.println("\n启动成功,Sa-Token 配置如下:" + SaManager.getConfig());
|
||||
System.out.println("\n后端地址使用 https://xxx.com 访问(必须为 https 连接),前端页面用 http://127.0.0.1 访问");
|
||||
}
|
||||
|
||||
}
|
||||
-79
@@ -1,79 +0,0 @@
|
||||
package com.pj.test;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.context.SaHolder;
|
||||
import cn.dev33.satoken.context.model.SaRequest;
|
||||
import cn.dev33.satoken.filter.SaServletFilter;
|
||||
import cn.dev33.satoken.interceptor.SaInterceptor;
|
||||
import cn.dev33.satoken.router.SaHttpMethod;
|
||||
import cn.dev33.satoken.router.SaRouter;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
|
||||
/**
|
||||
* [Sa-Token 权限认证] 配置类
|
||||
* @author click33
|
||||
*
|
||||
*/
|
||||
@Configuration
|
||||
public class SaTokenConfigure implements WebMvcConfigurer {
|
||||
|
||||
/**
|
||||
* 注册 [Sa-Token 全局过滤器]
|
||||
*/
|
||||
@Bean
|
||||
public SaServletFilter getSaServletFilter() {
|
||||
return new SaServletFilter()
|
||||
|
||||
// 指定 [拦截路由] 与 [放行路由]
|
||||
.addInclude("/**").addExclude("/favicon.ico")
|
||||
|
||||
// 认证函数: 每次请求执行
|
||||
.setAuth(obj -> {
|
||||
SaManager.getLog().debug("----- 请求path={} 提交token={}", SaHolder.getRequest().getRequestPath(), StpUtil.getTokenValue());
|
||||
// ...
|
||||
})
|
||||
|
||||
// 异常处理函数:每次认证函数发生异常时执行此函数
|
||||
.setError(e -> {
|
||||
return SaResult.error(e.getMessage());
|
||||
})
|
||||
|
||||
// 前置函数:在每次认证函数之前执行
|
||||
.setBeforeAuth(obj -> {
|
||||
|
||||
// 获得客户端domain
|
||||
SaRequest request = SaHolder.getRequest();
|
||||
String origin = request.getHeader("Origin");
|
||||
if (origin == null) {
|
||||
origin = request.getHeader("Referer");
|
||||
}
|
||||
|
||||
// ---------- 设置跨域响应头 ----------
|
||||
SaHolder.getResponse()
|
||||
// 允许第三方 Cookie
|
||||
.setHeader("Access-Control-Allow-Credentials", "true")
|
||||
// 允许指定域访问跨域资源
|
||||
.setHeader("Access-Control-Allow-Origin", origin)
|
||||
// 允许所有请求方式
|
||||
.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE")
|
||||
// 允许的header参数
|
||||
.setHeader("Access-Control-Allow-Headers", "x-requested-with,satoken")
|
||||
// 有效时间
|
||||
.setHeader("Access-Control-Max-Age", "3600")
|
||||
;
|
||||
|
||||
// 如果是预检请求,则立即返回到前端
|
||||
SaRouter.match(SaHttpMethod.OPTIONS)
|
||||
.free(r -> System.out.println("--------OPTIONS预检请求,不做处理"))
|
||||
.back();
|
||||
})
|
||||
;
|
||||
}
|
||||
|
||||
}
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
# 端口
|
||||
server:
|
||||
port: 8081
|
||||
|
||||
sa-token:
|
||||
is-log: true
|
||||
cookie:
|
||||
# 指明当前为 https 安全连接
|
||||
secure: true
|
||||
# 指明第三方 Cookie 限制级别为:不限制
|
||||
sameSite: None
|
||||
@@ -1,24 +0,0 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
@@ -1,20 +0,0 @@
|
||||
# Sa-Token 跨域测试 - Cookie 版,vue3 页面
|
||||
|
||||
在线文档:[https://sa-token.cc/](https://sa-token.cc/)
|
||||
|
||||
|
||||
## 运行
|
||||
先安装依赖
|
||||
``` bat
|
||||
npm install --registry=https://registry.npm.taobao.org
|
||||
```
|
||||
|
||||
运行
|
||||
``` bat
|
||||
npm run dev
|
||||
```
|
||||
|
||||
打包
|
||||
``` bat
|
||||
npm run build
|
||||
```
|
||||
@@ -1,13 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title> Sa-Token 跨域测试 - Cookie 版,vue3 页面 </title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
-1362
File diff suppressed because it is too large
Load Diff
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"name": "sa-token-demo-cross-cookie-vue3",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.1.3",
|
||||
"vue": "^3.2.41",
|
||||
"vue-router": "^4.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^3.2.0",
|
||||
"vite": "^3.2.7"
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -1,11 +0,0 @@
|
||||
<template>
|
||||
<router-view />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
||||
|
Before Width: | Height: | Size: 496 B |
@@ -1,14 +0,0 @@
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
|
||||
// createApp
|
||||
const app = createApp(App);
|
||||
|
||||
// 安装 vue-router
|
||||
import router from './router';
|
||||
app.use(router);
|
||||
|
||||
|
||||
|
||||
// 绑定dom
|
||||
app.mount('#app');
|
||||
@@ -1,25 +0,0 @@
|
||||
import { createRouter, createWebHashHistory } from 'vue-router';
|
||||
|
||||
/**
|
||||
* 创建 vue-router 实例
|
||||
*/
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
routes: [
|
||||
// 首页
|
||||
{
|
||||
name: 'index',
|
||||
path: "/index",
|
||||
component: () => import('../views/index.vue'),
|
||||
},
|
||||
|
||||
// 访问 / 时自动重定向到 /index
|
||||
{
|
||||
path: "/",
|
||||
redirect: '/index'
|
||||
}
|
||||
],
|
||||
});
|
||||
|
||||
// 导出
|
||||
export default router;
|
||||
@@ -1,63 +0,0 @@
|
||||
<!-- 项目首页 -->
|
||||
<template>
|
||||
<div style="text-align: center; padding-top: 200px;">
|
||||
<h2> Sa-Token 跨域测试 - Cookie 版,Vue3页面 </h2>
|
||||
<p>当前是否登录:<b>{{ state.isLogin }}</b></p>
|
||||
<p>
|
||||
<a href="javascript:;" @click="doLogin">登录</a>
|
||||
<a href="javascript:;" @click="doLogout">注销</a>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import {baseUrl, ajax} from './method-util.js'
|
||||
|
||||
// 是否登录
|
||||
const state = reactive({
|
||||
isLogin: false
|
||||
})
|
||||
|
||||
onMounted(function(){
|
||||
isLogin();
|
||||
})
|
||||
|
||||
// 查询当前会话是否登录
|
||||
const isLogin = function() {
|
||||
ajax('/acc/isLogin', {}, function (res) {
|
||||
state.isLogin = res.data;
|
||||
})
|
||||
}
|
||||
|
||||
// 去登录
|
||||
const doLogin = function() {
|
||||
const param = {
|
||||
name: "zhang",
|
||||
pwd: "123456"
|
||||
}
|
||||
ajax('/acc/doLogin', param, function (res) {
|
||||
if(res.code === 200) {
|
||||
// 浏览器会自动在 cookie 中保存 token
|
||||
state.isLogin = true;
|
||||
alert('登录成功');
|
||||
} else {
|
||||
alert(res.msg);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 去注销
|
||||
const doLogout = function() {
|
||||
ajax('/acc/logout', {}, function (res) {
|
||||
if(res.code === 200) {
|
||||
// 浏览器会自动清除 cookie 中的 token
|
||||
state.isLogin = false;
|
||||
alert('注销成功');
|
||||
} else {
|
||||
alert(res.msg);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
import axios from 'axios'
|
||||
|
||||
// 后端服务地址 (在 Cookie 版跨域模式中,此处应该是一个 https 地址)
|
||||
// export const baseUrl = "http://localhost:8081";
|
||||
export const baseUrl = "https://20e331r221.yicp.fun";
|
||||
|
||||
|
||||
// 封装一下 Ajax 方法
|
||||
export const ajax = function(path, data, successFn) {
|
||||
axios({
|
||||
url: baseUrl + path,
|
||||
method: 'post',
|
||||
data: data,
|
||||
// 重点:开启第三方 Cookie
|
||||
withCredentials: true,
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded"
|
||||
}
|
||||
}).
|
||||
then(function (response) { // 成功时执行
|
||||
const res = response.data;
|
||||
successFn(res);
|
||||
}).
|
||||
catch(function (error) {
|
||||
return alert("异常:" + JSON.stringify(error));
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [vue()]
|
||||
})
|
||||
@@ -1,63 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title> Sa-Token 跨域测试 - Header 参数版,h5 页面 </title>
|
||||
</head>
|
||||
<body>
|
||||
<div style="text-align: center; padding-top: 200px;">
|
||||
<h2> Sa-Token 跨域测试 - Header 参数版,h5 页面 </h2>
|
||||
<p>当前是否登录:<b class="is-login"></b></p>
|
||||
<p>
|
||||
<a href="javascript: doLogin();">登录</a>
|
||||
<a href="javascript: doLogout();">注销</a>
|
||||
</p>
|
||||
</div>
|
||||
<script src="https://unpkg.zhimg.com/jquery@3.4.1/dist/jquery.min.js"></script>
|
||||
<script src="./method-util.js"></script>
|
||||
<script type="text/javascript">
|
||||
|
||||
// 查询当前会话是否登录
|
||||
function isLogin() {
|
||||
ajax('/acc/isLogin', {}, function (res) {
|
||||
$('.is-login').html(res.data + '');
|
||||
})
|
||||
}
|
||||
isLogin();
|
||||
|
||||
// 去登录
|
||||
function doLogin() {
|
||||
const param = {
|
||||
name: "zhang",
|
||||
pwd: "123456"
|
||||
}
|
||||
ajax('/acc/doLogin', param, function (res) {
|
||||
if(res.code === 200) {
|
||||
// 保存 token
|
||||
localStorage.satoken = res.token;
|
||||
$('.is-login').html('true');
|
||||
alert('登录成功,token是:' + res.token);
|
||||
} else {
|
||||
alert(res.msg);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 去注销
|
||||
function doLogout() {
|
||||
ajax('/acc/logout', {}, function (res) {
|
||||
if(res.code === 200) {
|
||||
// 清除 token
|
||||
localStorage.removeItem('satoken');
|
||||
$('.is-login').html('false');
|
||||
alert('注销成功');
|
||||
} else {
|
||||
alert(res.msg);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,23 +0,0 @@
|
||||
|
||||
// 后端服务地址
|
||||
var baseUrl = "http://localhost:8081";
|
||||
|
||||
// 封装一下 Ajax 方法
|
||||
var ajax = function(path, data, successFn) {
|
||||
$.ajax({
|
||||
url: baseUrl + path,
|
||||
type: "post",
|
||||
data: data,
|
||||
dataType: 'json',
|
||||
headers: {
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
"satoken": localStorage.getItem("satoken")
|
||||
},
|
||||
success: function(res){
|
||||
successFn(res);
|
||||
},
|
||||
error: function(xhr, type, errorThrown){
|
||||
return alert("异常:" + JSON.stringify(xhr));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
target/
|
||||
|
||||
node_modules/
|
||||
bin/
|
||||
.settings/
|
||||
unpackage/
|
||||
.classpath
|
||||
.project
|
||||
|
||||
.idea/
|
||||
|
||||
.factorypath
|
||||
-41
@@ -1,41 +0,0 @@
|
||||
package com.pj.test;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 登录测试
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/acc/")
|
||||
public class LoginController {
|
||||
|
||||
// 登录 ---- http://localhost:8081/acc/doLogin?name=zhang&pwd=123456
|
||||
@RequestMapping("doLogin")
|
||||
public SaResult doLogin(String name, String pwd) {
|
||||
if("zhang".equals(name) && "123456".equals(pwd)) {
|
||||
StpUtil.login(10001);
|
||||
// 要点:通过请求响应体返回 token 信息
|
||||
return SaResult.ok("登录成功").set("token", StpUtil.getTokenValue());
|
||||
}
|
||||
return SaResult.error("登录失败");
|
||||
}
|
||||
|
||||
// 注销 ---- http://localhost:8081/acc/logout
|
||||
@RequestMapping("logout")
|
||||
public SaResult logout() {
|
||||
StpUtil.logout();
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
// 查询登录状态 ---- http://localhost:8081/acc/isLogin
|
||||
@RequestMapping("isLogin")
|
||||
public SaResult isLogin() {
|
||||
boolean isLogin = StpUtil.isLogin();
|
||||
System.out.println("当前会话是否登录:" + isLogin);
|
||||
return SaResult.data(isLogin);
|
||||
}
|
||||
|
||||
}
|
||||
-69
@@ -1,69 +0,0 @@
|
||||
package com.pj.test;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.context.SaHolder;
|
||||
import cn.dev33.satoken.filter.SaServletFilter;
|
||||
import cn.dev33.satoken.interceptor.SaInterceptor;
|
||||
import cn.dev33.satoken.router.SaHttpMethod;
|
||||
import cn.dev33.satoken.router.SaRouter;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
|
||||
/**
|
||||
* [Sa-Token 权限认证] 配置类
|
||||
* @author click33
|
||||
*
|
||||
*/
|
||||
@Configuration
|
||||
public class SaTokenConfigure implements WebMvcConfigurer {
|
||||
|
||||
/**
|
||||
* 注册 [Sa-Token 全局过滤器]
|
||||
*/
|
||||
@Bean
|
||||
public SaServletFilter getSaServletFilter() {
|
||||
return new SaServletFilter()
|
||||
|
||||
// 指定 [拦截路由] 与 [放行路由]
|
||||
.addInclude("/**").addExclude("/favicon.ico")
|
||||
|
||||
// 认证函数: 每次请求执行
|
||||
.setAuth(obj -> {
|
||||
SaManager.getLog().debug("----- 请求path={} 提交token={}", SaHolder.getRequest().getRequestPath(), StpUtil.getTokenValue());
|
||||
// ...
|
||||
})
|
||||
|
||||
// 异常处理函数:每次认证函数发生异常时执行此函数
|
||||
.setError(e -> {
|
||||
return SaResult.error(e.getMessage());
|
||||
})
|
||||
|
||||
// 前置函数:在每次认证函数之前执行
|
||||
.setBeforeAuth(obj -> {
|
||||
SaHolder.getResponse()
|
||||
|
||||
// ---------- 设置跨域响应头 ----------
|
||||
// 允许指定域访问跨域资源
|
||||
.setHeader("Access-Control-Allow-Origin", "*")
|
||||
// 允许所有请求方式
|
||||
.setHeader("Access-Control-Allow-Methods", "*")
|
||||
// 允许的header参数
|
||||
.setHeader("Access-Control-Allow-Headers", "*")
|
||||
// 有效时间
|
||||
.setHeader("Access-Control-Max-Age", "3600")
|
||||
;
|
||||
|
||||
// 如果是预检请求,则立即返回到前端
|
||||
SaRouter.match(SaHttpMethod.OPTIONS)
|
||||
.free(r -> System.out.println("--------OPTIONS预检请求,不做处理"))
|
||||
.back();
|
||||
})
|
||||
;
|
||||
}
|
||||
|
||||
}
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
# 端口
|
||||
server:
|
||||
port: 8081
|
||||
|
||||
sa-token:
|
||||
is-log: true
|
||||
@@ -1,24 +0,0 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
@@ -1,20 +0,0 @@
|
||||
# Sa-Token 跨域测试 - Header 参数版,vue3 页面
|
||||
|
||||
在线文档:[https://sa-token.cc/](https://sa-token.cc/)
|
||||
|
||||
|
||||
## 运行
|
||||
先安装依赖
|
||||
``` bat
|
||||
npm install --registry=https://registry.npm.taobao.org
|
||||
```
|
||||
|
||||
运行
|
||||
``` bat
|
||||
npm run dev
|
||||
```
|
||||
|
||||
打包
|
||||
``` bat
|
||||
npm run build
|
||||
```
|
||||
@@ -1,13 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title> Sa-Token 跨域测试 - Header 参数版,vue3 页面 </title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
-1362
File diff suppressed because it is too large
Load Diff
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"name": "sa-token-demo-cross-header-vue3",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.1.3",
|
||||
"vue": "^3.2.41",
|
||||
"vue-router": "^4.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^3.2.0",
|
||||
"vite": "^3.2.7"
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -1,11 +0,0 @@
|
||||
<template>
|
||||
<router-view />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
||||
|
Before Width: | Height: | Size: 496 B |
@@ -1,14 +0,0 @@
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
|
||||
// createApp
|
||||
const app = createApp(App);
|
||||
|
||||
// 安装 vue-router
|
||||
import router from './router';
|
||||
app.use(router);
|
||||
|
||||
|
||||
|
||||
// 绑定dom
|
||||
app.mount('#app');
|
||||
@@ -1,25 +0,0 @@
|
||||
import { createRouter, createWebHashHistory } from 'vue-router';
|
||||
|
||||
/**
|
||||
* 创建 vue-router 实例
|
||||
*/
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
routes: [
|
||||
// 首页
|
||||
{
|
||||
name: 'index',
|
||||
path: "/index",
|
||||
component: () => import('../views/index.vue'),
|
||||
},
|
||||
|
||||
// 访问 / 时自动重定向到 /index
|
||||
{
|
||||
path: "/",
|
||||
redirect: '/index'
|
||||
}
|
||||
],
|
||||
});
|
||||
|
||||
// 导出
|
||||
export default router;
|
||||
@@ -1,65 +0,0 @@
|
||||
<!-- 项目首页 -->
|
||||
<template>
|
||||
<div style="text-align: center; padding-top: 200px;">
|
||||
<h2> Sa-Token 跨域测试-header参数版,vue3 页面 </h2>
|
||||
<p>当前是否登录:<b>{{ state.isLogin }}</b></p>
|
||||
<p>
|
||||
<a href="javascript:;" @click="doLogin">登录</a>
|
||||
<a href="javascript:;" @click="doLogout">注销</a>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, reactive } from 'vue'
|
||||
import { ajax } from './method-util.js'
|
||||
|
||||
// 是否登录
|
||||
const state = reactive({
|
||||
isLogin: false
|
||||
})
|
||||
|
||||
onMounted(function(){
|
||||
isLogin();
|
||||
})
|
||||
|
||||
// 查询当前会话是否登录
|
||||
const isLogin = function() {
|
||||
ajax('/acc/isLogin', {}, function (res) {
|
||||
state.isLogin = res.data;
|
||||
})
|
||||
}
|
||||
|
||||
// 去登录
|
||||
const doLogin = function() {
|
||||
const param = {
|
||||
name: "zhang",
|
||||
pwd: "123456"
|
||||
}
|
||||
ajax('/acc/doLogin', param, function (res) {
|
||||
if(res.code === 200) {
|
||||
// 保存 token
|
||||
localStorage.satoken = res.token;
|
||||
state.isLogin = true;
|
||||
alert('登录成功,token是:' + res.token);
|
||||
} else {
|
||||
alert(res.msg);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 去注销
|
||||
const doLogout = function() {
|
||||
ajax('/acc/logout', {}, function (res) {
|
||||
if(res.code === 200) {
|
||||
// 清除 token
|
||||
localStorage.removeItem('satoken');
|
||||
state.isLogin = false;
|
||||
alert('注销成功');
|
||||
} else {
|
||||
alert(res.msg);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
import axios from 'axios'
|
||||
|
||||
// 后端服务地址
|
||||
export const baseUrl = "http://localhost:8081";
|
||||
|
||||
// 封装一下 Ajax 方法
|
||||
export const ajax = function(path, data, successFn) {
|
||||
axios({
|
||||
url: baseUrl + path,
|
||||
method: 'post',
|
||||
data: data,
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"satoken": localStorage.getItem("satoken")
|
||||
}
|
||||
}).
|
||||
then(function (response) { // 成功时执行
|
||||
const res = response.data;
|
||||
successFn(res);
|
||||
}).
|
||||
catch(function (error) {
|
||||
return alert("异常:" + JSON.stringify(error));
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [vue()]
|
||||
})
|
||||
@@ -17,7 +17,7 @@
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
|
||||
<sa-token.version>1.37.0</sa-token.version>
|
||||
<sa-token.version>1.38.0</sa-token.version>
|
||||
<dubbo.version>2.7.21</dubbo.version>
|
||||
<nacos.version>1.4.2</nacos.version>
|
||||
</properties>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
|
||||
<sa-token.version>1.37.0</sa-token.version>
|
||||
<sa-token.version>1.38.0</sa-token.version>
|
||||
<dubbo.version>2.7.21</dubbo.version>
|
||||
<nacos.version>1.4.2</nacos.version>
|
||||
</properties>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
|
||||
<sa-token.version>1.37.0</sa-token.version>
|
||||
<sa-token.version>1.38.0</sa-token.version>
|
||||
<dubbo.version>3.2.2</dubbo.version>
|
||||
<nacos.version>2.2.2</nacos.version>
|
||||
</properties>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
|
||||
<sa-token.version>1.37.0</sa-token.version>
|
||||
<sa-token.version>1.38.0</sa-token.version>
|
||||
<dubbo.version>3.2.2</dubbo.version>
|
||||
<nacos.version>2.2.2</nacos.version>
|
||||
</properties>
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<lombok.version>1.18.10</lombok.version>
|
||||
<sa-token.version>1.37.0</sa-token.version>
|
||||
<sa-token.version>1.38.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
+23
-6
@@ -3,21 +3,21 @@
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-demo-cross-cookie-server</artifactId>
|
||||
<artifactId>sa-token-demo-hutool-timed-cache</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
|
||||
<!-- SpringBoot -->
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.5.15</version>
|
||||
<version>2.5.14</version>
|
||||
<!-- <version>1.5.9.RELEASE</version> -->
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.37.0</sa-token.version>
|
||||
<java.run.main.class>com.pj.SaTokenCrossCookieApplication</java.run.main.class>
|
||||
<sa-token.version>1.38.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
@@ -27,6 +27,10 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Sa-Token 权限认证, 在线文档:https://sa-token.cc/ -->
|
||||
<dependency>
|
||||
@@ -35,8 +39,21 @@
|
||||
<version>${sa-token.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Sa-Token 整合 Hutool-TimedCache -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-hutool-timed-cache</artifactId>
|
||||
<version>${sa-token.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- @ConfigurationProperties -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
||||
|
||||
|
||||
</project>
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package com.pj;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* Sa-Token 整合 Hutool-TimedCache 示例
|
||||
* @author click33
|
||||
*
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class SaTokenDemoApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SaTokenDemoApplication.class, args);
|
||||
System.out.println("\n启动成功:Sa-Token配置如下:" + SaManager.getConfig());
|
||||
System.out.println(SaManager.getSaTokenDao());
|
||||
}
|
||||
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
package com.pj.current;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
import com.pj.util.AjaxJson;
|
||||
|
||||
import cn.dev33.satoken.exception.DisableServiceException;
|
||||
import cn.dev33.satoken.exception.NotLoginException;
|
||||
import cn.dev33.satoken.exception.NotPermissionException;
|
||||
import cn.dev33.satoken.exception.NotRoleException;
|
||||
|
||||
/**
|
||||
* 全局异常处理
|
||||
*/
|
||||
@RestControllerAdvice
|
||||
public class GlobalException {
|
||||
|
||||
// 全局异常拦截(拦截项目中的所有异常)
|
||||
@ExceptionHandler
|
||||
public AjaxJson handlerException(Exception e, HttpServletRequest request, HttpServletResponse response)
|
||||
throws Exception {
|
||||
|
||||
// 打印堆栈,以供调试
|
||||
System.out.println("全局异常---------------");
|
||||
e.printStackTrace();
|
||||
|
||||
// 不同异常返回不同状态码
|
||||
AjaxJson aj = null;
|
||||
if (e instanceof NotLoginException) { // 如果是未登录异常
|
||||
NotLoginException ee = (NotLoginException) e;
|
||||
aj = AjaxJson.getNotLogin().setMsg(ee.getMessage());
|
||||
}
|
||||
else if(e instanceof NotRoleException) { // 如果是角色异常
|
||||
NotRoleException ee = (NotRoleException) e;
|
||||
aj = AjaxJson.getNotJur("无此角色:" + ee.getRole());
|
||||
}
|
||||
else if(e instanceof NotPermissionException) { // 如果是权限异常
|
||||
NotPermissionException ee = (NotPermissionException) e;
|
||||
aj = AjaxJson.getNotJur("无此权限:" + ee.getPermission());
|
||||
}
|
||||
else if(e instanceof DisableServiceException) { // 如果是被封禁异常
|
||||
DisableServiceException ee = (DisableServiceException) e;
|
||||
aj = AjaxJson.getNotJur("当前账号 " + ee.getService() + " 服务已被封禁 (level=" + ee.getLevel() + "):" + ee.getDisableTime() + "秒后解封");
|
||||
}
|
||||
else { // 普通异常, 输出:500 + 异常信息
|
||||
aj = AjaxJson.getError(e.getMessage());
|
||||
}
|
||||
|
||||
// 返回给前端
|
||||
return aj;
|
||||
}
|
||||
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package com.pj.current;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.boot.web.servlet.error.ErrorController;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
|
||||
/**
|
||||
* 处理 404
|
||||
* @author click33
|
||||
*/
|
||||
@RestController
|
||||
public class NotFoundHandle implements ErrorController {
|
||||
|
||||
@RequestMapping("/error")
|
||||
public Object error(HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||
response.setStatus(200);
|
||||
return SaResult.get(404, "not found", null);
|
||||
}
|
||||
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
package com.pj.satoken;
|
||||
|
||||
import cn.dev33.satoken.context.SaHolder;
|
||||
import cn.dev33.satoken.filter.SaServletFilter;
|
||||
import cn.dev33.satoken.interceptor.SaInterceptor;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
|
||||
/**
|
||||
* [Sa-Token 权限认证] 配置类
|
||||
* @author click33
|
||||
*
|
||||
*/
|
||||
@Configuration
|
||||
public class SaTokenConfigure implements WebMvcConfigurer {
|
||||
|
||||
/**
|
||||
* 注册 Sa-Token 拦截器打开注解鉴权功能
|
||||
*/
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
// 注册 Sa-Token 拦截器打开注解鉴权功能
|
||||
registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册 [Sa-Token 全局过滤器]
|
||||
*/
|
||||
@Bean
|
||||
public SaServletFilter getSaServletFilter() {
|
||||
return new SaServletFilter()
|
||||
|
||||
// 指定 [拦截路由] 与 [放行路由]
|
||||
.addInclude("/**")// .addExclude("/favicon.ico")
|
||||
|
||||
// 认证函数: 每次请求执行
|
||||
.setAuth(obj -> {
|
||||
// SaManager.getLog().debug("----- 请求path={} 提交token={}", SaHolder.getRequest().getRequestPath(), StpUtil.getTokenValue());
|
||||
|
||||
})
|
||||
|
||||
// 异常处理函数:每次认证函数发生异常时执行此函数
|
||||
.setError(e -> {
|
||||
System.out.println("---------- sa全局异常 ");
|
||||
return SaResult.error(e.getMessage());
|
||||
})
|
||||
|
||||
// 前置函数:在每次认证函数之前执行 (BeforeAuth不受 includeList 与 excludeList 的限制,所有请求都会进入)
|
||||
.setBeforeAuth(r -> {
|
||||
// ---------- 设置一些安全响应头 ----------
|
||||
SaHolder.getResponse()
|
||||
// 服务器名称
|
||||
.setServer("sa-server")
|
||||
// 是否可以在iframe显示视图: DENY=不可以 | SAMEORIGIN=同域下可以 | ALLOW-FROM uri=指定域名下可以
|
||||
.setHeader("X-Frame-Options", "SAMEORIGIN")
|
||||
// 是否启用浏览器默认XSS防护: 0=禁用 | 1=启用 | 1; mode=block 启用, 并在检查到XSS攻击时,停止渲染页面
|
||||
.setHeader("X-XSS-Protection", "1; mode=block")
|
||||
// 禁用浏览器内容嗅探
|
||||
.setHeader("X-Content-Type-Options", "nosniff")
|
||||
;
|
||||
})
|
||||
;
|
||||
}
|
||||
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package com.pj.satoken;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import cn.dev33.satoken.stp.StpInterface;
|
||||
|
||||
/**
|
||||
* 自定义权限验证接口扩展
|
||||
*/
|
||||
@Component // 打开此注解,保证此类被springboot扫描,即可完成sa-token的自定义权限验证扩展
|
||||
public class StpInterfaceImpl implements StpInterface {
|
||||
|
||||
/**
|
||||
* 返回一个账号所拥有的权限码集合
|
||||
*/
|
||||
@Override
|
||||
public List<String> getPermissionList(Object loginId, String loginType) {
|
||||
// 本list仅做模拟,实际项目中要根据具体业务逻辑来查询权限
|
||||
List<String> list = new ArrayList<String>();
|
||||
list.add("101");
|
||||
list.add("user-add");
|
||||
list.add("user-delete");
|
||||
list.add("user-update");
|
||||
list.add("user-get");
|
||||
list.add("article-get");
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回一个账号所拥有的角色标识集合
|
||||
*/
|
||||
@Override
|
||||
public List<String> getRoleList(Object loginId, String loginType) {
|
||||
// 本list仅做模拟,实际项目中要根据具体业务逻辑来查询角色
|
||||
List<String> list = new ArrayList<String>();
|
||||
list.add("admin");
|
||||
list.add("super-admin");
|
||||
return list;
|
||||
}
|
||||
|
||||
}
|
||||
+22
-15
@@ -1,41 +1,48 @@
|
||||
package com.pj.test;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
|
||||
/**
|
||||
* 登录测试
|
||||
* 登录测试
|
||||
* @author click33
|
||||
*
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/acc/")
|
||||
public class LoginController {
|
||||
|
||||
// 登录 ---- http://localhost:8081/acc/doLogin?name=zhang&pwd=123456
|
||||
// 测试登录 ---- http://localhost:8081/acc/doLogin?name=zhang&pwd=123456
|
||||
@RequestMapping("doLogin")
|
||||
public SaResult doLogin(String name, String pwd) {
|
||||
// 此处仅作模拟示例,真实项目需要从数据库中查询数据进行比对
|
||||
if("zhang".equals(name) && "123456".equals(pwd)) {
|
||||
StpUtil.login(10001);
|
||||
// 要点:通过请求响应体返回 token 信息
|
||||
return SaResult.ok("登录成功");
|
||||
}
|
||||
return SaResult.error("登录失败");
|
||||
}
|
||||
|
||||
// 注销 ---- http://localhost:8081/acc/logout
|
||||
// 查询登录状态 ---- http://localhost:8081/acc/isLogin
|
||||
@RequestMapping("isLogin")
|
||||
public SaResult isLogin() {
|
||||
return SaResult.ok("是否登录:" + StpUtil.isLogin());
|
||||
}
|
||||
|
||||
// 查询 Token 信息 ---- http://localhost:8081/acc/tokenInfo
|
||||
@RequestMapping("tokenInfo")
|
||||
public SaResult tokenInfo() {
|
||||
return SaResult.data(StpUtil.getTokenInfo());
|
||||
}
|
||||
|
||||
// 测试注销 ---- http://localhost:8081/acc/logout
|
||||
@RequestMapping("logout")
|
||||
public SaResult logout() {
|
||||
StpUtil.logout();
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
// 查询登录状态 ---- http://localhost:8081/acc/isLogin
|
||||
@RequestMapping("isLogin")
|
||||
public SaResult isLogin() {
|
||||
boolean isLogin = StpUtil.isLogin();
|
||||
System.out.println("当前会话是否登录:" + isLogin);
|
||||
return SaResult.data(isLogin);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
package com.pj.test;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
import com.pj.util.Ttime;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 压力测试
|
||||
* @author click33
|
||||
*
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/s-test/")
|
||||
public class StressTestController {
|
||||
|
||||
// 测试 浏览器访问: http://localhost:8081/s-test/login
|
||||
// 测试前,请先将 is-read-cookie 配置为 false
|
||||
@RequestMapping("login")
|
||||
public SaResult login() {
|
||||
// StpUtil.getTokenSession().logout();
|
||||
// StpUtil.logoutByLoginId(10001);
|
||||
|
||||
int count = 10; // 循环多少轮
|
||||
int loginCount = 10000; // 每轮循环多少次
|
||||
|
||||
// 循环10次 取平均时间
|
||||
List<Double> list = new ArrayList<>();
|
||||
for (int i = 1; i <= count; i++) {
|
||||
System.out.println("\n---------------------第" + i + "轮---------------------");
|
||||
Ttime t = new Ttime().start();
|
||||
// 每次登录的次数
|
||||
for (int j = 1; j <= loginCount; j++) {
|
||||
StpUtil.login("1000" + j, "PC-" + j);
|
||||
if(j % 1000 == 0) {
|
||||
System.out.println("已登录:" + j);
|
||||
}
|
||||
}
|
||||
t.end();
|
||||
list.add((t.returnMs() + 0.0) / 1000);
|
||||
System.out.println("第" + i + "轮" + "用时:" + t.toString());
|
||||
}
|
||||
// System.out.println(((SaTokenDaoDefaultImpl)SaTokenManager.getSaTokenDao()).dataMap.size());
|
||||
|
||||
System.out.println("\n---------------------测试结果---------------------");
|
||||
System.out.println(list.size() + "次测试: " + list);
|
||||
double ss = 0;
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
ss += list.get(i);
|
||||
}
|
||||
System.out.println("平均用时: " + ss / list.size());
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package com.pj.test;
|
||||
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
|
||||
/**
|
||||
* 测试专用Controller
|
||||
* @author click33
|
||||
*
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/test/")
|
||||
public class TestController {
|
||||
|
||||
// 测试 浏览器访问: http://localhost:8081/test/test
|
||||
@RequestMapping("test")
|
||||
public SaResult test() {
|
||||
System.out.println("------------进来了");
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
// 测试 浏览器访问: http://localhost:8081/test/test2
|
||||
@RequestMapping("test2")
|
||||
public SaResult test2() {
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
}
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
package com.pj.util;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* ajax请求返回Json格式数据的封装
|
||||
*/
|
||||
public class AjaxJson implements Serializable{
|
||||
|
||||
private static final long serialVersionUID = 1L; // 序列化版本号
|
||||
|
||||
public static final int CODE_SUCCESS = 200; // 成功状态码
|
||||
public static final int CODE_ERROR = 500; // 错误状态码
|
||||
public static final int CODE_WARNING = 501; // 警告状态码
|
||||
public static final int CODE_NOT_JUR = 403; // 无权限状态码
|
||||
public static final int CODE_NOT_LOGIN = 401; // 未登录状态码
|
||||
public static final int CODE_INVALID_REQUEST = 400; // 无效请求状态码
|
||||
|
||||
public int code; // 状态码
|
||||
public String msg; // 描述信息
|
||||
public Object data; // 携带对象
|
||||
public Long dataCount; // 数据总数,用于分页
|
||||
|
||||
/**
|
||||
* 返回code
|
||||
* @return
|
||||
*/
|
||||
public int getCode() {
|
||||
return this.code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 给msg赋值,连缀风格
|
||||
*/
|
||||
public AjaxJson setMsg(String msg) {
|
||||
this.msg = msg;
|
||||
return this;
|
||||
}
|
||||
public String getMsg() {
|
||||
return this.msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* 给data赋值,连缀风格
|
||||
*/
|
||||
public AjaxJson setData(Object data) {
|
||||
this.data = data;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将data还原为指定类型并返回
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getData(Class<T> cs) {
|
||||
return (T) data;
|
||||
}
|
||||
|
||||
// ============================ 构建 ==================================
|
||||
|
||||
public AjaxJson(int code, String msg, Object data, Long dataCount) {
|
||||
this.code = code;
|
||||
this.msg = msg;
|
||||
this.data = data;
|
||||
this.dataCount = dataCount;
|
||||
}
|
||||
|
||||
// 返回成功
|
||||
public static AjaxJson getSuccess() {
|
||||
return new AjaxJson(CODE_SUCCESS, "ok", null, null);
|
||||
}
|
||||
public static AjaxJson getSuccess(String msg) {
|
||||
return new AjaxJson(CODE_SUCCESS, msg, null, null);
|
||||
}
|
||||
public static AjaxJson getSuccess(String msg, Object data) {
|
||||
return new AjaxJson(CODE_SUCCESS, msg, data, null);
|
||||
}
|
||||
public static AjaxJson getSuccessData(Object data) {
|
||||
return new AjaxJson(CODE_SUCCESS, "ok", data, null);
|
||||
}
|
||||
public static AjaxJson getSuccessArray(Object... data) {
|
||||
return new AjaxJson(CODE_SUCCESS, "ok", data, null);
|
||||
}
|
||||
|
||||
// 返回失败
|
||||
public static AjaxJson getError() {
|
||||
return new AjaxJson(CODE_ERROR, "error", null, null);
|
||||
}
|
||||
public static AjaxJson getError(String msg) {
|
||||
return new AjaxJson(CODE_ERROR, msg, null, null);
|
||||
}
|
||||
|
||||
// 返回警告
|
||||
public static AjaxJson getWarning() {
|
||||
return new AjaxJson(CODE_ERROR, "warning", null, null);
|
||||
}
|
||||
public static AjaxJson getWarning(String msg) {
|
||||
return new AjaxJson(CODE_WARNING, msg, null, null);
|
||||
}
|
||||
|
||||
// 返回未登录
|
||||
public static AjaxJson getNotLogin() {
|
||||
return new AjaxJson(CODE_NOT_LOGIN, "未登录,请登录后再次访问", null, null);
|
||||
}
|
||||
|
||||
// 返回没有权限的
|
||||
public static AjaxJson getNotJur(String msg) {
|
||||
return new AjaxJson(CODE_NOT_JUR, msg, null, null);
|
||||
}
|
||||
|
||||
// 返回一个自定义状态码的
|
||||
public static AjaxJson get(int code, String msg){
|
||||
return new AjaxJson(code, msg, null, null);
|
||||
}
|
||||
|
||||
// 返回分页和数据的
|
||||
public static AjaxJson getPageData(Long dataCount, Object data){
|
||||
return new AjaxJson(CODE_SUCCESS, "ok", data, dataCount);
|
||||
}
|
||||
|
||||
// 返回,根据受影响行数的(大于0=ok,小于0=error)
|
||||
public static AjaxJson getByLine(int line){
|
||||
if(line > 0){
|
||||
return getSuccess("ok", line);
|
||||
}
|
||||
return getError("error").setData(line);
|
||||
}
|
||||
|
||||
// 返回,根据布尔值来确定最终结果的 (true=ok,false=error)
|
||||
public static AjaxJson getByBoolean(boolean b){
|
||||
return b ? getSuccess("ok") : getError("error");
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public String toString() {
|
||||
String data_string = null;
|
||||
if(data == null){
|
||||
|
||||
} else if(data instanceof List){
|
||||
data_string = "List(length=" + ((List)data).size() + ")";
|
||||
} else {
|
||||
data_string = data.toString();
|
||||
}
|
||||
return "{"
|
||||
+ "\"code\": " + this.getCode()
|
||||
+ ", \"msg\": \"" + this.getMsg() + "\""
|
||||
+ ", \"data\": " + data_string
|
||||
+ ", \"dataCount\": " + dataCount
|
||||
+ "}";
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.pj.util;
|
||||
|
||||
|
||||
/**
|
||||
* 用于测试用时
|
||||
* @author click33
|
||||
*
|
||||
*/
|
||||
public class Ttime {
|
||||
|
||||
private long start=0; //开始时间
|
||||
private long end=0; //结束时间
|
||||
|
||||
public static Ttime t = new Ttime(); //static快捷使用
|
||||
|
||||
/**
|
||||
* 开始计时
|
||||
* @return
|
||||
*/
|
||||
public Ttime start() {
|
||||
start=System.currentTimeMillis();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 结束计时
|
||||
*/
|
||||
public Ttime end() {
|
||||
end=System.currentTimeMillis();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 返回所用毫秒数
|
||||
*/
|
||||
public long returnMs() {
|
||||
return end-start;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化输出结果
|
||||
*/
|
||||
public void outTime() {
|
||||
System.out.println(this.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束并格式化输出结果
|
||||
*/
|
||||
public void endOutTime() {
|
||||
this.end().outTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return (returnMs() + 0.0) / 1000 + "s"; // 格式化为:0.01s
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
# 端口
|
||||
server:
|
||||
port: 8081
|
||||
|
||||
# sa-token 配置
|
||||
sa-token:
|
||||
# token 名称 (同时也是 cookie 名称)
|
||||
token-name: satoken
|
||||
# token 有效期(单位:秒) 默认30天,-1 代表永久有效
|
||||
timeout: 2592000
|
||||
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
|
||||
active-timeout: -1
|
||||
# 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
|
||||
is-concurrent: true
|
||||
# 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
|
||||
is-share: true
|
||||
# token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
|
||||
token-style: uuid
|
||||
# 是否输出操作日志
|
||||
is-log: true
|
||||
|
||||
spring:
|
||||
# redis配置
|
||||
redis:
|
||||
# Redis数据库索引(默认为0)
|
||||
database: 1
|
||||
# Redis服务器地址
|
||||
host: 127.0.0.1
|
||||
# Redis服务器连接端口
|
||||
port: 6379
|
||||
# Redis服务器连接密码(默认为空)
|
||||
password:
|
||||
# 连接超时时间
|
||||
timeout: 10s
|
||||
lettuce:
|
||||
pool:
|
||||
# 连接池最大连接数
|
||||
max-active: 200
|
||||
# 连接池最大阻塞等待时间(使用负值表示没有限制)
|
||||
max-wait: -1ms
|
||||
# 连接池中的最大空闲连接
|
||||
max-idle: 10
|
||||
# 连接池中的最小空闲连接
|
||||
min-idle: 0
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user