数据库有三张表 menu(菜单表) 、 menu_role 和 role(角色表)
菜单表
url字段存放该菜单下的所有请求的一个通用形式,用于后面匹配请求是哪一个菜单
menu_role表连接menu表和role表
角色表
name为权限名
动态权限判定原理:
查询每一个资源的所有权限,拦截request,将requestUrl与资源url匹配
将匹配到的资源对应的所有权限存入SpringSecurity管理
未匹配到的资源,默认为登录即可访问的资源
制定决策,获取当前登录用户,将用户的权限集合与之前存储的访问资源
的权限集合进行匹配,如果有交集就说明当前用户拥有权限访问该资源
实现过程:
查询所有资源及其权限
@Component
public class CustomFilter implements FilterInvocationSecurityMetadataSource {
@Autowired
private IMenuService menuService;
AntPathMatcher antPathMatcher = new AntPathMatcher();
/**
* 将请求的url对应的角色加入到SecurityConfig中
* @param o
* @return
* @throws IllegalArgumentException
*/
@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与菜单角色是否匹配
if(antPathMatcher.match(menu.getUrl(), requestUrl)){
//将url匹配到的菜单所需要的所有角色加入到Security中
String[] str = menu.getRoles().stream().map(Role::getName).toArray(String[]::new);
//返回角色列表Collection<ConfigAttribute>
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;
}
}
制定决策
@Component
public class CustomUrlDecisionManager implements AccessDecisionManager {
/**
* 该方法的参数Collection<ConfigAttribute>
* 通过拦截Request传入FilterInvocationSecurityMetadataSource的getAttributes获取到的角色集合
*/
@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".equalsIgnoreCase(needRole)) {
//判断是否登录
if (authentication instanceof AnonymousAuthenticationToken) {
throw new AccessDeniedException("尚未登录,请登录!");
} else {
return;
}
}
//判断用户权限是否为url所需权限
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
for (GrantedAuthority authority : authorities) {
if(authority.getAuthority().equals(needRole)){
return;
}
}
}
throw new AccessDeniedException("权限不足,请联系管理员");
}
@Override
public boolean supports(ConfigAttribute configAttribute) {
return false;
}
@Override
public boolean supports(Class<?> aClass) {
return false;
}
}
SecurityConfig配置
//动态权限配置
http.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O object) {
object.setAccessDecisionManager(customUrlDecisionManager);
object.setSecurityMetadataSource(customFilter);
return object;
}
})
注意
用户登录时,不仅要查询用户基本信息,还需要查询拥有的权限信息
查询的所有权限需要封装成UserDetails指定的的权限集合
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
//map就是一个加工方法,将stream中的每一个元素按照一定规则加工
//collect就是能将stream转换为集合
List<SimpleGrantedAuthority> authorities = roles
.stream()
.map(role -> new SimpleGrantedAuthority(role.getName()))
.collect(Collectors.toList());
return authorities;
}
SpringSecurity整合SpringBoot中的动态权限配置
原文:https://www.cnblogs.com/fkPrograming/p/14409269.html