一、RBAC模型
1.什么是RBAC?
RBAC(Role-BasedAccessControl )基于角色的访问控制。
RBAC 认为权限的过程可以抽象概括为: 判断【Who 是否可以对 What 进行 How 的访问操作(Operator)】
Who:权限的拥用者或主体 ;
What:权限针对的对象或资源 ;
How:具体的权限 ;
Operator:操作。表明对 What 的 How 操作。也就是 Privilege+Resource; Role:角色,一定数量的权限的集合。 权限分配的单位与载体,目的是隔离User与Privilege 的逻辑关系。
2.RBAC模型分类:
模型图RBAC96 模型家族,其中包括了 RBAC0~RBAC3 四个概念模型。
- RBAC0:
定义了能构成一个 RBAC 控制系统的最小的元素集合。
在 RBAC 之中,包含用户 users(USERS)、角色 roles(ROLES)、目标 objects(OBS)、操作 operations(OPS)、许可权 permissions(PRMS)五个基本数据元素,权限被赋予角色,而不是用 户,当一个角色被指定给一个用户时,此用户就拥有了该角色所包含的权限。会话 sessions 是用户与激活的角色集合之间的映射。
RBAC0 与传统访问控制的差别在于增加一层间接性 带来了灵活性,RBAC1、 RBAC2、RBAC3 都是先后在 RBAC0 上的扩展。
3.RBAC0:
详情图- RBAC1:
详情图角色间的继承关系可分为一般继承关系和受限继承关系。一般继承关系仅要求角色继承 关系是一个绝对偏序关系,允许角色间的多继承。而受限继承关系则进一步要求角色继承关 系是一个树结构。
- RBAC2:
详情RBAC2 的约束规定了权限被赋予角色时,或角色被赋予用户时,以及当用户在某一时刻 激活一个角色时所应遵循的强制性规则。责任分离包括静态责任分离和动态责任分离。约束 与用户-角色-权限关系一起决定了 RBAC2 模型中用户的访问许可。
- RBAC3:
详情RBAC3 包含了 RBAC1 和 RBAC2 既提供了角色间的继承关系,又提供了责任分离关系。
二、RBAC项目分析
1.需求:
(1)实现用户登录功能;
(2)使用 RBAC0 模型管理系统权限;
(3)对系统的菜单以及菜单中的链接进行管理;
(4)用户登录后首页根据用户角色显示该角色所对应的菜单;
(5)禁止用户越级访问。
2.数据库设计:
示例图一个角色对多个用户;多个角色对多个菜单,创建中间表;一个菜单对多个功能操作。
- 创建表:
(1)用户表:
CREATE TABLE `users` (
`userid` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(20) DEFAULT NULL,
`userpwd` varchar(30) DEFAULT NULL,
`role_id` int(11) DEFAULT NULL,
PRIMARY KEY (`userid`),
KEY `role_id_pk` (`role_id`) USING BTREE,
CONSTRAINT `users_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `roles` (`roleid`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
(2)角色表:
CREATE TABLE `roles` (
`roleid` int(11) NOT NULL,
`rolename` varchar(50) DEFAULT NULL,
PRIMARY KEY (`roleid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
(3)菜单表:
CREATE TABLE `menus` (
`menuid` int(11) NOT NULL AUTO_INCREMENT,
`menuname` varchar(40) DEFAULT NULL,
`menuurl` varchar(40) DEFAULT NULL,
`fatherid` int(11) DEFAULT NULL,
PRIMARY KEY (`menuid`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;
(4)菜单和角色的中间表:
CREATE TABLE `roles_menus` (
`roles_id` int(11) NOT NULL,
`menus_id` int(11) NOT NULL,
PRIMARY KEY (`roles_id`,`menus_id`),
KEY `roles_menus_fk_id` (`menus_id`),
CONSTRAINT `roles_menus_fk` FOREIGN KEY (`roles_id`) REFERENCES `roles` (`roleid`),
CONSTRAINT `roles_menus_fk_id` FOREIGN KEY (`menus_id`) REFERENCES `menus` (`menuid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
(5)功能表:
CREATE TABLE `funs` (
`funid` int(11) NOT NULL AUTO_INCREMENT,
`funname` varchar(50) DEFAULT NULL,
`funurl` varchar(50) DEFAULT NULL,
`menu_id` int(11) DEFAULT NULL,
PRIMARY KEY (`funid`),
KEY `menu_id_fk` (`menu_id`),
CONSTRAINT `menu_id_fk` FOREIGN KEY (`menu_id`) REFERENCES `menus` (`menuid`)
) ENGINE=InnoDB AUTO_INCREMENT=209 DEFAULT CHARSET=utf8;
3.搭建ssm项目框架:
(1).导包:
jar文件
(2)配置web.xml文件:
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0">
<display-name>rbacDemo</display-name>
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- 字符编码 -->
<filter>
<filter-name>encod</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encod</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext-*.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>
(3)配置springmvc.xml:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 扫描@Controller -->
<context:component-scan base-package="com.zlw.controller"></context:component-scan>
<!-- @RequestMapping -->
<mvc:annotation-driven></mvc:annotation-driven>
<!-- 对静态资源放行 -->
<mvc:default-servlet-handler />
<!-- 视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
<!-- 配置静态资源映射 -->
<mvc:resources location="/WEB-INF/css/" mapping="/css/**" />
<mvc:resources location="/WEB-INF/js/" mapping="/js/**" />
<mvc:resources location="/WEB-INF/images/" mapping="/images/**" />
</beans>
(4)配置applicationContext-*.xml:
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/rbac"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
<bean id="factory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<property name="typeAliasesPackage" value="com.zlw.pojo"></property>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="factory"></property>
<property name="basePackage" value="com.zlw.mapper"></property>
</bean>
<!-- 扫描@Service -->
<context:component-scan base-package="com.zlw.service"></context:component-scan>
(5)创建项目需要的包:
package
4.创建实体类:
(1)Users类:
private int userid;//用户ID
private String username;//用户名
private String userpwd;//密码
private Roles roles;//角色信息
private List<Menus> menus = new ArrayList<Menus>();//菜单信息
private List<Funs> funs = new ArrayList<Funs>();//功能信息
(2)Roles类:
private int roleid;//角色ID
private String rolename;//角色名称
private List<Menus> menus = new ArrayList<Menus>();//功能信息
(3)Menus类:
private int menuid;//菜单ID
private String menuname;//菜单名称
private String menuurl;//菜单url
private int fatherid;//父级ID
private List<Funs> funs = new ArrayList<Funs>();//功能信息
(4)Funs类:
private int funid;
private String funname;
private String funurl;
- mapper数据访问层:
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.zlw.mapper.UsersMapper">
<resultMap type="com.zlw.pojo.Users" id="userMapper">
<id property="username" column="username" />
<result property="userpwd" column="userpwd" />
<!-- 配置关联对象Roles -->
<association property="roles" javaType="com.zlw.pojo.Roles">
<id property="roleid" column="roleid" />
<result property="rolename" column="rolename" />
</association>
<!-- 配置关联对象Menus -->
<collection property="menus" ofType="com.zlw.pojo.Menus">
<id property="menuid" column="menuid" />
<result property="menuname" column="menuname" />
<result property="menuurl" column="menuurl" />
<result property="fatherid" column="fatherid" />
</collection>
<!-- 配置关联对象Funs -->
<collection property="funs" ofType="com.zlw.pojo.Funs">
<id property="funid" column="funid" />
<result property="funname" column="funname" />
<result property="funurl" column="funurl" />
</collection>
</resultMap>
<select id="findByName" resultMap="userMapper"
parameterType="java.lang.String">
select *from users u,roles r,roles_menus rm,menus m
LEFT JOIN funs f ON m.menuid=f.menu_id
where u.role_id=r.roleid
and
r.roleid=rm.roles_id
and rm.menus_id=m.menuid
and u.username=#{0}
</select>
</mapper>
- Service业务层:
@Service("userService")
public class UserServiceImpl implements UserService {
@Autowired
private UsersMapper usersMapper;
@Override
public Users userLogin(String username, String pwd) {
Users users = usersMapper.findByName(username);
if(users ==null) {
//用户不存在
throw new UsersException("用户不存在!");
}else if(!users.getUserpwd().equals(pwd)){
//密码有误
throw new UsersException("密码错误!");
}
return users;
}
}
- Controller控制层:
@Controller
@RequestMapping("user")
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("login")
public String login(Users users,Model model,HttpSession session,HttpServletRequest request) {
try {
Users us= userService.userLogin(users.getUsername(), users.getUserpwd());
System.out.println(us);
session.setAttribute("user", us);
} catch (Exception e) {
e.printStackTrace();
model.addAttribute("msg",e.getMessage());
return "login";
}
return "redirect:/index.jsp";
}
}
- jsp页面:
<body>
<h3 >用户登录</h3>
<span style="color: red">${requestScope.msg }</span>
<form action="user/login" method="post">
<p>
用户名:<input type="text" name="username"/>
</p>
<p>
密码:<input type="password" name="userpwd"/>
</p>
<p>
<input type="submit" value="登录"/>
</p>
</form>
</body>
- 自定义异常:
package com.zlw.exception;
/**
* 自定义异常
* @author zhang
*
*/
public class UsersException extends RuntimeException{
public UsersException() {
super();
}
public UsersException(String message, Throwable cause) {
super(message, cause);
}
public UsersException(String message) {
super(message);
}
}
-
实现基本效果:
登录界面
自定义异常
5.使用Dtree组件实现菜单的自动生成:
- 使用过滤器限制除登录页面的访问:
继承Filter接口:import javax.servlet.Filter;Filter中的方法。在没有通过登录页面访问其他页面都会被拒绝,并跳转到登录界面。
public class UserLoginFilter implements Filter {
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest request, ServletResponse respose, FilterChain chain)
throws IOException, ServletException {
//获取用户访问的uri
HttpServletRequest req = (HttpServletRequest) request;
String uri = req.getRequestURI();
//判断当前访问的uri是否是登录资源,如果是放行
if(uri.indexOf("login")!=-1) {
chain.doFilter(req, respose);
}else {
//用户是否登录的判断
HttpSession session = req.getSession();
Users users = (Users) session.getAttribute("user");
if(users!=null&&users.getUsername().length()>0) {
chain.doFilter(req, respose);
}else {
req.setAttribute("msg","请先进行登录!");
req.getRequestDispatcher("login.jsp").forward(req, respose);
}
}
}
@Override
public void init(FilterConfig arg0) throws ServletException {
}
}
(2)在web.xml中配置过滤器:
<filter>
<filter-name>UserLoginFilter</filter-name>
<filter-class>com.zlw.filter.UserLoginFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>UserLoginFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
- 实现菜单的生成:
在项目中使用dtree的组件;并在主界面中搭建主框架;
(1)主界面:
<frameset rows="15%,*,10%" border="1">
<frame src="head.jsp" scrolling="no" name="head.jsp">
<frameset cols="20%,*">
<frame src="menu.jsp" scrolling="auto" name="menu">
<frame src="body.jsp" scrolling="auto" name="body">
</frameset>
<frame src="foot2.jsp" scrolling="auto" name="foot2.jsp">
(2)菜单展现的jsp:
<html>
<head>
<title>Insert title here</title>
<SCRIPT language=javascript src="js/dtree/dtree.js"></SCRIPT>
<script type="text/javascript" src="js/java-like.util.js"></script>
<link rel="stylesheet" href="js/dtree/dtree.css" type="text/css">
</head>
<body>
<form action="" name="form1" method=POST>
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td rowspan="5" width="1" bgcolor="CCCCCC"></td>
<td bgcolor="CCCCCC" height="1"></td>
<td rowspan="4" bgcolor="CCCCCC" width="1"></td>
</tr>
<tr>
<td bgcolor="E3E7FF" align="center" height="5"></td>
</tr>
<tr>
<td bgcolor="CCCCCC" height="1"></td>
</tr>
<tr>
<td bgcolor="F9F9F9" align="center" valign="top">
<table width="90%" border="0" align="center" cellpadding="1" cellspacing="1" bgcolor="F5F5F5">
<tr bgcolor="F3F9FF">
<td bgcolor="F5F5F5">
<SCRIPT LANGUAGE="JavaScript">
d = new dTree('d');
d.config.target = "body";
d.config.imageDir = 'js/dtree/img';
d.reSetImagePath();
d.config.folderLinks = false;
d.config.closeSameLevel =true;
var isOpen ;
//根节点
<%
Users user = (Users)session.getAttribute("user");
List menus = user.getMenus();
for(int i=0;i<menus.size();i++){
Menus menu = (Menus)menus.get(i);
%>
d.add(<%=menu.getMenuid() %>, <%=menu.getFatherid() %>, '<%=menu.getMenuname() %>', '<%=menu.getMenuurl() %>', '', 'body');
<%}%>
document.write(d);
</script>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td background="images/jao1.gif" colspan="2" align="right"><img
src="images/jao.gif" width="8" height="8"></td>
</tr>
</table>
</form>
</body>
</html>
-
实现的效果:
实现菜单效果
6.权限的管理:
主要解决越级访问问题;所谓的越级访问就是使用低级别的角色访问高级别的资源。
-
将数据录入资源管理数据库中:
资源管理基础数据 - 创建权限过滤器:
继承Filter接口:import javax.servlet.Filter;实现Filter中的方法;先对静态资源和用户登录放行;通过判断前访问的 URI 是否在功能数据中包
含;决定是否赋予访问的权限。
public class SafeFilter implements Filter {
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
String uri = req.getRequestURI();
//对静态页面做放行处理
if(uri.endsWith(".js")||uri.endsWith(".css")||uri.endsWith(".gif")) {
chain.doFilter(request, response);
}else {
//对用户的登录放行
if(uri.indexOf("login")!=-1) {
chain.doFilter(request, response);
}else {
HttpSession session = req.getSession();
Users users = (Users) session.getAttribute("user");
List<Funs> funs = users.getFuns();
//定义一个开关控制
boolean flag = false;
for(Funs f:funs) {
System.out.println(uri);
//判断当前访问的URI是否在功能数据中包含
if(uri.indexOf(f.getFunurl())!=-1) {
flag = true;
break;
}
}
System.out.println(flag);
//根据开关的值进行跳转
if(flag) {
System.out.println(flag);
chain.doFilter(request, response);
}else {
System.out.println("ok!");
resp.sendRedirect("roleerror.jsp");
}
}
}
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
}
(2)配置web.xml中的过滤器:
<filter>
<filter-name>SafeFilter</filter-name>
<filter-class>com.zlw.filter.SafeFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>SafeFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
-
实现的效果:
网友评论