Skip to content

Sa-Token ​

概述 ​

Tio-Boot 已经内置对 Sa-Token 的支持,为处理身份验证和授权提供了一种高效的方式。虽然 Tio-Boot 自带此支持,但仍需手动添加 sa-token-core 依赖。此外,如果希望将 Token 存储在 Redis 中,还需要添加 java-db、jedis 和 fst 依赖。

使用 Sa-Token 实现登录功能 ​

将 Token 存储到浏览器 ​

添加依赖 ​

首先,在项目的 pom.xml 文件中添加以下依赖,以引入必要的库:

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 设置等。

java
import nexus.io.annotation.AConfiguration;
import nexus.io.annotation.Initialization;
import nexus.io.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);
  }
}

添加拦截器 ​

为了验证请求,需要设置一个拦截器,根据配置的规则允许或阻止请求。

java
import nexus.io.annotation.AConfiguration;
import nexus.io.annotation.Initialization;
import nexus.io.tio.boot.http.interceptor.HttpInterceptorModel;
import nexus.io.tio.boot.http.interceptor.HttpInteceptorConfigure;
import nexus.io.tio.boot.satoken.SaTokenInterceptor;
import nexus.io.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);
  }
}
java
import java.util.function.Predicate;

import nexus.io.tio.boot.http.TioRequestContext;
import nexus.io.tio.http.common.HttpRequest;
import nexus.io.tio.http.common.HttpResponse;
import nexus.io.tio.http.common.HttpResponseStatus;
import nexus.io.tio.http.common.RequestLine;
import nexus.io.tio.http.server.intf.HttpRequestInterceptor;
import nexus.io.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 UserTokenInterceptor() {

  }

  public UserTokenInterceptor(Object body) {
    this.body = body;
  }

  public UserTokenInterceptor(Predicate<String> validateTokenLogic) {
    this.validateTokenLogic = validateTokenLogic;
  }

  public UserTokenInterceptor(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

  }
}

实现登录和登出逻辑 ​

在控制器中实现登录和登出的逻辑,使应用能够处理用户身份验证。

java
package nexus.io.tio.web.hello;

import nexus.io.tio.boot.http.TioControllerContext;
import nexus.io.tio.http.common.HttpResponse;
import nexus.io.annotation.RequestPath;
import nexus.io.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 来验证用户的登录状态,未登录的用户将无法访问此控制器的路由。

java
package nexus.io.tio.web.hello;

import nexus.io.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 的集成:

xml
<!-- Sa-Token 整合 JWT -->
<dependency>
  <groupId>cn.dev33</groupId>
  <artifactId>sa-token-jwt</artifactId>
  <version>1.37.0</version>
</dependency>

配置 JWT ​

修改 SaTokenConfiguration,添加 JWT 相关配置:

java
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。

添加依赖 ​

xml
<!-- Java-DB 插件,用于与 Redis 交互 -->
<dependency>
  <groupId>nexus.io</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
java
import nexus.io.annotation.AConfiguration;
import nexus.io.annotation.Initialization;
import nexus.io.redis.Redis;
import nexus.io.redis.RedisDb;
import nexus.io.redis.RedisPlugin;
import nexus.io.tio.boot.server.TioBootServer;
import nexus.io.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();
    HookCan.me().addDestroyMethod(redisPlugin::stop);

    // 测试连接
    RedisDb redisDb = Redis.use("main");
    redisDb.getJedis().connect();

    return redisPlugin;
  }
}

使用 Sa-Token 的 Redis 存储 ​

在 Sa-Token 的配置中指定使用 Redis 来存储 Token。

java
import nexus.io.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 调用本系统。

java
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 中的整合,能够处理复杂的身份验证和授权场景,为您的应用增加了一层安全保护。

MIT Licensed