Shiro 是 Apache 基金会旗下的开源安全框架,它把认证、授权、加密和会话管理等功能封装得简洁易用。相比 Spring Security 庞大的体系,Shiro 的学习曲线更平缓,配置更灵活,特别适合中小型项目快速搭建权限体系。本文将从零开始,演示如何在 Spring Boot 项目中整合 Shiro,实现一套完整的基于角色的权限管理系统。

一、Shiro 核心概念速览
在动手写代码之前,先理清 Shiro 的几个核心组件,这对理解后面的配置至关重要。Shiro 的架构围绕三个核心概念展开:Subject、SecurityManager 和 Realm。
Subject 代表当前与系统交互的用户主体,可以是人、第三方服务或定时任务。它本质上是一个门面,所有安全操作都通过 Subject 发起。SecurityManager 是真正的安全管理者,管理所有 Subject 的安全操作,是 Shiro 的核心大脑,任何集成了 Shiro 的应用都必须配置它。Realm 则是数据桥梁,负责从数据库或其他数据源中读取用户的身份信息、角色和权限,Shiro 的认证与授权最终都依赖 Realm 提供的数据。
一次典型的认证流程是这样的:用户提交用户名和密码后,Subject 会把token 交给 SecurityManager,SecurityManager 再委托配置好的 Realm 去数据库中查询用户信息并完成比对。理解了这条链路,后面的代码就非常好懂了。
二、项目搭建与依赖引入
创建一个 Spring Boot 项目,引入 Shiro 相关依赖。推荐使用 shiro-spring-boot-web-starter,它与 Spring Boot 的自动装配机制结合得更好。
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>1.13.0</version>
</dependency>
<!-- 数据库访问,这里用 MyBatis-Plus 做示例 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>数据库设计上至少需要三张表:用户表(sys_user)、角色表(sys_role)以及用户角色关联表(sys_user_role)。如果需要更细粒度的控制,可以再加一张权限表和角色权限关联表。本文示例采用用户和角色两级结构,通过角色来控制接口访问权限。
三、自定义 Realm 实现认证与授权
Realm 是整个整合过程中最核心的类,需要继承 AuthorizingRealm 并重写两个方法:doGetAuthenticationInfo 负责认证,doGetAuthorizationInfo 负责授权。
@Component
public class UserRealm extends AuthorizingRealm {
@Autowired
private SysUserService sysUserService;
/**
* 授权:查询当前用户的角色和权限
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = (String) principals.getPrimaryPrincipal();
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 查询用户角色,实际项目中从数据库读取
Set<String> roles = sysUserService.findRolesByUsername(username);
info.setRoles(roles);
return info;
}
/**
* 认证:校验用户名和密码
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException {
String username = (String) token.getPrincipal();
SysUser user = sysUserService.findByUsername(username);
if (user == null) {
// 用户不存在,Shiro 会抛出 UnknownAccountException
throw new UnknownAccountException("用户不存在");
}
// 第一个参数是 principals,第二个参数是数据库中的密码,第三个参数是 realm 名称
return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), getName());
}
}认证方法中返回的 SimpleAuthenticationInfo 携带了数据库中存储的加密密码,Shiro 会自动用配置的 CredentialsMatcher 将用户提交的明文密码加密后与它比对,比对失败则抛出 IncorrectCredentialsException。授权方法只在访问需要权限的接口时才被调用,Shiro 默认带有缓存机制,不会每次请求都查库。
四、编写 ShiroConfig 配置类
配置类中需要做三件事:注册 SecurityManager、配置密码匹配器、定义过滤规则。过滤规则决定了哪些路径需要登录、哪些路径需要特定角色才能访问。
@Configuration
public class ShiroConfig {
@Autowired
private UserRealm userRealm;
@Bean
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
// 配置密码匹配器,采用 MD5 加盐并迭代两次
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
matcher.setHashAlgorithmName("MD5");
matcher.setHashIterations(2);
matcher.setStoredCredentialsHexEncoded(true);
userRealm.setCredentialsMatcher(matcher);
manager.setRealm(userRealm);
return manager;
}
/**
* 配置路径过滤规则,注意使用 ShiroFilterFactoryBean
*/
@Bean
public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(securityManager);
bean.setLoginUrl("/login");
bean.setUnauthorizedUrl("/403");
Map<String, String> filterMap = new LinkedHashMap<>();
// 登录接口和静态资源放行
filterMap.put("/login", "anon");
filterMap.put("/css/**", "anon");
filterMap.put("/js/**", "anon");
// admin 角色才能访问的接口
filterMap.put("/admin/**", "authc,roles[admin]");
// 其余接口都需要登录
filterMap.put("/**", "authc");
bean.setFilterChainDefinitionMap(filterMap);
return bean;
}
}这里有几个关键点需要注意。过滤链使用 LinkedHashMap 保证顺序匹配,一旦匹配成功就不会继续往下匹配,所以放行规则必须写在拦截规则前面。anon 表示匿名访问,authc 表示必须认证通过,roles[admin] 表示需要具备 admin 角色,多个角色之间用逗号分隔表示或的关系。
密码加密方面,数据库中存储的密码应该在用户注册时用相同算法生成。例如密码明文是 123456,盐值是 username,迭代两次后生成的 MD5 值可以通过 SimpleHash 计算出来,注册逻辑和登录校验的算法参数必须完全一致,否则认证永远失败。
五、登录接口实战与注解授权
完成配置后,编写登录接口。登录的本质是把用户名密码包装成 UsernamePasswordToken 交给 Subject 处理。
@RestController
public class LoginController {
@PostMapping("/login")
public Result login(@RequestBody LoginDTO dto) {
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token =
new UsernamePasswordToken(dto.getUsername(), dto.getPassword());
try {
subject.login(token);
return Result.success("登录成功");
} catch (UnknownAccountException e) {
return Result.error("用户不存在");
} catch (IncorrectCredentialsException e) {
return Result.error("密码错误");
}
}
@GetMapping("/logout")
public Result logout() {
SecurityUtils.getSubject().logout();
return Result.success("退出成功");
}
}除了过滤器链,Shiro 还支持方法级别的注解授权,常见的有 @RequiresRoles 和 @RequiresPermissions。要在 Controller 上生效,需要开启 AOP 支持,可以在配置类上加 @Bean 注册 AuthorizationAttributeSourceAdvisor,并配合 DefaultAdvisorAutoProxyCreator 使用。注解方式比过滤链更灵活,适合控制到具体的方法级别。
@RestController
@RequestMapping("/user")
public class UserController {
@RequiresRoles("admin")
@GetMapping("/list")
public Result listUsers() {
// 只有 admin 角色才能调用该方法
return Result.success(userService.list());
}
@RequiresPermissions("user:delete")
@DeleteMapping("/{id}")
public Result deleteUser(@PathVariable Long id) {
userService.removeById(id);
return Result.success("删除成功");
}
}六、常见坑点与排查思路
整合过程中最容易踩的坑有几个。第一,前后端分离项目中,默认的 Session 会话保持方式会遇到跨域 Cookie 问题,建议改用 JWT 方案,自定义一个继承 BasicHttpAuthenticationFilter 的过滤器,从请求头中解析 token。第二,使用注解授权时如果 AOP 代理没有生效,注解会被静默忽略,接口不再校验角色,这是很大的安全隐患,务必确认 advisor 配置正确。
第三,密码比对失败时优先检查加密算法、盐值、迭代次数三要素是否与注册逻辑一致。第四,Shiro 的 UnavailableSecurityManagerException 通常是因为在非 Web 线程中调用了 SecurityUtils.getSubject(),异步任务中需要手动绑定 Subject 或传递 SessionId。
最后说一下技术选型的考量。Shiro 适合快速开发、权限模型相对简单的场景;如果项目需要 OAuth2、SAML 等企业级安全特性,或者深度依赖 Spring 生态的 SpEL 表达式权限控制,Spring Security 会是更合适的选择。两者没有绝对优劣,关键是匹配项目规模和团队技术栈。
Spring BootShiro权限管理修改时间:2026-09-02 10:26:44