戒色诗: 二八佳人体似酥,腰间仗剑斩凡夫。虽然不见人头落,暗里教君骨髓枯。
老蝴蝶提醒,在学习这一章节之前,一定要学习前面的章节及RBAC教程部分。
一. Servlet 整合 Shiro 实现 RBAC准备
一.一 数据库准备
数据库与上一章节一致, user 表里面添加了盐,并且更新了密码, 同时 相对应的pojo也进行了更新。
相对应的 rbac.sql 文件会更新到链接里面。
一.二 前端页面准备
前端页面采用 RBAC系列中的页面,与前面的实现一致。
二. 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>
二.三 后端开发
目录结构:
基本与前面的 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> 修改 </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> 删除 </a></shiro:hasPermission">';
}
return udpateIcon+delIcon;
}
二.七 测试整合
二.七.一 admin 用户登录测试
输入网址: http://localhost:8080/Shiro_Servlet/User/toLogin
进入登录页面, 填写用户名 admin, 密码 12345, 错误的密码
填写正确的用户名和密码 admin, 1234
用户对部门没有添加和修改的权限,只有删除的权限, 故 显示删除的按钮,不显示添加和修改的按钮。
手动输入网址: localhost:8080/Shiro_Servlet/Dept/add, 进行添加
提示没有权限,跳转到没有权限的页面,拦截add 成功。
手动输入网址: http://localhost:8080/Shiro_Servlet/Dept/delete, 进行删除
能够正确的进行删除。
当用户点击退出之后,再输入刚才的那个 删除网址, 会跳转到退出的页面
二.七.二 yuejl 用户登录测试
输入用户名, yuejl, 密码 1234
没有部门的权限,所以不显示部门的相关信息。
点击用户管理,可以正常的显示
二.七.三 yuezl 用户登录测试
输入用户名 yuezl, 密码 1234
当用户输入 查询员工的链接, 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
谢谢您的观看,我是两个蝴蝶飞, 如果喜欢,请关注我,再次感谢 !!!