Servlet整合Shiro实现RBAC(七)

戒色诗: 二八佳人体似酥,腰间仗剑斩凡夫。虽然不见人头落,暗里教君骨髓枯。

老蝴蝶提醒,在学习这一章节之前,一定要学习前面的章节及RBAC教程部分。

一. Servlet 整合 Shiro 实现 RBAC准备

一.一 数据库准备

数据库与上一章节一致, user 表里面添加了盐,并且更新了密码, 同时 相对应的pojo也进行了更新。

相对应的 rbac.sql 文件会更新到链接里面。

一.二 前端页面准备

前端页面采用 RBAC系列中的页面,与前面的实现一致。

![有图片]( https://img-blog.csdnimg.cn/20200514094339612.png?x-oss-process=image/watermark ,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lqbHR4MTIzNGNzZG4=,size_16,color_FFFFFF,t_70)

二. Servlet 整合 Shiro 实现 RBAC

二.一 添加 pom.xml 依赖

包括 servlet, shiro,数据库,日志,json转换等常用依赖。

<dependencies>
  	
  	<!--tomcat中 jsp与 servlet依赖 -->
		<dependency>
			<groupId>javax.servlet.jsp</groupId>
			<artifactId>javax.servlet.jsp-api</artifactId>
			<version>2.3.1</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>javax.servlet-api</artifactId>
			<version>3.1.0</version>
			<scope>provided</scope>
		</dependency>
		<!-- jstl 与 standard 依赖-->
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>jstl</artifactId>
			<version>1.2</version>
		</dependency>
		<dependency>
			<groupId>taglibs</groupId>
			<artifactId>standard</artifactId>
			<version>1.1.2</version>
		</dependency>
		
  	<dependency>
  		<groupId>org.apache.shiro</groupId>
  		<artifactId>shiro-all</artifactId>
  		<version>1.2.2</version>
  	</dependency>
  	<dependency>
  		<groupId>junit</groupId>
  		<artifactId>junit</artifactId>
  		<version>4.12</version>
  	</dependency>
  	<dependency>
  		<groupId>org.slf4j</groupId>
  		<artifactId>slf4j-log4j12</artifactId>
  		<version>1.7.25</version>
  	</dependency>
  	<dependency>
  		<groupId>commons-logging</groupId>
  		<artifactId>commons-logging</artifactId>
  		<version>1.2</version>
  	</dependency>

	<dependency>
			<groupId>net.sf.json-lib</groupId>
			<artifactId>json-lib</artifactId>
			<version>2.4</version>
		</dependency>

		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-lang3</artifactId>
			<version>3.1</version>
		</dependency>
		<dependency>
			<groupId>commons-collections</groupId>
			<artifactId>commons-collections</artifactId>
			<version>3.2.2</version>
	 </dependency>
	 
	 <!-- mysql依赖 -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.32</version>
		</dependency>
		<!-- c3p0依赖 -->
		<dependency>
			<groupId>com.mchange</groupId>
			<artifactId>c3p0</artifactId>
			<version>0.9.5.2</version>
		</dependency>
		
  </dependencies>
  <build>
  	 <plugins>
            <!-- 编译的jdk版本 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
            <plugin>
				<groupId>org.apache.tomcat.maven</groupId>
				<!--tomcat的插件名, tomcat7-maven-plugin, 用的是tomcat7版本 -->
				<artifactId>tomcat7-maven-plugin</artifactId>
				<version>2.2</version>
				<configuration>
					<port>8080</port>  <!--tomcat的端口号 -->
					<path>/Shiro_Servlet</path> <!--tomcat的项目名 -->
					<uriEncoding>UTF-8</uriEncoding> <!-- 防止get 提交时乱码 -->
				</configuration>
			</plugin>
      </plugins>
  </build>

二.二 web.xml 添加配置

  <!-- 配置监听器 -->
 <listener>
   <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
</listener>
<context-param>
   <param-name>shiroEnvironmentClass</param-name>
   <param-value>org.apache.shiro.web.env.IniWebEnvironment</param-value>
</context-param>
	<!-- 配置文件的路径 -->
    <context-param>
        <param-name>shiroConfigLocations</param-name>
        <param-value>classpath:shiro.ini</param-value>
    </context-param>
	<!-- 配置shiro 过滤器 -->
  <filter>
    <filter-name>ShiroFilter</filter-name>
    <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>ShiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
     

二.三 后端开发

目录结构:

![有图片]( https://img-blog.csdnimg.cn/20200514094348405.png?x-oss-process=image/watermark ,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lqbHR4MTIzNGNzZG4=,size_16,color_FFFFFF,t_70)

基本与前面的 Servlet 内容一致。

只简单复制几个比较重要的类。

二.三.一 权限查询实现 PrivilegeServiceImpl

有一个方法, getPrivilegeByUId 用于查询权限。

public class PrivilegeServiceImpl implements PrivilegeService {

	private PrivilegeDao privilegeDao=new PrivilegeDaoImpl();
	@Override
	public List<Privilege> getPrivilegeByUId(Integer uId,Integer type) {
		//如果类型为空,就查询全部类型的权限
		if(type==null){
			String sql="select * from privilege a where a.id in ( select rp.pid from user_role ur "
					+ " left join role_privilege rp "
					+"on ur.rid=rp.rid  where ur.uid=? )";
					return privilegeDao.findInfosBySql(sql,uId);
		}else{
			//查询指定类型的权限
			String sql="select * from privilege a where a.id in ( select rp.pid from user_role ur "
					+ " left join role_privilege rp "
					+"on ur.rid=rp.rid  where ur.uid=? ) and a.type=?";
					return privilegeDao.findInfosBySql(sql,uId,type);
		}
		
	}
}

二.三.二 根据用户编号查询权限数据 PrivilegeListServlet

package com.yjl.servlet;

import java.io.IOException;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.yjl.pojo.Privilege;
import com.yjl.service.PrivilegeService;
import com.yjl.service.impl.PrivilegeServiceImpl;

import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import net.sf.json.JsonConfig;

@WebServlet("/Privilege/getPrivilegeByUId")
public class PrivilegeListServlet extends HttpServlet{
	private static final long serialVersionUID = 1L;

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		doPost(req,resp);
	}
	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		//接收用户的编号
		 PrivilegeService privilegeService=new PrivilegeServiceImpl();
		Integer uId=Integer.parseInt(req.getParameter("userId"));
		privilegeService=new PrivilegeServiceImpl();
		//查询全部的菜单
		List<Privilege> privilegeList=privilegeService.getPrivilegeByUId(uId,1);
		
		java2Json(resp, privilegeList, new String[]{});
	}
	/**
	 * 将指定Java对象转为json,并响应到客户端页面
	 * @param o
	 * @param exclueds
	 */
	public void java2Json(HttpServletResponse resp,@SuppressWarnings("rawtypes") List o ,String[] exclueds){
		JsonConfig jsonConfig = new JsonConfig();
		//指定哪些属性不需要转json
		jsonConfig.setExcludes(exclueds);
		JSONArray objData=JSONArray.fromObject(o,jsonConfig);
		JSONObject objMap=new JSONObject();
		objMap.put("data",objData);
		objMap.put("status",true);
		resp.setContentType("text/json;charset=utf-8");
		try {
			resp.getWriter().print(objMap.toString());
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

二.三.四 登录 UserLoginServlet

package com.yjl.servlet;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;

import com.yjl.pojo.User;

import net.sf.json.JSONObject;

@WebServlet("/User/login")
public class UserLoginServlet extends HttpServlet{
	private static final long serialVersionUID = 1L;

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		doPost(req,resp);
	}
	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		//找到相应的Subject
		Subject subject=SecurityUtils.getSubject();
		
		UsernamePasswordToken token=new UsernamePasswordToken(req.getParameter("code"),
				req.getParameter("password"));
		try{
			subject.login(token);
			boolean2Json(resp, true);
			
			Session session=subject.getSession();
			User user=(User)subject.getPrincipal();
			session.setAttribute("loginUser", user);
			
			
		}catch(Exception e){
			//代码为001,表示用户名或者密码错误
			 map2Json(resp,"001");
		}
	}
	
	/**
	 * 将状态返回到前台,通常是添加,删除,更新的操作,如果错误,则传入错误代码。
	 * @param o
	 * @param exclueds
	 */
	public void map2Json(HttpServletResponse resp,String ... code){
		//指定哪些属性不需要转json
		JSONObject objMap=new JSONObject();
		if(code==null||code.length<1){
			objMap.put("status",true);
		}else{
			objMap.put("status",false);
			objMap.put("error_code",code[0]);
		}
		resp.setContentType("text/json;charset=utf-8");
		try {
			resp.getWriter().print(objMap.toString());
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	/**
	 * 传入是否成功,只返回状态
	 * @param o
	 * @param exclueds
	 */
	public void boolean2Json(HttpServletResponse resp,boolean flag){
		//指定哪些属性不需要转json
		JSONObject objMap=new JSONObject();
		objMap.put("status",true);
		objMap.put("flag",flag);
		resp.setContentType("text/json;charset=utf-8");
		try {
			resp.getWriter().print(objMap.toString());
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

二.三.五 退出登录 UserLoginOutServlet

package com.yjl.servlet;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;

@WebServlet("/User/logout")
public class UserLoginOutServlet extends HttpServlet{
	private static final long serialVersionUID = 1L;

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		doPost(req,resp);
	}
	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		Subject subject=SecurityUtils.getSubject();
		//退出登录
		subject.logout();
		req.getRequestDispatcher("/WEB-INF/pages/login.jsp").forward(req, resp);
	}
}

二.四 自定义Realm

package com.yjl.shiro;
import java.util.List;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;

import com.yjl.dao.PrivilegeDao;
import com.yjl.dao.RoleDao;
import com.yjl.dao.UserDao;
import com.yjl.dao.impl.PrivilegeDaoImpl;
import com.yjl.dao.impl.RoleDaoImpl;
import com.yjl.dao.impl.UserDaoImpl;
import com.yjl.pojo.Privilege;
import com.yjl.pojo.Role;
import com.yjl.pojo.User;

public class MyRealm extends AuthorizingRealm{

	private UserDao userDao=new UserDaoImpl();
	private PrivilegeDao privilegeDao=new PrivilegeDaoImpl();
	private RoleDao roleDao=new RoleDaoImpl();
	@Override
	public String getName() {
		return "MyRealm";
	}
	//授权
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection paramPrincipalCollection) {
		System.out.println("进入授权");
		User user=(User)paramPrincipalCollection.getPrimaryPrincipal();
		System.out.println("输出登录的用户编号:"+user.getCode());
		
		//查询权限
		String priSql="select a.* from privilege a where a.id in ( select rp.pid from user_role ur "
				+ " left join role_privilege rp "
				+"on ur.rid=rp.rid  where ur.uid=? ) and a.type=?";
		List<Privilege> privilegeList= privilegeDao.findInfosBySql(priSql,user.getId(),2);
		
		SimpleAuthorizationInfo simpleAuthorizationInfo=new SimpleAuthorizationInfo();		
		
		for(Privilege pri:privilegeList){
			if(pri.getPercode()!=null&&!("".equals(pri.getPercode()))){	
				simpleAuthorizationInfo.addStringPermission(pri.getPercode());
			}
		}
		
		//查询角色
		
		String roleSql="select a.* from role a left join user_role b on a.id=b.rid "
				+" where b.uid=?";
		
		List<Role> roleList=roleDao.findInfosBySql(roleSql, user.getId());
		
		for(Role role:roleList){
			simpleAuthorizationInfo.addRole(role.getId()+"");
		}
		
		return simpleAuthorizationInfo;	
	}
	//认证
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken paramAuthenticationToken)
			throws AuthenticationException {
		System.out.println("进入认证");
		String code=(String)paramAuthenticationToken.getPrincipal();
		//根据用户名,去查询相应的数据
		User user=userDao.getInfoByNameAndValue("select * from user","code", code);
		
		if(user==null){
			//没有查询出来
			return null;
		}
		SimpleAuthenticationInfo simpleAuthenticationInfo=
				new SimpleAuthenticationInfo(user,user.getPassword(),
						//传入转换后的盐
						ByteSource.Util.bytes(user.getSalt()),getName());
		
		return simpleAuthenticationInfo;
		
		
	}

}

二.五 配置文件 shiro.ini

用于注入自定义Realm, 添加拦截器规则, 加入密码验证

[main]
#加密类
credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
#加密方式
credentialsMatcher.hashAlgorithmName=md5
#加密次数
credentialsMatcher.hashIterations=10
#存储散列后的密码是否为16进制 
credentialsMatcher.storedCredentialsHexEncoded=false
#配置自定义realm
myRealm=com.yjl.shiro.MyRealm
#配置加密
myRealm.credentialsMatcher=$credentialsMatcher
#注入自定义的realm
securityManager.realm=$myRealm

#配置权限
authc.loginUrl=/User/toLogin
#跳转到权限不足的路径
roles.unauthorizedUrl=/NoPermission/NoPermission
perms.unauthorizedUrl=/NoPermission/NoPermission

[urls]
#静态页面可以访问
/static/**=anon
#跳转到登录页面和登录方法可以访问
/User/toLogin=anon
/User/login=anon
#跳转到主页,需要认证
/Main/toMain=authc
/Privilege/getPrivilegeByUId=authc
#执行方法,不仅需要认证,还需要有相应的方法
/Dept/add=authc,perms["dept:add"]
/Dept/update=authc,perms["dept:update"]
/Dept/list=authc,perms["dept:list"]
/Dept/delete=authc,perms["dept:delete"]
#退出登录
/User/logout=logout
#其他的一切资源,都需要认证
/**=authc

二.六 前端处理

以前的各个页面,不需要做太大的改变, 只需要将 以前的 ?jsp=toLogin, ?method=login, 这样的链接改变即可。(不能用传参的方式进行控制拦截)

只讲解一下 部门表里面关于 添加,删除,修改 三个按钮的控制和隐藏. 这个也与前面是一致的。

二.六.一 部门表 jsp页面 设置标识

<!-- 查看明细部门 -->
		<shiro:hasPermission name="dept:add">
			<script>
				 sessionStorage.setItem("dept:add",true);
			</script>
		</shiro:hasPermission>
		<shiro:hasPermission name="dept:update">
			<script>
				 sessionStorage.setItem("dept:update",true);
			</script>
		</shiro:hasPermission>
		<shiro:hasPermission name="dept:delete">
			<script>
				 sessionStorage.setItem("dept:delete","true")
			</script>
		</shiro:hasPermission>
	<script type="text/javascript" src="${pageContext.request.contextPath}/static/js/dept.js"></script>

二.六.二 部门表 js 脚本通过标识控制按钮的显示和隐藏

//看是否有添加的权限
var add=sessionStorage.getItem("dept:add");
if(add){
	$("#add").show();
}else{
	$("#add").hide();
}
//看修改和删除是否显示和隐藏
function operateFormatter(value, row, index) {
	var update=sessionStorage.getItem("dept:update");
	var del=sessionStorage.getItem("dept:delete");
	//console.log("update:"+update+",del:"+del);
	
	var udpateIcon="";
	if(update){
		udpateIcon='<a class="update text-primary" href="javascript:void(0)" data-toggle="tooltip" title="修改">'
			+'<i class="fa fa-pencil"></i>&nbsp修改&nbsp;&nbsp;&nbsp;&nbsp;</a>';
	}
	var delIcon="";
	if(del){
		 delIcon='<shiro:hasPermission name="dept:delete"><a class="delete text-danger" href="javascript:void(0)" data-toggle="tooltip" title="撤销">'
			+'<i class="fa fa-minus"></i>&nbsp;删除&nbsp;</a></shiro:hasPermission">';
	}
	return udpateIcon+delIcon;
}

二.七 测试整合

二.七.一 admin 用户登录测试

输入网址: http://localhost:8080/Shiro_Servlet/User/toLogin

进入登录页面, 填写用户名 admin, 密码 12345, 错误的密码

![有图片]( https://img-blog.csdnimg.cn/20200514094402394.png?x-oss-process=image/watermark ,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lqbHR4MTIzNGNzZG4=,size_16,color_FFFFFF,t_70)

填写正确的用户名和密码 admin, 1234

![有图片]( https://img-blog.csdnimg.cn/20200514094410660.png?x-oss-process=image/watermark ,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lqbHR4MTIzNGNzZG4=,size_16,color_FFFFFF,t_70)

![有图片]( https://img-blog.csdnimg.cn/2020051409441645.png?x-oss-process=image/watermark ,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lqbHR4MTIzNGNzZG4=,size_16,color_FFFFFF,t_70)

用户对部门没有添加和修改的权限,只有删除的权限, 故 显示删除的按钮,不显示添加和修改的按钮。

手动输入网址: localhost:8080/Shiro_Servlet/Dept/add, 进行添加

有图片

提示没有权限,跳转到没有权限的页面,拦截add 成功。

手动输入网址: http://localhost:8080/Shiro_Servlet/Dept/delete , 进行删除

有图片

能够正确的进行删除。

当用户点击退出之后,再输入刚才的那个 删除网址, 会跳转到退出的页面

![有图片]( https://img-blog.csdnimg.cn/20200514094430538.png?x-oss-process=image/watermark ,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lqbHR4MTIzNGNzZG4=,size_16,color_FFFFFF,t_70)

二.七.二 yuejl 用户登录测试

输入用户名, yuejl, 密码 1234

![有图片]( https://img-blog.csdnimg.cn/20200514094434904.png?x-oss-process=image/watermark ,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lqbHR4MTIzNGNzZG4=,size_16,color_FFFFFF,t_70)

![有图片]( https://img-blog.csdnimg.cn/20200514094439840.png?x-oss-process=image/watermark ,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lqbHR4MTIzNGNzZG4=,size_16,color_FFFFFF,t_70)

没有部门的权限,所以不显示部门的相关信息。

点击用户管理,可以正常的显示

![有图片]( https://img-blog.csdnimg.cn/20200514094445175.png?x-oss-process=image/watermark ,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lqbHR4MTIzNGNzZG4=,size_16,color_FFFFFF,t_70)

二.七.三 yuezl 用户登录测试

输入用户名 yuezl, 密码 1234

![有图片]( https://img-blog.csdnimg.cn/20200514094451225.png?x-oss-process=image/watermark ,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lqbHR4MTIzNGNzZG4=,size_16,color_FFFFFF,t_70)

![有图片]( https://img-blog.csdnimg.cn/20200514094456124.png?x-oss-process=image/watermark ,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lqbHR4MTIzNGNzZG4=,size_16,color_FFFFFF,t_70)

当用户输入 查询员工的链接, http://localhost:8080/Shiro_Servlet/User/toList , yuezl没有这个权限

有图片

是正常的, 这个为什么呢? 因为没有在 shiro.ini 里面配置,配置上就好了。

/User/toList=authc,perms["user:toList"]

发现,在 shiro.ini 里面配置拦截 每一个url, 是不是特别麻烦呢?

可以用注解方式,进行配置拦截的每一个url, 后面 Spring 整合时会讲到。


本章节代码链接为:

链接:https://pan.baidu.com/s/1s19kY7k5f4GHvHecy0CcfQ 
提取码:6pig 

谢谢您的观看,我是两个蝴蝶飞, 如果喜欢,请关注我,再次感谢 !!!

posted @ 2021-01-29 09:27  两个蝴蝶飞  阅读(305)  评论(0)    收藏  举报