云e办(后端)——权限管理RABC
如果用户直接在地址上输入本身权限没有的URL,我们也应该对其控制。这样就设计到权限的概念。
思路:需要用户登录成功后,才会进行判断有无权限查看相应内容。
- 1.根据请求的url判断角色,,根据角色进行判断能访问哪些资源(菜单列表)
- 2.分析登录的用户有哪些角色,根据角色进行判断能访问哪些资源(菜单列表)
权限管理RABC
RBAC是基于角色的访问控制( Role-Based Access Control )在RBAC中,权限与角色相关联,用户通过扮演适当的角色从而得到这些角色的权限。这样管理都是层级相互依赖的,权限赋予给角色,角色又赋予用户,这样的权限设计很清楚,管理起来很方便。
RBAC授权实际上是 Who 、 What 、 How 三元组之间的关系,也就是 Who 对 What 进行 How 的操作,简单说明就是谁对什么资源做了怎样的操作。
本次项目中:
用户与角色实体对应关系为多对多
角色与资源对应关系同样为多对多关系
所以在实体设计上用户与角色间增加用户角色实体,将多对多的对应关系拆分为一对多,同理,角色与资源多对多对应关系拆分出中间实体对象权限实体
表结构设计
权限表设计分为以下基本的五张表结构:
用户表(admin),角色表(role),用户角色表(admin_role),菜单表(menu),菜单角色表(menu_role),
表结构关系如下:
多对多,会用到中间表。如果是一对多不需要中间表,用外键就可以。
根据请求的url判断角色,从角色中获取菜单
1.POJO类、Menu
2.server: IMenusServer
/**
* 根据角色获取菜单列表
*/
List<Menu> getMenusWithRole();
/**
* 根据角色获取菜单列表
*/
@Override
public List<Menu> getMenusWithRole() {
return menuMapper.getMenusWithRole();
}
3.mapper: MenuMapper
/**
* 根据用户 获取菜单列表
*/
List<Menu> getMenusWithRole();
<resultMap id="MenusWithRole" type="com.xxxx.server.pojo.Menu" extends="BaseResultMap">
<collection property="roles" ofType="com.xxxx.server.pojo.Role">
<id column="rid" property="id"/>
<result column="rname" property="name" />
<result column="rnameZh" property="nameZh"/>
</collection>
</resultMap>
<!--根据角色获取菜单列表-->
<select id="getMenusWithRole" resultMap="MenusWithRole">
SELECT
m.*,
r.id AS rid,
r.`name` AS rname,
r.nameZh AS rnameZH
FROM
t_menu m,
t_menu_role mr,
t_role r
WHERE
m.id=mr.mid
and
r.id=mr.rid
ORDER BY m.id
</select>
二、过滤器:判断menu的url有没有请求的URL,有则返回能请求该URL的角色列表
package com.xxxx.server.config.security.component;
import com.xxxx.server.pojo.Menu;
import com.xxxx.server.pojo.Role;
import com.xxxx.server.service.IMenuService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import java.util.Collection;
import java.util.List;
/**
* 权限控制
* 根据请求URL 分析请求所需的角色
*/
@Component
public class CustomFilter implements FilterInvocationSecurityMetadataSource {
@Autowired
private IMenuService menuService;
AntPathMatcher antPathMatcher =new AntPathMatcher();
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
// 获取请求的URL
String requestUrl = ((FilterInvocation) object).getRequestUrl();
//根据角色查询所有的菜单
List<Menu> menus = menuService.getMenusWithRole();
for (Menu menu : menus) {
//判断请求URL与菜单中的URL能否匹配上。
if(antPathMatcher.match(menu.getUrl(),requestUrl)){
//如果能匹配上,则返回菜单URL所对应的所有角色是
String[] strings = menu.getRoles().stream().map(Role::getName).toArray(String[]::new);
//返回能访问该URL的所有角色
return SecurityConfig.createList(strings);
}
}
//没匹配的URL默认登录即可访问
return SecurityConfig.createList("ROLE_LOGIN");
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> aClass) {
return false;
}
}
三、判断用户都有哪些角色列表 与 请求URL所需的角色,进行比较:一致则可以访问
1.修改管理员类,获取当前用户角色–pojo/Admin.java
在管理员类里添加角色列表属性,并且可以获取到当前用户的角色
pojo/Admin.java
/**
* 已经是SpringSecurity框架了
* 真正登录的方法就是UserDetails的Username
* 登陆成功就是details
* @return
*/
@ApiModelProperty(value = "权限")
@TableField(exist = false)
private List<Role> roles;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<SimpleGrantedAuthority> authorities =
roles.stream()
.map(role -> new SimpleGrantedAuthority(role.getName()))
.collect(Collectors.toList());
return authorities;
}
2、Controller层,获取当前登录的用户id,传给service层…
LoginController.java
/**
* 获取当前登录的用户信息
*
* springsecurity将当前登录的对象设置到全局里面了:SecurityContextHolder.getContext().setAuthentication(authentication);
* 可以通过principal获取当前登录的对象
*/
@ApiOperation(value = "获取当前登录用户的信息")
@GetMapping("/admin/info")
public Admin getAdminInfo(Principal principal) {
if (null == principal) {
return null;
}
//principal 直接获取当前登录的对象
String username = principal.getName(); //获取当前登录的用户名
//根据当前的用户名去查询所有信息
Admin admin = adminService.getAdminByUserName(username);
//故意将密码返回空。
admin.setPassword(null);
//获取所有用户信息,并将角色也一起返回。角色表是角色表,没有在用户表当中。
admin.setRoles(adminService.getRoles(admin.getId()));
return admin;
}
3.根据当前登录的用户id,查询他的角色权限列表–》AdminServiceImpl.java
/**
* 根据用户id或者权限列表
*
* @param adminId
* @return
*/
List<Role> getRoles(Integer adminId);
/**
* 根据用户id获取权限列表
*
* @param adminId
* @return
*/
@Override
public List<Role> getRoles(Integer adminId) {
return roleMapper.getRoles(adminId);
}
4. RoleMapper
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
id, name, nameZh
</sql>
<!--根据用户id获取权限列表-->
<select id="getRoles" resultType="com.xxxx.server.pojo.Role">
SELECT
r.id,
r.`name`,
r.nameZh
FROM
t_role AS r
LEFT JOIN t_admin_role AS ar ON ar.rid = r.id
WHERE
ar.adminId = #{adminId}
</select>
4、添加拦截器:判断登录用户所拥有的角色,是否为url所需角色
CustomUrlDecisionManager
package com.xxxx.server.config.security.component;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;
import java.util.Collection;
/**
* 权限控制
* 判断用户角色
*
* @author zhoubin
* @since 1.0.0
*/
@Component
public class CustomUrlDecisionManager implements AccessDecisionManager {
@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
for (ConfigAttribute configAttribute : configAttributes) {
//当前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 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;
}
}
5、security动态权限配置
//动态权限配置
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O object) {
object.setAccessDecisionManager(customUrlDecisionManager);
object.setSecurityMetadataSource(customFilter);
return object;
}
})