Servlet整合Shiro(四)

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

一. 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 

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

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