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,我们填补了通用安全框架与具体业务逻辑之间的鸿沟。掌握这三板斧,足以应对大多数企业级应用开发中的权限管理挑战。

相关推荐
用户729429432232 小时前
uni-app实战在线教育类app开发
后端
用户729429432232 小时前
数据中心虚拟化之KVM虚拟化基本部署视频课程
后端
幌才_loong2 小时前
深入解析 C# async/await 执行原理:从语法糖到状态机
后端·.net
俞凡2 小时前
分布式日志指标系统设计
后端
策策策lv112 小时前
杂记-@Transactional使用的一点记录
后端
code_std3 小时前
保存文件到指定位置,读取/删除指定文件夹中文件
java·spring boot·后端
汤姆yu3 小时前
基于springboot的热门文创内容推荐分享系统
java·spring boot·后端
武昌库里写JAVA3 小时前
在iview中使用upload组件上传文件之前先做其他的处理
java·vue.js·spring boot·后端·sql