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