Shiro是Apache基金会旗下的开源安全框架,核心功能覆盖身份认证、授权、加密和会话管理四大块。与Spring Security相比,Shiro的架构更轻量,学习成本更低,不依赖Spring容器也能独立运行,因此在中小型项目和老项目改造中非常常见。本文将从核心概念入手,逐步演示SpringBoot整合Shiro的完整流程,并总结开发中最容易踩到的坑。

一、Shiro的核心架构与三大组件
在学习任何框架之前,理解它的整体架构比记住API更重要。Shiro的整体设计围绕三个核心对象展开:Subject、SecurityManager和Realm。三者关系可以理解为:应用程序代码只与Subject交互,Subject把请求委托给SecurityManager,SecurityManager再通过Realm获取真实的用户数据。
Subject直译为主题,代表当前与系统交互的用户,它本质上是一个门面对象。我们在代码中调用subject.login(token)时,Subject本身不做任何校验,而是把token交给SecurityManager处理。这个设计让业务代码与安全逻辑完全解耦。
SecurityManager是Shiro的大脑,负责调度内部各个组件,包括认证器Authenticator、授权器Authorizer、会话管理器SessionManager和缓存管理器。一个应用只需要配置一个SecurityManager实例,它是所有安全操作的入口。
Realm是数据桥梁,负责从数据库或其他数据源中读取用户信息、角色和权限。Shiro并不知道你的用户数据存在哪里,开发者必须自定义Realm并实现doGetAuthenticationInfo和doGetAuthorizationInfo两个方法。前者提供认证数据,后者提供授权数据,这也是整个框架中唯一需要我们写业务逻辑的地方。
二、SpringBoot整合Shiro的完整代码实现
下面通过一个完整的例子演示整合过程。假设我们有一个用户表,包含用户名和加密后的密码,需要实现登录认证和基于角色的授权。
第一步是引入依赖,在pom.xml中添加shiro-spring-boot-starter:
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>1.13.0</version>
</dependency>
第二步编写自定义Realm,这是整合中最核心的类。注意密码比对时使用MD5加盐,盐值要与注册时保持一致:
public class UserRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
// 授权:查询当前用户的角色和权限
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = (String) principals.getPrimaryPrincipal();
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 实际项目中从数据库查询,这里演示写死
info.addRole("admin");
info.addStringPermission("user:delete");
return info;
}
// 认证:验证用户名和密码
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException {
String username = (String) token.getPrincipal();
// 模拟查询数据库,查不到用户直接返回null表示账号不存在
User user = userService.findByUsername(username);
if (user == null) {
return null;
}
// 第一个参数是用户名,第二个是数据库中的密码,第三个是盐值,第四个是Realm名称
return new SimpleAuthenticationInfo(
username,
user.getPassword(),
ByteSource.Util.bytes(user.getSalt()),
getName()
);
}
}
第三步编写Shiro配置类,将SecurityManager和ShiroFilterFactoryBean注册到容器中。过滤器链的定义顺序非常关键,具体注意事项下一节详细说明:
@Configuration
public class ShiroConfig {
@Bean
public UserRealm userRealm() {
return new UserRealm();
}
@Bean
public DefaultWebSecurityManager securityManager(UserRealm userRealm) {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
// 设置自定义的密码匹配器,MD5加盐两次
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
matcher.setHashAlgorithmName("MD5");
matcher.setHashIterations(2);
userRealm.setCredentialsMatcher(matcher);
manager.setRealm(userRealm);
return manager;
}
@Bean
public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(securityManager);
bean.setLoginUrl("/login");
// 过滤器链,注意顺序:具体的路径写在前面,通配路径写在后面
Map<String, String> map = new LinkedHashMap<>();
map.put("/login", "anon");
map.put("/css/**", "anon");
map.put("/user/**", "authc");
map.put("/**", "authc");
bean.setFilterChainDefinitionMap(map);
return bean;
}
}
最后是登录接口,通过Subject执行登录。登录失败时Shiro会抛出具体的异常类型,可以据此给用户返回友好提示:
@RestController
public class LoginController {
@PostMapping("/login")
public Result login(String username, String password) {
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try {
subject.login(token);
return Result.success("登录成功");
} catch (UnknownAccountException e) {
return Result.error("账号不存在");
} catch (IncorrectCredentialsException e) {
return Result.error("密码错误");
} catch (LockedAccountException e) {
return Result.error("账号被锁定");
}
}
@RequiresRoles("admin")
@GetMapping("/admin/data")
public Result adminData() {
return Result.success("管理员专属数据");
}
}
三、认证与授权的执行流程剖析
当调用subject.login(token)后,内部流程是:Subject把token交给SecurityManager,SecurityManager调用Authenticator进行认证,Authenticator再调用Realm的doGetAuthenticationInfo方法获取数据库中的用户信息,最后由CredentialsMatcher比对提交的密码与数据库密码是否一致。
密码比对失败会抛出IncorrectCredentialsException,账号不存在时如果Realm返回null会抛出UnknownAccountException。如果我们没有配置CredentialsMatcher,默认使用的是明文比对,而数据库中存的是加密后的密文,必然导致登录永远失败,这是初学者最常见的错误之一。
授权流程类似。当访问标注了@RequiresRoles或@RequiresPermissions的方法时,Shiro会通过AOP拦截请求,调用Realm的doGetAuthorizationInfo获取当前用户的角色权限集合,再校验是否包含所需权限。授权数据默认有缓存,修改用户权限后需要调用clearCachedAuthorizationInfo清理缓存才能立即生效。
四、常见错误与注意事项总结
第一,过滤器链顺序错误。Shiro按定义顺序逐个匹配,一旦匹配成功就不会继续往下走。如果把/**写在前面,后面所有的放行规则都会失效。正确做法是具体的路径在前,宽泛的路径在后,并且使用LinkedHashMap保证顺序。
第二,前后端分离项目中使用注解不生效。Shiro的@RequiresRoles等注解依赖AOP代理,需要手动开启注解支持。另外要注意,Controller内部方法自调用时注解不会生效,因为绕过了代理对象,这一点与Spring事务注解失效的原理相同。
第三,Session问题。前后端分离架构下,服务端不保存Session,Shiro默认的SessionDao无法工作。解决方案是改用JWT无状态方案,重写Realm时从请求头中解析token,或者将Session存储到Redis中实现分布式会话。
第四,密码加盐比对失败。注册时加密用的盐值、加密次数必须与登录时CredentialsMatcher中配置的完全一致,任何一项不匹配都会导致IncorrectCredentialsException。建议将盐值存在用户表中,而不是写死在代码里。
第五,静态资源被拦截。CSS、JS、图片等资源如果没有配置anon放行,登录页面会出现样式丢失的问题。可以在过滤器链中添加map.put("/static/**", "anon")之类的规则。
掌握以上内容后,基本的认证授权场景就都能应对了。如果项目规模较大,建议进一步学习Shiro的缓存机制和多Realm配置,这些进阶特性在权限体系复杂时会明显提升系统的可维护性。
Shiro SpringBoot整合Shiro Java安全框架修改时间:2026-09-02 19:27:11