基于RBAC的权限表达式动态访问控制是Spring Security中常用的一种权限控制方式。以下是具体的实现方法:
1. 定义RBAC模型
可参考以下示例:
### 角色
1. 管理员
2. 普通用户
### 权限
1. 用户管理:创建、删除用户
2. 文章管理:查看、修改、删除所有文章;创建、修改、删除自己的文章
### 资源
- 用户: /user/**
- 文章: /article/**
2. 实现自定义AccessDecisionManager
继承于AbstractAccessDecisionManager
并实现decide()
方法。其中,自定义的AccessDecisionManager需要获取到当前用户的所有角色和请求所需要的权限,然后判断用户所拥有的角色中是否有权限,决定用户是否可以访问特定的请求。
方法参数说明:
- authentication:当前登录用户的身份验证对象(即spring security的
Authentication
对象) - object:封装了请求信息(如请求URL),可以通过它来判断用户请求的资源
- configAttributes:可以获取到访问某个资源所需要的角色
可参考以下示例:
@Component
public class CustomAccessDecisionManager extends AbstractAccessDecisionManager {
private final UserService userService;
public CustomAccessDecisionManager(UserService userService,
List<AccessDecisionVoter<?>> decisionVoters) {
super(decisionVoters);
this.userService = userService;
}
@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
throws AccessDeniedException, InsufficientAuthenticationException {
// 判断当前用户是否已登录
if (authentication == null || !authentication.isAuthenticated()) {
throw new AccessDeniedException("当前用户未登录");
}
// 获取当前用户所拥有的角色
List<Role> userRoles = userService.getUserRoles(authentication.getName());
if (userRoles == null || userRoles.isEmpty()) {
throw new AccessDeniedException("当前用户未分配角色");
}
// 获取当前请求需要的角色
Collection<String> requiredRoles = getRequiredRoles(configAttributes);
if (requiredRoles == null || requiredRoles.isEmpty()) {
throw new AccessDeniedException("当前请求未配置权限");
}
// 判断当前用户所拥有的角色和当前请求所需的角色是否匹配
boolean isAuthorized = userRoles.stream()
.map(Role::getRoleName)
.anyMatch(requiredRoles::contains);
if (!isAuthorized) {
throw new AccessDeniedException("当前用户无权访问");
}
}
private Collection<String> getRequiredRoles(Collection<ConfigAttribute> configAttributes) {
if (configAttributes == null || configAttributes.isEmpty()) {
return null;
}
return configAttributes.stream()
.map(ConfigAttribute::getAttribute)
.collect(Collectors.toList());
}
}
3. 实现自定义Security MetadataSource
继承于FilterInvocationSecurityMetadataSource
并实现getAttributes()
和supports()
方法。其中,getAttributes()
方法需要根据请求的URL返回该请求需要的角色,而supports()
方法必须返回true
。之后将该MetadataSource注入到Spring容器中。
可参考以下示例:
@Component
public class CustomInvocationSecurityMetadataSource extends AbstractSecurityInterceptor implements Filter {
private final CustomAccessDecisionManager customAccessDecisionManager;
public CustomInvocationSecurityMetadataSource(CustomAccessDecisionManager customAccessDecisionManager,
FilterInvocationSecurityMetadataSource metadataSource) {
super();
this.customAccessDecisionManager = customAccessDecisionManager;
setAccessDecisionManager(customAccessDecisionManager);
setSecurityMetadataSource(metadataSource);
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
FilterInvocation filterInvocation = new FilterInvocation(request, response, chain);
// 调用父类的beforeInvocation进行访问控制
super.invoke(filterInvocation);
}
@Override
public Class<?> getSecureObjectClass() {
return FilterInvocation.class;
}
@Override
public SecurityMetadataSource obtainSecurityMetadataSource() {
return super.obtainSecurityMetadataSource();
}
@Override
public void destroy() {}
@Override
public void init(FilterConfig filterConfig) throws ServletException {}
@Override
public boolean supports(Class<?> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
}
4. 配置Spring Security
对Spring Security进行自定义配置,将CustomAccessDecisionManager和CustomInvocationSecurityMetadataSource注册到Security配置中。
可参考以下示例:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final CustomAccessDecisionManager customAccessDecisionManager;
private final FilterInvocationSecurityMetadataSource metadataSource;
public SecurityConfig(CustomAccessDecisionManager customAccessDecisionManager,
FilterInvocationSecurityMetadataSource metadataSource) {
this.customAccessDecisionManager = customAccessDecisionManager;
this.metadataSource = metadataSource;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
/**
* 为了演示方便,这里省略了其他配置
*/
http.authorizeRequests()
.anyRequest()
.access("@customInvocationSecurityMetadataSource.decide(request,authentication)");
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
/**
* 为了演示方便,这里省略了其他配置
*/
auth.userDetailsService(userDetailsService());
}
@Bean
public CustomAccessDecisionManager customAccessDecisionManager(UserService userService) {
List<AccessDecisionVoter<?>> voters = Arrays.asList(
new RoleVoter(),
new AuthenticatedVoter(),
new WebExpressionVoter()
);
return new CustomAccessDecisionManager(userService, voters);
}
@Bean
public FilterInvocationSecurityMetadataSource customInvocationSecurityMetadataSource() throws Exception {
UrlFilterInvocationSecurityMetadataSource metadataSource = new UrlFilterInvocationSecurityMetadataSource(
securityInterceptor().getSecurityMetadataSource(), new PatternMatcher());
// 用户 -> 角色 -> 权限
metadataSource.addSecurityMapping(new SecurityMapping("/user/**", "ROLE_ADMIN"));
metadataSource.addSecurityMapping(new SecurityMapping("/article/**", "ROLE_USER", "ROLE_ADMIN"));
return metadataSource;
}
@Bean
public CustomInvocationSecurityMetadataSource customInvocationSecurityMetadataSource(
CustomAccessDecisionManager customAccessDecisionManager,
FilterInvocationSecurityMetadataSource metadataSource
) {
return new CustomInvocationSecurityMetadataSource(customAccessDecisionManager, metadataSource);
}
@Bean
public SecurityInterceptor securityInterceptor() {
return new SecurityInterceptor();
}
}
示例说明
示例一:
当前用户为管理员,请求URL为/user/delete
,则可以完成对当前用户的删除操作。若请求URL为/article/create
则不能创建文章。
示例二:
当前用户为普通用户,请求URL为/user/create
,则无法完成对其他用户创建的用户进行创建。但是可以通过请求URL/article/update
来完成自己的文章修改。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Spring Security实现基于RBAC的权限表达式动态访问控制的操作方法 - Python技术站