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 ​


在 Spring Boot 下集成 MongoDB:

xml
<!-- 提供MongoDB依赖 -->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
1
2
3
4
5
  1. 创建一个 MySaSession 并继承 SaSession
java
public class MySaSession extends SaSession {
    public MySaSession(String id) {
        super(id);
    }

    public void setDataMap(Map<String, Object> dataMap) {
        refreshDataMap(dataMap);
    }
}
1
2
3
4
5
6
7
8
9

原因:由于 SaSession 中的 dataMap 字段没有 setter 方法,当 spring-data-mongodb 反序列化 SaSession 时会报 Cannot set property dataMap because no setter, no wither and it's not part of the persistence constructor public cn.dev33.satoken.session.SaSession() 错误

  1. 在 SpringBoot 启动方法中重写 SaStrategy.instance.createSession 方法,使我们自定义的 MySaSession 生效
java
@SpringBootApplication
public class SpringApplication {

    public static void main(String[] args) {

	// 重写 SaStrategy.instance.createSession 方法
        SaStrategy.instance.createSession = (sessionId) -> {
            return new MySaSession(sessionId);
        };

        SpringApplication.run(SpringApplication.class, args);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
  1. 实现 SaTokenDao 接口
java
//定义一个类用于保存SaSession
@Document
public class SaTokenWrap {
    private String id;
    private String value;
    private Object object;
    // 这里利用MongoDB的TTL索引,当过期时MongoDB会自动删除过期的数据,同时如果timeout如果为null那么视为永不删除
    @Indexed(expireAfterSeconds = 1, background = true)
    private Date timeout;


    public boolean live() {
        return getTimeout() == null || getTimeout().after(new Date());
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java
// SaTokenDao 实现
@Component
public class SaTokenDaoMongo implements SaTokenDao {


    private final MongoTemplate mongoTemplate;

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


    Optional<SaTokenWrap> getByKey(String key) {
        SaTokenWrap tokenWrap = mongoTemplate.findById(key, SaTokenWrap.class);

        return Optional.ofNullable(tokenWrap).filter(SaTokenWrap::live);
    }

    Date timeoutToDate(long timeout) {

        return new Date(timeout * 1000 + System.currentTimeMillis());
    }

    void upsertByPath(String key, String path, Object value, long timeout) {
        if (timeout == 0 || timeout <= SaTokenDao.NOT_VALUE_EXPIRE) {
            return;
        }
        Update update = Update.update(path, value);

        if (timeout != SaTokenDao.NEVER_EXPIRE) {
            update.set("timeout", timeoutToDate(timeout));
        } else {
            update.unset("timeout");
        }

        mongoTemplate.upsert(
                Query.query(Criteria.where("id").is(key)),
                update,
                SaTokenWrap.class
        );
    }

    void updateByPath(String key, String path, Object value) {
        mongoTemplate.updateFirst(
                Query.query(Criteria.where("id").is(key).and("timeout").gte(new Date())),
                Update.update(path, value),
                SaTokenWrap.class
        );
    }

    // ------------------------ String 读写操作

    @Override
    public String get(String key) {

        return getByKey(key).map(SaTokenWrap::getValue).orElse(null);
    }

    @Override
    public void set(String key, String value, long timeout) {
        upsertByPath(key, "value", value, timeout);
    }

    @Override
    public void update(String key, String value) {
        updateByPath(key, "value", value);
    }

    @Override
    public void delete(String key) {
        mongoTemplate.remove(Query.query(Criteria.where("id").is(key)));
    }

    @Override
    public long getTimeout(String key) {

        SaTokenWrap tokenWrap = mongoTemplate.findById(key, SaTokenWrap.class);

        if (tokenWrap == null) {
            return SaTokenDao.NOT_VALUE_EXPIRE;
        }

        if (tokenWrap.getTimeout() == null) {
            return SaTokenDao.NEVER_EXPIRE;
        }

        long expire = tokenWrap.getTimeout().getTime();
        long timeout = (expire - System.currentTimeMillis()) / 1000;

        // 小于零时,视为不存在
        if (timeout < 0) {
            mongoTemplate.remove(Query.query(Criteria.where("id").is(key)));
            return SaTokenDao.NOT_VALUE_EXPIRE;
        }
        return timeout;
    }

    @Override
    public void updateTimeout(String key, long timeout) {

        Update update = new Update();

        if (timeout == SaTokenDao.NEVER_EXPIRE) {
            update.unset("timeout");
        } else {
            update.set("timeout", timeoutToDate(timeout));
        }

        mongoTemplate.upsert(
                Query.query(Criteria.where("id").is(key)),
                update,
                SaTokenWrap.class
        );
    }


    // ------------------------ Object 读写操作

    @Override
    public Object getObject(String key) {
        return getByKey(key).map(SaTokenWrap::getObject).orElse(null);
    }

    @Override
    public void setObject(String key, Object object, long timeout) {
        upsertByPath(key, "object", object, timeout);
    }

    @Override
    public void updateObject(String key, Object object) {
        updateByPath(key, "object", object);
    }

    @Override
    public void deleteObject(String key) {
        delete(key);
    }

    @Override
    public long getObjectTimeout(String key) {
        return getTimeout(key);
    }

    @Override
    public void updateObjectTimeout(String key, long timeout) {
        updateTimeout(key, timeout);
    }


    // ------------------------ Session 读写操作
    // 使用接口默认实现


    // --------- 会话管理

    @Override
    public List<String> searchData(String prefix, String keyword, int start, int size, boolean sortType) {

        List<SaTokenWrap> wrapList = mongoTemplate.find(
                Query.query(Criteria.where("id").regex(prefix + "*" + keyword + "*").and("timeout").gte(new Date())),
                SaTokenWrap.class
        );

        List<String> list = wrapList.stream().map(SaTokenWrap::getValue).filter(StringUtils::hasText).collect(Collectors.toList());

        return SaFoxUtil.searchList(list, start, size, sortType);
    }


}
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








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

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


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

本页无章节

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

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

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

加油,工程师!