Skip to content

SaToken 与 SpringSecurity 切换

forge 的鉴权能力被拆成「核心契约」与「具体实现」两层:核心包 forge-auth 只定义接口与流程,真正签发、校验令牌的活儿交给 AuthProvider 的实现。框架内置两套实现,你引入哪个 jar,就用哪套实现,无需改一行代码。

本篇以宠物商店为背景,演示如何在 SpringSecurity(无状态 JWT)与 Sa-Token 两套方案之间切换,以及切换时需要注意的配置差异。涉及「为什么这样设计」的部分,请移步设计篇 鉴权实现的 SPI 选择

两套实现一句话对比

维度forge-auth-spring-securityforge-auth-sa-token
底层依赖JJWT(0.12.6)+ spring-boot-starter-securitysa-token-spring-boot3-starter(1.39.0)+ sa-token-jwt
令牌形态纯无状态 HS256 JWTSa-Token 令牌,有状态/无状态可切换
LoginUser 来源解析 JWT 声明回填userId 回源数据库重建
主动失效不支持(invalidate 空实现)支持(StpUtil.logoutByTokenValue
JWT 编解码下沉到 ForgeJwtTokenCodec,独立持有密钥由 Sa-Token 内部管理

怎么选

追求纯无状态、轻量、与 Spring Security 原生集成,选 spring-security 实现;需要主动踢人下线、后续要用权限注解等更多能力,选 sa-token 实现。

第一步:引入核心包

无论选哪套实现,forge-auth 都是必须的,它提供 AuthProvider 契约、@Public 注解、拦截器与默认管理员初始化等基础设施。

xml
<dependency>
    <groupId>cn.cvking.forge</groupId>
    <artifactId>forge-auth</artifactId>
</dependency>

第二步:二选一引入实现包

必须恰好引一个

两套实现都通过 SPI 贡献 AuthProvider。框架在启动时强制要求「有且仅有一个」实现,引零个或引两个都会 fail-fast 启动失败(详见文末反例)。

方案 A:SpringSecurity(无状态 JWT)

xml
<dependency>
    <groupId>cn.cvking.forge</groupId>
    <artifactId>forge-auth-spring-security</artifactId>
</dependency>

方案 B:Sa-Token

xml
<dependency>
    <groupId>cn.cvking.forge</groupId>
    <artifactId>forge-auth-sa-token</artifactId>
</dependency>

引入即生效——这两个实现分别被标注为 @SPI.Service("spring-security")@SPI.Service("sa-token"),框架通过 Spider.getAllExtensions(AuthProvider.class) 把当前 classpath 里的唯一实现挑出来使用。

第三步:配置 forge.auth.*

通用配置键对两套实现都生效;forge.auth.jwt.* 一节主要供 spring-security 实现使用。

yaml
forge:
  auth:
    enabled: true                 # 是否启用鉴权,默认 true
    tokenHeader: "Authorization"  # 令牌请求头,默认 Authorization
    tokenPrefix: "Bearer "        # 令牌前缀,默认 "Bearer "
    defaultAdminUsername: "admin" # 默认管理员用户名
    defaultAdminPassword: "admin" # 默认管理员密码
    white-list:                   # 额外放行的白名单路径
      - /v1/api/petshop/public/**
    jwt:                          # 仅 spring-security 实现消费
      secret: "your-production-256bit-secret-key-here-32bytes-minimum"
      expireMinutes: 720          # 令牌有效期(分钟),默认 12 小时

生产环境务必覆盖 JWT 密钥

spring-security 实现的 forge.auth.jwt.secret 默认值是内置占位密钥 forge-auth-default-secret-please-change-it-in-production-env。HS256 要求密钥长度不小于 32 字节,生产环境必须自行覆盖为足够长的随机串。

第四步:切换实现

切换实现的动作非常克制——只换 jar,不动代码、不动业务配置:

  1. pom.xml 移除当前实现包;
  2. 引入另一套实现包;
  3. 重新构建启动。

由于宠物商店的登录端点、@Public 放行、AuthUser/AuthRole 等 RBAC 模型都定义在 forge-auth 核心包,切换实现后这些都不受影响。例如给宠物列表加个免登录的「在售统计」端点,无论用哪套实现,写法都一样:

java
@RestController
@RequestMapping("/v1/api/petshop/pet")
public class PetController {

    @Public
    @GetMapping("/stats")
    public Map<String, Object> onSaleStats() {
        // 标注 @Public 后无需携带令牌即可访问
        return petService.countByStatus();
    }
}

验证结果

application.yml 打开鉴权模块的 debug 日志:

yaml
logging:
  level:
    cn.cvking.forge.auth: debug

启动应用,控制台会打印当前选中的鉴权实现。引入 spring-security 实现时:

[auth] 使用鉴权实现: spring-security
[auth] 已创建默认管理员账号: admin
[auth] 默认管理员初始化完成,耗时 18ms

若使用的是内置默认 JWT 密钥,spring-security 实现还会额外告警:

[auth] 正在使用内置默认 JWT 密钥,生产环境务必通过 forge.auth.jwt.secret 覆盖!

切换成 Sa-Token 实现后重新启动,第一行随之变化,其余初始化日志一致:

[auth] 使用鉴权实现: sa-token
[auth] 已创建默认管理员账号: admin
[auth] 默认管理员初始化完成,耗时 21ms

接着用默认管理员登录验证链路(端点带 /v1 前缀):

bash
curl -X POST http://localhost:8080/v1/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"admin"}'

拿到返回的 token 后,带上请求头访问受保护的宠物接口:

bash
curl http://localhost:8080/v1/api/petshop/pet/1 \
  -H "Authorization: Bearer <token>"

令牌请求头可配置

上面的 Authorization 头与 Bearer 前缀均取自 forge.auth.tokenHeaderforge.auth.tokenPrefix,两套实现共用同一套配置。

常见反例与排查

同时引入了两套实现 → 启动失败

现象:应用启动即抛 IllegalStateException,提示检测到多个鉴权实现同时存在。

原因forge-auth-spring-securityforge-auth-sa-token 都通过 SPI 贡献 AuthProvider,框架在 Spider.getAllExtensions(AuthProvider.class) 后发现实现数大于 1,触发 fail-fast。常见于子模块各引一个、最终聚合到同一个可执行 jar。

修复:检查依赖树(mvn dependency:tree),确保最终 classpath 里只保留一套实现包,移除多余的那个。

一个实现都没引 → 启动失败

现象:启动抛 IllegalStateException,提示未检测到任何鉴权实现。

原因:只引了核心包 forge-auth,没有引入任何 AuthProvider 实现,而 forge.auth.enabled 仍为默认的 true

修复:按第二步二选一引入 forge-auth-spring-securityforge-auth-sa-token

JWT 密钥太短 → spring-security 实现启动报错

现象:使用 spring-security 实现时,初始化 ForgeJwtTokenCodec 阶段报错,提示密钥长度不足。

原因:HS256 经由 Keys.hmacShaKeyFor 做 fail-fast 校验,要求密钥长度不小于 32 字节。forge.auth.jwt.secret 配成了短串。

修复:把 forge.auth.jwt.secret 改为不少于 32 字节的随机串,切勿沿用内置默认占位密钥。

配置键大小写或层级写错 → 配置不生效

现象:明明配了 forge.auth.jwt.secret,启动却仍打印「正在使用内置默认 JWT 密钥」告警。

原因:常见于把 expireMinutes 误写成 expire-minutes 之外的形式、或把 jwt 一节错放到 forge.auth 之外的层级,导致绑定失败回落默认值。

修复:对照第三步的配置树逐层核对,secretexpireMinutes 必须在 forge.auth.jwt 节点下;white-listforge.auth 节点下。


想了解两套实现背后的 SPI 一元性、JWT codec 下沉到 spring-security 实现等设计取舍,参见 鉴权实现的 SPI 选择(设计篇);SPI 与包扫描的通用机制见 前置知识