先看上图
当认证成功之后我们往securityContex中放了Authentication对象,内部出了用户名密码还放了一个 Authorities,权限列表如下:我们基于rbac所以我们放了一个admin角色进去,实际上SimpleGrantedAuthority这个类约定只允许你放入角色,当然没有规定格式,你也可以放角色id 比如123;
SimpleGrantedAuthority admin = new SimpleGrantedAuthority("admin");
Set<SimpleGrantedAuthority> authorities=new HashSet<>();
authorities.add(admin);
userDetails.getAuthorities().addAll(authorities);
从这篇【过滤器访问控制流程分析】文章我们了解到
AbstractSecurityInterceptor.beforeInvocation方法中有这样两行代码
①表示从请求传进来的对象中提取访问当前请求链接需要的访问权限
②表示将1步骤获取的权限和当前认证用户的的信息进行比较判断是否能访问
① Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
.getAttributes(object);
//。。。省略
② this.accessDecisionManager.decide(authenticated, object, attributes);
接着我们来看第二个类图
先看看这个默认实现的DefaultFilterInvocationSecurityMetadataSource.getAttributes方法即上面①步骤调用到的
public Collection<ConfigAttribute> getAttributes(Object object) {
final HttpServletRequest request = ((FilterInvocation) object).getRequest();
for (Map.Entry<RequestMatcher, Collection<ConfigAttribute>> entry : requestMap
.entrySet()) {
if (entry.getKey().matches(request)) {
return entry.getValue();
}
}
return null;
}
requestMap 是在子类被初始化的,但是子类又在这个ExpressionUrlAuthorizationConfigurer 类中被重写了最终是通过REGISTRY创建出来的
LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap = REGISTRY
.createRequestMap();
而REGISTRY 则是在ExpressionUrlAuthorizationConfigurer这个类中配置的
private final ExpressionInterceptUrlRegistry REGISTRY;
以上创建出来的内容就是通过以下几种方式在代码里面写死内容提取出来的。
http.authorizeRequests().antMatchers("/test/**").hasRole("admin")
http.authorizeRequests().mvcMatcher("/test/**").hasAuthority("xxx")
@Secured("hasRole('admin')")
很显然,默认的这个securityMetadataSource不能满足我们当前的需求。因为我们需要从数据库进行获取。所以我们需要自定义一个FilterInvocationSecutrityMetadataSource实现
public class UrlFilterInvocationSecurityMetadataSource implements
FilterInvocationSecurityMetadataSource {
private ResourcesProvider resourcesProvider;
AntPathMatcher antPathMatcher = new AntPathMatcher();
public UrlFilterInvocationSecurityMetadataSource(ResourcesProvider resourcesProvider){
this.resourcesProvider=resourcesProvider;
}
@Override
public Collection<ConfigAttribute> getAttributes(Object object)
throws IllegalArgumentException {
//获取请求地址
String requestUrl = ((FilterInvocation) object).getRequestUrl();
List<Menu> allMenu = resourcesProvider.getAllMenu();
for (Menu menu : allMenu) {
if (antPathMatcher.match(menu.getPath(), requestUrl)&&menu.getRoles().size()>0) {
List<Role> roles = menu.getRoles();
int size = roles.size();
String[] values = new String[size];
for (int i = 0; i < size; i++) {
values[i] = roles.get(i).getRoleName();
}
//匹配上则获取role返回
return SecurityConfig.createList(values);
}
}
//没有匹配上的资源,就创建一个匿名对象即可
return SecurityConfig.createList("ROLE_ANONYMOUS");
}
//没用到放空即可
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
//这里也可以直接放回true
@Override
public boolean supports(Class<?> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
}
接着我们在实现以上说的②步骤也可以自定义如何比较
public class UrlAccessDecisionManager implements AccessDecisionManager {
@Override
public void decide(Authentication authentication, Object object,
Collection<ConfigAttribute> configAttributes)
throws AccessDeniedException, InsufficientAuthenticationException {
// TODO: 2020/11/16 这里写访问控制流程
Iterator<ConfigAttribute> iterator = configAttributes.iterator();
while (iterator.hasNext()) {
ConfigAttribute ca = iterator.next();
//当前请求需要的权限
String needRole = ca.getAttribute();
//当前用户所具有的权限
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
for (GrantedAuthority authority : authorities) {
if (authority.getAuthority().equals(needRole)) {
return;
}
}
}
throw new AccessDeniedException("权限不足!");
}
@Override
public boolean supports(ConfigAttribute attribute) {
return true;
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
}
扩展完成后如何生效呢?记得前面文章介绍过【ObjectPostProcessor解析】
即可以在实例化的时候修改掉对象在http中配置即可
http.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O o) {
o.setSecurityMetadataSource(urlFilterInvocationSecurityMetadataSource);
o.setAccessDecisionManager(urlAccessDecisionManager);
return o;
}
})