sa-token做登录验证
简介:sa-token是一个轻量级 java 权限认证框架,和spring security,shiro一样,能实现登录认证、权限认证等功能,但使用上比spring security,shiro简单。本文主要简述sa-token的使用,以及登录认证及权限认证等操作。
1.引入maven依赖
<!-- Sa-Token 权限认证,在线文档:https://sa-token.cc -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.37.0</version>
</dependency>
2.配置application文件
默认为application.properties,我习惯使用application.yaml
server:
# 端口
port: 8080
############## Sa-Token 配置 (文档: https://sa-token.cc) ##############
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
只需要以上简单两步,即完成sa-token的引入
3.简单测试
创建简单的登录,验证测试接口
@RestController
@RequestMapping("/user/")
public class UserController {
// 测试登录,浏览器访问: http://localhost:8081/user/doLogin?username=zhang&password=123456
@RequestMapping("doLogin")
public String doLogin(String username, String password) {
// 此处仅作模拟示例,真实项目需要从数据库中查询数据进行比对
if("zhang".equals(username) && "123456".equals(password)) {
StpUtil.login(10001);
return "登录成功";
}
return "登录失败";
}
// 查询登录状态,浏览器访问: http://localhost:8081/user/isLogin
@RequestMapping("isLogin")
public String isLogin() {
return "当前会话是否登录:" + StpUtil.isLogin();
}
}
在浏览器上访问http://127.0.0.1:8080/user/doLogin?username=zhang&password=123456
,显示登录成功
接着在当前窗口修改连接访问http://127.0.0.1:8080/user/isLogin
,显示登录状态:true
阶段性总结
以上已经实现sa-token的简单使用(登录认证),过程是
1.前端访问doLogin接口
2.后端验证账号密码正确后,通过调用sa-token的StpUtil.login(10001);
接口实现登录,StpUtil.login(object)会创建key-value形式信息存储在服务端session中,然后通过set-cookie将浏览器当前会话的请求都带上satoken的sessionId。
3.当前页面会话的请求都会带上satoken的cookie,例如后面访问的isLogin接口,浏览器向后端请求isLogin接口
4.后端接收请求,通过调用StpUtil.isLogin();
接口,后端在request的header中找是否存在satoken的cookie信息,找到后,获取其sessionId,也就是satoken的值,接着在服务端session中查找是否有该值,有则表示该请求是登录成功后的请求,返回登录认证成功。
4.前后端分离模式
看完上述流程,多次提到统一窗口,就知道上面的方式有其局限性,事实上,在前后端分离模式中,像当下很流行的vue,react,ajax+jquery等等,其每个请求都是独立的,可以理解为每个对后端的请求都相当于新开一个窗口。那这样通过以上方式(后端向前端设置cookie)就无法实现登录验证功能。
这个时候需要我们手动给每个需要认证登录的请求添加cookie
首先先从后端获取身份标识字符串,其实就是上面讲到的sessionId,后端将sessionId,下面成为tokenValue,返回给前端,服务端通过sa-token的StpUtil.getTokenInfo()获取到token的信息,其中关键有写进cookie的key-value,tokenValue的有效时间等等。
或者直接通过StpUtil.getTokenValue()
只获取tokenValue也可以。
示例代码:
@RequestMapping("doLogin")
public String doLogin(String username, String password) {
// 此处仅作模拟示例,真实项目需要从数据库中查询数据进行比对
if("zhang".equals(username) && "123456".equals(password)) {
StpUtil.login(new User("10001","zhangsan"));
SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
return tokenInfo.toString();
}
return "登录失败";
}
至于前端怎么设置cookie,添加请求头,此处不赘述,可以参考我的另一篇文章
其中封装axios请求部分,中request.js文件有示例。
5.redis的引入
实现前后端分离后,再看看性能以及数据安全问题。
session是存储在服务端的,是跟springboot共用jvm空间的,多少会影响系统空间性能,但**更重要的是,如果Java项目shutdown,或者restart,那么存储在session中的登录信息将全部丢失,且无法寻回。**为了避免项目重启,登录信息失效,导致客户端集体需要重新登录,造成极坏的体验,我们应该将登录信息与系统解耦,并做持久化,所以考虑到引入redis
sa-token给我们提供了封装redis的sa-token依赖包,在上面的sa-token-spring-boot-starter
依赖包的基础上添加sa-token-redis
或者 sa-token-redis-jackson
。
<!-- Sa-Token 整合 Redis (使用 jdk 默认序列化方式) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redis</artifactId>
<version>1.37.0</version>
</dependency>
或者
<!-- Sa-Token 整合 Redis (使用 jackson 序列化方式) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redis-jackson</artifactId>
<version>1.37.0</version>
</dependency>
以上无论哪种方式还需携带下面依赖
<!-- 提供Redis连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
然后在application.yaml中配置redis
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
至此,redis配置完成,登录信息不需要手动存进redis,sa-token已经封装好,当我们调用StpUtil.login(object)的时候,sa-token会自动将登录信息存进redis,当调用getLoginId的时候则会将信息重新取出。
注意
在编码时发现一个坑,就是StpUtil.login(object)的时候,object如果是自定义对象类型,不能反序列化为自定义的对象(即使该自定义对象实现Serializable接口),例如用User类做参数,当getLoginId的时候不会返回User型,而是User类的toString字符串。
所以不要将用户对象作为LoginId,使用UserId做loginId就好了