戒色诗: 二八佳人体似酥,腰间仗剑斩凡夫。虽然不见人头落,暗里教君骨髓枯。
一. Shiro 配置文件的四大部分
在 shiro.ini 这个配置文件中, 有四个部分, [main], [users],[roles], 还有一个 [urls], 前面三个部分,我们都讲解了,只剩下一个 urls。 这儿进行讲解一下。
一. 一 [main] 部分
提供了对根对象securityManager及其依赖对象的配置。
如,前面的 jdbc 配置 和策略配置。
[main]
#配置数据源
dataSource=com.mchange.v2.c3p0.ComboPooledDataSource
#配置数据库的信息
dataSource.driverClass=com.mysql.jdbc.Driver
dataSource.jdbcUrl=jdbc:mysql://localhost:3306/shiro?characterEncoding=utf8
dataSource.user=root
dataSource.password=abc123
#配置 realm
jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm
#配置数据源
jdbcRealm.dataSource=$dataSource
#注入多个realm 到securityManager里面
securityManager.realm=$jdbcRealm
#配置验证器
authenticationStrategy=org.apache.shiro.authc.pam.FirstSuccessfulStrategy
securityManager.authenticator.authenticationStrategy=$authenticationStrategy
在与 Servlet 进行整合时, [main] 部分也常常添加 如下两个部分:
authc.loginUrl=/User/toLogin
roles.unauthorizedUrl=/NoPermission/NoPermission
perms.unauthorizedUrl=/NoPermission/NoPermission
authc.loginUrl=/User/toLogin, 是配置没有通过认证时,跳转的页面, 通常是 登录页面
roles.unauthorizedUrl=/NoPermission/NoPermission 是没有配置要求的角色时跳转的页面, 通常是权限不足页面
perms.unauthorizedUrl=/NoPermission/NoPermission 是没有当前要求的权限时跳转的页面, 通常是权限不足页面。
一.二 [users] 部分
主要是配置用户的信息, 用户名=密码,角色1,角色2 ... 角色可以省略。
[users]
#用户名=密码,角色1,角色2
yuejl=1234,role1,role3
yuezl=1234,role2
一.三 [roles] 部分
主要是配置角色与权限的信息, 角色=权限1,权限2
[roles]
role1=user:add,user:delete
role2=user:*
role3=user:select
一.四 [urls] 部分
配置 url 及相应的拦截器之间的关系, url=拦截器1[参数],拦截器2[参数]
[urls]
/static/**=anon
/User/toLogin=anon
/User/login=anon
/Main/toMain=authc
/User/add=authc,perms["user:add"]
/User/update=authc,perms["user:update"]
/User/select=authc,perms["user:select"]
/User/delete=authc,perms["user:delete"]
即 访问 /static/ 路径时, 被 anon 拦截器拦截,
访问 /Main/toMain 路径时,被 authc 拦截器拦截
访问 /User/add 路径时, 被authc 拦截器拦截,并且被拦截器 perms 拦截器 进行拦截
其中, anon, authc,perms 都是拦截器的别名缩写,分别代表着不同的功能。
二. Shiro 的拦截器
关于拦截器的详细使用,可以看 张开涛前辈写得文章: 第八章 拦截器机制——《跟我学Shiro》
Shiro 为了方便 认证和授权,提供了好多默认拦截器。
二.一 拦截器定义位置
拦截器 被定义在 org.apache.shiro.web.filter.mgt.DefaultFilter 类下。
public enum DefaultFilter
{
anon(AnonymousFilter.class),
authc(FormAuthenticationFilter.class),
authcBasic(BasicHttpAuthenticationFilter.class),
logout(LogoutFilter.class),
noSessionCreation(NoSessionCreationFilter.class),
perms(PermissionsAuthorizationFilter.class),
port(PortFilter.class),
rest(HttpMethodPermissionFilter.class),
roles(RolesAuthorizationFilter.class),
ssl(SslFilter.class),
user(UserFilter.class);
...
}
前面的 anon,authc 就是别名缩写, AnonymousFilter,FormAuthenticationFilter 是其对应的拦截器类。
二.二 各个拦截器的意义
默认拦截器名 | 对应类 | 说明 |
---|---|---|
认证有关的 | ||
authc | org.apache.shiro.web.filter.authc .FormAuthenticationFilter |
基于表单的拦截器;如“/**=authc”, 如果没有登录会跳到相应的登录页面登录; 主要属性:usernameParam:表单提交的用户名参数名( username); passwordParam:表单提交的密码参数名(password); rememberMeParam:表单提交的密码参数名(rememberMe); loginUrl:登录页面地址(/login.jsp); successUrl:登录成功后的默认重定向地址; failureKeyAttribute:登录失败后错误信息存储key( shiroLoginFailure); |
anon | org.apache.shiro.web.filter.authc .AnonymousFilter |
匿名拦截器,即不需要登录即可访问; 一般用于静态资源过滤;示例“/static/**=anon” |
authcBasic | org.apache.shiro.web.filter.authc .BasicHttpAuthenticationFilter |
Basic HTTP身份验证拦截器, 主要属性: applicationName: 弹出登录框显示的信息(application); |
logout | org.apache.shiro.web.filter.authc .LogoutFilter |
退出拦截器,主要属性:redirectUrl:退出成功后重定向的地址(/); 示例“/logout=logout” |
user | org.apache.shiro.web.filter.authc .UserFilter |
用户拦截器,用户已经身份验证/记住我登录的都可;示例“/**=user” |
授权有关的 | ||
roles | org.apache.shiro.web.filter.authz .RolesAuthorizationFilter |
角色授权拦截器,验证用户是否拥有所有角色;主要属性: loginUrl:登录页面地址(/login.jsp); unauthorizedUrl:未授权后重定向的地址; 示例“/admin/**=roles[admin]” |
perms | org.apache.shiro.web.filter.authz .PermissionsAuthorizationFilter |
权限授权拦截器,验证用户是否拥有所有权限;属性和roles一样;示例“/user/**=perms["user:create"]” |
port | org.apache.shiro.web.filter.authz .PortFilter |
端口拦截器,主要属性:port(80): 可以通过的端口;示例“/test= port[80]”, 如果用户访问该页面是非80, 将自动将请求端口改为80并重定向到该80端口, 其他路径/参数等都一样 |
rest | org.apache.shiro.web.filter.authz .HttpMethodPermissionFilter |
rest风格拦截器, 自动根据请求方法构建权限字符串(GET=read, POST=create,PUT=update,DELETE=delete,HEAD=read,TRACE=read,OPTIONS=read, MKCOL=create) 构建权限字符串;示例“/users=rest[user]”,会自动拼出“user:read,user:create,user:update,user:delete”权限字符串进行权限匹配 (所有都得匹配,isPermittedAll); |
ssl | org.apache.shiro.web.filter.authz .SslFilter |
SSL拦截器,只有请求协议是https才能通过; 否则自动跳转会https端口(443);其他和port拦截器一样; |
其他 | ||
noSessionCreation | org.apache.shiro.web.filter.session .NoSessionCreationFilter |
不创建会话拦截器, 调用 subject.getSession(false)不会有什么问题, 但是如果 subject.getSession(true)将抛出 DisabledSessionException异常; |
可以通过 配置文件 来做一个简单的 小Demo. 由于还没有学习自定义Realm, 故先用配置文件的形式获取数据。
三. Servlet 整合 Shiro
三.一 添加依赖
需要添加 关于 shiro 的依赖,日志的依赖,还有tomcat 的依赖。 用到了 json,需要添加 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>
</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_Web</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>
有的教程说,1.2版本以上不用配置过滤器,但没有配置时,老蝴蝶这项目运行报错,创建securityManager 报错,添加上就没有问题了。 故老蝴蝶这儿添加上shiro 过滤器。
三.三 编写配置文件 shiro.ini
[main]
#没有登录时,跳转到登录的路径
authc.loginUrl=/User/toLogin
#跳转到权限不足的路径
roles.unauthorizedUrl=/NoPermission/NoPermission
perms.unauthorizedUrl=/NoPermission/NoPermission
# 配置用户的信息
[users]
#用户名=密码,角色1,角色2
yuejl=1234,role1,role3
yuezl=1234,role2
# 定义角色的信息, 角色,权限, *表示全部的权限
[roles]
role1=user:add,user:delete
role2=user:*
role3=user:select
[urls]
#静态页面可以访问
/static/**=anon
#跳转到登录页面和登录方法可以访问
/User/toLogin=anon
/User/login=anon
#跳转到主页,需要认证
/Main/toMain=authc
#执行方法,不仅需要认证,还需要有相应的方法
/User/add=authc,perms["user:add"]
/User/update=authc,perms["user:update"]
/User/select=authc,perms["user:select"]
/User/delete=authc,perms["user:delete"]
#退出登录
/User/logout=logout
由于配置文件中,用的是 key=value, 所以在访问路径时,不能用以前的 /User?jsp=toLogin, 和 /User?method=login 了。
BaseServlet 不能使用了, 需要用原始的一个方法,一个Servlet的形式了。
yuejl 没有修改的权限, yuezl 具有全部的权限。
三.四 编写后台控制
后台类结构:
三.四.一 跳转到登录的类
@WebServlet("/User/toLogin")
public class UserToLoginServlet 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 {
req.getRequestDispatcher("/WEB-INF/pages/login.jsp").forward(req, resp);
}
}
三.四.二 登录类
@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);
}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();
}
}
}
注意,这两个Json 转换的方法是通用的,每一个需要转换的类里面都有,避免代码过多,老蝴蝶这不重复写了。
三.四.三 登录成功后跳转到主页
@WebServlet("/Main/toMain")
public class MainServlet 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 {
req.getRequestDispatcher("/WEB-INF/pages/main.jsp").forward(req, resp);
}
}
三.四.四 退出方法
@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);
}
}
三.四.五 跳转到员工页面
@WebServlet("/User/toList")
public class UserToListServlet 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 {
req.getRequestDispatcher("/WEB-INF/pages/user.jsp").forward(req, resp);
}
}
三.四.六 员工添加
@WebServlet("/User/add")
public class UserAddServlet 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 {
System.out.println("执行添加的方法");
boolean2Json(resp, true);
}
...
//json 转换的方法,具体见登录方法
}
三.四.七 员工修改
@WebServlet("/User/update")
public class UserUpdateServlet 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 {
System.out.println("执行修改的方法");
boolean2Json(resp, true);
}
}
三.四.八 员工删除
@WebServlet("/User/delete")
public class UserDeleteServlet 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 {
System.out.println("执行删除的方法");
boolean2Json(resp, true);
}
}
三.四.九 员工查询
@WebServlet("/User/select")
public class UserSelectServlet 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 {
System.out.println("执行查询的方法");
boolean2Json(resp, true);
}
}
三.五 编写前端静态页面
结构目录如下所示:
三.五.一 编写 index.jsp
<body>
谢谢您访问我,我是两个蝴蝶飞
<jsp:forward page="User/toLogin"></jsp:forward>
</body>
三.五.二 编写登录页面 login.jsp
<body>
<div class="col-sm-6 col-sm-offset-3">
<div style="margin-top:40px;">
<div class="row col-md-offset-3 ">
<h3>两个蝴蝶飞登录页面</h3>
</div>
<div class="row" style="margin-top:30px;">
<form class="form-horizontal" role="form">
<div class="form-group">
<label for="code" class="col-md-3 control-label">用户名:</label>
<div class="col-md-4">
<input type="text" class="form-control" id="code"
name="code" value=""/>
</div>
</div>
<div class="form-group">
<label for="password" class="col-md-3 control-label">密码:</label>
<div class="col-md-4">
<input type="password" class="form-control" id="password"
name="password"/>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-4">
<input type="button" value="登录" id="submit" class="btn btn-success"/>
</div>
</div>
</form>
</div>
</div>
</div>
</body>
ajax 方法,进行提交跳转
<script>
$(function(){
$("#submit").click(function(){
var code=$("#code").val();
var password=$("#password").val();
var info=new Object();
//传入进去,员工的id编号
info.code=code;
info.password=password;
$.post("${pageContext.request.contextPath}/User/login",info,function(data){
if(data.status){
alert("登录成功");
window.location.href="${pageContext.request.contextPath}/Main/toMain";
}else{
if(data.error_code=="001"){
alert("用户名或者密码错误");
}
}
})
})
})
</script>
三.五.三 主页 main.jsp
<%@ taglib uri="http://shiro.apache.org/tags" prefix="shiro"%>
<body>
进入到主页
<shiro:user>
欢迎[<shiro:principal/>]登录,<a href="${pageContext.request.contextPath}/User/logout">退出</a>
</shiro:user>
</body>
三.五.四 权限不足页面 noPrivilege.jsp
<body>
抱歉,您没有权限访问!!!
</body>
三.五.五 员工按钮显示页面 user.jsp
<%@ taglib uri="http://shiro.apache.org/tags" prefix="shiro"%>
<body>
<br/>
是否有查询权限:
<shiro:hasPermission name="user:select">
有查询权限
</shiro:hasPermission>
<br/>
是否有添加权限:
<shiro:hasPermission name="user:add">
有添加权限
</shiro:hasPermission>
<br/>
是否有修改权限:
<shiro:hasPermission name="user:update">
有修改权限
</shiro:hasPermission>
<br/>
是否有删除权限:
<shiro:hasPermission name="user:delete">
有删除权限
</shiro:hasPermission>
<br/>
</body>
shiro:user, shiro:hasPermission 是shiro 提供的标签库, 通过判断是否有权限,来显示页面元素的显示和隐藏。
注意, 不要忘记引用 shiro 标签库
<%@ taglib uri="http://shiro.apache.org/tags" prefix="shiro"%>
关于 shiro 标签库的使用,可以看 第九章 JSP标签——《跟我学Shiro》
记住常用的 shiro:user, shiro:hasPermission 即可。
三.六 验证
三.六.一 用户 yuejl 验证
输入网址: http://localhost:8080/Shiro_Web/
填写账号 yuejl, 密码是 123456, 错误的密码
填写账号 yuejl, 密码是 1234, 正确的密码
员工yuejl 正常登录, 输入网址,跳转到员工的界面
http://localhost:8080/Shiro_Web/User/toList
员工yuejl 没有修改的权限,故修改那一块的元素不显示
手动输入网址,看是否可以访问:
输入添加的网址: http://localhost:8080/Shiro_Web/User/add
员工具有添加的权限,故可以执行添加的操作。
输入修改的网址,http://localhost:8080/Shiro_Web/User/update
员工不具有修改的权限,故不可以执行修改的操作,会显示权限不足。
点击退出,退出之后, 输入刚才的 添加的那个网址,会跳转到登录的页面
三.六.二 用户 yuezl 验证
前面的测试,与yuejl 一样。
yuezl 具有修改的权限:
http://localhost:8080/Shiro_Web/User/toList
当手动输入修改的网址时: http://localhost:8080/Shiro_Web/User/update
Servlet 整合 Shiro, 控制权限成功。
本章节代码链接为:
链接:https://pan.baidu.com/s/1dMQkpcxU04WKLnIdbsm-Aw
提取码:kmji
谢谢您的观看,我是两个蝴蝶飞, 如果喜欢,请关注我,再次感谢 !!!