Skip to content

1.4.0版本发布

Latest

Choose a tag to compare

@llllllxy llllllxy released this 08 Sep 14:00
· 33 commits to master since this release

1.4.0

升级路径:1.3.3 → 1.4.0 | 本次为双 API 语义统一(破坏性变更)

⚠️ 行为变更(升级必读)

  • AuthUtil / TinySecurityFacade 的 get* 与 has* 系列在未登录/会话失效时统一抛 UnAuthorizedException,不再返回 null:
    • 影响方法:getSecurityContext / getLoginSubject / getLoginId / getLoginIdAsString / getLoginIdAsInt / getLoginIdAsLong / getRoleSet / getPermissionSet / hasRole / hasAllRole / hasAnyRole / hasPermission / hasAllPermission / hasAnyPermission
    • 此前 AuthProvider 系列已抛异常,而 AuthUtil/TinySecurityFacade 系列返回 null,两套 API 行为不一致;本次统一为"未登录即抛异常",与 AuthProvider、Spring Security 语义对齐
    • 破坏性:依赖 AuthUtil.getLoginId() 等返回 null 进行容错判断(如可选鉴权接口、日志埋点)的代码,升级后将抛 UnAuthorizedException,需改为显式捕获或先判断登录状态
  • 移除冗余转发类 SecurityContextUtils(原仅转发到 AuthUtil,无自有逻辑):直接使用 AuthUtil.getSecurityContext() 等替代。破坏性:若外部代码 import 了 SecurityContextUtils,请改用 AuthUtil。
  • 移除冗余转发类 ThreadLocalSecurityContextHolder(原仅封装一个 ThreadLocal):ThreadLocal 已内联进 ThreadLocalSecurityContextRepository,通过其静态方法 getContext() / setContext() / clearContext() 访问。破坏性:若外部代码 import 了 ThreadLocalSecurityContextHolder,请改用 ThreadLocalSecurityContextRepository。
  • 移除冗余抽象 LocalMapContainer 接口及其唯一实现 LocalMapContainerByConcurrentHashMap:LocalTimeCache 的 dataMap/expireMap 字段与构造直接使用 ConcurrentHashMap(底层本就依赖它,行为不变,删除 getSource() 死方法)。破坏性:若外部代码直接 new LocalTimeCache(...),参数类型改为 ConcurrentHashMap<String,Object> / ConcurrentHashMap<String,Long>。
  • SessionRepository.deleteByLoginId 返回值语义统一为"操作已执行"(幂等):此前 Single/Caffeine 无会话时返回 true,而 Jdbc/Redis 返回 false,四个仓储行为不一致。本次统一为:只要操作正常完成即返回 true(无会话可删也算成功,仅异常返回 false)。破坏性:依赖 Jdbc/Redis deleteByLoginId 返回 false 判断"该账号无会话"的代码,升级后将收到 true,请改用 countValidOnlineSessions 判断是否存在会话。

升级检查清单

  • 排查代码中所有 AuthUtil.getLoginId() / AuthUtil.getLoginSubject() / AuthUtil.getSecurityContext() / AuthUtil.getRoleSet() / AuthUtil.getPermissionSet() 及 has* 系列的调用点
  • 若存在"未登录也调用上述方法且依赖返回 null"的场景(如未登录可访问的接口里读取当前用户做日志),改为:先 try { AuthUtil.getLoginId(); } catch (UnAuthorizedException e) { /* 未登录处理 */ },或在确信已登录的路径内调用
  • hasRole / hasPermission 等"判断型"方法:未登录时也会抛异常(不再返回 false)。若希望未登录容错判断,需在调用前确保已登录或显式捕获
  • 全局异常处理器若已处理 UnAuthorizedException(默认 TinySecurityHandlerExceptionResolver 会转为 401),则无需额外改动

新增特性

  • 新增按账号查询会话凭证 getCredentialsByLoginId(Object loginId):返回该账号下全部**有效(未过期)**会话凭证列表,无会话时返回空列表(不返回 null)。
    • 用途:多设备管理(列出账号所有在线设备/会话)、定向下线(配合 deleteByCredentials 只踢指定凭证,而非 deleteByLoginId 全量踢出)
    • 入口:AuthProvider.getCredentialsByLoginId(loginId);四个内置仓储(single / caffeine / redis / jdbc)均已实现
    • 接口方法声明为 default,默认实现抛 UnsupportedOperationException:自定义仓储未实现时显式报错而非静默返回空列表,同时保持存量自定义仓储的编译兼容
    • ⚠️ 安全提示:凭证等同于会话钥匙,请勿写入日志或返回给前端
  • 新增 Caffeine 本地缓存会话仓储(store-type=caffeine):提供容量上限保护(caffeine-maximum-size,默认 10000,近似 LRU 驱逐)与惰性过期驱逐,适合"单机部署、会话量大、需内存上限"场景。
    • 可选依赖:caffeine 以 provided 提供,使用前需自行引入:
      <dependency>
          <groupId>com.github.ben-manes.caffeine</groupId>
          <artifactId>caffeine</artifactId>
      </dependency>
    • 过期语义与其它仓储一致:由 LoginSubject.getLoginExpireTime() 驱动,续期时重新写入即重置。

契约修复

  • getCredentialsByLoginId 返回空列表时统一为可变 ArrayList:此前空列表分支返回 Collections.emptyList()(不可变),非空分支返回 new ArrayList<>(list)(可变),同一份语义却给出不同 mutability(外部 add/remove 会爆 UnsupportedOperationException)。本次将所有内置仓储的空列表分支统一改为 new ArrayList<>(),保证契约一致。

测试加固

  • 新增 AuthProviderCredentialsTest(4 用例):门面透传、空账号返回空列表、logout() 后查询收敛、自定义仓储未实现时抛 UnsupportedOperationException
  • 四个内置仓储测试各 +2 用例:覆盖多凭证返回、过期凭证排除、空账号可变空列表、返回防御性副本

1.3.3

升级路径:1.3.2 → 1.3.3 | 本次为安全加固 + 正确性修复批次(基于 2026-08-31 安全审计)

安全修复

  • 恢复 LoginSubject.toString() 凭证脱敏:1.3.2 后 aae08b7 曾回退为输出真实 credentials(便于排查),现恢复为固定输出 credentials=****,杜绝日志泄露会话凭证;同步恢复并修正 LoginSubjectTest。
  • 移除可预测凭证生成器:删除 snowflake/objectid/ulid 三种凭证风格(含时间戳,凭证可被预测/枚举,存在会话冒充风险),仅保留 uuid/random128/nanoid;CredentialsGenUtil 对未知风格一律回退 uuid。同时删除仅被其引用的 Snowflake/ObjectId/Ulid*/LocalHostUtil 实现。
  • URL 参数 token 默认关闭:新增 enable-url-token(默认 false),token 默认仅从 header(及开启后的 Cookie)读取,不再回退 URL 参数,避免凭证进入访问日志/Referer。

Bug 修复 / 正确性

  • 滑动续期阈值修正:DefaultAuthenticationManager 续期阈值由 timeout*0.8 改为 timeout*0.2(剩余 TTL 不足 20% 才续期),消除"会话度过 20% 后每请求触发存储写"的写放大。
  • ThreadLocal 异步残留防御:AuthenticationInterceptor.preHandle 进入时先 clearContext 兜底清理一次,降低 Servlet 异步/异常场景下线程回池残留导致的"用户串号"风险。
  • JsonUtil 序列化失败显式抛错:不再静默返回空串,改为抛 TinySecurityException,杜绝"登录成功但立即 401"的隐蔽故障。
  • SingleSessionRepository 在线索引并发收敛:list 变更统一由单锁保护,弃用 put 整体替换,修复并发计数丢失更新。
  • login() 成功路径异常隔离:成功事件发布移出 try 块,自定义事件监听器抛异常不再被误判为登录失败(仅 WARN)。
  • getLoginIdAsInt/Long 异常语义统一:loginId 非数字时抛 TinySecurityException(不再直接抛 NumberFormatException 导致 500)。

API 调整

  • JwtUtil 新增 getVerifiedSubject(secret, token)(验签后取 subject);原 getSubject(token) 不验签,标注 @Deprecated 并在 javadoc 中警告;sign 增加 payload 空校验。

工程清理

  • 清理生产类残留 main()/System.out/printStackTrace(BCrypt/SimpleHash/SM3Hash/SM3ConvertUtil/banner 输出);README 版本号同步至 1.3.3 并更新配置说明。

1.3.2

发布日期:2026-08-27 | 升级路径:1.3.1 → 1.3.2 | 完整分析见 docs/project-analysis-2026-08.md

类型概览

维度 内容
新增特性 4 项(enable-cookie 开关、jwt-timeout 配置、Cookie 安全属性配置、登出清理浏览器 Cookie)
Bug 修复 3 项(JWT 公开默认密钥、JWT 过期时间硬编码、非 Web 线程登录 NPE)
安全修复 1 项(日志凭证泄露链路)
行为变更 2 项(未配置 jwt-secret 时改用随机密钥、Cookie 模式默认关闭)

安全修复

  • 移除 JWT 硬编码默认密钥:此前未配置 tiny-security.jwt-secret 时会静默使用一个已在开源文档中公开的内置密钥,且 LoginSubject.toString() 会输出会话凭证(日志泄露)——两者组合存在伪造会话的风险。现在 JwtUtil 对空密钥直接抛出 IllegalArgumentException,不再有任何内置兜底密钥;LoginSubject.toString() 对 credentials 脱敏输出。
  • 非 Web 线程登录 NPE 修复:定时任务 / MQ 消费者等未绑定请求上下文的线程调用 login() 时,CookieUtil.setCookie 不再因 response 为 null 抛出空指针,改为跳过 Cookie 写入并正常返回 token。

Bug 修复

  • JWT 过期时间硬编码 30 天:timeout 配置超过 30 天时会话会在 30 天处静默失效(401)。现新增 jwt-timeout 配置(默认 2592000 秒即 30 天),且实际生效值取 max(jwt-timeout, timeout):默认行为不变,超长会话配置不再提前失效。
  • Cookie 生存期与会话不同步:登录 Cookie 的 maxAge 由硬编码 86400 秒改为与会话 timeout 一致,不再出现「Cookie 比会话活得久」。
  • 登出未清理浏览器 Cookie:logout() / logout(request) 现在会写回 maxAge=0 的同名 Cookie,浏览器侧凭证立即失效。

新增特性

  • Cookie 模式开关(enable-cookie):

    tiny-security:
      enable-cookie: false  # 默认 false;开启后登录写 Cookie、登出清理 Cookie、并允许从 Cookie 读取 token
  • JWT 自身有效期可配置(jwt-timeout):

    tiny-security:
      jwt-timeout: 2592000  # 单位秒,默认30天;实际生效值不低于会话timeout
  • Cookie 安全属性配置:

    tiny-security:
      cookie-secure: false    # 是否仅 HTTPS 传输,默认 false(生产建议开启)
      cookie-same-site: LAX   # SameSite 属性,默认 LAX(可选 STRICT/LAX/NONE,防御 CSRF)
  • 未配置 jwt-secret 时的安全兜底:启动时自动生成 128 位随机密钥并打印 WARN 日志(提示重启后会话失效、生产环境必须配置固定密钥),替代原先静默使用公开默认密钥的行为。

⚠️ 行为变更(升级必读)

  • Cookie 模式默认关闭(enable-cookie,默认 false):此前登录总是写 Cookie、且 token 会自动从 Cookie 中读取;现在默认纯 token 模式(仅 header 与 URL 参数)。前后端不分离、依赖 Cookie 传递 token 的项目升级后必须显式配置 enable-cookie: true,否则登录不再写 Cookie、请求也无法从 Cookie 中取到 token(全部 401)。
  • 未配置 jwt-secret 时改用随机密钥:此前未配置密钥的项目可以跨重启保持会话(因为使用内置固定密钥);升级后每次重启会话全部失效。生产环境请务必配置固定密钥。
  • 直接调用 JwtUtil.sign(null, ...) / JwtUtil.getClaims(null, ...) 的代码将抛出 IllegalArgumentException(原先静默使用内置密钥);JwtUtil.sign 新增带 expireSeconds 参数的重载,原三参重载保持兼容(默认 30 天)。

升级检查清单

  • 依赖 Cookie 传 token 的(前后端不分离)项目:务必配置 enable-cookie: true,否则升级后无法从 Cookie 读取 token
  • 检查是否已配置 tiny-security.jwt-secret(未配置的项目重启后会话失效)
  • 如直接使用 JwtUtil 工具类,确认未依赖「空密钥走内置默认值」的旧行为
  • 生产环境建议开启 cookie-secure: true
  • LoginSubject.toString() 不再输出 credentials,如有依赖该输出的日志排查逻辑需注意