什么是 Sa-Token?

Sa-Token 是一个轻量级 Java 权限认证框架,主要解决以下一系列权限相关问题:

  • 登录认证
  • 权限认证
  • Session 会话
  • 单点登录
  • OAuth2.0
  • 分布式 Session 会话
  • 微服务网关鉴权

Sa-Token 致力于让权限认证变得简单、优雅,开发者可以通过简单的几行代码实现复杂的权限控制功能。

Sa-Token 的核心特性

1. 登录认证

支持多种登录策略:

  • 多端登录
  • 单端登录
  • 同端互斥登录
  • 七天免登录

2. 权限认证

提供灵活的鉴权方式:

  • 权限认证
  • 角色认证
  • 会话二级认证
  • 注解鉴权
  • 路由鉴权

3. 会话管理

完善的会话管理方案:

  • 强制注销
  • 踢人下线
  • 账号封禁
  • 身份切换
  • 自动续签

4. 微服务支持

开箱即用的微服务认证方案:

  • 分布式 Session 会话
  • 网关统一鉴权
  • RPC 调用鉴权

集成 Sa-Token

Maven 依赖

在 Spring Boot 项目中添加以下依赖:

1
2
3
4
5
6
<!-- Sa-Token 权限认证 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.38.0</version>
</dependency>

基本配置

application.yml 中添加基本配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Sa-Token 配置
sa-token:
# token 名称 (同时也是 cookie 名称)
token-name: satoken
# token 有效期(单位:秒) 默认30天,-1代表永久有效
timeout: 2592000
# token 临时有效期(单位:秒)
activity-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: false

使用 Sa-Token

登录认证

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
@RestController
@RequestMapping("/user/")
public class UserController {

// 用户登录
@PostMapping("login")
public SaResult doLogin(@RequestBody LoginVo loginVo) {
// 第一步:比对前端提交的账号和密码
if ("admin".equals(loginVo.getUsername())
&& "123456".equals(loginVo.getPassword())) {
// 第二步:根据账号ID生成 token
StpUtil.login(10001);
// 第三步:返回 token
return SaResult.ok("登录成功").setData(StpUtil.getTokenInfo());
}
return SaResult.error("登录失败");
}

// 查询登录状态
@GetMapping("isLogin")
public SaResult isLogin() {
return SaResult.ok("是否登录:" + StpUtil.isLogin());
}

// 查询 Token 信息
@GetMapping("tokenInfo")
public SaResult tokenInfo() {
return SaResult.ok("token信息").setData(StpUtil.getTokenInfo());
}

// 用户退出登录
@PostMapping("logout")
public SaResult logout() {
StpUtil.logout();
return SaResult.ok("退出成功");
}
}

权限认证

1. 注解方式鉴权

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
@RestController
@RequestMapping("/article/")
public class ArticleController {

// 登录认证:当前会话必须登录才能通过
@SaCheckLogin
@GetMapping("info")
public SaResult info() {
return SaResult.ok("查询文章信息");
}

// 权限认证:当前会话必须具有 user-create 权限才能通过
@SaCheckPermission("user-create")
@PostMapping("create")
public SaResult create() {
return SaResult.ok("创建文章");
}

// 角色认证:当前会话必须具有 admin 角色才能通过
@SaCheckRole("admin")
@DeleteMapping("delete")
public SaResult delete() {
return SaResult.ok("删除文章");
}

// 权限认证并指定校验模式:当前会话必须同时具有 user-create 和 user-update 权限才能通过
@SaCheckPermission({"user-create", "user-update"})
@PutMapping("update")
public SaResult update() {
return SaResult.ok("修改文章");
}
}

2. 代码方式鉴权

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
@RestController
@RequestMapping("/goods/")
public class GoodsController {

@GetMapping("info")
public SaResult info() {
// 获取当前登录账号的 id
Object loginId = StpUtil.getLoginId();

// 当前会话是否登录
boolean isLogin = StpUtil.isLogin();

// 检验当前会话是否登录,未登录则抛出异常
StpUtil.checkLogin();

// 获取当前会话账号所拥有的权限列表
List<String> permissionList = StpUtil.getPermissionList();

// 获取当前会话账号所拥有的角色列表
List<String> roleList = StpUtil.getRoleList();

// 当前账号是否拥有指定权限
boolean hasPermission = StpUtil.hasPermission("user-create");

// 当前账号是否拥有指定角色
boolean hasRole = StpUtil.hasRole("admin");

return SaResult.ok("商品信息");
}
}

会话管理

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
@RestController
@RequestMapping("/admin/")
public class AdminController {

// 获取所有在线用户
@GetMapping("onlineUser")
public SaResult onlineUser() {
// 查询所有 token
List<String> tokenList = StpUtil.searchTokenValue("", 0, -1, false);
return SaResult.ok("在线用户").setData(tokenList);
}

// 强制指定账号下线
@GetMapping("kickout/{userId}")
public SaResult kickout(@PathVariable Long userId) {
// 强制指定账号下线
StpUtil.kickout(userId);
return SaResult.ok("强制下线成功");
}

// 封禁指定账号
@GetMapping("disable/{userId}")
public SaResult disable(@PathVariable Long userId) {
// 封禁指定账号,1小时后解封
StpUtil.disable(userId, 3600);
return SaResult.ok("账号封禁成功");
}

// 解封指定账号
@GetMapping("untieDisable/{userId}")
public SaResult untieDisable(@PathVariable Long userId) {
// 解封指定账号
StpUtil.untieDisable(userId);
return SaResult.ok("账号解封成功");
}
}

集成 Redis

为了实现分布式会话,我们需要集成 Redis:

添加 Redis 依赖

1
2
3
4
5
6
7
8
9
10
11
12
<!-- Sa-Token 整合 Redis -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redis</artifactId>
<version>1.38.0</version>
</dependency>

<!-- 提供 Redis 连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>

Redis 配置

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
# Redis 配置
spring:
redis:
# Redis 数据库索引(默认为 0)
database: 0
# Redis 服务器地址
host: 127.0.0.1
# Redis 服务器连接端口
port: 6379
# Redis 服务器连接密码(默认为空)
password:
# 连接池配置
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0

# Sa-Token 配置
sa-token:
# token 名称
token-name: satoken
# token 有效期(单位:秒)
timeout: 2592000
# token 临时有效期(单位:秒)
activity-timeout: -1
# 是否允许同一账号并发登录
is-concurrent: true
# 在多人登录同一账号时,是否共用一个 token
is-share: true
# token 风格
token-style: uuid
# 是否输出操作日志
is-log: false
# 配置 Redis 连接
is-read-cookie: true

微服务集成

在微服务架构中,我们可以通过以下方式集成 Sa-Token:

网关统一鉴权

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
@Component
public class AuthGlobalFilter implements GlobalFilter {

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 获取请求路径
String path = exchange.getRequest().getURI().getPath();

// 不需要鉴权的路径
if (path.contains("/login") || path.contains("/register")) {
return chain.filter(exchange);
}

// 获取 token
String token = exchange.getRequest().getHeaders().getFirst("Authorization");
if (token == null) {
token = exchange.getRequest().getQueryParams().getFirst("satoken");
}

// 鉴权
try {
StpUtil.checkLogin(token);
} catch (Exception e) {
// 鉴权失败,返回 401
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
String data = JSON.toJSONString(SaResult.error("未登录"));
DataBuffer buffer = response.bufferFactory().wrap(data.getBytes(StandardCharsets.UTF_8));
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
return response.writeWith(Mono.just(buffer));
}

return chain.filter(exchange);
}
}

结语

Sa-Token 是一个功能强大且易于使用的 Java 权限认证框架,它提供了登录认证、权限认证、会话管理、单点登录、OAuth2.0 等一系列权限相关功能。通过简单的配置和注解,开发者可以快速实现复杂的权限控制需求。

无论是在单体应用还是微服务架构中,Sa-Token 都能提供完善的权限认证解决方案,大大简化了权限控制的开发工作。