前端设计:左侧菜单设计
将每一级的菜单放到数据库中,而不是写死到前端
enabled表示当前项是否启用。
keepAlive表示不显示这个标签的时候,这个标签是隐藏还是销毁。
requireAuth表示组件是否需要登录才能访问。
后台设计:动态权限控制
数据库:hr、hr_role、role、menu_role、menu
用户->角色->权限:在menu中定义了当前权限的url接口,例如员工奖惩对应的url为/personnel/ec/**
流程:用户发送http请求(hr),查看http请求中的url与menu表中字段的url匹配(menu),从而得到他要进行什么操作,查看当前的menu需要什么角色(menu_role),查看当前用户是否满足所需角色(hr_role).
步骤1:
(1)查询初所有的menu和对应的role
(2)匹配请求url对应的menu,获取对应的role
(3)类型转换,将多个role返回
(4)否则返回登录角色
步骤2:
(1)查询当前用户的所有角色
(2)匹配当前用户角色是否包含所需角色
前端设计:左侧菜单设计中就不给一级菜单分配角色了,因为它没有实质性的接口,也更好的对角色划分。
代码实现:
步骤1代码:这个类的作用,主要是根据用户传来的请求地址,分析出请求需要的角色
(menuService.getAllMenusWithRole()中查询初所有的menu以及对应的role,因为经常用到将其缓存到redis中,使用@Cacheable)
@Component
public class CustomFilterInvocationSecurityMetadataSource implements
FilterInvocationSecurityMetadataSource{
@Autowired
MenuService menuService;
AntPathMatcher antPathMatcher = new AntPathMatcher();
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
String requestUrl = ((FilterInvocation) object).getRequestUrl();//获取当前数据库的地址,从而与去menu中查询
List<Menu> menus = menuService.getAllMenusWithRole();//查询所有的menu和对应的role
for (Menu menu : menus) {
if (antPathMatcher.match(menu.getUrl(), requestUrl)) {//查询当前的请求url和哪一个menu匹配
List<Role> roles = menu.getRoles();//获取这个menu对应的所有角色role
String[] str = new String[roles.size()];
for (int i = 0; i < roles.size(); i++) {
str[i] = roles.get(i).getName();
}
return SecurityConfig.createList(str);//类型转换,将roles转换成Collection<ConfigAttribute>并返回
}
}
return SecurityConfig.createList("ROLE_LOGIN");
}
步骤2代码:判断当前用户角色时候包含所需角色
@Component
public class CustomUrlDecisionManager implements AccessDecisionManager {
@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
for (ConfigAttribute configAttribute : configAttributes) {
String needRole = configAttribute.getAttribute();//获取所需角色
if ("ROLE_LOGIN".equals(needRole)) {//登录角色,则要求登录
if (authentication instanceof AnonymousAuthenticationToken) {//判断auth实例时匿名实例,则抛出异常
throw new AccessDeniedException("尚未登录,请登录!");
}else {
return;
}
}
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();//获取当前用户的角色
for (GrantedAuthority authority : authorities) {//遍历当前用户角色是否包含所需角色
if (authority.getAuthority().equals(needRole)) {
return;
}
}
}
throw new AccessDeniedException("权限不足,请联系管理员!");
}
步骤3代码:将上面两个类引入总的security配置文件中
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
HrService hrService;
@Autowired
CustomFilterInvocationSecurityMetadataSource customFilterInvocationSecurityMetadataSource;
@Autowired
CustomUrlDecisionManager customUrlDecisionManager;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O object) {
object.setAccessDecisionManager(customUrlDecisionManager);
object.setSecurityMetadataSource(customFilterInvocationSecurityMetadataSource);
return object;
}
})
动态权限控制扩展知识1:
现在的权限和url绑死的,如果想展示menu,不给权限,或者给权限,不展示menu,一个解决方式,是不将menu和url写死,将menu在前端写死(不从数据库中读取),给每一个menu定义一个meta属性:
meta:{
roles:['admin','user']
}
发送请求的时候,将meta添加到请求中。
动态权限控制扩展知识2:
认证失败的时候,想要重定向还是返回错误信息。