文章目录
-
- 引言
- 第一章:SA-Token框架概述与核心特性
-
- [1.1 SA-Token简介与设计理念](#1.1 SA-Token简介与设计理念)
-
- [1.1.1 什么是SA-Token](#1.1.1 什么是SA-Token)
- [1.1.2 SA-Token的设计理念](#1.1.2 SA-Token的设计理念)
- [1.2 SA-Token核心特性详解](#1.2 SA-Token核心特性详解)
-
- [1.2.1 登录认证特性](#1.2.1 登录认证特性)
- [1.2.2 权限认证特性](#1.2.2 权限认证特性)
- [1.2.3 会话管理特性](#1.2.3 会话管理特性)
- [1.3 SA-Token架构设计](#1.3 SA-Token架构设计)
-
- [1.3.1 核心组件架构](#1.3.1 核心组件架构)
- [1.3.2 扩展机制设计](#1.3.2 扩展机制设计)
- 第二章:SpringBoot集成SA-Token基础配置
-
- [2.1 项目环境搭建](#2.1 项目环境搭建)
-
- [2.1.1 Maven依赖配置](#2.1.1 Maven依赖配置)
- [2.1.2 应用配置文件](#2.1.2 应用配置文件)
- [2.1.3 数据库表结构设计](#2.1.3 数据库表结构设计)
- [2.2 核心配置类实现](#2.2 核心配置类实现)
-
- [2.2.1 SA-Token配置类](#2.2.1 SA-Token配置类)
- [2.2.2 权限认证接口实现](#2.2.2 权限认证接口实现)
- [2.2.3 全局异常处理器](#2.2.3 全局异常处理器)
- [2.3 实体类和数据访问层](#2.3 实体类和数据访问层)
-
- [2.3.1 实体类定义](#2.3.1 实体类定义)
- [2.3.2 数据访问层实现](#2.3.2 数据访问层实现)
- [2.4 业务服务层实现](#2.4 业务服务层实现)
-
- [2.4.1 用户服务实现](#2.4.1 用户服务实现)
- [2.4.2 角色服务实现](#2.4.2 角色服务实现)
- [2.4.3 权限服务实现](#2.4.3 权限服务实现)
- 第三章:权限认证与授权实战
-
- [3.1 登录认证实现](#3.1 登录认证实现)
-
- [3.1.1 登录控制器实现](#3.1.1 登录控制器实现)
- [3.1.2 验证码服务实现](#3.1.2 验证码服务实现)
- [4.1.2 SSO-Server端实现](#4.1.2 SSO-Server端实现)
- [4.1.3 SSO-Client端实现](#4.1.3 SSO-Client端实现)
- [4.2 OAuth2.0集成](#4.2 OAuth2.0集成)
-
- [4.2.1 OAuth2配置](#4.2.1 OAuth2配置)
- [4.3 微服务网关鉴权](#4.3 微服务网关鉴权)
-
- [4.3.1 网关鉴权配置](#4.3.1 网关鉴权配置)
- [4.4 多账户体系支持](#4.4 多账户体系支持)
-
- [4.4.1 多账户配置](#4.4.1 多账户配置)
- 第五章:生产环境最佳实践
-
- [5.1 性能优化策略](#5.1 性能优化策略)
-
- [5.1.1 Token存储优化](#5.1.1 Token存储优化)
- [5.1.2 权限缓存优化](#5.1.2 权限缓存优化)
- [5.2 安全加固措施](#5.2 安全加固措施)
-
- [5.2.1 Token安全增强](#5.2.1 Token安全增强)
- [5.2.2 防攻击策略](#5.2.2 防攻击策略)
- [5.3 监控与日志](#5.3 监控与日志)
-
- [5.3.1 认证监控](#5.3.1 认证监控)
- [5.3.2 审计日志](#5.3.2 审计日志)
- 第六章:总结与展望
-
- [6.1 知识点回顾](#6.1 知识点回顾)
-
- [6.1.1 核心概念与特性](#6.1.1 核心概念与特性)
- [6.1.2 技术架构要点](#6.1.2 技术架构要点)
- [6.1.3 实战应用总结](#6.1.3 实战应用总结)
- [6.2 最佳实践总结](#6.2 最佳实践总结)
-
- [6.2.1 开发规范](#6.2.1 开发规范)
- [6.2.2 性能优化建议](#6.2.2 性能优化建议)
- [6.2.3 安全防护要点](#6.2.3 安全防护要点)
- [6.3 技术发展趋势](#6.3 技术发展趋势)
-
- [6.3.1 云原生权限管理](#6.3.1 云原生权限管理)
- [6.3.2 零信任安全架构](#6.3.2 零信任安全架构)
- [6.3.3 AI驱动的智能权限](#6.3.3 AI驱动的智能权限)
- [6.4 扩展阅读与学习资源](#6.4 扩展阅读与学习资源)
-
- [6.4.1 官方文档与社区](#6.4.1 官方文档与社区)
- [6.4.2 相关技术书籍](#6.4.2 相关技术书籍)
- [6.4.3 在线学习资源](#6.4.3 在线学习资源)
- [6.5 实践练习与思考](#6.5 实践练习与思考)
-
- [6.5.1 动手实践项目](#6.5.1 动手实践项目)
- [6.5.2 思考讨论题](#6.5.2 思考讨论题)
- [6.5.3 开源贡献](#6.5.3 开源贡献)
- [6.6 结语](#6.6 结语)

引言
在现代Web应用开发中,权限管理是一个不可或缺的核心功能。传统的权限框架如Spring Security虽然功能强大,但配置复杂、学习成本高,对于中小型项目来说往往显得过于臃肿。SA-Token作为一个轻量级的Java权限认证框架,以其简洁的API设计、丰富的功能特性和极低的学习成本,正在成为越来越多开发者的首选。
SA-Token(Simple And Token)是一个轻量级Java权限认证框架,主要解决登录认证、权限认证、单点登录、OAuth2、微服务网关鉴权等一系列权限相关问题。它以简单、强大、优雅为设计理念,让权限认证变得简单而不失灵活。
本文将从SA-Token的基础概念出发,深入探讨其在SpringBoot项目中的集成方案,通过丰富的代码示例和实战案例,帮助读者全面掌握SA-Token的使用技巧和最佳实践。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的技术洞察和实践指导。
第一章:SA-Token框架概述与核心特性

1.1 SA-Token简介与设计理念
1.1.1 什么是SA-Token
SA-Token是一个轻量级Java权限认证框架,专注于解决Web应用中的权限认证问题。与传统的重量级框架不同,SA-Token采用了更加简洁直观的API设计,让开发者能够快速上手并高效开发。
java
// SA-Token的核心理念:简单即是美
// 登录用户
StpUtil.login(userId);
// 检查登录状态
StpUtil.checkLogin();
// 获取当前用户ID
Object userId = StpUtil.getLoginId();
// 注销登录
StpUtil.logout();
1.1.2 SA-Token的设计理念
SA-Token的设计遵循以下核心理念:
- 简单性:API设计简洁明了,学习成本低
- 灵活性:支持多种认证模式和扩展机制
- 高性能:轻量级设计,运行效率高
- 易集成:与主流框架无缝集成
java
/**
* SA-Token设计理念体现
*/
@RestController
public class AuthController {
/**
* 用户登录 - 体现简单性
*/
@PostMapping("/login")
public Result login(@RequestBody LoginRequest request) {
// 验证用户名密码(省略具体实现)
User user = userService.authenticate(request.getUsername(), request.getPassword());
if (user != null) {
// 一行代码完成登录
StpUtil.login(user.getId());
// 获取Token信息
SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
return Result.success(tokenInfo);
}
return Result.error("用户名或密码错误");
}
/**
* 获取用户信息 - 体现灵活性
*/
@GetMapping("/userinfo")
public Result getUserInfo() {
// 检查登录状态,未登录会抛出异常
StpUtil.checkLogin();
// 获取当前登录用户ID
Object loginId = StpUtil.getLoginId();
// 获取用户详细信息
User user = userService.getById(loginId);
return Result.success(user);
}
/**
* 需要特定权限的接口 - 体现权限控制的简洁性
*/
@GetMapping("/admin/users")
@SaCheckPermission("user:list") // 注解方式权限校验
public Result getUserList() {
List<User> users = userService.getAllUsers();
return Result.success(users);
}
}
1.2 SA-Token核心特性详解

1.2.1 登录认证特性
SA-Token提供了完整的登录认证解决方案,支持多种登录模式和会话管理策略。
java
/**
* 登录认证核心特性演示
*/
@Service
public class AuthService {
/**
* 基础登录功能
*/
public void basicLogin(Long userId) {
// 基础登录
StpUtil.login(userId);
// 指定设备登录
StpUtil.login(userId, "PC");
// 登录并指定Token有效期(单位:秒)
StpUtil.login(userId, 3600);
// 登录时携带扩展信息
StpUtil.login(userId, new SaLoginModel()
.setDevice("mobile")
.setTimeout(7200)
.setIsLastingCookie(true));
}
/**
* 会话查询功能
*/
public void sessionQuery() {
// 获取当前登录用户ID
Object loginId = StpUtil.getLoginId();
// 获取当前登录用户ID,并转换为指定类型
Long userId = StpUtil.getLoginIdAsLong();
String userIdStr = StpUtil.getLoginIdAsString();
// 获取当前登录设备
String device = StpUtil.getLoginDevice();
// 获取Token剩余有效时间(单位:秒)
long timeout = StpUtil.getTokenTimeout();
// 获取Token信息
SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
System.out.println("Token名称:" + tokenInfo.getTokenName());
System.out.println("Token值:" + tokenInfo.getTokenValue());
System.out.println("是否登录:" + tokenInfo.getIsLogin());
System.out.println("登录ID:" + tokenInfo.getLoginId());
System.out.println("登录类型:" + tokenInfo.getLoginType());
System.out.println("Token超时时间:" + tokenInfo.getTokenTimeout());
}
/**
* 登录状态检查
*/
public void loginCheck() {
// 检查当前是否登录,如未登录则抛出异常
StpUtil.checkLogin();
// 检查当前是否登录,返回boolean值
boolean isLogin = StpUtil.isLogin();
// 检查指定用户是否登录
boolean userLogin = StpUtil.isLogin(10001);
// 检查当前Token是否有效
boolean isValid = StpUtil.getTokenInfo().getIsLogin();
}
/**
* 注销登录功能
*/
public void logout() {
// 注销当前用户登录
StpUtil.logout();
// 注销指定用户登录
StpUtil.logout(10001);
// 注销指定用户在指定设备的登录
StpUtil.logout(10001, "PC");
// 踢掉指定用户下线
StpUtil.kickout(10001);
// 踢掉指定用户在指定设备下线
StpUtil.kickout(10001, "mobile");
}
}
1.2.2 权限认证特性
SA-Token提供了灵活的权限认证机制,支持基于角色和权限的访问控制。
java
/**
* 权限认证特性演示
*/
@Service
public class PermissionService {
/**
* 权限校验
*/
public void permissionCheck() {
// 检查当前用户是否拥有指定权限
StpUtil.checkPermission("user:add");
// 检查当前用户是否拥有指定权限,返回boolean
boolean hasPermission = StpUtil.hasPermission("user:delete");
// 检查当前用户是否拥有指定权限列表中的任意一个
StpUtil.checkPermissionOr("user:add", "user:edit", "user:delete");
// 检查当前用户是否拥有指定权限列表中的所有权限
StpUtil.checkPermissionAnd("user:add", "role:add");
}
/**
* 角色校验
*/
public void roleCheck() {
// 检查当前用户是否拥有指定角色
StpUtil.checkRole("admin");
// 检查当前用户是否拥有指定角色,返回boolean
boolean hasRole = StpUtil.hasRole("admin");
// 检查当前用户是否拥有指定角色列表中的任意一个
StpUtil.checkRoleOr("admin", "manager", "operator");
// 检查当前用户是否拥有指定角色列表中的所有角色
StpUtil.checkRoleAnd("admin", "manager");
}
/**
* 获取权限和角色信息
*/
public void getPermissionInfo() {
// 获取当前用户的权限列表
List<String> permissions = StpUtil.getPermissionList();
// 获取当前用户的角色列表
List<String> roles = StpUtil.getRoleList();
// 获取指定用户的权限列表
List<String> userPermissions = StpUtil.getPermissionList(10001);
// 获取指定用户的角色列表
List<String> userRoles = StpUtil.getRoleList(10001);
System.out.println("当前用户权限:" + permissions);
System.out.println("当前用户角色:" + roles);
}
}
1.2.3 会话管理特性
SA-Token提供了强大的会话管理功能,支持会话存储、会话共享、会话监听等特性。
java
/**
* 会话管理特性演示
*/
@Service
public class SessionService {
/**
* Session存储操作
*/
public void sessionStorage() {
// 获取当前用户的Session对象
SaSession session = StpUtil.getSession();
// 在Session中存储数据
session.set("username", "张三");
session.set("email", "zhangsan@example.com");
session.set("loginTime", System.currentTimeMillis());
// 从Session中获取数据
String username = session.get("username", String.class);
String email = (String) session.get("email");
// 获取Session中的所有key
Set<String> keys = session.keys();
// 删除Session中的指定数据
session.delete("email");
// 清空Session
session.clear();
// 获取Session的剩余存活时间
long timeout = session.getTimeout();
// 修改Session的存活时间
session.updateTimeout(3600);
}
/**
* Token-Session双Token模式
*/
public void tokenSessionMode() {
// 获取Token-Session(专门存储业务数据的Session)
SaSession tokenSession = StpUtil.getTokenSession();
// 在Token-Session中存储数据
tokenSession.set("currentProject", "SA-Token集成项目");
tokenSession.set("theme", "dark");
// Token-Session与User-Session的区别:
// User-Session: 以用户为单位,同一用户的多次登录共享同一个Session
// Token-Session: 以Token为单位,每个Token都有自己独立的Session
// 获取User-Session
SaSession userSession = StpUtil.getSession();
userSession.set("userInfo", "这是用户级别的数据");
// 获取指定用户的Session
SaSession specificUserSession = StpUtil.getSessionByLoginId(10001);
specificUserSession.set("lastLoginTime", System.currentTimeMillis());
}
/**
* 自定义Session操作
*/
public void customSession() {
// 获取自定义Session
SaSession customSession = SaSessionCustomUtil.getSessionById("custom-session-001");
// 在自定义Session中存储数据
customSession.set("customData", "这是自定义Session数据");
// 设置自定义Session的存活时间
customSession.updateTimeout(1800);
// 删除自定义Session
SaSessionCustomUtil.deleteSessionById("custom-session-001");
// 获取所有自定义Session的ID列表
List<String> sessionIds = SaSessionCustomUtil.searchSessionId("custom-*", 0, 100, true);
}
}
1.3 SA-Token架构设计
1.3.1 核心组件架构
SA-Token采用模块化设计,核心组件包括:
java
/**
* SA-Token核心组件架构演示
*/
public class SaTokenArchitecture {
/**
* 1. StpLogic - 权限认证逻辑核心
*/
public void stpLogicDemo() {
// StpLogic是SA-Token的核心逻辑类
// 所有的登录、权限校验等操作都通过StpLogic实现
// 获取默认的StpLogic实例
StpLogic stpLogic = StpUtil.stpLogic;
// 使用StpLogic进行登录
stpLogic.login(10001);
// 使用StpLogic进行权限校验
stpLogic.checkPermission("user:add");
// 自定义StpLogic实现多账户体系
StpLogic adminLogic = new StpLogic("admin");
StpLogic userLogic = new StpLogic("user");
// 管理员登录
adminLogic.login(1001);
// 普通用户登录
userLogic.login(2001);
}
/**
* 2. SaTokenDao - 数据持久化接口
*/
public void saTokenDaoDemo() {
// SaTokenDao负责Token和Session的持久化
// 默认实现:SaTokenDaoDefaultImpl(基于内存)
// 获取当前使用的Dao实例
SaTokenDao dao = SaManager.getSaTokenDao();
// 存储Token
dao.set("token:abc123", "user:10001", 3600);
// 获取Token对应的值
String value = dao.get("token:abc123");
// 删除Token
dao.delete("token:abc123");
// 获取Token剩余存活时间
long timeout = dao.getTimeout("token:abc123");
// 修改Token存活时间
dao.updateTimeout("token:abc123", 7200);
}
/**
* 3. SaTokenConfig - 全局配置类
*/
public void saTokenConfigDemo() {
// 获取全局配置对象
SaTokenConfig config = SaManager.getConfig();
// 查看配置信息
System.out.println("Token名称:" + config.getTokenName());
System.out.println("Token超时时间:" + config.getTimeout());
System.out.println("是否允许同一账号并发登录:" + config.getIsConcurrent());
System.out.println("是否共享Token:" + config.getIsShare());
System.out.println("Token风格:" + config.getTokenStyle());
// 动态修改配置(不推荐在生产环境使用)
config.setTokenName("Authorization");
config.setTimeout(7200);
}
/**
* 4. SaStrategy - 策略模式接口
*/
public void saStrategyDemo() {
// SA-Token使用策略模式来处理各种业务逻辑
// 自定义Token生成策略
SaStrategy.me.createToken = (loginId, loginType) -> {
return "custom-token-" + loginId + "-" + System.currentTimeMillis();
};
// 自定义Session生成策略
SaStrategy.me.createSession = (sessionId) -> {
return new SaSession(sessionId);
};
// 自定义权限验证失败处理策略
SaStrategy.me.notPermission = (loginType, permission) -> {
throw new SaTokenException("权限不足:" + permission);
};
// 自定义角色验证失败处理策略
SaStrategy.me.notRole = (loginType, role) -> {
throw new SaTokenException("角色不足:" + role);
};
}
}
1.3.2 扩展机制设计
SA-Token提供了丰富的扩展机制,支持自定义实现各种组件:
java
/**
* SA-Token扩展机制演示
*/
public class SaTokenExtension {
/**
* 自定义权限数据源
*/
@Component
public class CustomStpInterface implements StpInterface {
@Autowired
private UserService userService;
@Autowired
private RoleService roleService;
@Autowired
private PermissionService permissionService;
/**
* 返回指定用户的权限列表
*/
@Override
public List<String> getPermissionList(Object loginId, String loginType) {
Long userId = Long.valueOf(loginId.toString());
// 从数据库查询用户权限
List<Permission> permissions = permissionService.getPermissionsByUserId(userId);
return permissions.stream()
.map(Permission::getPermissionCode)
.collect(Collectors.toList());
}
/**
* 返回指定用户的角色列表
*/
@Override
public List<String> getRoleList(Object loginId, String loginType) {
Long userId = Long.valueOf(loginId.toString());
// 从数据库查询用户角色
List<Role> roles = roleService.getRolesByUserId(userId);
return roles.stream()
.map(Role::getRoleCode)
.collect(Collectors.toList());
}
}
/**
* 自定义Token持久化实现
*/
@Component
public class CustomSaTokenDao implements SaTokenDao {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Override
public String get(String key) {
return (String) redisTemplate.opsForValue().get(key);
}
@Override
public void set(String key, String value, long timeout) {
if (timeout == SaTokenDao.NEVER_EXPIRE) {
redisTemplate.opsForValue().set(key, value);
} else {
redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
}
}
@Override
public void update(String key, String value) {
long expire = getTimeout(key);
if (expire == SaTokenDao.NOT_VALUE_EXPIRE) {
return;
}
this.set(key, value, expire);
}
@Override
public void delete(String key) {
redisTemplate.delete(key);
}
@Override
public long getTimeout(String key) {
Long expire = redisTemplate.getExpire(key);
return expire == null ? SaTokenDao.NOT_VALUE_EXPIRE : expire;
}
@Override
public void updateTimeout(String key, long timeout) {
redisTemplate.expire(key, timeout, TimeUnit.SECONDS);
}
@Override
public Object getObject(String key) {
return redisTemplate.opsForValue().get(key);
}
@Override
public void setObject(String key, Object object, long timeout) {
if (timeout == SaTokenDao.NEVER_EXPIRE) {
redisTemplate.opsForValue().set(key, object);
} else {
redisTemplate.opsForValue().set(key, object, timeout, TimeUnit.SECONDS);
}
}
@Override
public void updateObject(String key, Object object) {
long expire = getObjectTimeout(key);
if (expire == SaTokenDao.NOT_VALUE_EXPIRE) {
return;
}
this.setObject(key, object, expire);
}
@Override
public long getObjectTimeout(String key) {
Long expire = redisTemplate.getExpire(key);
return expire == null ? SaTokenDao.NOT_VALUE_EXPIRE : expire;
}
@Override
public void updateObjectTimeout(String key, long timeout) {
redisTemplate.expire(key, timeout, TimeUnit.SECONDS);
}
@Override
public List<String> searchData(String prefix, String keyword, int start, int size, boolean sortType) {
// 实现数据搜索逻辑
Set<String> keys = redisTemplate.keys(prefix + "*" + keyword + "*");
List<String> list = new ArrayList<>(keys);
// 排序
if (sortType) {
Collections.sort(list);
} else {
Collections.sort(list, Collections.reverseOrder());
}
// 分页
int fromIndex = start;
int toIndex = Math.min(start + size, list.size());
if (fromIndex >= list.size()) {
return new ArrayList<>();
}
return list.subList(fromIndex, toIndex);
}
}
}
第二章:SpringBoot集成SA-Token基础配置

2.1 项目环境搭建
2.1.1 Maven依赖配置
首先,我们需要在SpringBoot项目中添加SA-Token的相关依赖:
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.0</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>satoken-demo</artifactId>
<version>1.0.0</version>
<name>SA-Token集成示例</name>
<description>SpringBoot集成SA-Token权限校验框架示例项目</description>
<properties>
<java.version>8</java.version>
<sa-token.version>1.34.0</sa-token.version>
</properties>
<dependencies>
<!-- SpringBoot Web启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- SA-Token 权限认证,在线文档:https://sa-token.cc -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>${sa-token.version}</version>
</dependency>
<!-- SA-Token 整合 Redis (使用 jackson 序列化方式) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis-jackson</artifactId>
<version>${sa-token.version}</version>
</dependency>
<!-- 提供Redis连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- SpringBoot数据库启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- MySQL数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- JSON处理 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
<!-- Lombok简化代码 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- SpringBoot测试启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
2.1.2 应用配置文件
在application.yml中配置SA-Token和相关组件:
yaml
# 服务器配置
server:
port: 8080
servlet:
context-path: /api
# 数据源配置
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/satoken_demo?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8
username: root
password: 123456
# JPA配置
jpa:
hibernate:
ddl-auto: update
show-sql: true
properties:
hibernate:
format_sql: true
# Redis配置
redis:
host: localhost
port: 6379
password:
database: 0
timeout: 10000ms
lettuce:
pool:
max-active: 8
max-wait: -1ms
max-idle: 8
min-idle: 0
# SA-Token配置
sa-token:
# token名称 (同时也是cookie名称)
token-name: Authorization
# token有效期,单位s 默认30天, -1代表永不过期
timeout: 2592000
# token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
activity-timeout: -1
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
is-share: true
# token风格
token-style: uuid
# 是否输出操作日志
is-log: false
# 是否从cookie中读取token
is-read-cookie: true
# 是否从header中读取token
is-read-header: true
# 是否从body中读取token
is-read-body: false
# token前缀
token-prefix: "Bearer"
# jwt秘钥
jwt-secret-key: abcdefghijklmnopqrstuvwxyz
# 日志配置
logging:
level:
com.example: debug
org.springframework.web: debug
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
2.1.3 数据库表结构设计
创建用户权限相关的数据库表:
sql
-- 用户表
CREATE TABLE `sys_user` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`username` varchar(50) NOT NULL COMMENT '用户名',
`password` varchar(100) NOT NULL COMMENT '密码',
`nickname` varchar(50) DEFAULT NULL COMMENT '昵称',
`email` varchar(100) DEFAULT NULL COMMENT '邮箱',
`phone` varchar(20) DEFAULT NULL COMMENT '手机号',
`avatar` varchar(200) DEFAULT NULL COMMENT '头像',
`status` tinyint DEFAULT '1' COMMENT '状态:0-禁用,1-启用',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
-- 角色表
CREATE TABLE `sys_role` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '角色ID',
`role_code` varchar(50) NOT NULL COMMENT '角色编码',
`role_name` varchar(50) NOT NULL COMMENT '角色名称',
`description` varchar(200) DEFAULT NULL COMMENT '角色描述',
`status` tinyint DEFAULT '1' COMMENT '状态:0-禁用,1-启用',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_role_code` (`role_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色表';
-- 权限表
CREATE TABLE `sys_permission` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '权限ID',
`permission_code` varchar(100) NOT NULL COMMENT '权限编码',
`permission_name` varchar(100) NOT NULL COMMENT '权限名称',
`resource_type` varchar(20) DEFAULT NULL COMMENT '资源类型:menu-菜单,button-按钮',
`url` varchar(200) DEFAULT NULL COMMENT '资源路径',
`method` varchar(10) DEFAULT NULL COMMENT '请求方法',
`parent_id` bigint DEFAULT '0' COMMENT '父权限ID',
`sort_order` int DEFAULT '0' COMMENT '排序',
`status` tinyint DEFAULT '1' COMMENT '状态:0-禁用,1-启用',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_permission_code` (`permission_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='权限表';
-- 用户角色关联表
CREATE TABLE `sys_user_role` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` bigint NOT NULL COMMENT '用户ID',
`role_id` bigint NOT NULL COMMENT '角色ID',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_user_role` (`user_id`,`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户角色关联表';
-- 角色权限关联表
CREATE TABLE `sys_role_permission` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`role_id` bigint NOT NULL COMMENT '角色ID',
`permission_id` bigint NOT NULL COMMENT '权限ID',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_role_permission` (`role_id`,`permission_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色权限关联表';
-- 插入测试数据
INSERT INTO `sys_user` (`username`, `password`, `nickname`, `email`, `status`) VALUES
('admin', '$2a$10$7JB720yubVSOfvVMe6/YqO4wkhWGEn4bJJnNpSn0kfzOLuTOQHHiq', '系统管理员', 'admin@example.com', 1),
('user', '$2a$10$7JB720yubVSOfvVMe6/YqO4wkhWGEn4bJJnNpSn0kfzOLuTOQHHiq', '普通用户', 'user@example.com', 1);
INSERT INTO `sys_role` (`role_code`, `role_name`, `description`, `status`) VALUES
('admin', '系统管理员', '拥有系统所有权限', 1),
('user', '普通用户', '拥有基础权限', 1);
INSERT INTO `sys_permission` (`permission_code`, `permission_name`, `resource_type`, `url`, `method`) VALUES
('system:user:list', '用户列表', 'menu', '/system/user/list', 'GET'),
('system:user:add', '添加用户', 'button', '/system/user/add', 'POST'),
('system:user:edit', '编辑用户', 'button', '/system/user/edit', 'PUT'),
('system:user:delete', '删除用户', 'button', '/system/user/delete', 'DELETE'),
('system:role:list', '角色列表', 'menu', '/system/role/list', 'GET'),
('system:role:add', '添加角色', 'button', '/system/role/add', 'POST');
INSERT INTO `sys_user_role` (`user_id`, `role_id`) VALUES
(1, 1),
(2, 2);
INSERT INTO `sys_role_permission` (`role_id`, `permission_id`) VALUES
(1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (1, 6),
(2, 1), (2, 5);
2.2 核心配置类实现
2.2.1 SA-Token配置类
java
/**
* SA-Token配置类
*/
@Configuration
@EnableConfigurationProperties
public class SaTokenConfig {
/**
* 获取StpInterface权限认证接口的实现类
*/
@Bean
public StpInterface stpInterface() {
return new StpInterfaceImpl();
}
/**
* SA-Token全局异常处理
*/
@Bean
public GlobalExceptionHandler globalExceptionHandler() {
return new GlobalExceptionHandler();
}
/**
* SA-Token拦截器配置
*/
@Configuration
public static class SaTokenInterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册Sa-Token拦截器,校验规则为StpUtil.checkLogin()登录校验
registry.addInterceptor(new SaInterceptor(handle -> {
// 指定一条match规则
SaRouter.match("/**") // 拦截所有路由
.notMatch("/auth/login") // 排除登录接口
.notMatch("/auth/register") // 排除注册接口
.notMatch("/auth/captcha") // 排除验证码接口
.notMatch("/doc.html") // 排除swagger文档
.notMatch("/swagger-ui/**") // 排除swagger资源
.notMatch("/swagger-resources/**") // 排除swagger资源
.notMatch("/v2/api-docs") // 排除swagger接口
.notMatch("/v3/api-docs") // 排除swagger接口
.notMatch("/webjars/**") // 排除swagger资源
.notMatch("/favicon.ico") // 排除网站图标
.notMatch("/actuator/**") // 排除监控端点
.check(r -> StpUtil.checkLogin()); // 登录校验
})).addPathPatterns("/**");
}
}
/**
* 自定义JSON序列化方式
*/
@Bean
@Primary
public SaJsonTemplate saJsonTemplate() {
return new SaJsonTemplateForFastjson();
}
}
2.2.2 权限认证接口实现
java
/**
* 自定义权限验证接口扩展
*/
@Component
public class StpInterfaceImpl implements StpInterface {
@Autowired
private UserService userService;
@Autowired
private RoleService roleService;
@Autowired
private PermissionService permissionService;
/**
* 返回一个账号所拥有的权限码集合
*/
@Override
public List<String> getPermissionList(Object loginId, String loginType) {
try {
Long userId = Long.valueOf(loginId.toString());
// 查询用户权限列表
List<String> permissions = permissionService.getPermissionsByUserId(userId);
log.debug("用户[{}]拥有权限: {}", userId, permissions);
return permissions;
} catch (Exception e) {
log.error("获取用户权限列表失败, loginId: {}", loginId, e);
return Collections.emptyList();
}
}
/**
* 返回一个账号所拥有的角色标识集合
*/
@Override
public List<String> getRoleList(Object loginId, String loginType) {
try {
Long userId = Long.valueOf(loginId.toString());
// 查询用户角色列表
List<String> roles = roleService.getRolesByUserId(userId);
log.debug("用户[{}]拥有角色: {}", userId, roles);
return roles;
} catch (Exception e) {
log.error("获取用户角色列表失败, loginId: {}", loginId, e);
return Collections.emptyList();
}
}
}
2.2.3 全局异常处理器
java
/**
* 全局异常处理器
*/
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/**
* 拦截:未登录异常
*/
@ExceptionHandler(NotLoginException.class)
public Result handleNotLoginException(NotLoginException e) {
String message = "";
// 判断场景值,定制化异常信息
switch (e.getType()) {
case NotLoginException.NOT_TOKEN:
message = "未提供Token";
break;
case NotLoginException.INVALID_TOKEN:
message = "Token无效";
break;
case NotLoginException.TOKEN_TIMEOUT:
message = "Token已过期";
break;
case NotLoginException.BE_REPLACED:
message = "Token已被顶下线";
break;
case NotLoginException.KICK_OUT:
message = "Token已被踢下线";
break;
default:
message = "当前会话未登录";
break;
}
log.warn("用户未登录访问受保护资源: {}", message);
return Result.error(401, message);
}
/**
* 拦截:缺少权限异常
*/
@ExceptionHandler(NotPermissionException.class)
public Result handleNotPermissionException(NotPermissionException e) {
log.warn("用户权限不足, 缺少权限: {}", e.getPermission());
return Result.error(403, "权限不足,缺少权限:" + e.getPermission());
}
/**
* 拦截:缺少角色异常
*/
@ExceptionHandler(NotRoleException.class)
public Result handleNotRoleException(NotRoleException e) {
log.warn("用户角色不足, 缺少角色: {}", e.getRole());
return Result.error(403, "角色不足,缺少角色:" + e.getRole());
}
/**
* 拦截:禁用账号异常
*/
@ExceptionHandler(DisableServiceException.class)
public Result handleDisableServiceException(DisableServiceException e) {
log.warn("账号被禁用, 禁用服务: {}, 禁用级别: {}, 禁用时间: {}秒",
e.getService(), e.getLevel(), e.getDisableTime());
return Result.error(423, "账号已被禁用:" + e.getDisableTime() + "秒后解封");
}
/**
* 拦截:二级认证异常
*/
@ExceptionHandler(NotSafeException.class)
public Result handleNotSafeException(NotSafeException e) {
log.warn("二级认证失败: {}", e.getMessage());
return Result.error(901, "请完成二级认证:" + e.getMessage());
}
/**
* 拦截:服务封禁异常
*/
@ExceptionHandler(SaTokenException.class)
public Result handleSaTokenException(SaTokenException e) {
log.error("SA-Token异常: {}", e.getMessage(), e);
return Result.error(500, "系统异常:" + e.getMessage());
}
/**
* 拦截:其他所有异常
*/
@ExceptionHandler(Exception.class)
public Result handleException(Exception e) {
log.error("系统异常: {}", e.getMessage(), e);
return Result.error(500, "系统繁忙,请稍后重试");
}
}
2.3 实体类和数据访问层
2.3.1 实体类定义
java
/**
* 用户实体类
*/
@Entity
@Table(name = "sys_user")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false, length = 50)
private String username;
@Column(nullable = false, length = 100)
private String password;
@Column(length = 50)
private String nickname;
@Column(length = 100)
private String email;
@Column(length = 20)
private String phone;
@Column(length = 200)
private String avatar;
@Column(columnDefinition = "TINYINT DEFAULT 1")
private Integer status;
@CreationTimestamp
@Column(name = "create_time")
private LocalDateTime createTime;
@UpdateTimestamp
@Column(name = "update_time")
private LocalDateTime updateTime;
// 多对多关联角色
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(
name = "sys_user_role",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id")
)
private Set<Role> roles = new HashSet<>();
}
/**
* 角色实体类
*/
@Entity
@Table(name = "sys_role")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false, length = 50)
private String roleCode;
@Column(nullable = false, length = 50)
private String roleName;
@Column(length = 200)
private String description;
@Column(columnDefinition = "TINYINT DEFAULT 1")
private Integer status;
@CreationTimestamp
@Column(name = "create_time")
private LocalDateTime createTime;
@UpdateTimestamp
@Column(name = "update_time")
private LocalDateTime updateTime;
// 多对多关联权限
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(
name = "sys_role_permission",
joinColumns = @JoinColumn(name = "role_id"),
inverseJoinColumns = @JoinColumn(name = "permission_id")
)
private Set<Permission> permissions = new HashSet<>();
}
/**
* 权限实体类
*/
@Entity
@Table(name = "sys_permission")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Permission {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false, length = 100)
private String permissionCode;
@Column(nullable = false, length = 100)
private String permissionName;
@Column(length = 20)
private String resourceType;
@Column(length = 200)
private String url;
@Column(length = 10)
private String method;
@Column(columnDefinition = "BIGINT DEFAULT 0")
private Long parentId;
@Column(columnDefinition = "INT DEFAULT 0")
private Integer sortOrder;
@Column(columnDefinition = "TINYINT DEFAULT 1")
private Integer status;
@CreationTimestamp
@Column(name = "create_time")
private LocalDateTime createTime;
@UpdateTimestamp
@Column(name = "update_time")
private LocalDateTime updateTime;
}
2.3.2 数据访问层实现
java
/**
* 用户数据访问接口
*/
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
/**
* 根据用户名查找用户
*/
Optional<User> findByUsername(String username);
/**
* 根据用户名和状态查找用户
*/
Optional<User> findByUsernameAndStatus(String username, Integer status);
/**
* 检查用户名是否存在
*/
boolean existsByUsername(String username);
/**
* 检查邮箱是否存在
*/
boolean existsByEmail(String email);
/**
* 根据状态查找用户列表
*/
List<User> findByStatus(Integer status);
/**
* 根据用户名模糊查询
*/
@Query("SELECT u FROM User u WHERE u.username LIKE %:username%")
List<User> findByUsernameLike(@Param("username") String username);
}
/**
* 角色数据访问接口
*/
@Repository
public interface RoleRepository extends JpaRepository<Role, Long> {
/**
* 根据角色编码查找角色
*/
Optional<Role> findByRoleCode(String roleCode);
/**
* 根据状态查找角色列表
*/
List<Role> findByStatus(Integer status);
/**
* 检查角色编码是否存在
*/
boolean existsByRoleCode(String roleCode);
/**
* 根据用户ID查找角色列表
*/
@Query("SELECT r FROM Role r JOIN r.users u WHERE u.id = :userId AND r.status = 1")
List<Role> findByUserId(@Param("userId") Long userId);
}
/**
* 权限数据访问接口
*/
@Repository
public interface PermissionRepository extends JpaRepository<Permission, Long> {
/**
* 根据权限编码查找权限
*/
Optional<Permission> findByPermissionCode(String permissionCode);
/**
* 根据状态查找权限列表
*/
List<Permission> findByStatus(Integer status);
/**
* 根据资源类型查找权限列表
*/
List<Permission> findByResourceType(String resourceType);
/**
* 根据父权限ID查找子权限列表
*/
List<Permission> findByParentId(Long parentId);
/**
* 根据用户ID查找权限列表
*/
@Query("SELECT DISTINCT p FROM Permission p " +
"JOIN p.roles r " +
"JOIN r.users u " +
"WHERE u.id = :userId AND p.status = 1")
List<Permission> findByUserId(@Param("userId") Long userId);
/**
* 根据角色ID查找权限列表
*/
@Query("SELECT p FROM Permission p JOIN p.roles r WHERE r.id = :roleId AND p.status = 1")
List<Permission> findByRoleId(@Param("roleId") Long roleId);
}
2.4 业务服务层实现
2.4.1 用户服务实现
java
/**
* 用户服务实现类
*/
@Service
@Transactional
@Slf4j
public class UserServiceImpl implements UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private RoleRepository roleRepository;
@Autowired
private PasswordEncoder passwordEncoder;
/**
* 用户认证
*/
@Override
public User authenticate(String username, String password) {
log.debug("用户认证开始, username: {}", username);
// 查找用户
Optional<User> userOpt = userRepository.findByUsernameAndStatus(username, 1);
if (!userOpt.isPresent()) {
log.warn("用户不存在或已被禁用, username: {}", username);
return null;
}
User user = userOpt.get();
// 验证密码
if (!passwordEncoder.matches(password, user.getPassword())) {
log.warn("用户密码错误, username: {}", username);
return null;
}
log.info("用户认证成功, userId: {}, username: {}", user.getId(), username);
return user;
}
/**
* 根据ID获取用户
*/
@Override
@Transactional(readOnly = true)
public User getById(Long id) {
return userRepository.findById(id).orElse(null);
}
/**
* 根据用户名获取用户
*/
@Override
@Transactional(readOnly = true)
public User getByUsername(String username) {
return userRepository.findByUsername(username).orElse(null);
}
/**
* 创建用户
*/
@Override
public User createUser(UserCreateRequest request) {
log.info("创建用户开始, username: {}", request.getUsername());
// 检查用户名是否已存在
if (userRepository.existsByUsername(request.getUsername())) {
throw new BusinessException("用户名已存在");
}
// 检查邮箱是否已存在
if (StringUtils.hasText(request.getEmail()) &&
userRepository.existsByEmail(request.getEmail())) {
throw new BusinessException("邮箱已存在");
}
// 创建用户对象
User user = User.builder()
.username(request.getUsername())
.password(passwordEncoder.encode(request.getPassword()))
.nickname(request.getNickname())
.email(request.getEmail())
.phone(request.getPhone())
.status(1)
.build();
// 保存用户
user = userRepository.save(user);
// 分配默认角色
if (request.getRoleIds() != null && !request.getRoleIds().isEmpty()) {
assignRoles(user.getId(), request.getRoleIds());
} else {
// 分配默认用户角色
Role defaultRole = roleRepository.findByRoleCode("user").orElse(null);
if (defaultRole != null) {
assignRoles(user.getId(), Collections.singletonList(defaultRole.getId()));
}
}
log.info("用户创建成功, userId: {}, username: {}", user.getId(), user.getUsername());
return user;
}
/**
* 更新用户信息
*/
@Override
public User updateUser(Long userId, UserUpdateRequest request) {
log.info("更新用户信息开始, userId: {}", userId);
User user = userRepository.findById(userId)
.orElseThrow(() -> new BusinessException("用户不存在"));
// 更新基本信息
if (StringUtils.hasText(request.getNickname())) {
user.setNickname(request.getNickname());
}
if (StringUtils.hasText(request.getEmail())) {
// 检查邮箱是否已被其他用户使用
if (userRepository.existsByEmail(request.getEmail()) &&
!request.getEmail().equals(user.getEmail())) {
throw new BusinessException("邮箱已被其他用户使用");
}
user.setEmail(request.getEmail());
}
if (StringUtils.hasText(request.getPhone())) {
user.setPhone(request.getPhone());
}
if (StringUtils.hasText(request.getAvatar())) {
user.setAvatar(request.getAvatar());
}
// 保存更新
user = userRepository.save(user);
log.info("用户信息更新成功, userId: {}", userId);
return user;
}
/**
* 修改密码
*/
@Override
public void changePassword(Long userId, String oldPassword, String newPassword) {
log.info("修改用户密码开始, userId: {}", userId);
User user = userRepository.findById(userId)
.orElseThrow(() -> new BusinessException("用户不存在"));
// 验证旧密码
if (!passwordEncoder.matches(oldPassword, user.getPassword())) {
throw new BusinessException("原密码错误");
}
// 更新密码
user.setPassword(passwordEncoder.encode(newPassword));
userRepository.save(user);
log.info("用户密码修改成功, userId: {}", userId);
}
/**
* 分配角色
*/
@Override
public void assignRoles(Long userId, List<Long> roleIds) {
log.info("分配用户角色开始, userId: {}, roleIds: {}", userId, roleIds);
User user = userRepository.findById(userId)
.orElseThrow(() -> new BusinessException("用户不存在"));
// 清除现有角色
user.getRoles().clear();
// 分配新角色
if (roleIds != null && !roleIds.isEmpty()) {
List<Role> roles = roleRepository.findAllById(roleIds);
user.getRoles().addAll(roles);
}
userRepository.save(user);
log.info("用户角色分配成功, userId: {}, roleCount: {}", userId, user.getRoles().size());
}
/**
* 启用/禁用用户
*/
@Override
public void updateUserStatus(Long userId, Integer status) {
log.info("更新用户状态开始, userId: {}, status: {}", userId, status);
User user = userRepository.findById(userId)
.orElseThrow(() -> new BusinessException("用户不存在"));
user.setStatus(status);
userRepository.save(user);
// 如果是禁用用户,则踢下线
if (status == 0) {
StpUtil.kickout(userId);
log.info("用户已被踢下线, userId: {}", userId);
}
log.info("用户状态更新成功, userId: {}, status: {}", userId, status);
}
/**
* 删除用户
*/
@Override
public void deleteUser(Long userId) {
log.info("删除用户开始, userId: {}", userId);
User user = userRepository.findById(userId)
.orElseThrow(() -> new BusinessException("用户不存在"));
// 踢下线
StpUtil.kickout(userId);
// 删除用户
userRepository.delete(user);
log.info("用户删除成功, userId: {}", userId);
}
/**
* 获取用户列表
*/
@Override
@Transactional(readOnly = true)
public List<User> getAllUsers() {
return userRepository.findAll();
}
/**
* 分页获取用户列表
*/
@Override
@Transactional(readOnly = true)
public Page<User> getUserPage(Pageable pageable) {
return userRepository.findAll(pageable);
}
}
2.4.2 角色服务实现
java
/**
* 角色服务实现类
*/
@Service
@Transactional
@Slf4j
public class RoleServiceImpl implements RoleService {
@Autowired
private RoleRepository roleRepository;
@Autowired
private PermissionRepository permissionRepository;
/**
* 根据用户ID获取角色列表
*/
@Override
@Transactional(readOnly = true)
public List<String> getRolesByUserId(Long userId) {
List<Role> roles = roleRepository.findByUserId(userId);
return roles.stream()
.map(Role::getRoleCode)
.collect(Collectors.toList());
}
/**
* 创建角色
*/
@Override
public Role createRole(RoleCreateRequest request) {
log.info("创建角色开始, roleCode: {}", request.getRoleCode());
// 检查角色编码是否已存在
if (roleRepository.existsByRoleCode(request.getRoleCode())) {
throw new BusinessException("角色编码已存在");
}
// 创建角色对象
Role role = Role.builder()
.roleCode(request.getRoleCode())
.roleName(request.getRoleName())
.description(request.getDescription())
.status(1)
.build();
// 保存角色
role = roleRepository.save(role);
// 分配权限
if (request.getPermissionIds() != null && !request.getPermissionIds().isEmpty()) {
assignPermissions(role.getId(), request.getPermissionIds());
}
log.info("角色创建成功, roleId: {}, roleCode: {}", role.getId(), role.getRoleCode());
return role;
}
/**
* 分配权限
*/
@Override
public void assignPermissions(Long roleId, List<Long> permissionIds) {
log.info("分配角色权限开始, roleId: {}, permissionIds: {}", roleId, permissionIds);
Role role = roleRepository.findById(roleId)
.orElseThrow(() -> new BusinessException("角色不存在"));
// 清除现有权限
role.getPermissions().clear();
// 分配新权限
if (permissionIds != null && !permissionIds.isEmpty()) {
List<Permission> permissions = permissionRepository.findAllById(permissionIds);
role.getPermissions().addAll(permissions);
}
roleRepository.save(role);
log.info("角色权限分配成功, roleId: {}, permissionCount: {}", roleId, role.getPermissions().size());
}
}
2.4.3 权限服务实现
java
/**
* 权限服务实现类
*/
@Service
@Transactional
@Slf4j
public class PermissionServiceImpl implements PermissionService {
@Autowired
private PermissionRepository permissionRepository;
/**
* 根据用户ID获取权限列表
*/
@Override
@Transactional(readOnly = true)
public List<String> getPermissionsByUserId(Long userId) {
List<Permission> permissions = permissionRepository.findByUserId(userId);
return permissions.stream()
.map(Permission::getPermissionCode)
.collect(Collectors.toList());
}
/**
* 获取所有权限
*/
@Override
@Transactional(readOnly = true)
public List<Permission> getAllPermissions() {
return permissionRepository.findByStatus(1);
}
/**
* 构建权限树
*/
@Override
@Transactional(readOnly = true)
public List<PermissionTreeNode> buildPermissionTree() {
List<Permission> allPermissions = getAllPermissions();
// 构建权限树
Map<Long, PermissionTreeNode> nodeMap = new HashMap<>();
List<PermissionTreeNode> rootNodes = new ArrayList<>();
// 创建所有节点
for (Permission permission : allPermissions) {
PermissionTreeNode node = PermissionTreeNode.builder()
.id(permission.getId())
.permissionCode(permission.getPermissionCode())
.permissionName(permission.getPermissionName())
.resourceType(permission.getResourceType())
.url(permission.getUrl())
.method(permission.getMethod())
.parentId(permission.getParentId())
.sortOrder(permission.getSortOrder())
.children(new ArrayList<>())
.build();
nodeMap.put(permission.getId(), node);
}
// 构建树形结构
for (PermissionTreeNode node : nodeMap.values()) {
if (node.getParentId() == 0) {
rootNodes.add(node);
} else {
PermissionTreeNode parent = nodeMap.get(node.getParentId());
if (parent != null) {
parent.getChildren().add(node);
}
}
}
// 排序
sortPermissionTree(rootNodes);
return rootNodes;
}
private void sortPermissionTree(List<PermissionTreeNode> nodes) {
nodes.sort(Comparator.comparing(PermissionTreeNode::getSortOrder));
for (PermissionTreeNode node : nodes) {
if (!node.getChildren().isEmpty()) {
sortPermissionTree(node.getChildren());
}
}
}
}
第三章:权限认证与授权实战
3.1 登录认证实现
3.1.1 登录控制器实现
java
/**
* 认证控制器
*/
@RestController
@RequestMapping("/auth")
@Slf4j
public class AuthController {
@Autowired
private UserService userService;
@Autowired
private CaptchaService captchaService;
/**
* 用户登录
*/
@PostMapping("/login")
public Result login(@RequestBody @Valid LoginRequest request) {
log.info("用户登录请求, username: {}", request.getUsername());
// 验证验证码
if (!captchaService.verifyCaptcha(request.getCaptchaKey(), request.getCaptchaCode())) {
return Result.error("验证码错误");
}
// 用户认证
User user = userService.authenticate(request.getUsername(), request.getPassword());
if (user == null) {
return Result.error("用户名或密码错误");
}
// 检查用户状态
if (user.getStatus() != 1) {
return Result.error("账号已被禁用");
}
// 执行登录
StpUtil.login(user.getId(), new SaLoginModel()
.setDevice(request.getDevice())
.setTimeout(request.getRememberMe() ? 30 * 24 * 3600 : -1) // 记住我30天
.setIsLastingCookie(request.getRememberMe()));
// 获取Token信息
SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
// 构建登录响应
LoginResponse response = LoginResponse.builder()
.tokenName(tokenInfo.getTokenName())
.tokenValue(tokenInfo.getTokenValue())
.isLogin(tokenInfo.getIsLogin())
.loginId(tokenInfo.getLoginId())
.loginType(tokenInfo.getLoginType())
.tokenTimeout(tokenInfo.getTokenTimeout())
.sessionTimeout(tokenInfo.getSessionTimeout())
.tokenSessionTimeout(tokenInfo.getTokenSessionTimeout())
.tokenActivityTimeout(tokenInfo.getTokenActivityTimeout())
.loginDevice(tokenInfo.getLoginDevice())
.tag(tokenInfo.getTag())
.userInfo(UserInfo.builder()
.id(user.getId())
.username(user.getUsername())
.nickname(user.getNickname())
.email(user.getEmail())
.avatar(user.getAvatar())
.build())
.build();
log.info("用户登录成功, userId: {}, username: {}, tokenValue: {}",
user.getId(), user.getUsername(), tokenInfo.getTokenValue());
return Result.success(response);
}
/**
* 用户注销
*/
@PostMapping("/logout")
public Result logout() {
Object loginId = StpUtil.getLoginId();
StpUtil.logout();
log.info("用户注销成功, userId: {}", loginId);
return Result.success("注销成功");
}
/**
* 获取当前用户信息
*/
@GetMapping("/userinfo")
public Result getUserInfo() {
// 检查登录状态
StpUtil.checkLogin();
// 获取当前用户ID
Long userId = StpUtil.getLoginIdAsLong();
// 查询用户信息
User user = userService.getById(userId);
if (user == null) {
return Result.error("用户不存在");
}
// 获取用户权限和角色
List<String> permissions = StpUtil.getPermissionList();
List<String> roles = StpUtil.getRoleList();
// 构建用户信息响应
UserInfoResponse response = UserInfoResponse.builder()
.id(user.getId())
.username(user.getUsername())
.nickname(user.getNickname())
.email(user.getEmail())
.phone(user.getPhone())
.avatar(user.getAvatar())
.status(user.getStatus())
.createTime(user.getCreateTime())
.permissions(permissions)
.roles(roles)
.build();
return Result.success(response);
}
/**
* 刷新Token
*/
@PostMapping("/refresh")
public Result refreshToken() {
// 检查登录状态
StpUtil.checkLogin();
// 续签Token
StpUtil.renewTimeout(7200); // 续签2小时
// 获取新的Token信息
SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
return Result.success(tokenInfo);
}
/**
* 获取验证码
*/
@GetMapping("/captcha")
public Result getCaptcha() {
CaptchaResponse captcha = captchaService.generateCaptcha();
return Result.success(captcha);
}
}
3.1.2 验证码服务实现
java
/**
* 验证码服务实现
*/
@Service
@Slf4j
public class CaptchaServiceImpl implements CaptchaService {
@Autowired
private RedisTemplate<String, String> redisTemplate;
private static final String CAPTCHA_PREFIX = "captcha:";
private static final int CAPTCHA_EXPIRE_TIME = 300; // 5分钟
private static final int CAPTCHA_LENGTH = 4;
/**
* 生成验证码
*/
@Override
public CaptchaResponse generateCaptcha() {
// 生成验证码key
String captchaKey = UUID.randomUUID().toString();
// 生成验证码内容
String captchaCode = generateRandomCode(CAPTCHA_LENGTH);
// 生成验证码图片
String captchaImage = generateCaptchaImage(captchaCode);
// 存储到Redis
redisTemplate.opsForValue().set(
CAPTCHA_PREFIX + captchaKey,
captchaCode.toLowerCase(),
CAPTCHA_EXPIRE_TIME,
TimeUnit.SECONDS
);
log.debug("生成验证码, key: {}, code: {}", captchaKey, captchaCode);
return CaptchaResponse.builder()
.captchaKey(captchaKey)
.captchaImage(captchaImage)
.expireTime(CAPTCHA_EXPIRE_TIME)
.build();
}
/**
* 验证验证码
*/
@Override
public boolean verifyCaptcha(String captchaKey, String captchaCode) {
if (!StringUtils.hasText(captchaKey) || !StringUtils.hasText(captchaCode)) {
return false;
}
String redisKey = CAPTCHA_PREFIX + captchaKey;
String storedCode = redisTemplate.opsForValue().get(redisKey);
if (storedCode == null) {
log.warn("验证码已过期或不存在, key: {}", captchaKey);
return false;
}
// 验证后删除验证码
redisTemplate.delete(redisKey);
boolean isValid = storedCode.equalsIgnoreCase(captchaCode);
log.debug("验证码校验结果, key: {}, code: {}, result: {}", captchaKey, captchaCode, isValid);
return isValid;
}
private String generateRandomCode(int length) {
String chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
StringBuilder sb = new StringBuilder();
Random random = new Random();
for (int i = 0; i < length; i++) {
sb.append(chars.charAt(random.nextInt(chars.length())));
}
return sb.toString();
}
private String generateCaptchaImage(String code) {
// 创建图片
int width = 120;
int height = 40;
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g = image.createGraphics();
// 设置背景色
g.setColor(Color.WHITE);
g.fillRect(0, 0, width, height);
// 设置字体
g.setFont(new Font("Arial", Font.BOLD, 20));
// 绘制验证码
Random random = new Random();
for (int i = 0; i < code.length(); i++) {
g.setColor(new Color(random.nextInt(255), random.nextInt(255), random.nextInt(255)));
g.drawString(String.valueOf(code.charAt(i)), 20 + i * 20, 25);
}
// 添加干扰线
for (int i = 0; i < 5; i++) {
g.setColor(new Color(random.nextInt(255), random.nextInt(255), random.nextInt(255)));
g.drawLine(random.nextInt(width), random.nextInt(height),
random.nextInt(width), random.nextInt(height));
}
g.dispose();
// 转换为Base64
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(image, "png", baos);
byte[] imageBytes = baos.toByteArray();
return "data:image/png;base64," + Base64.getEncoder().encodeToString(imageBytes);
} catch (IOException e) {
log.error("生成验证码图片失败", e);
return null;
}
}
}
## 第四章:高级特性与扩展应用
### 4.1 单点登录(SSO)实现
#### 4.1.1 SSO基础配置
SA-Token提供了强大的单点登录功能,支持同域和跨域的SSO实现。
```java
/**
* SSO配置类
*/
@Configuration
public class SsoConfig {
/**
* SSO相关配置
*/
@Bean
@ConfigurationProperties(prefix = "sa-token.sso")
public SaSsoConfig getSaSsoConfig() {
return new SaSsoConfig()
// SSO-Server端 统一认证地址
.setAuthUrl("http://sa-sso-server.com:9000/sso/auth")
// SSO-Server端 ticket校验地址
.setCheckTicketUrl("http://sa-sso-server.com:9000/sso/checkTicket")
// SSO-Server端 单点注销地址
.setSloUrl("http://sa-sso-server.com:9000/sso/signout")
// 当前Client端 单点注销回调URL
.setSsoLogoutCall("http://sa-sso-client1.com:9001/sso/logoutCall")
// 是否打开单点注销功能
.setIsSlo(true);
}
}
4.1.2 SSO-Server端实现
java
/**
* SSO认证服务端控制器
*/
@RestController
@RequestMapping("/sso")
@Slf4j
public class SsoServerController {
/**
* SSO统一认证页面
*/
@GetMapping("/auth")
public SaResult auth(String redirect, String mode, HttpServletRequest request) {
log.info("SSO统一认证,redirect={}, mode={}", redirect, mode);
// 如果已经登录,则直接重定向到Client端
if (StpUtil.isLogin()) {
return SaSsoUtil.buildRedirectUrl(StpUtil.getLoginId(), redirect);
}
// 未登录,显示登录页面
return SaResult.ok().setData(buildLoginPage(redirect, mode));
}
/**
* 处理登录请求
*/
@PostMapping("/doLogin")
public SaResult doLogin(String username, String password, String redirect) {
log.info("SSO登录处理,username={}, redirect={}", username, redirect);
// 验证用户名密码
if (validateUser(username, password)) {
// 登录成功,生成ticket并重定向
StpUtil.login(username);
return SaSsoUtil.buildRedirectUrl(username, redirect);
}
return SaResult.error("用户名或密码错误");
}
/**
* 校验ticket
*/
@GetMapping("/checkTicket")
public SaResult checkTicket(String ticket, String ssoLogoutCall) {
log.info("校验ticket,ticket={}, ssoLogoutCall={}", ticket, ssoLogoutCall);
// 校验ticket,获取账号id
Object loginId = SaSsoUtil.checkTicket(ticket);
if (loginId != null) {
// 注册此客户端的单点注销回调URL
SaSsoUtil.registerClient(loginId, ssoLogoutCall);
return SaResult.data(loginId);
}
return SaResult.error("无效的ticket");
}
/**
* 单点注销
*/
@GetMapping("/signout")
public SaResult signout(String loginId, String secretkey) {
log.info("SSO单点注销,loginId={}", loginId);
// 校验秘钥
SaSsoUtil.checkSecretkey(secretkey);
// 遍历通知所有Client端注销
SaSsoUtil.singleLogout(loginId);
return SaResult.ok("单点注销成功");
}
/**
* 验证用户凭据
*/
private boolean validateUser(String username, String password) {
// 这里应该连接数据库验证用户信息
// 为了演示,简单验证
return "admin".equals(username) && "123456".equals(password);
}
/**
* 构建登录页面HTML
*/
private String buildLoginPage(String redirect, String mode) {
return """
<!DOCTYPE html>
<html>
<head>
<title>SSO统一认证中心</title>
<meta charset="utf-8">
<style>
.login-form { width: 300px; margin: 100px auto; padding: 20px; border: 1px solid #ddd; }
.form-item { margin: 10px 0; }
.form-item input { width: 100%; padding: 8px; }
.btn { width: 100%; padding: 10px; background: #007bff; color: white; border: none; cursor: pointer; }
</style>
</head>
<body>
<div class="login-form">
<h2>统一认证中心</h2>
<form action="/sso/doLogin" method="post">
<input type="hidden" name="redirect" value="%s">
<div class="form-item">
<input type="text" name="username" placeholder="用户名" required>
</div>
<div class="form-item">
<input type="password" name="password" placeholder="密码" required>
</div>
<div class="form-item">
<button type="submit" class="btn">登录</button>
</div>
</form>
</div>
</body>
</html>
""".formatted(redirect != null ? redirect : "");
}
}
4.1.3 SSO-Client端实现
java
/**
* SSO客户端控制器
*/
@RestController
@RequestMapping("/sso")
@Slf4j
public class SsoClientController {
/**
* 首页
*/
@GetMapping("/")
public SaResult index() {
String loginId = (String) StpUtil.getLoginIdDefaultNull();
if (loginId != null) {
return SaResult.ok("欢迎用户:" + loginId);
}
return SaResult.ok("当前未登录").setData("<a href='/sso/login'>点击登录</a>");
}
/**
* 发起登录
*/
@GetMapping("/login")
public SaResult login(String back) {
// 构建授权地址
String authUrl = SaSsoUtil.buildAuthUrl();
log.info("重定向到SSO认证中心:{}", authUrl);
return SaResult.ok().setData("redirect:" + authUrl);
}
/**
* SSO登录回调
*/
@GetMapping("/login/callback")
public SaResult loginCallback(String ticket, String back) {
log.info("SSO登录回调,ticket={}, back={}", ticket, back);
// 根据ticket进行登录
Object loginId = SaSsoUtil.checkTicket(ticket, "/sso/logoutCall");
if (loginId != null) {
StpUtil.login(loginId);
return SaResult.ok("登录成功").setData("用户ID:" + loginId);
}
return SaResult.error("登录失败");
}
/**
* 单点注销回调
*/
@GetMapping("/logoutCall")
public SaResult logoutCall(String loginId, String secretkey) {
log.info("收到单点注销回调,loginId={}", loginId);
// 校验秘钥
SaSsoUtil.checkSecretkey(secretkey);
// 注销当前用户
StpUtil.logout(loginId);
return SaResult.ok("注销成功");
}
/**
* 查询登录状态
*/
@GetMapping("/isLogin")
public SaResult isLogin() {
boolean isLogin = StpUtil.isLogin();
Object loginId = StpUtil.getLoginIdDefaultNull();
return SaResult.ok()
.set("isLogin", isLogin)
.set("loginId", loginId)
.set("tokenInfo", StpUtil.getTokenInfo());
}
}
4.2 OAuth2.0集成

4.2.1 OAuth2配置
SA-Token提供了完整的OAuth2.0支持,可以快速构建OAuth2认证服务器。
java
/**
* OAuth2配置类
*/
@Configuration
public class OAuth2Config {
/**
* OAuth2配置
*/
@Bean
@ConfigurationProperties(prefix = "sa-token.oauth2")
public SaOAuth2Config oauth2Config() {
return new SaOAuth2Config()
// 是否打开模式:授权码(Authorization Code)
.setIsCode(true)
// 是否打开模式:隐藏式(Implicit)
.setIsImplicit(true)
// 是否打开模式:密码式(Password)
.setIsPassword(true)
// 是否打开模式:客户端凭证(Client Credentials)
.setIsClient(true)
// 是否在每次Refresh-Token刷新Access-Token时,产生一个新的Refresh-Token
.setIsNewRefresh(true);
}
/**
* OAuth2数据加载器
*/
@Component
public static class OAuth2DataLoader implements SaOAuth2DataLoader {
@Autowired
private ClientService clientService;
@Autowired
private UserService userService;
/**
* 根据 client_id 获取 Client 信息
*/
@Override
public SaClientModel getClientModel(String clientId) {
// 从数据库查询客户端信息
Client client = clientService.getByClientId(clientId);
if (client == null) {
return null;
}
return new SaClientModel()
.setClientId(client.getClientId())
.setClientSecret(client.getClientSecret())
.setAllowUrl(client.getAllowUrl())
.setContractScope(client.getContractScope())
.setIsAutoMode(client.getIsAutoMode());
}
/**
* 根据 ClientId 和 LoginId 获取openid
*/
@Override
public String getOpenid(String clientId, Object loginId) {
// 可以根据 clientId 和 loginId 生成openid
return DigestUtils.md5Hex(clientId + ":" + loginId);
}
/**
* 校验:指定 LoginId 是否对指定 Client 授权给定 Scope
*/
@Override
public boolean isGrant(Object loginId, String clientId, String scope) {
// 查询用户是否已授权
return userService.hasGranted(String.valueOf(loginId), clientId, scope);
}
/**
* 保存:指定 LoginId 对指定 Client 授权给定 Scope
*/
@Override
public void saveGrant(Object loginId, String clientId, String scope) {
// 保存用户授权信息
userService.saveGrant(String.valueOf(loginId), clientId, scope);
}
}
}
4.3 微服务网关鉴权
4.3.1 网关鉴权配置
在微服务架构中,SA-Token可以作为统一的鉴权组件集成到网关中。
java
/**
* 网关鉴权配置
*/
@Configuration
@EnableWebFluxSecurity
public class GatewayAuthConfig {
/**
* 注册Sa-Token全局过滤器
*/
@Bean
public SaReactorFilter getSaReactorFilter() {
return new SaReactorFilter()
// 拦截地址
.addInclude("/**")
// 排除地址
.addExclude("/favicon.ico", "/actuator/**")
// 鉴权方法:每次访问进入
.setAuth(obj -> {
// 登录校验 -- 拦截所有路由,并排除/user/doLogin用于开放登录
SaRouter.match("/**", "/auth/login", r -> StpUtil.checkLogin());
// 权限认证 -- 不同模块, 校验不同权限
SaRouter.match("/user/**", r -> StpUtil.checkPermission("user"));
SaRouter.match("/admin/**", r -> StpUtil.checkPermission("admin"));
SaRouter.match("/goods/**", r -> StpUtil.checkPermission("goods"));
// 角色认证 -- 拦截以 admin 开头的路由,必须具备 admin 角色或者 super-admin 角色才可以通过认证
SaRouter.match("/admin/**", r -> StpUtil.checkRoleOr("admin", "super-admin"));
})
// 异常处理方法:每次setAuth函数出现异常时进入
.setError(e -> {
return SaResult.error(e.getMessage());
});
}
/**
* 配置跨域
*/
@Bean
public CorsWebFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedMethod("*");
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
source.registerCorsConfiguration("/**", config);
return new CorsWebFilter(source);
}
}
4.4 多账户体系支持
4.4.1 多账户配置
SA-Token支持多账户体系,可以同时管理用户账户和管理员账户。
java
/**
* 多账户体系配置
*/
@Configuration
public class MultiAccountConfig {
/**
* 用户账户StpLogic
*/
@Bean("userStpLogic")
public StpLogic getUserStpLogic() {
return new StpLogic("user") {
// 重写获取权限列表的方法
@Override
public List<String> getPermissionList(Object loginId, String loginType) {
// 查询用户权限
return userService.getPermissionsByUserId(String.valueOf(loginId));
}
// 重写获取角色列表的方法
@Override
public List<String> getRoleList(Object loginId, String loginType) {
// 查询用户角色
return userService.getRolesByUserId(String.valueOf(loginId));
}
};
}
/**
* 管理员账户StpLogic
*/
@Bean("adminStpLogic")
public StpLogic getAdminStpLogic() {
return new StpLogic("admin") {
@Override
public List<String> getPermissionList(Object loginId, String loginType) {
// 查询管理员权限
return adminService.getPermissionsByAdminId(String.valueOf(loginId));
}
@Override
public List<String> getRoleList(Object loginId, String loginType) {
// 查询管理员角色
return adminService.getRolesByAdminId(String.valueOf(loginId));
}
};
}
}
第五章:生产环境最佳实践
5.1 性能优化策略
5.1.1 Token存储优化
在高并发场景下,Token的存储和检索性能至关重要。
java
/**
* Token存储优化配置
*/
@Configuration
public class TokenStorageOptimization {
/**
* Redis连接池优化
*/
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
// 连接池配置
GenericObjectPoolConfig<StatefulRedisConnection<String, String>> poolConfig =
new GenericObjectPoolConfig<>();
poolConfig.setMaxTotal(200);
poolConfig.setMaxIdle(50);
poolConfig.setMinIdle(10);
poolConfig.setTestOnBorrow(true);
poolConfig.setTestOnReturn(true);
poolConfig.setTestWhileIdle(true);
// Lettuce连接工厂
LettucePoolingClientConfiguration clientConfig = LettucePoolingClientConfiguration.builder()
.poolConfig(poolConfig)
.commandTimeout(Duration.ofSeconds(5))
.shutdownTimeout(Duration.ofSeconds(10))
.build();
RedisStandaloneConfiguration serverConfig = new RedisStandaloneConfiguration();
serverConfig.setHostName("localhost");
serverConfig.setPort(6379);
serverConfig.setDatabase(0);
return new LettuceConnectionFactory(serverConfig, clientConfig);
}
/**
* 自定义Token存储实现
*/
@Component
public class OptimizedSaTokenDao implements SaTokenDao {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private final String TOKEN_PREFIX = "satoken:";
@Override
public String get(String key) {
try {
Object value = redisTemplate.opsForValue().get(TOKEN_PREFIX + key);
return value != null ? value.toString() : null;
} catch (Exception e) {
log.error("Redis获取Token失败,key={}", key, e);
return null;
}
}
@Override
public void set(String key, String value, long timeout) {
try {
if (timeout > 0) {
redisTemplate.opsForValue().set(TOKEN_PREFIX + key, value,
Duration.ofSeconds(timeout));
} else {
redisTemplate.opsForValue().set(TOKEN_PREFIX + key, value);
}
} catch (Exception e) {
log.error("Redis设置Token失败,key={}, value={}", key, value, e);
}
}
@Override
public void update(String key, String value) {
try {
Long expire = redisTemplate.getExpire(TOKEN_PREFIX + key);
if (expire != null && expire > 0) {
redisTemplate.opsForValue().set(TOKEN_PREFIX + key, value,
Duration.ofSeconds(expire));
} else {
redisTemplate.opsForValue().set(TOKEN_PREFIX + key, value);
}
} catch (Exception e) {
log.error("Redis更新Token失败,key={}, value={}", key, value, e);
}
}
@Override
public void delete(String key) {
try {
redisTemplate.delete(TOKEN_PREFIX + key);
} catch (Exception e) {
log.error("Redis删除Token失败,key={}", key, e);
}
}
@Override
public long getTimeout(String key) {
try {
Long expire = redisTemplate.getExpire(TOKEN_PREFIX + key);
return expire != null ? expire : -1;
} catch (Exception e) {
log.error("Redis获取Token过期时间失败,key={}", key, e);
return -1;
}
}
@Override
public void updateTimeout(String key, long timeout) {
try {
redisTemplate.expire(TOKEN_PREFIX + key, Duration.ofSeconds(timeout));
} catch (Exception e) {
log.error("Redis更新Token过期时间失败,key={}, timeout={}", key, timeout, e);
}
}
}
}
5.1.2 权限缓存优化
java
/**
* 权限缓存优化服务
*/
@Service
@Slf4j
public class PermissionCacheService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private UserService userService;
private static final String PERMISSION_CACHE_PREFIX = "permission:";
private static final String ROLE_CACHE_PREFIX = "role:";
private static final int CACHE_EXPIRE_SECONDS = 3600; // 1小时
/**
* 获取用户权限列表(带缓存)
*/
public List<String> getUserPermissions(String userId) {
String cacheKey = PERMISSION_CACHE_PREFIX + userId;
try {
// 先从缓存获取
List<String> permissions = (List<String>) redisTemplate.opsForValue().get(cacheKey);
if (permissions != null) {
log.debug("从缓存获取用户权限,userId={}", userId);
return permissions;
}
// 缓存未命中,从数据库查询
permissions = userService.getPermissionsByUserId(userId);
// 写入缓存
redisTemplate.opsForValue().set(cacheKey, permissions,
Duration.ofSeconds(CACHE_EXPIRE_SECONDS));
log.debug("从数据库查询用户权限并缓存,userId={}, permissions={}", userId, permissions);
return permissions;
} catch (Exception e) {
log.error("获取用户权限失败,userId={}", userId, e);
// 缓存异常时直接查询数据库
return userService.getPermissionsByUserId(userId);
}
}
/**
* 获取用户角色列表(带缓存)
*/
public List<String> getUserRoles(String userId) {
String cacheKey = ROLE_CACHE_PREFIX + userId;
try {
List<String> roles = (List<String>) redisTemplate.opsForValue().get(cacheKey);
if (roles != null) {
log.debug("从缓存获取用户角色,userId={}", userId);
return roles;
}
roles = userService.getRolesByUserId(userId);
redisTemplate.opsForValue().set(cacheKey, roles,
Duration.ofSeconds(CACHE_EXPIRE_SECONDS));
log.debug("从数据库查询用户角色并缓存,userId={}, roles={}", userId, roles);
return roles;
} catch (Exception e) {
log.error("获取用户角色失败,userId={}", userId, e);
return userService.getRolesByUserId(userId);
}
}
/**
* 清除用户权限缓存
*/
public void clearUserPermissionCache(String userId) {
try {
redisTemplate.delete(PERMISSION_CACHE_PREFIX + userId);
redisTemplate.delete(ROLE_CACHE_PREFIX + userId);
log.info("清除用户权限缓存,userId={}", userId);
} catch (Exception e) {
log.error("清除用户权限缓存失败,userId={}", userId, e);
}
}
/**
* 批量预热权限缓存
*/
@Async
public void preloadPermissionCache(List<String> userIds) {
log.info("开始预热权限缓存,用户数量={}", userIds.size());
for (String userId : userIds) {
try {
getUserPermissions(userId);
getUserRoles(userId);
Thread.sleep(10); // 避免过快请求
} catch (Exception e) {
log.error("预热用户权限缓存失败,userId={}", userId, e);
}
}
log.info("权限缓存预热完成");
}
}
5.2 安全加固措施
5.2.1 Token安全增强
java
/**
* Token安全增强配置
*/
@Configuration
public class TokenSecurityConfig {
/**
* 自定义Token生成策略
*/
@Bean
public SaTokenAction saTokenAction() {
return new SaTokenAction() {
@Override
public String createToken(Object loginId, String loginType) {
// 生成更安全的Token
String timestamp = String.valueOf(System.currentTimeMillis());
String randomStr = UUID.randomUUID().toString().replace("-", "");
String userAgent = SaHolder.getRequest().getHeader("User-Agent");
String clientIp = SaFoxUtil.getClientIP();
// 组合信息进行加密
String tokenData = loginId + ":" + loginType + ":" + timestamp +
":" + randomStr + ":" + DigestUtils.md5Hex(userAgent + clientIp);
// 使用AES加密
return AESUtil.encrypt(tokenData, getTokenSecret());
}
@Override
public Object getLoginIdByToken(String tokenValue) {
try {
// 解密Token
String tokenData = AESUtil.decrypt(tokenValue, getTokenSecret());
String[] parts = tokenData.split(":");
if (parts.length >= 5) {
String loginId = parts[0];
String timestamp = parts[2];
// 检查Token时效性(额外的时间校验)
long createTime = Long.parseLong(timestamp);
long maxAge = 24 * 60 * 60 * 1000; // 24小时
if (System.currentTimeMillis() - createTime > maxAge) {
throw new SaTokenException("Token已过期");
}
return loginId;
}
} catch (Exception e) {
log.error("Token解析失败", e);
}
return null;
}
};
}
/**
* Token签名密钥
*/
private String getTokenSecret() {
// 从配置文件或环境变量获取密钥
return "your-secret-key-here";
}
/**
* IP白名单验证
*/
@Component
public class IpWhitelistValidator {
private final Set<String> whitelistIps = new HashSet<>();
@PostConstruct
public void init() {
// 从配置文件加载IP白名单
whitelistIps.add("127.0.0.1");
whitelistIps.add("192.168.1.0/24");
}
public boolean isAllowed(String clientIp) {
return whitelistIps.contains(clientIp) || isInSubnet(clientIp);
}
private boolean isInSubnet(String clientIp) {
// 实现子网匹配逻辑
for (String subnet : whitelistIps) {
if (subnet.contains("/") && matchSubnet(clientIp, subnet)) {
return true;
}
}
return false;
}
private boolean matchSubnet(String ip, String subnet) {
// 子网匹配实现
try {
String[] subnetParts = subnet.split("/");
String networkIp = subnetParts[0];
int prefixLength = Integer.parseInt(subnetParts[1]);
InetAddress targetAddr = InetAddress.getByName(ip);
InetAddress networkAddr = InetAddress.getByName(networkIp);
byte[] targetBytes = targetAddr.getAddress();
byte[] networkBytes = networkAddr.getAddress();
int bytesToCheck = prefixLength / 8;
int bitsToCheck = prefixLength % 8;
// 检查完整字节
for (int i = 0; i < bytesToCheck; i++) {
if (targetBytes[i] != networkBytes[i]) {
return false;
}
}
// 检查剩余位
if (bitsToCheck > 0 && bytesToCheck < targetBytes.length) {
int mask = 0xFF << (8 - bitsToCheck);
return (targetBytes[bytesToCheck] & mask) == (networkBytes[bytesToCheck] & mask);
}
return true;
} catch (Exception e) {
log.error("子网匹配失败", e);
return false;
}
}
}
}
5.2.2 防攻击策略
java
/**
* 防攻击策略实现
*/
@Component
@Slf4j
public class SecurityDefenseService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private static final String LOGIN_ATTEMPT_PREFIX = "login_attempt:";
private static final String RATE_LIMIT_PREFIX = "rate_limit:";
private static final int MAX_LOGIN_ATTEMPTS = 5;
private static final int LOGIN_LOCK_DURATION = 300; // 5分钟
private static final int RATE_LIMIT_REQUESTS = 100;
private static final int RATE_LIMIT_WINDOW = 60; // 1分钟
/**
* 检查登录尝试次数
*/
public boolean checkLoginAttempts(String clientIp, String username) {
String key = LOGIN_ATTEMPT_PREFIX + clientIp + ":" + username;
try {
Integer attempts = (Integer) redisTemplate.opsForValue().get(key);
if (attempts != null && attempts >= MAX_LOGIN_ATTEMPTS) {
log.warn("登录尝试次数超限,IP={}, username={}, attempts={}", clientIp, username, attempts);
return false;
}
return true;
} catch (Exception e) {
log.error("检查登录尝试次数失败", e);
return true; // 异常时允许登录
}
}
/**
* 记录登录失败
*/
public void recordLoginFailure(String clientIp, String username) {
String key = LOGIN_ATTEMPT_PREFIX + clientIp + ":" + username;
try {
Integer attempts = (Integer) redisTemplate.opsForValue().get(key);
attempts = attempts != null ? attempts + 1 : 1;
redisTemplate.opsForValue().set(key, attempts, Duration.ofSeconds(LOGIN_LOCK_DURATION));
log.info("记录登录失败,IP={}, username={}, attempts={}", clientIp, username, attempts);
} catch (Exception e) {
log.error("记录登录失败次数异常", e);
}
}
/**
* 清除登录失败记录
*/
public void clearLoginFailures(String clientIp, String username) {
String key = LOGIN_ATTEMPT_PREFIX + clientIp + ":" + username;
try {
redisTemplate.delete(key);
log.info("清除登录失败记录,IP={}, username={}", clientIp, username);
} catch (Exception e) {
log.error("清除登录失败记录异常", e);
}
}
/**
* 检查请求频率限制
*/
public boolean checkRateLimit(String clientIp) {
String key = RATE_LIMIT_PREFIX + clientIp;
try {
Integer requests = (Integer) redisTemplate.opsForValue().get(key);
if (requests != null && requests >= RATE_LIMIT_REQUESTS) {
log.warn("请求频率超限,IP={}, requests={}", clientIp, requests);
return false;
}
// 增加请求计数
if (requests == null) {
redisTemplate.opsForValue().set(key, 1, Duration.ofSeconds(RATE_LIMIT_WINDOW));
} else {
redisTemplate.opsForValue().increment(key);
}
return true;
} catch (Exception e) {
log.error("检查请求频率限制失败", e);
return true; // 异常时允许请求
}
}
/**
* SQL注入检测
*/
public boolean detectSqlInjection(String input) {
if (input == null || input.isEmpty()) {
return false;
}
String[] sqlKeywords = {
"select", "insert", "update", "delete", "drop", "create", "alter",
"union", "exec", "execute", "script", "javascript", "vbscript",
"onload", "onerror", "onclick", "'", "\"", ";", "--", "/*", "*/"
};
String lowerInput = input.toLowerCase();
for (String keyword : sqlKeywords) {
if (lowerInput.contains(keyword)) {
log.warn("检测到可疑SQL注入尝试,input={}", input);
return true;
}
}
return false;
}
/**
* XSS攻击检测
*/
public boolean detectXssAttack(String input) {
if (input == null || input.isEmpty()) {
return false;
}
String[] xssPatterns = {
"<script", "</script>", "javascript:", "vbscript:", "onload=",
"onerror=", "onclick=", "onmouseover=", "onfocus=", "onblur=",
"alert(", "confirm(", "prompt(", "document.cookie", "document.write"
};
String lowerInput = input.toLowerCase();
for (String pattern : xssPatterns) {
if (lowerInput.contains(pattern)) {
log.warn("检测到可疑XSS攻击尝试,input={}", input);
return true;
}
}
return false;
}
}
5.3 监控与日志
5.3.1 认证监控
java
/**
* 认证监控服务
*/
@Service
@Slf4j
public class AuthMonitorService {
@Autowired
private MeterRegistry meterRegistry;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private final Counter loginSuccessCounter;
private final Counter loginFailureCounter;
private final Counter logoutCounter;
private final Timer authenticationTimer;
public AuthMonitorService(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
this.loginSuccessCounter = Counter.builder("auth.login.success")
.description("成功登录次数")
.register(meterRegistry);
this.loginFailureCounter = Counter.builder("auth.login.failure")
.description("登录失败次数")
.register(meterRegistry);
this.logoutCounter = Counter.builder("auth.logout")
.description("注销次数")
.register(meterRegistry);
this.authenticationTimer = Timer.builder("auth.authentication.duration")
.description("认证耗时")
.register(meterRegistry);
}
/**
* 记录登录成功
*/
public void recordLoginSuccess(String userId, String clientIp, String userAgent) {
loginSuccessCounter.increment();
// 记录详细日志
log.info("用户登录成功 - userId={}, clientIp={}, userAgent={}", userId, clientIp, userAgent);
// 记录登录历史
recordLoginHistory(userId, clientIp, userAgent, true);
// 更新在线用户统计
updateOnlineUserStats(userId, true);
}
/**
* 记录登录失败
*/
public void recordLoginFailure(String username, String clientIp, String reason) {
loginFailureCounter.increment(Tags.of("reason", reason));
log.warn("用户登录失败 - username={}, clientIp={}, reason={}", username, clientIp, reason);
// 记录失败历史
recordLoginHistory(username, clientIp, null, false);
}
/**
* 记录注销
*/
public void recordLogout(String userId, String clientIp) {
logoutCounter.increment();
log.info("用户注销 - userId={}, clientIp={}", userId, clientIp);
// 更新在线用户统计
updateOnlineUserStats(userId, false);
}
/**
* 记录认证耗时
*/
public void recordAuthenticationTime(Duration duration) {
authenticationTimer.record(duration);
}
/**
* 记录登录历史
*/
private void recordLoginHistory(String userId, String clientIp, String userAgent, boolean success) {
try {
LoginHistory history = new LoginHistory();
history.setUserId(userId);
history.setClientIp(clientIp);
history.setUserAgent(userAgent);
history.setSuccess(success);
history.setLoginTime(LocalDateTime.now());
// 异步保存到数据库
CompletableFuture.runAsync(() -> {
// 保存登录历史逻辑
saveLoginHistory(history);
});
} catch (Exception e) {
log.error("记录登录历史失败", e);
}
}
/**
* 更新在线用户统计
*/
private void updateOnlineUserStats(String userId, boolean online) {
try {
String key = "online_users";
if (online) {
redisTemplate.opsForSet().add(key, userId);
} else {
redisTemplate.opsForSet().remove(key, userId);
}
// 更新Micrometer指标
Long onlineCount = redisTemplate.opsForSet().size(key);
Gauge.builder("auth.online.users")
.description("在线用户数")
.register(meterRegistry, this, obj -> onlineCount != null ? onlineCount : 0);
} catch (Exception e) {
log.error("更新在线用户统计失败", e);
}
}
/**
* 获取认证统计信息
*/
public AuthStatistics getAuthStatistics() {
try {
AuthStatistics stats = new AuthStatistics();
// 从Micrometer获取统计数据
stats.setLoginSuccessCount((long) loginSuccessCounter.count());
stats.setLoginFailureCount((long) loginFailureCounter.count());
stats.setLogoutCount((long) logoutCounter.count());
stats.setAverageAuthTime(authenticationTimer.mean(TimeUnit.MILLISECONDS));
// 获取在线用户数
Long onlineUsers = redisTemplate.opsForSet().size("online_users");
stats.setOnlineUserCount(onlineUsers != null ? onlineUsers : 0);
return stats;
} catch (Exception e) {
log.error("获取认证统计信息失败", e);
return new AuthStatistics();
}
}
/**
* 保存登录历史(实际实现)
*/
private void saveLoginHistory(LoginHistory history) {
// 实际的数据库保存逻辑
log.debug("保存登录历史:{}", history);
}
}
5.3.2 审计日志
java
/**
* 审计日志服务
*/
@Service
@Slf4j
public class AuditLogService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@EventListener
public void handleLoginEvent(LoginEvent event) {
AuditLog auditLog = AuditLog.builder()
.userId(event.getUserId())
.action("LOGIN")
.resource("AUTH")
.clientIp(event.getClientIp())
.userAgent(event.getUserAgent())
.timestamp(LocalDateTime.now())
.success(event.isSuccess())
.details(event.getDetails())
.build();
saveAuditLog(auditLog);
}
@EventListener
public void handlePermissionCheckEvent(PermissionCheckEvent event) {
AuditLog auditLog = AuditLog.builder()
.userId(event.getUserId())
.action("PERMISSION_CHECK")
.resource(event.getResource())
.permission(event.getPermission())
.clientIp(SaFoxUtil.getClientIP())
.timestamp(LocalDateTime.now())
.success(event.isSuccess())
.details(event.getDetails())
.build();
saveAuditLog(auditLog);
}
/**
* 保存审计日志
*/
private void saveAuditLog(AuditLog auditLog) {
try {
// 异步保存到数据库
CompletableFuture.runAsync(() -> {
// 实际的数据库保存逻辑
log.info("审计日志:{}", auditLog);
});
// 同时保存到Redis用于实时查询
String key = "audit_logs:" + LocalDate.now().toString();
redisTemplate.opsForList().leftPush(key, auditLog);
redisTemplate.expire(key, Duration.ofDays(7)); // 保留7天
} catch (Exception e) {
log.error("保存审计日志失败", e);
}
}
/**
* 查询审计日志
*/
public List<AuditLog> queryAuditLogs(String userId, LocalDate date, String action) {
try {
String key = "audit_logs:" + date.toString();
List<Object> logs = redisTemplate.opsForList().range(key, 0, -1);
return logs.stream()
.map(obj -> (AuditLog) obj)
.filter(log -> userId == null || userId.equals(log.getUserId()))
.filter(log -> action == null || action.equals(log.getAction()))
.collect(Collectors.toList());
} catch (Exception e) {
log.error("查询审计日志失败", e);
return Collections.emptyList();
}
}
}
第六章:总结与展望
6.1 知识点回顾
通过本文的深入学习,我们全面掌握了SA-Token权限认证框架的核心技术和实践应用。让我们回顾一下主要的知识点:
6.1.1 核心概念与特性
SA-Token作为一个轻量级的Java权限认证框架,具有以下核心优势:
- 简洁的API设计 :
StpUtil.login()、StpUtil.checkLogin()等简单易用的API - 丰富的功能特性:支持登录认证、权限验证、角色验证、踢人下线、会话管理等
- 灵活的集成方式:与SpringBoot、SpringCloud等主流框架无缝集成
- 强大的扩展能力:支持自定义Token生成策略、存储方式、权限验证逻辑等
6.1.2 技术架构要点
java
/**
* SA-Token技术架构核心组件回顾
*/
public class SaTokenArchitectureReview {
/**
* 1. 核心组件
*/
// StpUtil - 权限认证工具类
// SaTokenDao - Token存储接口
// SaTokenConfig - 框架配置类
// StpInterface - 权限数据接口
/**
* 2. 认证流程
*/
public void authenticationFlow() {
// 用户登录 -> 生成Token -> 存储会话 -> 返回Token
StpUtil.login(userId);
// 请求验证 -> 解析Token -> 校验会话 -> 检查权限
StpUtil.checkLogin();
StpUtil.checkPermission("user:list");
}
/**
* 3. 扩展机制
*/
// 自定义Token生成:实现SaTokenAction接口
// 自定义存储方式:实现SaTokenDao接口
// 自定义权限验证:实现StpInterface接口
// 自定义配置策略:继承SaTokenConfig类
}
6.1.3 实战应用总结
在实际项目中,我们学会了如何:
- 基础集成:SpringBoot项目中快速集成SA-Token
- 权限设计:构建RBAC权限模型,实现细粒度权限控制
- 高级特性:单点登录、OAuth2.0、微服务网关鉴权等企业级应用
- 性能优化:Token存储优化、权限缓存、连接池配置等
- 安全加固:防暴力破解、SQL注入检测、XSS防护等
- 监控运维:认证监控、审计日志、性能指标等
6.2 最佳实践总结
6.2.1 开发规范
java
/**
* SA-Token开发最佳实践
*/
@Component
public class SaTokenBestPractices {
/**
* 1. 统一异常处理
*/
@ExceptionHandler(NotLoginException.class)
public SaResult handleNotLoginException(NotLoginException e) {
return SaResult.error("请先登录").setCode(401);
}
/**
* 2. 权限注解使用
*/
@SaCheckPermission("user:list")
@GetMapping("/users")
public SaResult getUsers() {
// 业务逻辑
return SaResult.ok();
}
/**
* 3. 会话管理
*/
public void sessionManagement() {
// 设置会话数据
StpUtil.getSession().set("userInfo", userInfo);
// 获取会话数据
UserInfo info = (UserInfo) StpUtil.getSession().get("userInfo");
// 清理会话
StpUtil.getSession().clear();
}
/**
* 4. 多端登录控制
*/
public void multiDeviceLogin() {
// 允许多端登录
StpUtil.login(userId, "PC");
StpUtil.login(userId, "MOBILE");
// 踢掉其他端
StpUtil.kickout(userId, "PC");
}
}
6.2.2 性能优化建议
- 合理配置Token过期时间:平衡安全性和用户体验
- 使用Redis集群:提高Token存储的可用性和性能
- 权限缓存策略:减少数据库查询,提升响应速度
- 异步日志记录:避免影响主业务流程性能
- 连接池优化:合理配置数据库和Redis连接池参数
6.2.3 安全防护要点
- Token安全:使用强加密算法,定期轮换密钥
- 传输安全:HTTPS传输,避免Token泄露
- 存储安全:Redis密码保护,网络隔离
- 访问控制:IP白名单,请求频率限制
- 审计监控:完整的操作日志,异常告警机制
6.3 技术发展趋势
6.3.1 云原生权限管理
随着云原生技术的发展,权限管理也在向云原生方向演进:
yaml
# Kubernetes RBAC集成示例
apiVersion: v1
kind: ConfigMap
metadata:
name: satoken-config
data:
application.yml: |
sa-token:
token-name: satoken
timeout: 2592000
is-concurrent: true
token-style: uuid
# 云原生配置
jwt:
secret-key: ${JWT_SECRET:default-secret}
redis:
host: ${REDIS_HOST:redis-service}
port: ${REDIS_PORT:6379}
password: ${REDIS_PASSWORD:}
6.3.2 零信任安全架构
零信任安全模型要求对每个请求都进行验证:
java
/**
* 零信任安全验证
*/
@Component
public class ZeroTrustValidator {
public boolean validateRequest(HttpServletRequest request) {
// 1. 身份验证
if (!StpUtil.isLogin()) {
return false;
}
// 2. 设备验证
if (!validateDevice(request)) {
return false;
}
// 3. 网络验证
if (!validateNetwork(request)) {
return false;
}
// 4. 行为验证
if (!validateBehavior(request)) {
return false;
}
return true;
}
}
6.3.3 AI驱动的智能权限
未来的权限系统将更加智能化:
java
/**
* AI智能权限推荐
*/
@Service
public class IntelligentPermissionService {
/**
* 基于用户行为的权限推荐
*/
public List<String> recommendPermissions(String userId) {
// 分析用户历史行为
UserBehavior behavior = analyzeUserBehavior(userId);
// 机器学习模型预测
List<String> recommendations = mlModel.predict(behavior);
return recommendations;
}
/**
* 异常行为检测
*/
public boolean detectAnomalousAccess(String userId, String resource) {
// 获取用户正常访问模式
AccessPattern normalPattern = getUserAccessPattern(userId);
// 当前访问行为
AccessBehavior currentBehavior = getCurrentBehavior(userId, resource);
// AI模型检测异常
return anomalyDetectionModel.isAnomalous(normalPattern, currentBehavior);
}
}
6.4 扩展阅读与学习资源
6.4.1 官方文档与社区
6.4.2 相关技术书籍
- 《Spring Security实战》- 深入理解Spring Security权限框架
- 《OAuth 2.0实战》- 掌握OAuth2.0协议和实现
- 《微服务安全架构与实践》- 微服务环境下的安全设计
- 《Redis实战》- 深入学习Redis在权限系统中的应用
6.4.3 在线学习资源
- 慕课网:SA-Token实战课程
- 极客时间:权限系统设计专栏
- B站:SA-Token作者孔明老师的视频教程
- 掘金社区:SA-Token技术文章和实践分享
6.5 实践练习与思考
6.5.1 动手实践项目
为了更好地掌握SA-Token,建议完成以下实践项目:
-
基础项目:构建一个简单的用户管理系统
- 用户注册、登录、注销
- 基本的权限控制
- 会话管理
-
进阶项目:开发企业级权限管理平台
- RBAC权限模型
- 动态权限配置
- 多租户支持
-
高级项目:微服务权限网关
- 统一认证中心
- 服务间鉴权
- 分布式会话管理
6.5.2 思考讨论题
- 架构设计:如何在大型分布式系统中设计高可用的权限服务?
- 性能优化:面对百万级用户的权限验证,如何优化性能?
- 安全防护:如何防范权限系统面临的各种安全威胁?
- 技术选型:SA-Token与Spring Security的适用场景对比?
6.5.3 开源贡献
鼓励读者参与SA-Token开源社区建设:
- 提交Bug报告和功能建议
- 贡献代码和文档
- 分享使用经验和最佳实践
- 帮助其他开发者解决问题
6.6 结语
SA-Token作为一个优秀的权限认证框架,以其简洁、强大、灵活的特性,为Java开发者提供了一个高效的权限管理解决方案。通过本文的深入学习,相信读者已经掌握了SA-Token的核心技术和实践应用。
在实际项目开发中,权限管理不仅仅是技术问题,更是业务安全的重要保障。希望读者能够结合具体的业务场景,灵活运用SA-Token的各种特性,构建安全、高效、易维护的权限系统。
技术在不断发展,权限管理领域也在持续演进。保持学习的热情,关注技术发展趋势,积极参与开源社区,是每个技术人员成长的必经之路。
最后,感谢SA-Token开源团队的辛勤付出,感谢所有为权限管理技术发展做出贡献的开发者们。让我们一起推动Java权限认证技术的发展,为构建更安全的软件系统而努力!
如果这篇文章对你有帮助,请不要忘记点赞👍、收藏⭐、分享📤!你的支持是我创作的最大动力!
有任何问题欢迎在评论区讨论,我会及时回复大家!让我们一起在技术的道路上不断前行! 🚀