共计 2612 个字符,预计需要花费 7 分钟才能阅读完成。
Apache Shiro 认证流程简介
Apache Shiro 是一个强大且易用的 Java 安全框架,负责处理身份认证、授权、加密和会话管理。它的认证流程主要分为三个步骤:

- 应用程序构建 AuthenticationToken(包含用户凭证)
- 调用 Subject.login(token) 提交认证请求
- Shiro 通过配置的 Realm 验证凭证有效性
当出现 ’authentication failed for token submission’ 错误时,意味着在第二步或第三步出现了问题。这个错误通常伴随着 org.apache.shiro.authc.usernamep 的堆栈信息,表明系统无法正确处理提交的认证令牌。
错误原因深度分析
导致这个错误的常见原因有以下五种:
- Token 格式不匹配 :提交的 token 类型与 Realm 期望的类型不一致,例如提交了 UsernamePasswordToken 但 Realm 配置为验证 JWTToken
- Realm 配置错误 :可能未正确注册自定义 Realm,或 Realm 的 supports 方法实现有问题
- 凭证验证失败 :用户名 / 密码不匹配,但错误处理不够友好导致抛出通用异常
- Session 管理异常 :当启用 Session 时,创建 Session 失败会导致认证流程中断
- 加密配置问题 :密码加密方式与存储的加密方式不匹配
完整解决方案
正确的 Token 生成与验证
// 创建认证 token 的规范做法
UsernamePasswordToken token = new UsernamePasswordToken(
username, // 用户名
password, // 密码
rememberMe // 记住我
);
try {
// 获取当前用户
Subject currentUser = SecurityUtils.getSubject();
// 执行认证
currentUser.login(token);
// 认证成功处理
return "登录成功";
} catch (AuthenticationException ae) {
// 认证失败处理
log.error("认证失败: {}", ae.getMessage());
return "用户名或密码错误";
}
Realm 配置最佳实践
public class CustomRealm extends AuthorizingRealm {
// 必须正确实现 supports 方法
@Override
public boolean supports(AuthenticationToken token) {return token instanceof UsernamePasswordToken;}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {UsernamePasswordToken upToken = (UsernamePasswordToken) token;
String username = upToken.getUsername();
// 1. 从数据库或其他存储中获取用户信息
User user = userService.findByUsername(username);
if (user == null) {throw new UnknownAccountException("用户不存在");
}
// 2. 验证密码(注意密码加密方式)String hashedPassword = hashService.hashPassword(new String(upToken.getPassword()));
if (!user.getPassword().equals(hashedPassword)) {throw new IncorrectCredentialsException("密码错误");
}
// 3. 返回认证信息
return new SimpleAuthenticationInfo(user.getUsername(), // principal
user.getPassword(), // credentials
getName() // realmName);
}
// 授权相关方法省略...
}
Session 管理配置
在 shiro.ini 或配置类中:
[main]
# 禁用 URL 重写(提高安全性)sessionManager.sessionIdUrlRewritingEnabled = false
# 设置全局会话超时时间(毫秒)sessionManager.globalSessionTimeout = 1800000
# 配置 Session DAO(可选)sessionDAO = org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO
sessionManager.sessionDAO = $sessionDAO
生产环境避坑指南
- Token 过期时间设置 :
- 访问令牌和刷新令牌应设置不同的过期时间
-
生产环境建议访问令牌有效期不超过 1 小时
-
多 Realm 排序问题 :
- 当配置多个 Realm 时,确保它们的执行顺序正确
-
使用 ModularRealmAuthenticator 并设置 realms 属性
-
日志记录不足 :
- 确保记录足够的认证日志,但不要记录敏感信息
- 建议记录:认证尝试时间、IP 地址、用户名(不含密码)
性能优化与安全加固
认证方式性能比较
| 认证方式 | 性能影响 | 适用场景 |
|---|---|---|
| 简单用户名密码 | 低 | 内部系统、低安全需求 |
| 加密密码 | 中 | 大多数 Web 应用 |
| JWT | 高 | 分布式系统、无状态 API |
| 双因素认证 | 很高 | 高安全需求系统 |
安全建议
- 防止暴力破解 :
- 实现登录尝试限制(如 5 次失败后锁定账户 30 分钟)
-
使用验证码应对自动化攻击
-
Token 安全存储 :
- 前端使用 HttpOnly 和 Secure 的 Cookie 存储 Token
-
服务端对 Token 进行签名验证
-
定期密钥轮换 :
- 加密密钥和签名密钥应定期更换
- 建议每 3 - 6 个月轮换一次
总结与思考
排查 ’authentication failed for token submission’ 错误时,建议按照以下顺序检查:
- 确认 token 类型与 Realm 匹配
- 验证 Realm 配置是否正确加载
- 检查凭证验证逻辑
- 查看 Session 配置
- 确认加密配置一致性
思考题 :在微服务架构下,如何优化 Shiro 的认证性能?可能的解决方案包括使用集中式认证服务、JWT 无状态认证、或结合缓存减少数据库查询等。
正文完
发表至: 技术分享
近两天内
