若依框架是一个基于Spring Boot和Spring Cloud的快速开发平台,它内置了权限管理模块,并且使用Apache Shiro作为其权限控制的核心组件。本文将深入探讨如何在若依框架中使用Shiro实现更高级的权限控制技巧。
Shiro是Java领域中一个强大且灵活的安全框架,主要功能包括认证(Authentication)、授权(Authorization)、会话管理(Session Management)和加密(Cryptography)。在若依框架中,Shiro被用来处理用户登录、角色与权限的验证以及会话管理等核心安全需求。
在若依框架中,Shiro的配置通过ShiroConfig
类完成。以下是关键步骤:
定义Realm:
若依框架中使用UserRealm
来加载用户信息和权限数据。该类实现了AuthorizingRealm
接口,重写了doGetAuthenticationInfo
和doGetAuthorizationInfo
方法。
public class UserRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 根据用户名查询角色和权限
String username = (String) principals.getPrimaryPrincipal();
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
List<String> roles = userRoleService.getRolesByUsername(username);
List<String> permissions = userPermissionService.getPermissionsByUsername(username);
info.setRoles(new HashSet<>(roles));
info.setStringPermissions(new HashSet<>(permissions));
return info;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 验证用户登录信息
String username = (String) token.getPrincipal();
User user = userService.getUserByUsername(username);
if (user == null) {
throw new UnknownAccountException("用户不存在");
}
if (!user.isEnabled()) {
throw new LockedAccountException("账户已被锁定");
}
return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), getName());
}
}
配置Filter Chain:
在ShiroConfig
中定义URL与权限的映射关系。
@Bean
public ShiroFilterChainDefinition shiroFilterChainDefinition() {
DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();
chainDefinition.addPathDefinition("/login", "anon"); // 登录页面无需认证
chainDefinition.addPathDefinition("/logout", "logout"); // 退出登录
chainDefinition.addPathDefinition("/**", "authc"); // 其他页面需要认证
return chainDefinition;
}
在实际项目中,权限可能需要根据业务逻辑动态调整。以下是一些实现动态权限控制的技巧。
传统的Shiro权限控制通常是在应用启动时静态加载所有权限规则。但在若依框架中,可以通过数据库动态更新权限信息。
doGetAuthorizationInfo
方法中,从数据库实时获取用户的角色和权限。@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = (String) principals.getPrimaryPrincipal();
// 查询缓存中的权限信息
String cacheKey = "shiro:authorization:" + username;
AuthorizationInfo cachedInfo = redisTemplate.opsForValue().get(cacheKey);
if (cachedInfo != null) {
return cachedInfo;
}
// 如果缓存中没有,则从数据库加载
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
List<String> roles = userRoleService.getRolesByUsername(username);
List<String> permissions = userPermissionService.getPermissionsByUsername(username);
info.setRoles(new HashSet<>(roles));
info.setStringPermissions(new HashSet<>(permissions));
// 将权限信息写入缓存
redisTemplate.opsForValue().set(cacheKey, info, Duration.ofMinutes(30));
return info;
}
当用户角色或权限发生变化时,需要及时刷新Shiro的权限缓存。
SecurityUtils.getSubject().getSession().invalidate()
清除当前用户的会话。AuthorizationAttributeSourceAdvisor
的refreshAuthorizationCache()
方法手动刷新缓存。public void refreshAuthorizationCache(String username) {
Subject currentUser = SecurityUtils.getSubject();
if (currentUser.isAuthenticated()) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
List<String> roles = userRoleService.getRolesByUsername(username);
List<String> permissions = userPermissionService.getPermissionsByUsername(username);
info.setRoles(new HashSet<>(roles));
info.setStringPermissions(new HashSet<>(permissions));
((DefaultSecurityManager) SecurityUtils.getSecurityManager()).clearCache(currentUser.getPrincipals());
}
}
在多租户系统中,不同租户之间的权限需要隔离。若依框架可以通过以下方式实现多租户权限控制。
为每个租户创建独立的数据库表或使用统一表并通过tenant_id
字段区分数据。
在UserRealm
中加入租户ID的校验逻辑。
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = (String) principals.getPrimaryPrincipal();
String tenantId = getTenantIdFromContext(); // 从上下文中获取租户ID
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
List<String> roles = userRoleService.getRolesByUsernameAndTenant(username, tenantId);
List<String> permissions = userPermissionService.getPermissionsByUsernameAndTenant(username, tenantId);
info.setRoles(new HashSet<>(roles));
info.setStringPermissions(new HashSet<>(permissions));
return info;
}
为了支持无状态的分布式系统,可以结合JWT(JSON Web Token)替代传统的Session管理。
通过自定义注解或AOP切面实现更细粒度的权限控制。
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresPermission {
String value();
}
// 切面拦截器
@Aspect
@Component
public class PermissionInterceptor {
@Around("@annotation(requiresPermission)")
public Object checkPermission(ProceedingJoinPoint joinPoint, RequiresPermission requiresPermission) throws Throwable {
Subject subject = SecurityUtils.getSubject();
if (!subject.isPermitted(requiresPermission.value())) {
throw new UnauthorizedException("无权限访问");
}
return joinPoint.proceed();
}
}