Shiro框架工作原理与实践精讲

在 Java 企业级开发中,Apache Shiro 凭借其轻量级、灵活且易于理解的特点,依然是众多开发者进行权限管理的首选框架。然而,在实战中,很多开发者往往止步于简单的 INI 配置或硬编码的权限拦截,导致系统在面对复杂的业务需求时缺乏弹性。本文将结合个人实战经验,深入探讨 Shiro 的注解式权限控制以及如何结合数据库实现动态权限配置,帮助你打造一个安全、灵活的权限系统。学习地址:pan.baidu.com/s/1WwerIZ_elz_FyPKqXAiZCA?pwd=waug

一、 注解式权限控制:优雅的代码防护

Shiro 提供了五个核心注解,分别是 @RequiresAuthentication@RequiresUser@RequiresGuest@RequiresRoles@RequiresPermissions。通过在 Controller 或 Service 层方法上直接添加注解,我们可以将权限校验逻辑从业务代码中剥离出来。

1. 开启注解支持

首先,必须在 Spring 的配置类中启用 Shiro 注解支持,否则注解将不会生效。

java

复制

java 复制代码
@Configuration
public class ShiroConfig {

    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
        creator.setProxyTargetClass(true);
        return creator;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }
}

2. 使用注解实战

在实际开发中,建议使用 "角色" 粗粒度控制用户类型,使用 "权限" 精细控制操作行为。

java

复制

less 复制代码
@RestController
@RequestMapping("/order")
public class OrderController {

    // 只有登录用户才能访问
    @RequiresUser
    @GetMapping("/list")
    public String list() {
        return "订单列表";
    }

    // 需要具备 'admin' 角色
    @RequiresRoles("admin")
    @DeleteMapping("/delete/{id}")
    public String delete(@PathVariable Long id) {
        return "删除订单 ID: " + id;
    }

    // 需要同时具备 'order:create' 和 'order:audit' 权限
    // logical = Logical.AND 表示必须同时拥有,OR 表示满足其一
    @RequiresPermissions(value = {"order:create", "order:audit"}, logical = Logical.AND)
    @PostMapping("/create")
    public String create() {
        return "创建订单";
    }
}

实战心得: 尽量不要将过于复杂的逻辑组合放在注解中,保持注解的简洁性。复杂的权限判断建议在自定义 Filter 中处理。

二、 动态权限配置:摆脱硬编码的枷锁

真实世界的业务需求是瞬息万变的。如果每次新增一个菜单或权限都需要修改代码重启服务,那显然是不可接受的。我们需要实现动态权限加载,即权限配置存储在数据库中,用户登录后实时加载。

1. 自定义 Realm 实现动态授权

Shiro 的核心在于 Realm。我们需要重写 doGetAuthorizationInfo 方法,从数据库查询当前用户的角色和权限。

java

复制

scala 复制代码
@Component
public class UserRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;
    @Autowired
    private PermissionService permissionService;

    // 认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {
        // ... 省略用户名密码校验逻辑
        return new SimpleAuthenticationInfo(user, user.getPassword(), getName());
    }

    // 授权 (核心逻辑)
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        // 1. 获取当前登录用户
        User user = (User) principals.getPrimaryPrincipal();
        
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

        // 2. 动态查询数据库中的角色
        Set<String> roleCodes = userService.findRolesByUserId(user.getId());
        info.setRoles(roleCodes);

        // 3. 动态查询数据库中的权限
        Set<String> permCodes = permissionService.findPermsByUserId(user.getId());
        info.setStringPermissions(permCodes);

        return info;
    }
}

2. 实现热加载:清除缓存

默认情况下,Shiro 会将授权信息缓存起来。如果管理员在后台修改了用户的权限并同步到数据库,但用户 Session 未过期,用户依然拥有旧权限。此时需要动态刷新缓存

我们可以编写一个工具类,当权限变更时主动调用。

java

复制

typescript 复制代码
@Component
public class ShiroCacheUtil {

    @Autowired
    private Realm realm;

    public void clearCachedAuthorizationInfo(String principal) {
        // 获取当前 Realm 的缓存管理器
        CacheManager cacheManager = realm.getCacheManager();
        if (cacheManager != null) {
            // 获取缓存对象
            Cache<Object, AuthorizationInfo> cache = cacheManager.getCache(realm.getName());
            if (cache != null) {
                // 移除指定用户的授权缓存
                // 这里的 key 通常是 principal (如用户名或用户对象)
                cache.remove(principal);
            }
        }
    }
}

// 调用示例:管理员修改权限后
// shiroCacheUtil.clearCachedAuthorizationInfo("zhangsan");

三、 进阶技巧:自定义 Filter 处理特殊场景

有时候注解无法满足所有需求,比如:只有 "创建人本人" 才能修改订单。这涉及到业务数据的校验,Shiro 提供的注解做不到。此时我们需要自定义 Filter。

java

复制

scala 复制代码
public class OwnerOrAdminFilter extends PermissionsAuthorizationFilter {

    @Override
    public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        Subject subject = getSubject(request, response);
        
        // 1. 如果是 admin,直接放行
        if (subject.hasRole("admin")) {
            return true;
        }

        // 2. 检查是否是资源拥有者(模拟逻辑)
        String orderId = request.getParameter("orderId");
        User currentUser = (User) subject.getPrincipal();
        
        // 假设调用 Service 检查该订单是否属于当前用户
        boolean isOwner = checkOrderOwner(orderId, currentUser.getId());
        
        return isOwner;
    }
}

然后在 ShiroConfig 中注册这个 Filter,并将其应用到特定的 URL 上:

java

复制

typescript 复制代码
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
    ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
    shiroFilter.setSecurityManager(securityManager);
    
    Map<String, Filter> filters = new HashMap<>();
    filters.put("ownerOrAdmin", new OwnerOrAdminFilter());
    shiroFilter.setFilters(filters);

    Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
    filterChainDefinitionMap.put("/order/update/**", "ownerOrAdmin");
    // ...
    shiroFilter.setFilterChainDefinitionMap(filterChainDefinitionMap);
    return shiroFilter;
}

总结

通过 Shiro 的注解,我们可以让代码逻辑更加清晰;通过结合数据库与自定义 Realm,我们实现了权限的动态配置与热加载;而通过自定义 Filter,我们填补了通用安全框架与具体业务逻辑之间的鸿沟。掌握这三板斧,足以应对大多数企业级应用开发中的权限管理挑战。

相关推荐
索荣荣12 小时前
Java Session 全面指南:原理、应用与实践(含 Spring Boot 实战)
java·spring boot·后端
千寻技术帮13 小时前
10333_基于SpringBoot的家电进存销系统
java·spring boot·后端·源码·项目·家电进存销
dear_bi_MyOnly13 小时前
【多线程——线程状态与安全】
java·开发语言·数据结构·后端·中间件·java-ee·intellij-idea
小信丶15 小时前
@EnableTransactionManagement注解介绍、应用场景和示例代码
java·spring boot·后端
To Be Clean Coder15 小时前
【Spring源码】createBean如何寻找构造器(四)——类型转换与匹配权重
java·后端·spring
-孤存-15 小时前
SpringBoot核心注解与配置详解
java·spring boot·后端
2301_8187320616 小时前
项目启动报错,错误指向xml 已解决
xml·java·数据库·后端·springboot
小王不爱笑13217 小时前
SpringBoot 整合 Ollama + 本地 DeepSeek 模型
java·spring boot·后端
短剑重铸之日18 小时前
《设计模式》第七篇:适配器模式
java·后端·设计模式·适配器模式
树码小子19 小时前
SpringIoC & DI (1):IOC介绍 & Spring IoC使用 & DI
java·后端·spring