security自定义角色权限
通过注解标记controller的方式与config配置的方式过于繁琐。这样每写一个接口都要去写这个注解,关键还要记相对应的权限,根本不符合当前的开发。
//注解方式
@PreAuthorize("hasAuthority('test')")
public RespBean test(){
....
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/login","/logout").hasAuthority("test");
}
在过滤器链中FilterSecurityInterceptor负责权限的效验,所以从这个过滤器来展开
①对角色权限进行设置
- 通过阅读源码发现FilterSecurityInterceptord通过调用SecurityMetadataSource子类对象的getAttributes方法拿到Collection<ConfigAttribute>集合这里面的ConfigAttribute对象就是角色权限所以我们可以通过实现SecurityMetadataSource对象自定义设置角色权限。
- 我们实现SecurityMetadataSource对象的子接口后,重写getAttributes方法自定义需要设置的角色权限。
@Component
public class CustomFilter implements FilterInvocationSecurityMetadataSource {
@Autowired
private IMenuService menuService;
AntPathMatcher antPathMatcher=new AntPathMatcher();
@Override
public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
//获取请求的url
String requestUrl = ((FilterInvocation) o).getRequestUrl();
List<Menu> menus=menuService.getMenusWithRole();
for (Menu menu:menus) {
//判断请求的url是否在菜单角色是否匹配
//访问菜单(访问的url地址reuqestUrl)与之匹配上的菜单,此菜单所能够访问的角色返回
if(antPathMatcher.match(menu.getUrl(),requestUrl)){
String[] str = menu.getRoles().stream().map(Role::getName).toArray(String[]::new);
return SecurityConfig.createList(str);
}
}
//没匹配的url默认登录即可访问
return SecurityConfig.createList("ROLE_LOGIN");
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> aClass) {
return false;
}
}
- 以上例子:通过请求路径去数据库中查询对应的角色权限,得到角色权限。通过SecurityConfig.createList(str)方法对得到的角色权限进行设置。
- 其中SecurityConfig是上述ConfigAttribute的子类,其中createList方法对权限的信息进行了设置。然后又通过getAttribute进行获取。
//createList
public static List<ConfigAttribute> createList(String... attributeNames) {
Assert.notNull(attributeNames, "You must supply an array of attribute names");
List<ConfigAttribute> attributes = new ArrayList(attributeNames.length);
String[] var2 = attributeNames;
int var3 = attributeNames.length;
for(int var4 = 0; var4 < var3; ++var4) {
String attribute = var2[var4];
//此处进行了角色权限设置
attributes.add(new SecurityConfig(attribute.trim()));
}
return attributes;
}
//getAttribute
public String getAttribute() {
return this.attrib;
}
②自定义角色权限的判断
- 在FilterSecurityInterceptord中也对角色权限判断的方法进行调用,通过invoke方法调用父类AbstractSecurityInterceptor,然后父类通过调用accessDecisionManager.decide(authenticated, object, attributes)方法作出决定,是否符合权限的信息。
- 我们可以通过实现实现AccessDecisionManager接口,重写decide方法,自定义其中的权限规则。
@Component
public class CustomUrlDecisionManager implements AccessDecisionManager {
@Override
public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
for(ConfigAttribute configAttribute:collection){
//当前url所需要的角色
String needRole = configAttribute.getAttribute();
//判断角色是否登录,能否可以访问。此角色在CustomFilter中设置
if("ROLE_LOGIN".equals(needRole)){
//判断是否为登录
if(authentication instanceof AnonymousAuthenticationToken){
throw new AccessDeniedException("尚未登录,请登录!");
}else{
return;
}
}
//判断用户角色是否为url所需角色
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
for(GrantedAuthority grantedAuthority:authorities){
if(grantedAuthority.getAuthority().equals(needRole)){
return;
}
}
}
throw new AccessDeniedException("权限不足,请联系管理员!");
}
@Override
public boolean supports(ConfigAttribute configAttribute) {
return false;
}
@Override
public boolean supports(Class<?> aClass) {
return false;
}
}
③最后对我们自定义的SecurityMetadataSource对象和AccessDecisionManager对象进行设置。
- 对于FilterSecurityInterceptord是自动调用默认规则的,所以我们需要对这个规则进行改变,使用我们的规则。
@Override
protected void configure(HttpSecurity http) throws Exception {
//使用jwt不需要csrf
http.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// .antMatchers("/login","/logout")
// .permitAll()
//除上面,所有请求都需要认证
.anyRequest()
.authenticated()
//设置自定义规则
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O o) {
o.setAccessDecisionManager(customUrlDecisionManager);
o.setSecurityMetadataSource(customFilter);
return o;
}
})
}
流程图,做的不好请见谅