悠悠楠杉
网站页面
正文:
在企业级应用中,静态权限配置往往无法满足灵活多变的业务需求。Spring Security作为Java生态中最流行的安全框架,其动态权限管理能力尤为重要。本文将分步骤拆解如何实现基于数据库驱动的动态权限控制。
动态权限的核心是RBAC(基于角色的访问控制)模型。需设计以下表结构:
1. 用户表(user):存储用户基础信息
2. 角色表(role):定义角色层级(如ADMIN、USER)
3. 权限表(permission):记录具体权限标识(如user:add)
4. 用户-角色关联表(userrole)
5. 角色-权限关联表(rolepermission)
关键字段示例:
CREATE TABLE permission (
id BIGINT PRIMARY KEY,
name VARCHAR(50) COMMENT '权限名称',
code VARCHAR(100) COMMENT '权限标识符,如sys:user:delete'
);通过实现SecurityMetadataSource接口,从数据库加载权限规则:
@Component
public class DynamicSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
@Autowired
private PermissionService permissionService;
@Override
public Collection<ConfigAttribute> getAttributes(Object object) {
String requestUrl = ((FilterInvocation) object).getRequestUrl();
List<Permission> permissions = permissionService.listAll();
for (Permission permission : permissions) {
if (antPathMatcher.match(permission.getUrl(), requestUrl)) {
return SecurityConfig.createList(permission.getCode());
}
}
return SecurityConfig.createList("ROLE_LOGIN"); // 默认需登录
}
}自定义AccessDecisionManager实现投票逻辑,核心代码如下:
public class DynamicAccessDecisionManager implements AccessDecisionManager {
@Override
public void decide(Authentication authentication, Object object,
Collection<ConfigAttribute> configAttributes) {
for (ConfigAttribute configAttribute : configAttributes) {
String needPermission = configAttribute.getAttribute();
if ("ROLE_LOGIN".equals(needPermission)) {
if (authentication instanceof AnonymousAuthenticationToken) {
throw new AccessDeniedException("未登录");
}
return;
}
// 检查用户是否拥有权限
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
for (GrantedAuthority authority : authorities) {
if (authority.getAuthority().equals(needPermission)) {
return;
}
}
}
throw new AccessDeniedException("无访问权限");
}
}在安全配置类中注入自定义组件:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private DynamicSecurityMetadataSource metadataSource;
@Autowired
private DynamicAccessDecisionManager decisionManager;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O object) {
object.setSecurityMetadataSource(metadataSource);
object.setAccessDecisionManager(decisionManager);
return object;
}
});
}
}频繁查询数据库会影响性能,建议使用Redis缓存权限数据,并通过消息队列实现权限变更时的实时刷新:
@EventListener
public void handlePermissionUpdate(PermissionUpdateEvent event) {
redisTemplate.delete("PERMISSION_CACHE");
metadataSource.refreshPermissions();
}后端需提供权限树接口,前端根据返回的权限码控制按钮/菜单显隐。例如Vue中的实现:
// 全局权限校验方法
Vue.prototype.$hasPermission = function (code) {
return this.$store.state.user.permissions.includes(code);
};通过以上步骤,可实现从数据库到前端的完整动态权限链条。实际落地时需注意:
1. 权限标识需遵循模块:资源:操作的命名规范
2. 做好权限变更的审计日志
3. 对敏感接口增加二次校验(如数据权限)
这种方案已在多个千万级用户系统中验证,平衡了灵活性与性能需求。