权限控制中心思想
权限控制是靠后台实现的,而非前端不允许没有权限的人访问相应页面实现的,因为如果不在后台实现权限控制,那么可以利用postman等工具进行接口调用根据返回的json字符串来获取信息和修改数据库等操作,而前端对于没有访问权限的页面进行隐藏的设计仅仅为了给用户良好的体验。
实现步骤
一、实现根据url分析出所需角色的方法
1.创建myfilter,实现FilterInvocationSecurityMetadataSource接口
FilterInvocationSecurityMetadataSource提供了根据地址获取需要的角色的方法(getAttributes),获取某个受保护的安全对象object的所需要的权限信息,是一组ConfigAttribute对象的集合,如果该安全对象object不被当前SecurityMetadataSource对象支持,则抛出异常IllegalArgumentException。该方法通常配合boolean supports(Class<?> clazz)一起使用,先使用boolean supports(Class<?> clazz)确保安全对象能被当前SecurityMetadataSource支持,然后再调用该方法。
@Component
public class URLToRoleFilter implements FilterInvocationSecurityMetadataSource {
@Autowired
MenuService menuService;
AntPathMatcher antPathMatcher = new AntPathMatcher();
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
String requestUrl = ((FilterInvocation) object).getRequestUrl();
List<Menu> menus = menuService.getAllMenusWithRole();
for (Menu menu : menus) {
if (antPathMatcher.match(menu.getUrl(), requestUrl)) {
List<Role> roles = menu.getRoles();
String[] str = new String[roles.size()];
for (int i = 0; i < roles.size(); i++) {
str[i] = roles.get(i).getName();
}
return SecurityConfig.createList(str);
}
}
return SecurityConfig.createList("ROLE_LOGIN");
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
}
对于getAttributes方法中的参数object,其为FilterInvocation对象,在这个对象中保存了request,response和FilterChain对象保存起来,供FilterSecurityInterceptor的处理代码调用。其源码提供接口如下:
AntPathMatcher:是PathMatcher接口的一个实现类,内部的match方法提供了路径的判断。AntPathMatcher不仅可以匹配Spring的@RequestMapping路径,也可以用来匹配各种字符串,包括文件路径等。注意,需要匹配某目录下及其各级子目录下所有的文件,使用/**/*而非*.*,因为有的文件不一定含有文件后缀。
2. 添加字段roles,在menuService中编写方法(加缓存)
在menu实体类中添加roles字段,用以保存访问当前菜单需要哪些角色。在menuService中编写getAllMenusWithRole方法来获取所有菜单需要的所有权限。
3.编写SQL语
需要从menu菜单表,role权限表和menu_role关系表中查数据,并且在原映射的结果集上添加一对多的字段映射。
<resultMap id="MenuWithRole" type="org.rave.vhr.bean.Menu" extends="BaseResultMap">
<collection property="roles" ofType="org.rave.vhr.bean.Role">
<id property="id" column="rid"/>
<result property="name" column="rname"/>
<result property="namezh" column="rnameZh"/>
</collection>
</resultMap>
<select id="getAllMenusWithRole" resultMap="MenuWithRole">
SELECT m.*,r.`id` rid,r.name rname,r.`nameZh` rnameZh
FROM menu m,menu_role mr,role r
WHERE m.`id`=mr.`mid` AND mr.`rid`=r.`id`
ORDER BY m.`id`;
</select>
二、分析当前用户是否具备所需角色
1.编写PermissionAnalysisDecisionManager类
编写PermissionAnalysisDecisionManager类,实现AccessDecisionManager接口。AccessDecisionManager:访问决策管理器,定义了三个方法:
其中,decide是实现访问决策的关键,authentication保存了当前登录的用户的信息
getAuthorities(),权限信息列表,默认是GrantedAuthority接口的一些实现类,通常是代表权限信息的一系列字符串。
getCredentials(),密码信息,用户输入的密码字符串,在认证过后通常会被移除,用于保障安全。
getDetails(),细节信息,web应用中的实现接口通常为 WebAuthenticationDetails,它记录了访问者的ip地址和sessionId的值。
getPrincipal(),最重要的身份信息,大部分情况下返回的是UserDetails接口的实现类,也是框架中的常用接口之一。
configAttributes保存了当前需要的权限。在decide中可以自定义匹配规则,如满足所有角色才能访问或者具备其中一个就能访问等。
@Component
public class PermissionAnalysisDecisionManager 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) {
throw new AccessDeniedException("尚未登录,请登录!");
}else {
return;
}
}
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;
}
}
三、在SecurityConfig中配置权限访问
http.authorizeRequests()
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O object) {
object.setAccessDecisionManager(permissionAnalysisDecisionManager);
object.setSecurityMetadataSource(URLToRoleFilter);
return object;
}
})