RBAC思想

简述

RBAC思想是一种用于数据库设计的思想,一般来讲有三种场景:

  • 菜单
  • 资源可见
  • url权限
    在这里插入图片描述

一个网站可以有多个用户,而用户会有不同的角色,最常见的就是管理员和普通用户,有的情境下用户只有一种角色(有的情境下用户会有不同的角色,这里以只一种角色来进行分析),一种角色一般是对应多种功能,对于角色和功能的关系,就必须要用角色-功能中间表,在整个过程中,角色始终是数据库设计的核心,其他的表都是围绕着角色转的

菜单Demo

  • ssm环境下
  • 一个用户对应一个角色
  • 不同角色不同菜单
  • 用户登录后根据用户角色显示菜单
    在这里插入图片描述

数据库首先创建角色,角色表不需要外键,都是别的表依赖角色表

create table role(
	id int(10) primary key auto_increment,
	name varchar(20)
);

然后创建用户表,案例设定一个用户一个角色,所以角色id对应用户的外键rid

create table users(
	id int(10) primary key auto_increment,
	username varchar(20),
	password varchar(20),
	rid int(10)
)

上边用户和角色的管理完毕,下边是角色和菜单的关联,像多对多或者一对多的关联,就不必也不能使用外键

创建菜单表,其中pid是显示出菜单的层次而设计

create table menu(
id int(10) primary key auto_increment,
name varchar(20),
pid int(10)
)

创建role_menu,将role和menu联系起来,当业务需要查询某个角色的菜单,应将role_menu和menu两个表联合(rm.mid=m.id &where rm.id=?),这样就能查询出某个role的所有menu

create table role_menu(
id int(10) primary key auto_increment,
rid int(10),
mid int(10)

)

mapper

Login

public interface UsersMapper {
    
    
	Users selByUser(Users users);
}

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namesapce:理解成实现类的全路径(包名+类名) -->
<mapper namespace="cn.wit.mapper.UsersMapper" >
	
	<select id="selByUser" resultType="users">
		select * from users where username = #{username} and password = #{password}
	</select>
	
</mapper>

查询菜单

public interface MenuMapper {
    
    
	List<Menu> selByRid(@Param("rid") int rid ,@Param("pid")int pid);
}

菜单联合后,主要是联合了每个menu的role信息,where筛选role,就可以得到想要的某个role的全部menu,菜单需要有层次,进行递归调用(分级菜单案例),

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namesapce:理解成实现类的全路径(包名+类名) -->
<mapper namespace="cn.wit.mapper.MenuMapper">


	<resultMap type="menu" id="mmap">
		<id column="id" property="id" />
		<result column="name" property="name" />
		<result column="pid" property="pid" />
		<collection property="children" select="selByRid" column="{rid=rid,pid=id}">
		</collection>
	</resultMap>
	<select id="selByRid" resultMap="mmap">
		select m.* ,#{
    
    rid} rid
		from Menu m join
		role_menu r on r.mid = m.id
		where r.rid = #{
    
    rid} and m.pid=#{
    
    pid}
	</select>
</mapper>
</mapper>

pojo

Menu

	private int id;
	private String name;
	private int pid;

Users

private int id;
	private String username;
	private String password;
	private int rid;
	private List<Menu> menus;

service

@Service
public class MenuServiceImpl implements MenuService{
    
    
	@Resource
	
	private MenuMapper menuMapper;
	@Override
	public List<Menu> showMenu(int rid) {
    
    
		return menuMapper.selByRid(rid, 0);
	}

}

注入登录的Mapper和需要设置菜单的Impl,传入rid设置菜单List,另外user空指针

@Service
public class UsersServiceImpl implements UsersService {
    
    

	@Resource
	private UsersMapper usersMapper;
	@Resource
	private MenuService menuServiceImpl;
	
	@Override
	public Users login(Users users) {
    
    
		Users user = usersMapper.selByUser(users);
		if(user!=null){
    
    
			user.setMenus(menuServiceImpl.showMenu(user.getRid()));
		}
		return user;
	}

}

controller

基 本 操 作

@Controller
public class UsersController {
    
    
	@Resource
	private UsersService usersServiceImpl;
	@RequestMapping("login")
	public String login(Users users,HttpSession session){
    
    
		
		Users user = usersServiceImpl.login(users);
		if(user!=null){
    
    
			session.setAttribute("user", user);
			return "redirect:/main.jsp";
		}
		return "redirect:/login.jsp";
	}
	
}

jsp

login.jsp

<form action="login" method="post">
	用户名:<input type="text" name="username"/><br/>
	密码:<input type="password" name="password"/><br/>
	<input type="submit" value="登录"/><br/>
</form>
</body>
</html>

main.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<dl>
<c:forEach items="${user.menus }" var="menu">
	<dt>${
    
    menu.name }</dt>
	<c:forEach items="${menu.children }" var="child">
		<dd>${
    
    child.name }</dd>
	</c:forEach>
</c:forEach>
</dl>
</body>
</html>

资源可见

在菜单的例子上进行添加,由于rbac良好的扩展性,不管是菜单还是资源可见还是url权限,都是属于功能的部分,都是功能呢围绕角色进行数据库设计的,在具体操作时,只需要添加一个功能表,一个角色_功能表,因为添加了新功能,相当于角色也多了一个新属性,对应的用户自然也要同步这个属性,在业务需要查询角色对应的功能时,SQL语句联合操作即可拿到数据

数据库

  • 建立一个资源表element
  • 建立一个角色_资源表role_element

DDL

CREATE TABLE `element` (
  `id` int(10) NOT NULL AUTO_INCREMENT,
  `eleno` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

CREATE TABLE `role_element` (
  `id` int(10) NOT NULL AUTO_INCREMENT,
  `rid` int(10) DEFAULT NULL,
  `eid` int(10) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;


mapper

资源可见指的是资源对特定的角色可见,对其他角色隐藏,当用户登录时,通过用户的角色(rid)判断是否要显示这一资源,所以,落实到具体的操作上,就应该是selByRid,联合element和role_element来结合rid和具体的资源,筛选rid可以得到某个rid的资源(返回element对象)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  
  
  <mapper namespace="cn.wit.mapper.ElementMapper">
  	<resultMap type="Element" id="mymap">
  		<id column="id" property="id"/>
  		<result column="eleno" property="eleno"/>
  	</resultMap>
  	<select id="selByRid" parameterType="int" resultMap="mymap">
  		select e.*,re.rid from element e
  		join role_element re on e.id=re.eid 
  		where rid=#{
    
    id}
  	</select>
  </mapper>

Impl

Impl基本操作调用方法

@Service
public class ElementServiceImpl implements ElementService{
    
    
	
	@Resource
	private ElementMapper elementMapper;

	public List<Element> selByRid(int id) {
    
    
		return elementMapper.selByRid(id);
	}
	
	
}

将新功能添加到用户中

@Service
public class UsersServiceImpl implements UsersService {
    
    

	@Resource
	private UsersMapper usersMapper;
	@Resource
	private MenuService menuServiceImpl;
	@Resource
	private ElementService elementServiceImpl;

	
	@Override
	public Users login(Users users) {
    
    
		Users user = usersMapper.selByUser(users);
		if(user!=null){
    
    
			user.setMenus(menuServiceImpl.showMenu(user.getRid()));
			user.setElements(elementServiceImpl.selByRid(user.getRid()));
		}
		return user;
	}

}

jsp

通过验证用户的elements资源属性中的eleno来匹配显示资源,某个资源的eleno字符串匹配,则进行显示

<c:forEach items="${user.elements }" var="ele">
	<c:if test="${ele.eleno  eq 'grant' }">
		<button>授权</button>
	</c:if>
</c:forEach>

URL权限验证

权限验证相比前面两个不好理解一点,简单来说就是用户A用户B都能进行登录操作(重定向rbac/main.jsp这个uri),但是用户A能访问demo控制器(经过controller然后跳转到demo.jsp),用户B没有访问demo的权限,访问demo就重定向到登录页面

  • 同样的,跟上面两个功能相比,url同样是一种功能,同样是建立两个表,并且用户添加功能属性
  • 不一样的是,只能将用户的url添加到Users中,返回值为User,这样查询全部权限的数据无法传到controller中,解决办法可以改返回值,也可以将查询全部权限的操作直接放在controller中,下边的例子就是后者

数据库

  • 建立url表
  • 建立role_url表

DDL

CREATE TABLE `url` (
  `id` int(10) NOT NULL AUTO_INCREMENT,
  `name` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;


CREATE TABLE `role_url` (
  `id` int(10) NOT NULL AUTO_INCREMENT,
  `rid` int(10) DEFAULT NULL,
  `uid` int(10) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;


mapper

url权限控制需要查询某个角色的权限之外,还需要查询全部权限(当用户访问无权限内容时,应该做出某些动作,比如跳转会登陆界面,或者提醒你充VIP)


public interface UrlMapper {
    
    
	@Select("select *from url where id in(select uid from role_url where rid=#{rid})")
	List<Url> selByRid(int rid);
	@Select("select *from url")
	List<Url>selAll();
}

Impl


@Service
public class UrlServiceImpl implements UrlService{
    
    
	
	@Resource
	private UrlMapper urlMapper;
	@Override
	public List<Url> selByRid(int rid) {
    
    
		return urlMapper.selByRid(rid);
	}
	@Override
	public List<Url> showAll() {
    
    
		return urlMapper.selAll();
	}

}

@Service
public class UsersServiceImpl implements UsersService {
    
    

	@Resource
	private UsersMapper usersMapper;
	@Resource
	private MenuService menuServiceImpl;
	@Resource
	private ElementService elementServiceImpl;
	@Resource
	private UrlService urlServiceImpl;
	
	@Override
	public Users login(Users users) {
    
    
		Users user = usersMapper.selByUser(users);
		if(user!=null){
    
    
			user.setMenus(menuServiceImpl.showMenu(user.getRid()));
			user.setElements(elementServiceImpl.selByRid(user.getRid()));
			user.setUrl(urlServiceImpl.selByRid(user.getRid()));
		}
		return user;
	}

}

controller

  • 需要注入用户的所有信息,包括usersServiceImpl和url权限的查询全部需要的urlServiceImpl
  • 将users和全部url权限数据通过session流转给jsp
@Controller
public class UsersController {
    
    
	
	@Resource
	private UrlService urlServiceImpl;
	
	@Resource
	private UsersService usersServiceImpl;
	
	@RequestMapping("login")
	public String login(Users users,HttpSession session){
    
    
		
		Users user = usersServiceImpl.login(users);
		if(user!=null){
    
    
			System.out.println(user);
			List<Url> urlist=urlServiceImpl.showAll();
			session.setAttribute("user", user);
			session.setAttribute("urlist", urlist);
			return "redirect:/main.jsp";
		}
		return "redirect:/login.jsp";
	}
	
	
	@RequestMapping("demo")
	public String demo(){
    
    
		return "/a.jsp";
	}
}

filter

通过filter或者拦截器来筛选请求

逻辑思路:


uri=session.getURI()//请求uri
if(静态资源){
	放行
}else{
	所有权限url  urlist
	if(urlist!=null){
		isExist=false//请求uri是否在权限里边
		foreach(items=urlist var=url){
			if(url.getName==uri){
				isExist=true//全部权限中有与uri一致的,需要权限验证
			}
		}
		if(isExist){
			//uri在权限里边
			
			isRight//是否属于本用户权限
			urls=session.getAttribute()//本用户权限
			foreach(items=urls var=url){
				if(url.getName()==uri){
					isRight=true//用户权限中有与uri一致的
				}
			}
			if(isRight){
				//属于本用户权限
				放行
			}else{
				//本用户无该权限
				重定向登录页面
			}

		}else{
			//不在权限里边
			
			放行
		}
	}else{
		没有权限url,放行
	}
}
@WebFilter("/*")
public class UrlFilter implements Filter{
    
    

	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
    
    
		// TODO Auto-generated method stub
		
	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
    
    
		HttpServletRequest req=(HttpServletRequest)request;
		HttpServletResponse res=(HttpServletResponse)response;
		String uri=req.getRequestURI();
		if(uri.endsWith(".js")||uri.endsWith(".css")||uri.endsWith(".jpg")||uri.endsWith(".png")){
    
    
			//放行
			chain.doFilter(request, response);
		}else{
    
    
			if(uri.equals("/rbac/login")||uri.equals("/rbac/login.jsp")){
    
    
				//不拦截的uri
				chain.doFilter(request, response);
			}else{
    
    
				boolean isExist=false;
				HttpSession session = req.getSession();
				//先判断属不属于有权限的uri
				List<Url> urlist = (List<Url>) session.getAttribute("urlist");
				if(urlist!=null){
    
    
					System.out.println("urlist不为空");
					for (Url url : urlist) {
    
    
						System.out.println(url+":"+uri);
						if(uri.equals(url.getName())){
    
    
							isExist=true;
						}
					}
					if(isExist){
    
    
						System.out.println("存在有权限的uri");
						//如果属于有权限的uri,判断是否是当前用户是否有访问该uri的权限
						Users user = (Users) session.getAttribute("user");
						List<Url> urls = user.getUrl();
						boolean isRight=false;
						for (Url url : urls) {
    
    
							System.out.println(uri+":"+url.getName());
							if(uri.equals(url.getName())){
    
    
								isRight=true;
							}
						}
						if(isRight){
    
    
							//此时是uri属于权限,而且用户拥有该权限,放行
							System.out.println("有uri权限");
							chain.doFilter(request, response);
						}else{
    
    
							//此时uri属于权限,用户没该权限,无权限访问跳回登陆页面
							System.out.println("无uri权限");
							res.sendRedirect("/rbac/login.jsp");
						}
					}else{
    
    
						//此时是uri不属于权限,放行
						chain.doFilter(request, response);
					}
				}else{
    
    
					//此时该权限表里边没有数据,所有都放行
					chain.doFilter(request, response);
				}
				
			}
		}
	}

	@Override
	public void destroy() {
    
    
		// TODO Auto-generated method stub
		
	}
	

}

猜你喜欢

转载自blog.csdn.net/WA_MC/article/details/113280889