Sa-Token
概述
Tio-Boot 已经内置对 Sa-Token 的支持,为处理身份验证和授权提供了一种高效的方式。虽然 Tio-Boot 自带此支持,但仍需手动添加 sa-token-core
依赖。此外,如果希望将 Token 存储在 Redis 中,还需要添加 java-db
、jedis
和 fst
依赖。
使用 Sa-Token 实现登录功能
将 Token 存储到浏览器
添加依赖
首先,在项目的 pom.xml
文件中添加以下依赖,以引入必要的库:
<!-- Sa-Token 核心库 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-core</artifactId>
<version>1.37.0</version>
</dependency>
配置 Sa-Token
接下来,设置 Sa-Token 的配置类,负责初始化相关配置,如 Token 的存储方式、有效期和 Cookie 设置等。
import com.litongjava.jfinal.aop.annotation.AConfiguration;
import com.litongjava.jfinal.aop.annotation.AInitialization;
import com.litongjava.tio.boot.satoken.SaTokenContextForTio;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.config.SaCookieConfig;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.context.SaTokenContext;
import cn.dev33.satoken.util.SaTokenConsts;
@AConfiguration
public class SaTokenConfiguration {
@Initialization
public void config() {
// 初始化 Sa-Token 上下文
SaTokenContext saTokenContext = new SaTokenContextForTio();
// 设置 Cookie 配置,例如启用 HttpOnly 属性
SaCookieConfig saCookieConfig = new SaCookieConfig();
saCookieConfig.setHttpOnly(true);
// 初始化并配置 Sa-Token 主配置
SaTokenConfig saTokenConfig = new SaTokenConfig();
saTokenConfig.setTokenStyle(SaTokenConsts.TOKEN_STYLE_SIMPLE_UUID);
saTokenConfig.setActiveTimeout(50 * 60); // 设置活动超时时间为 50 分钟
saTokenConfig.setIsShare(false);
saTokenConfig.setTokenName("token"); // 设置 Token 的名称
saTokenConfig.setIsWriteHeader(true); // 将 Token 写入响应头
saTokenConfig.setIsReadHeader(true); // 从请求头中读取 Token
saTokenConfig.setCookie(saCookieConfig);
// 应用配置到 Sa-Token 管理器
SaManager.setConfig(saTokenConfig);
SaManager.setSaTokenContext(saTokenContext);
}
}
添加拦截器
为了验证请求,需要设置一个拦截器,根据配置的规则允许或阻止请求。
import com.litongjava.jfinal.aop.annotation.AConfiguration;
import com.litongjava.jfinal.aop.annotation.AInitialization;
import com.litongjava.tio.boot.http.interceptor.HttpInterceptorModel;
import com.litongjava.tio.boot.http.interceptor.HttpInteceptorConfigure;
import com.litongjava.tio.boot.satoken.SaTokenInterceptor;
import com.litongjava.tio.boot.server.TioBootServer;
@AConfiguration
public class InterceptorConfiguration {
@Initialization
public void config() {
// 创建 SaToken 拦截器实例
SaTokenInterceptor saTokenInterceptor = new SaTokenInterceptor();
HttpInterceptorModel model = new HttpInterceptorModel();
model.setInterceptor(saTokenInterceptor);
model.addblockeUrl("/**"); // 拦截所有路由
model.addAllowUrls("/register/*", "/auth/*"); // 设置例外路由
HttpInteceptorConfigure serverInteceptorConfigure = new HttpInteceptorConfigure();
serverInteceptorConfigure.add(model);
// 将拦截器配置添加到 Tio 服务器
TioBootServer.me().setServerInteceptorConfigure(serverInteceptorConfigure);
}
}
import java.util.function.Predicate;
import com.litongjava.tio.boot.http.TioRequestContext;
import com.litongjava.tio.http.common.HttpRequest;
import com.litongjava.tio.http.common.HttpResponse;
import com.litongjava.tio.http.common.HttpResponseStatus;
import com.litongjava.tio.http.common.RequestLine;
import com.litongjava.tio.http.server.intf.HttpRequestInterceptor;
import com.litongjava.tio.http.server.util.Resps;
import cn.dev33.satoken.stp.StpUtil;
public class SaTokenInterceptor implements HttpRequestInterceptor {
private Object body = null;
private Predicate<String> validateTokenLogic;
public AuthTokenInterceptor() {
}
public AuthTokenInterceptor(Object body) {
this.body = body;
}
public AuthTokenInterceptor(Predicate<String> validateTokenLogic) {
this.validateTokenLogic = validateTokenLogic;
}
public AuthTokenInterceptor(Object body, Predicate<String> validateTokenLogic) {
this.body = body;
this.validateTokenLogic = validateTokenLogic;
}
@Override
public HttpResponse doBeforeHandler(HttpRequest request, RequestLine requestLine, HttpResponse responseFromCache) {
if (validateTokenLogic != null) {
String token = request.getHeader("token");
if (validateTokenLogic.test(token)) {
return null;
}
String authorization = request.getHeader("authorization");
if (validateTokenLogic.test(authorization)) {
return null;
}
}
if (StpUtil.isLogin()) {
return null;
} else {
HttpResponse response = TioRequestContext.getResponse();
response.setStatus(HttpResponseStatus.C401);
if (body != null) {
Resps.json(response, body);
}
return response;
}
}
@Override
public void doAfterHandler(HttpRequest request, RequestLine requestLine, HttpResponse response, long cost)
throws Exception {
// TODO Auto-generated method stub
}
}
实现登录和登出逻辑
在控制器中实现登录和登出的逻辑,使应用能够处理用户身份验证。
package com.litongjava.tio.web.hello;
import com.litongjava.tio.boot.http.TioControllerContext;
import com.litongjava.tio.http.common.HttpResponse;
import com.litongjava.annotation.RequestPath;
import com.litongjava.tio.http.server.util.Resps;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import lombok.extern.slf4j.Slf4j;
@RequestPath("/auth")
@Slf4j
public class AuthController {
public Object doLogin() {
StpUtil.login("litong");
HttpResponse response = TioControllerContext.getResponse();
log.info("response: {}", response);
return response;
}
public HttpResponse logout() {
StpUtil.logout();
SaResult result = SaResult.ok("ok");
HttpResponse response = TioControllerContext.getResponse();
return Resps.json(response, result);
}
public boolean validateToken() {
try {
StpUtil.checkLogin();
return true;
} catch (Exception e) {
return false;
}
}
}
验证访问权限
创建 IndexController
来验证用户的登录状态,未登录的用户将无法访问此控制器的路由。
package com.litongjava.tio.web.hello;
import com.litongjava.annotation.RequestPath;
@RequestPath("/")
public class IndexController {
@RequestPath()
public String index() {
return "index";
}
}
测试应用
运行应用后,可以通过以下 URL 测试登录、登出和验证 Token 的功能:
- 登录:
http://localhost:8080/auth/doLogin?username=zhang&password=123456
- 登出:
http://localhost:8080/auth/logout
- 验证 Token:
http://localhost:8080/auth/validateToken
- 访问首页:
http://localhost:8080/
登录
登录成功后,响应头会包含以下信息:
token: c5edff10712b4c49b64da5b910ecdb53
或
Set-Cookie: token=c5edff10712b4c49b64da5b910ecdb53; Max-Age=2592000; Expires=Tue, 9 Apr 2024 23:07:50 -1000; Path=/; HttpOnly
可以使用任意一种方式进行 Token 验证。
验证
在 Cookie 中添加 token=c5edff10712b4c49b64da5b910ecdb53
进行验证:
GET /auth/validateToken HTTP/1.1
Cookie: token=c5edff10712b4c49b64da5b910ecdb53; PHPSESSID=e11a6f19b3504fec816a3d07862341ec
也支持在请求头中使用 token
参数。
生成 JWT Token
添加依赖
添加以下依赖以启用 Sa-Token 与 JWT 的集成:
<!-- Sa-Token 整合 JWT -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-jwt</artifactId>
<version>1.37.0</version>
</dependency>
配置 JWT
修改 SaTokenConfiguration
,添加 JWT 相关配置:
saTokenConfig.setJwtSecretKey("asdasdasifhueuiwyurfewbfjsdafjk");
saTokenConfig.setTokenPrefix("Bearer");
StpLogicJwtForSimple stpLogicJwtForSimple = new StpLogicJwtForSimple();
StpUtil.setStpLogic(stpLogicJwtForSimple);
生成的 Token 示例:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpblR5cGUiOiJsb2dpbiIsImxvZ2luSWQiOjI4NTk3NTExMjExMzkxMDgwLCJyblN0ciI6Ik05S2JuZ0ZwR2U3cFFpTUdLMnZTRms4bUw2cEhpVWJvIn0.aUxyQAIGhtGgO8IRi7_uLPaEeI3kHdkZLy7zeNInixw
其他配置保持不变,可以正常登录。
将 Token 存入 Redis
为什么将 Token 存储在 Redis 中
将访问令牌(Token)存储在 Redis 中的主要原因有:
易于管理过期时间:Token 通常有有效期,Redis 提供了设置键值对过期时间的功能,自动管理 Token 过期,节省存储空间。
支持分布式系统:在微服务或分布式系统中,使用 Redis 存储 Token,确保所有组件都能实时访问最新的 Token 信息,便于系统扩展和维护。
可扩展性:即使后端服务器重启,Token 存储在 Redis 中,重启后仍能识别前端传递的 Token。
添加依赖
<!-- Java-DB 插件,用于与 Redis 交互 -->
<dependency>
<groupId>com.litongjava</groupId>
<artifactId>java-db</artifactId>
<version>${java-db.version}</version>
</dependency>
<!-- Jedis,Redis 的 Java 客户端 -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.3.1</version>
</dependency>
<!-- FST 序列化工具,用于对象与字节流的转换 -->
<dependency>
<groupId>de.ruedigermoeller</groupId>
<artifactId>fst</artifactId>
<version>2.57</version> <!-- 注意:更高版本不支持 JDK 8 -->
</dependency>
配置 Redis 连接
创建一个配置类来启动和配置 Redis。
app.properties
redis.host=127.0.0.1
redis.port=6789
redis.password=fdsafgh
redis.database=2
redis.timeout=15000
redis.cacheName=main
import com.litongjava.annotation.AConfiguration;
import com.litongjava.annotation.Initialization;
import com.litongjava.redis.Redis;
import com.litongjava.redis.RedisDb;
import com.litongjava.redis.RedisPlugin;
import com.litongjava.tio.boot.server.TioBootServer;
import com.litongjava.tio.utils.environment.EnvUtils;
@AConfiguration
public class RedisDbConfig {
@Initialization
public RedisPlugin redisPlugin() {
String redisHost = EnvUtils.get("redis.host", "127.0.0.1");
int redisPort = EnvUtils.getInt("redis.port", 6379);
String redisPassword = EnvUtils.getStr("redis.password");
int redistimeout = EnvUtils.getInt("redis.timeout", 60);
int redisDatabase = EnvUtils.getInt("redis.database", 0);
// 创建并启动 Redis 插件
RedisPlugin redisPlugin = new RedisPlugin("main", redisHost, redisPort, redistimeout, redisPassword, redisDatabase);
redisPlugin.start();
TioBootServer.me().addDestroyMethod(redisPlugin::stop);
// 测试连接
RedisDb redisDb = Redis.use("main");
redisDb.getJedis().connect();
return redisPlugin;
}
}
使用 Sa-Token 的 Redis 存储
在 Sa-Token 的配置中指定使用 Redis 来存储 Token。
import com.litongjava.jfinal.plugin.satoken.SaTokenDaoRedis;
// 在 Sa-Token 配置类中添加
String cacheName = EnvUtils.get("redis.cacheName");
SaManager.setSaTokenDao(new SaTokenDaoRedis(cacheName));
通过这种方式,Sa-Token 将使用 Redis 作为 Token 的存储介质,提高应用的可扩展性和性能。
增加 API Key
可以添加一个 API 用户,设置 Token 永不过期,允许外部系统使用此 Token 调用本系统。
saTokenConfig.setDynamicActiveTimeout(true);
// 添加一个 API 用户,设置 Token 永不过期
SaLoginModel loginModel = new SaLoginModel();
loginModel.setTimeout(SaTokenDao.NEVER_EXPIRE);
loginModel.setActiveTimeout(SaTokenDao.NEVER_EXPIRE);
loginModel.setToken(saAdminToken);
StpUtil.createLoginSession("0", loginModel);
总结
将 Sa-Token 整合到 Tio-Boot 中,并使用 Redis 作为存储,为应用提供了一种强大且灵活的方式来处理用户认证和会话管理。通过上述步骤,您可以轻松地在 Tio-Boot 项目中实现基于 Token 的身份验证系统,并利用 Redis 的高性能和持久化特性来管理这些 Token。
在实际部署应用时,根据环境调整 Redis 的连接配置,确保应用的安全性和性能。同时,可能需要对登录逻辑进行进一步扩展,以支持用户验证、密码加密等功能。
通过以上步骤,您已完成 Sa-Token 在 Tio-Boot 中的整合,能够处理复杂的身份验证和授权场景,为您的应用增加了一层安全保护。