Shiro案例

wen java案例 2


从零到实战:Apache Shiro安全框架核心案例解析与权限模型落地指南**

Shiro案例


目录导读

  1. Shiro是什么?——先解决“为什么选它”
  2. 环境准备与最小化集成(Maven + Spring Boot)
  3. 基于数据库的用户认证(登录校验)
  4. 细粒度授权(角色与权限控制)
  5. 自定义Realm实现动态权限加载
  6. 会话管理与会话共享(Redis集群场景)
  7. 常见坑位与性能优化(含问答环节)
  8. 从Demo到生产环境的思维跃迁

Shiro是什么?——先解决“为什么选它”

Apache Shiro是一个强大且易用的Java安全框架,提供认证、授权、加密、会话管理四大核心功能,相比Spring Security,Shiro的API更直观,学习曲线平缓,尤其适合快速构建中小型系统的权限模块,根据2024年JetBrains生态调研,Shiro在传统企业级项目(非微服务)中的使用率仍占27%左右,这得益于其轻量级特性。

核心概念速览

  • Subject:当前操作用户(可能是人,也可能是第三方服务)。
  • SecurityManager:所有安全操作的最终仲裁者。
  • Realm:数据源桥接器,负责从数据库或缓存中读取用户、角色、权限数据。

关键认知:Shiro本身不维护用户数据,它只负责“校验逻辑”,而数据存储完全交由你的Realm实现。


环境准备与最小化集成(Maven + Spring Boot)

以Spring Boot 2.7.x为例,在pom.xml引入:

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring-boot-starter</artifactId>
    <version>1.12.0</version>
</dependency>

核心配置类

@Configuration
public class ShiroConfig {
    @Bean
    public DefaultWebSecurityManager securityManager(MyRealm realm) {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setRealm(realm);
        return manager;
    }
    @Bean
    public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager manager) {
        ShiroFilterFactoryBean filter = new ShiroFilterFactoryBean();
        filter.setSecurityManager(manager);
        // 匿名访问 /login, 其余需认证
        filter.setLoginUrl("/login");
        filter.setFilterChainDefinitionMap(Map.of("/login", "anon", "/**", "authc"));
        return filter;
    }
}

注意authc过滤器只校验登录状态,不校验角色,若需要角色细分,需配套使用roles["admin"]这种写法。


案例一:基于数据库的用户认证(登录校验)

需求:用户输入用户名/密码,查询MySQL数据库核对密码。

Realm实现

public class MyRealm extends AuthorizingRealm {
    @Autowired
    private UserMapper userMapper;
    // 认证:验证账号密码
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {
        String username = (String) token.getPrincipal();
        User user = userMapper.findByUsername(username);
        if (user == null) return null; // Shiro自动抛UnknownAccountException
        // 密码由Shiro自行比对(密文由Shiro的HashedCredentialsMatcher处理)
        return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), getName());
    }
    // 授权:稍后详述
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        // ... 见案例二
    }
}

密码匹配器(推荐BCrypt):

HashedCredentialsMatcher matcher = new HashedCredentialsMatcher("SHA-256");
matcher.setHashIterations(1024); // 或使用BCryptPasswordEncoder

Controller

Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try {
    subject.login(token); // 调用Realm
    return "登录成功";
} catch (AuthenticationException e) {
    return "登录失败:" + e.getMessage();
}

案例二:细粒度授权(角色与权限控制)

需求:管理员可以删除用户,普通用户只能查看。

数据库设计

  • user表 (id, username, password)
  • role表 (id, role_name)
  • permission表 (id, perm_code, perm_desc)
  • 中间表:user_role, role_permission

Realm授权方法

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    String username = (String) principals.getPrimaryPrincipal();
    List<String> roles = roleMapper.findRolesByUsername(username);
    List<String> perms = permMapper.findPermsByUsername(username);
    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
    info.addRoles(roles);   // 角色集合
    info.addStringPermissions(perms); // 权限集合
    return info;
}

在Controller中使用

@RequiresPermissions("user:delete")
@GetMapping("/delete/{id}")
public String delete(@PathVariable Long id) {
    // 只有具备 user:delete 权限的用户才能访问
    return "删除成功";
}

前端标签控制(JSP/Thymeleaf):

<shiro:hasPermission name="user:create"> <button>新增用户</button> </shiro:hasPermission>

案例三:自定义Realm实现动态权限加载

痛点:如果用户权限频繁变更,每次请求都查库会带来性能压力。

解决方案:引入Redis缓存,Shiro原生支持CacheManager接口。

配置缓存

@Bean
public CacheManager cacheManager(RedisConnectionFactory factory){
    RedisCacheManager cacheManager = new RedisCacheManager(factory);
    cacheManager.setKeyPrefix("shiro_cache_");
    return cacheManager;
}
// 在securityManager中注入
manager.setCacheManager(cacheManager);

动态刷新:当管理员修改某个用户的角色时,调用SecurityUtils.getSubject().logout()强制该用户下次请求重新授权。

注意:Shiro默认对每个Realm的getAuthorizationInfo结果缓存,但认证信息不缓存(每一次登录都查库),这一点与Spring Security不同,需要根据业务权衡。


案例四:会话管理与会话共享(Redis集群场景)

Shiro原生Session保存在JVM内存中,在分布式环境下会丢失,解决方案:使用RedisSessionDAO

@Bean
public AbstractSessionDAO sessionDAO(RedisTemplate<String, Object> redisTemplate) {
    RedisSessionDAO dao = new RedisSessionDAO();
    dao.setRedisManager(redisTemplate.getConnectionFactory());
    return dao;
}
@Bean
public DefaultWebSessionManager sessionManager(AbstractSessionDAO sessionDAO) {
    DefaultWebSessionManager sm = new DefaultWebSessionManager();
    sm.setSessionDAO(sessionDAO);
    sm.setGlobalSessionTimeout(1800000); // 30分钟
    sm.setDeleteInvalidSessions(true);
    return sm;
}

集成到SecurityManager

manager.setSessionManager(sessionManager);

会话监听器(可选):

public class MySessionListener implements SessionListener {
    @Override public void onStart(Session session) { /* 日志记录 */ }
    @Override public void onStop(Session session) { /* 清理资源 */ }
    @Override public void onExpiration(Session session) { /* 用户掉线处理 */ }
}

常见坑位与性能优化(含问答环节)

Q1:为什么用户登录成功后,一段时间后突然报“会话已过期”?
A:检查DefaultWebSessionManagerglobalSessionTimeout,默认是30分钟,若使用Redis,确认Redis持久化策略是否将session数据清空。

Q2:修改了用户角色,但是权限还是旧的?
A:首先检查是否启用了缓存;其次清理除user:delete外的权限缓存,最直接的方法是调用subject.logout()强制登出。

Q3:如何防止Shiro的URL拦截绕过?
A:在设置FilterChainDefinitionMap时,必须将放置于最后,并且避免使用Ant通配符的歧义,如/admin/*不会匹配/admin/*/edit,建议使用/admin/**

性能优化建议

  1. 认证缓存:虽然Shiro默认不缓存认证信息,但如果你确认用户密码不会频繁变动,可以手动启用RealmsetAuthenticationCachingEnabled(true),并配合HashedCredentialsMatcher
  2. 去除无用过滤器:Shiro默认注册多个过滤器(如logoutssl),如果不用就删除,减少匹配开销。
  3. 数据库索引:在user_rolerole_permission表的user_idrole_id字段上加联合索引。

从Demo到生产环境的思维跃迁

Shiro的核心不是一堆API,而是一种把复杂的安全逻辑抽象为“认证流 + 授权流”的思维方式,在实际案例中,你会发现:

  • 认证解决了“你是谁”的问题。
  • 授权解决了“你能做什么”的问题。
  • 会话解决了“你怎么保持状态”的问题。

最后的关键建议

  • 生产环境一定要使用密码加盐(如ByteSource.Util.bytes(user.getSalt()))。
  • 权限码命名规则建议采用资源:操作格式,例如order:export
  • 如果未来有微服务演进计划,建议尽早从Shiro迁移至Spring Security OAuth2或Spring Security + JWT。

(全文完)

抱歉,评论功能暂时关闭!