使用 JWT 升级 Spring Security 登录认证系统的两个关键问题与解决方案

在将传统的基于 Session 的用户认证系统迁移到基于 JWT(JSON Web Token)的无状态认证架构时,开发者经常会遇到两个典型问题。本文将围绕这两个核心问题展开说明,并介绍登录后如何正确携带 JWT 进行请求认证,以及如何从 Spring Security 上下文中安全地获取当前用户信息。


问题一:明文密码无法正确匹配

在旧系统中,用户密码以明文形式 直接存储在数据库中。当你引入 Spring Security 并使用 BCryptPasswordEncoder(或其他加密方式)对新注册用户的密码进行加密存储后,系统在验证用户登录时会自动对输入的原始密码进行加密,再与数据库中的密文比对。

然而,对于那些历史用户(其密码仍为明文),这种机制就会失败------因为系统尝试用加密后的输入去匹配未加密的数据库字段,自然无法通过验证。

因此,请确保数据库中所有用户的密码字段均为加密后的字符串 。只有这样,DaoAuthenticationProvider 才能通过 passwordEncoder.matches(rawPassword, encodedPassword) 正确完成验证。


问题二:UserDetails 实现类中的账户状态方法返回了错误值

在你的 UserDetailsImpl 类中,虽然代码看起来已经返回 true,但你提到"忘记修改让它恢复正常"。这通常是因为在早期开发阶段,为了快速测试,可能曾将这些方法硬编码为 false,导致 Spring Security 认为账户已过期、被锁定或凭证失效,从而拒绝登录。

Spring Security 在认证成功前会检查以下四个方法:

  • isAccountNonExpired():账户是否未过期
  • isAccountNonLocked():账户是否未被锁定
  • isCredentialsNonExpired():凭证(如密码)是否未过期
  • isEnabled():账户是否启用

只要其中任意一个返回 false,认证就会失败,并抛出相应异常(如 AccountExpiredException)。

正确做法:

确保这四个方法全部返回 true(除非你有明确的业务逻辑需要控制账户状态):

复制代码
@Override
public boolean isAccountNonExpired() {
    return true;
}

@Override
public boolean isAccountNonLocked() {
    return true;
}

@Override
public boolean isCredentialsNonExpired() {
    return true;
}

@Override
public boolean isEnabled() {
    return true;
}

这样,只要用户名和密码正确,用户就能顺利通过认证并获得 JWT Token。


登录成功后:前端如何携带 JWT 发起受保护请求

一旦用户登录成功,后端会返回一个 JWT Token。前端需将其保存(通常存入 Vuex/Pinia 的 store 或 localStorage),并在后续每个需要认证的 API 请求中,通过 Authorization Header 携带该 Token:

复制代码
const token = this.$store.state.user.token;
const linksResponse = await fetch("http://127.0.0.1:3000/api/link/all", {
  headers: {
    "Authorization": "Bearer " + token,
    "X-Request-ID": "test-" + Date.now(),
  }
});

✅ 格式必须为:Authorization: Bearer <your-jwt-token>

后端的 JWT 过滤器(如 JwtAuthenticationFilter)会从中提取 Token,验证签名和有效期,并重建 Authentication 对象放入 SecurityContext


进阶:在后端从 Security 上下文中获取当前用户信息

当请求携带有效 JWT 并通过认证后,Spring Security 会将认证信息存入当前线程的 SecurityContext。你可以在任何 Controller 或 Service 中安全地获取当前登录用户:

复制代码
UsernamePasswordAuthenticationToken authentication =  
        (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();  

UserDetailsImpl loginUser = (UserDetailsImpl) authentication.getPrincipal();  
User user = loginUser.getUser();

这段代码的前提是:

  • 你的 UserDetailsImpl 被正确设置为 Principal
  • JWT 认证过滤器在验证 Token 后,构建了包含该 UserDetailsImplAuthentication 对象

此时,user 对象即为当前操作用户,可直接用于业务逻辑(如权限校验、数据归属等)。


相关推荐
瀚高PG实验室2 分钟前
ERROR: invalid input syntax for type integer: “a“
数据库·瀚高数据库
S1998_1997111609•X3 分钟前
论next/js在打击省份及犯罪行为集团的系统分析[特殊字符]设计
网络·数据库·百度·ssh·开闭原则
dfdfadffa8 分钟前
如何创建仅在首次订阅时执行一次计算的 RxJS 懒加载 Observable
jvm·数据库·python
Irene19919 分钟前
Oracle 中:为什么 from 子查询后面需要一个别名
数据库·oracle
m0_6245785911 分钟前
SQL分组后如何计算移动平均值_利用窗口函数AVG配合ROWS
jvm·数据库·python
2401_8242226918 分钟前
如何修复待办事项列表无法添加任务的 JavaScript 错误
jvm·数据库·python
地球资源数据云23 分钟前
1900-2023年中国物种分布点位矢量数据集
大数据·数据结构·数据库·数据仓库·人工智能
sitellla1 小时前
MySQL 入门:最流行的开源关系型数据库介绍
数据库·mysql·其他·开源
精益数智工坊1 小时前
拆解制造业仓库物料管理流程:如何通过标准化仓库物料管理流程解决账实不符难题
大数据·前端·数据库·人工智能·精益工程
nbwenren1 小时前
办公AI实测:Gemini3、GPT-4o、Claude3.5谁更强?
服务器·数据库·php