记录一个初始化配置时机问题导致的401

问题背景:

  项目前后端分离,后端sa-token鉴权,会话存储使用 sa-token-redis-jackson,所有登录会话写入 Redis。

  配置片段:

复制代码
sa-token:
  token-name: Authorization
  token-prefix: Bearer
  is-read-header: true

  前端登录后,后续请求统一携带Authorization: Bearer <token>

问题场景:

  前端调用登录接口成功,返回信息:

复制代码
  {
    "flag": 1,
    "desc": null,
    "data": {
      "id": 1,
      "username": "admin",
      "token": "245652rgfze13e-cdf862-4rtwr55b-91fgfb0-755aac3dfgdga362d",
      "tokenType": "Bearer"
    }
  }

  但后续调用其他接口提示401。

  浏览器 Network 显示请求头已正确携带token:

复制代码
GET /admin/api_v1/user/info HTTP/1.1
Authorization: Bearer  245652rgfze13e-cdf862-4rtwr55b-91fgfb0-755aac3dfgdga362d

  后端日志(SaTokenExceptionHandler)抛 NotLoginException → HTTP 401。

  Redis 中数据完全正常:

复制代码
satoken:admin:token:245652rgfze13e-cdf862-4rtwr55b-91fgfb0-755aac3dfgdga362d  =  1
satoken:admin:session:1                                   =  <SaSession, loginId=1>

  token→loginId 映射存在、账号会话存在,token 也正确回传,却仍然 401。

问题排查及原因:

  401 来源 :SaTokenExceptionHandler 捕获 NotLoginException校验环节读不到会话。

  Redis 里的 token key 是纯 uuid,请求头是 Bearer 245652...,数据都在却查不到,怀疑是校验时 token-prefix没生效,未剥离前缀。

  经排查:问题出在SaTokenConfiguration初始化配置的时机取值来源

  代码片段:

复制代码
@PostConstruct
public void registerStpLogic() {
    SaTokenConfig global = SaManager.getConfig();   // ← 
    SaTokenConfig adminConfig = copyConfig(global);
    adminConfig.setMaxLoginCount(1);
    adminConfig.setIsConcurrent(false);
    StpAdminUtil.getStpLogic().setConfig(adminConfig);
    ...    
}

  SaManager.getConfig()的行为:

复制代码
public static SaTokenConfig getConfig() {
    if (config == null) {
        config = new SaTokenConfig();  
    }
    return config;
}

  new SaTokenConfig()的默认tokenPrefix为null(Sa-Token 默认不带前缀)。

  yml 中的token-prefix: Bearer 由 Sa-Token 自动配置在某一时刻调用 SaManager.setConfig(...)才写入。
引起时序静态问题:
  若 本类 @PostConstruct 先执行

    → SaManager.getConfig() 时全局 config 尚未被 setConfig

    → 返回 tokenPrefix=null 的默认对象

    → copyConfig 把 null 前缀拷进 admin/member 配置

  之后 Sa-Token 自动配置才 setConfig(带 Bearer 的全局配置)

    → 但 的 StpLogic 已用了 null 前缀副本,不再更新

导致的结果:

  登陆生成token、写 Redis→ 前端拿到正确 token ✓

  校验配置tokenPrefix=null → 不剥前缀 → 拿整串查 Redis → 查不到 → 401 ✗

问题修复:

  将配置来源从「静态、时序不确定」的SaManager.getconfig(),改为「Spring 保证已从 yml 绑定完成」的SaTokenConfig bean 构造注入

复制代码
private final RequestContextInterceptor requestContextInterceptor;
private final SaTokenConfig saTokenConfig;

public SaTokenConfiguration(RequestContextInterceptor requestContextInterceptor,
                            SaTokenConfig saTokenConfig) {
    this.requestContextInterceptor = requestContextInterceptor;
    this.saTokenConfig = saTokenConfig;
}

@PostConstruct
public void registerStpLogic() {
    // 使用 Spring 已绑定 yml 的 SaTokenConfig,确保前缀处理已生效
    SaTokenConfig global = saTokenConfig;
    SaTokenConfig adminConfig = copyConfig(global);
    adminConfig.setMaxLoginCount(1);
    adminConfig.setIsConcurrent(false);
    StpAdminUtil.getStpLogic().setConfig(adminConfig);
    SaManager.putStpLogic(StpAdminUtil.getStpLogic());
    SaManager.putStpLogic(StpMemberUtil.getStpLogic());
}

  修改逻辑是Spring 依赖注入提供顺序保证:

    要构造 SaTokenConfiguration,Spring必须先创建并绑定完成它依赖的SaTokenConfig bean才能注入。

    而SaTokenConfig由 Sa-Token用@ConfigurationProperties("sa-token")从yml 绑定,bean创建完成 = yml绑定完成。

    本质是把顺序不保证的静态取值换成Spring 保证顺序的依赖注入,消除时序竞态。

如上修改后,问题修复。