Sa-TokenSa-Token
主题
首页文档博客
视频
乐之者java(登录认证/权限管理/apiKey等)抓蛙师(23集)朱老师的小课堂(7集)王清江唷 SSO篇(29集)fox说技术(7集)架构驿站(11集)王清江唷(99集)筑梦信仰-joy(20集)达达-Java(26集)晒太阳的盐(22集)[ + 课程提交 ]
案例
Gitee - Awesome-Sa-TokenGitHub - Awesome-Sa-TokenAtomGit - Awesome-Sa-Token
加群需求提交赞助🔥 SSO/OAuth2 商业版
安全推荐
开发者安全 ChecklistAPI 安全 Checklist腾讯代码安全指南Web 安全学习笔记OWASP Cheat Sheet SeriesPayloadsAllTheThings
相关资源
更新日志常见报错推荐公众号在线考试在线提问问卷调查
  • 开始

    • 框架介绍
    • 在 SpringBoot 环境集成
    • 在 WebFlux 环境集成
    • 在 Solon 环境集成
    • 其它环境集成示例
    • 源码运行指南
    • Sa-Token 集成示例大全下载
  • 基础

    • 登录认证
    • 权限认证
    • 踢人下线
    • 注解鉴权
    • 路由拦截鉴权
    • Session会话
    • 框架配置
  • 深入

    • 集成 Redis
    • 前后端分离
    • 自定义 Token 风格
    • Token 提交前缀
    • 同端互斥登录
    • 记住我模式
    • 登录参数 & 注销参数
    • 二级认证
    • 模拟他人 & 身份切换
    • 账号封禁
    • 密码加密
    • 会话查询
    • Http Basic/Digest 认证
    • 全局侦听器
    • 全局过滤器
    • 多账号认证
  • 单点登录

    • 单点登录简述
    • 搭建统一认证中心:SSO-Server
    • SSO-Server 认证中心开放 API 接口
    • SSO模式一 共享Cookie同步会话
    • SSO模式二 URL重定向传播会话
    • SSO模式三 Http请求获取会话
    • 配置域名校验
    • 定制化登录页面
    • 自定义API路由
    • 平台中心跳转模式
    • 匿名 client 接入
    • 单点注销
    • 前后端分离下的整合方案
    • 消息推送机制
    • 用户数据同步 / 迁移
    • NoSdk、ReSdk 模式与非 java 项目
    • SSO 代码 API 参考
    • 常见问题总结
    • Sa-Pro:单点登录商业版
  • OAuth2.0

    • OAuth2.0简述
    • OAuth2-Server搭建
    • OAuth2-Server端开放 API 接口
    • 自定义数据加载器
    • 配置 client 域名校验
    • 自定义 Scope 权限及处理器
    • 为 Scope 划分等级
    • 自定义 grant_type
    • 定制化登录页面与授权页面
    • 自定义 API 路由
    • OAuth2-Server端前后台分离
    • OpenId 与 UnionId
    • 开启 OIDC 协议
    • 使用注解校验 Access-Token
    • OAuth2-与登录会话实现数据互通
    • OAuth2 代码 API 参考
    • 常见问题总结
    • Sa-Max:统一认证商业版
  • 微服务

    • 分布式Session会话
    • 网关统一鉴权
    • 内部服务外网隔离
    • 依赖引入说明
  • 插件

    • AOP注解鉴权
    • 临时 Token 认证
    • Quick-Login快速登录插件
    • Alone独立Redis插件
    • Alone独立Redisson插件
    • 缓存层扩展
    • JSON 序列化扩展
    • 序列化插件扩展包
    • HTTP 请求扩展
    • 和 Thymeleaf 集成
    • 和 Freemarker 集成
    • 注解鉴权 SpEL 表达式
    • 和 jwt 集成
    • 和 Dubbo 集成
    • 和 gRPC 集成
    • API 接口参数签名
    • API Key 接口调用秘钥
    • Sa-Token 插件开发指南
    • 自定义 SaTokenContext 指南
  • API手册

    • StpUtil-鉴权工具类
    • SaSession-会话对象
    • SaTokenDao-数据持久接口
    • SaStrategy-全局策略
    • 全局类、方法
  • 框架设计

    • 仓库目录
    • 数据结构
  • 其它

    • 更新日志
    • 框架生态
    • 框架博客
    • 推荐公众号
    • 加入讨论群
    • Sa-Token 内容合作群
    • 赞助 Sa-Token
    • 需求提交
    • 问卷调查
  • 附录

    • 常见问题排查
    • 框架名词解释
    • Sa-Token功能结构图
    • 全局 Log 输出
    • 异步 & Mock 上下文
    • 未登录场景值详解
    • Token有效期详解
    • Session模型详解
    • 数据读写三大作用域
    • TokenInfo参数详解
    • 异常细分状态码
    • 自定义注解
    • 防火墙
    • 参考:把权限放在缓存里
    • 参考:把路由拦截鉴权动态化
    • 解决反向代理 uri 丢失的问题
    • 解决跨域问题
    • 技术选型:SSO 与 OAuth2 对比
    • 集成 MongoDB 参考一
    • 集成 MongoDB 参考二
    • 从 Shiro、SpringSecurity、JWT 迁移
    • issue 提问模板
    • 为Sa-Token贡献代码
    • Sa-Token开源大事记
    • 团队成员
    • Sa-Token框架掌握度--在线考试







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

×

一个项目搞定:同域、跨域、共享Redis、跨Redis、前后端一体、前后端分离、纯 js、vue2、vue3、非 Sa-Token 项目、非 java 项目等架构下的 SSO 认证需求。

一次购买,永久授权。全源码交付,不含密 Jar。提供售后技术支持。

Sa-Token 集成 MongoDB ​


此章介绍如何通过扩展 SaTokenDao 接口来实现 MongodDB 的集成。

示例代码:sa-token-mongodb-demo

先决条件:

  1. Spring Boot 3
  2. Spring Data Mongodb

以下是依赖的引入:


xml
<!-- 引入 spring data mongodb -->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
1
2
3
4
5

优点:少量改造即可完成集成 MongodDB

集成代码: ​

1. 创建一个类来包装Sa—Token的数据

java
@Document("saTokenMongo") // 你也可以自定义集合名称
public class SaTokenMongoData {

    @Id
    private String id;

    // token
    @Indexed(unique = true)
    private String key;

    // sa-token 的 session
    private SaSession session;

    // sa-token 的 token string
    private String string;

    //使用 @SuppressWarnings("removal") 的目的是,防止IDEA报错,因为 expireAfterSeconds是不在支持的属性。
    @SuppressWarnings("removal")
    // 给 expireAt 添加 `@Indexed(expireAfterSeconds = 0)` 注解,当过期时MongoDB会自动帮我删除过期的数据
    @Indexed(expireAfterSeconds = 0)
    private LocalDateTime expireAt; // 你也可以使用 Date 类型,对应的在`SaTokenMongoDao`中,需要将LocalDateTime替换成Date

    // 忽略 getter setter
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

2.实现 SaTokenDao

这个 SaTokenMongoDao 是仿照官方的 redis 集成实现的

java
package com.xx.xx.security;

import cn.dev33.satoken.dao.SaTokenDao;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.regex.Pattern;

@Component
public class SaTokenMongoDao implements SaTokenDao {

    private final MongoTemplate mongoTemplate;

    public SaTokenMongoDao(MongoTemplate mongoTemplate) {
        this.mongoTemplate = mongoTemplate;
    }


    private Query keyQuery(String key) {
        return Query.query(Criteria.where("key").is(key));
    }

    /**
     * 获取 value,如无返空
     *
     * @param key 键名称
     * @return value
     */
    @Override
    public String get(String key) {

        return Optional.ofNullable(mongoTemplate.findOne(keyQuery(key), SaTokenMongoData.class)).map(SaTokenMongoData::getString).orElse(null);
    }


    LocalDateTime getExpireAtFromTimeout(long timeout) {
        // 当接受到的值是`SaTokenDao.NEVER_EXPIRE`时,说明永不过期,对应的我们需要把 expireAt 设置为null mongodb就不会删除这个记录
        return timeout == SaTokenDao.NEVER_EXPIRE ? null : LocalDateTime.now().plusSeconds(timeout);
    }

    /**
     * 写入 value,并设定存活时间(单位: 秒)
     *
     * @param key     键名称
     * @param value   值
     * @param timeout 数据有效期(值大于0时限时存储,值=-1时永久存储,值=0或小于等于-2时不存储)
     */
    @Override
    public void set(String key, String value, long timeout) {
        if (timeout == 0 || timeout <= SaTokenDao.NOT_VALUE_EXPIRE) {
            return;
        }

        // 判断是否为永不过期
        mongoTemplate.upsert(
                keyQuery(key),
                Update.update("string", value).set("expireAt", getExpireAtFromTimeout(timeout)),
                SaTokenMongoData.class
        );
    }

    /**
     * 更新 value (过期时间不变)
     *
     * @param key   键名称
     * @param value 值
     */
    @Override
    public void update(String key, String value) {
        long expire = getTimeout(key);
        // -2 = 无此键
        if (expire == SaTokenDao.NOT_VALUE_EXPIRE) {
            return;
        }
        this.set(key, value, expire);
    }

    /**
     * 删除 value
     *
     * @param key 键名称
     */
    @Override
    public void delete(String key) {
        mongoTemplate.remove(keyQuery(key), SaTokenMongoData.class);
    }

    /**
     * 获取 value 的剩余存活时间(单位: 秒)
     *
     * @param key 指定 key
     * @return 这个 key 的剩余存活时间
     */
    @Override
    public long getTimeout(String key) {

        LocalDateTime localDateTime = Optional.ofNullable(mongoTemplate.findOne(keyQuery(key), SaTokenMongoData.class)).map(SaTokenMongoData::getExpireAt).orElse(LocalDateTime.MIN);

        long seconds = Duration.between(LocalDateTime.now(), localDateTime).getSeconds();
        if (seconds < 0) {
            return 0;
        }
        return seconds;
    }

    /**
     * 修改 value 的剩余存活时间(单位: 秒)
     *
     * @param key     指定 key
     * @param timeout 过期时间(单位: 秒)
     */
    @Override
    public void updateTimeout(String key, long timeout) {
        // 判断是否想要设置为永久
        if (timeout == SaTokenDao.NEVER_EXPIRE) {
            long expire = getTimeout(key);
            //noinspection StatementWithEmptyBody
            if (expire == SaTokenDao.NEVER_EXPIRE) {
                // 如果其已经被设置为永久,则不作任何处理
            } else {
                // 如果尚未被设置为永久,那么再次set一次
                this.set(key, this.get(key), timeout);
            }
            return;
        }

        mongoTemplate.upsert(
                keyQuery(key),
                Update.update("expireAt", getExpireAtFromTimeout(timeout)),
                SaTokenMongoData.class
        );
    }

    /**
     * 获取 Object,如无返空
     *
     * @param key 键名称
     * @return object
     */
    @Override
    public Object getObject(String key) {
        return Optional.ofNullable(mongoTemplate.findOne(keyQuery(key), SaTokenMongoData.class)).map(SaTokenMongoData::getSession).orElse(null);
    }

    /**
     * 写入 Object,并设定存活时间 (单位: 秒)
     *
     * @param key     键名称
     * @param object  值
     * @param timeout 存活时间(值大于0时限时存储,值=-1时永久存储,值=0或小于等于-2时不存储)
     */
    @Override
    public void setObject(String key, Object object, long timeout) {
        if (timeout == 0 || timeout <= SaTokenDao.NOT_VALUE_EXPIRE) {
            return;
        }
        // 判断是否为永不过期
        mongoTemplate.upsert(
                keyQuery(key),
                Update.update("session", object).set("expireAt", getExpireAtFromTimeout(timeout)),
                SaTokenMongoData.class
        );
    }

    /**
     * 更新 Object (过期时间不变)
     *
     * @param key    键名称
     * @param object 值
     */
    @Override
    public void updateObject(String key, Object object) {
        long expire = getObjectTimeout(key);
        // -2 = 无此键
        if (expire == SaTokenDao.NOT_VALUE_EXPIRE) {
            return;
        }
        this.setObject(key, object, expire);
    }

    /**
     * 删除 Object
     *
     * @param key 键名称
     */
    @Override
    public void deleteObject(String key) {
        delete(key);
    }

    /**
     * 获取 Object 的剩余存活时间 (单位: 秒)
     *
     * @param key 指定 key
     * @return 这个 key 的剩余存活时间
     */
    @Override
    public long getObjectTimeout(String key) {
        return getTimeout(key);
    }

    /**
     * 修改 Object 的剩余存活时间(单位: 秒)
     *
     * @param key     指定 key
     * @param timeout 剩余存活时间
     */
    @Override
    public void updateObjectTimeout(String key, long timeout) {
        // 判断是否想要设置为永久
        updateTimeout(key, timeout);
    }

    /**
     * 搜索数据
     *
     * @param prefix   前缀
     * @param keyword  关键字
     * @param start    开始处索引
     * @param size     获取数量  (-1代表从 start 处一直取到末尾)
     * @param sortType 排序类型(true=正序,false=反序)
     * @return 查询到的数据集合
     */
    @Override
    public List<String> searchData(String prefix, String keyword, int start, int size, boolean sortType) {

        List<Criteria> criteriaList = new ArrayList<>();

        if (StringUtils.hasText(prefix)) {
            criteriaList.add(Criteria.where("key").regex(Pattern.compile("^" + Pattern.quote(prefix))));
        }
        if (StringUtils.hasText(keyword)) {
            Pattern keywordPattern = Pattern.compile(Pattern.quote(keyword), Pattern.CASE_INSENSITIVE);
            criteriaList.add(Criteria.where("key").regex(keywordPattern));
        }


        Criteria criteria = new Criteria();

        if (!criteriaList.isEmpty()) {
            criteria.andOperator(criteriaList);
        }

        long skip = (long) Math.max(start, 0) * Math.max(size, 1);

        Query query = Query.query(criteria).skip(skip).limit(size);

        query.fields().include("key");

        return mongoTemplate.find(query, SaTokenMongoData.class).stream().map(SaTokenMongoData::getKey).toList();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261








发现错误? 您可以在 Gitee 或 GitHub 或 AtomGit 帮助我们完善此页文档! 或 加入讨论群 交流反馈。

我们坚信,即使再复杂的技术,也可以用清晰、干练、易懂的文字描述出它的具体细节,如果你在阅读文档时有难以理解的章节,那一定是我们还没有优化好它, 请向我们 反馈 你的困惑之处,我们将持续优化文档。


鲁ICP备18046274号-5|公安备案鲁公网安备37011202002956号
目录

本页无章节

推荐关闭
Sa-Token 商业版:轻松搭建 SSO 单点登录、OAuth2.0 统一认证、API Key 认证。全源码交付、可二开。
加入 Sa-Token 框架交流群

离线版文档历史所有版本文档Demo 示例大全下载

如果 Sa-Token 帮助到了你,希望你可以向同事、朋友推荐了解本框架,这对我们非常重要,感谢支持!

加油,工程师!