【Java EE 学习 20】【使用过滤器实现登陆验证、权限认证】【观察者模式和监听器(使用监听器实现统计在线IP、登录IP 、踢人功能)】
一、使用过滤器实现登录验证、权限认证
1.创建5张表
/*使用过滤器实现权限过滤功能*/ /**创建数据库*/ DROP DATABASE day20; CREATE DATABASE day20; USE DAY20; /*用户表*/ DROP TABLE IF EXISTS USER; CREATE TABLE USER( userid VARCHAR(32) , username VARCHAR(32), userpassword VARCHAR(32), CONSTRAINT pk_userid PRIMARY KEY (userid) ); /*角色表*/ CREATE TABLE role( roleid VARCHAR(32), rolename VARCHAR(32), roledes VARCHAR(120),/*角色分配的菜单????*/ CONSTRAINT pk_roleid PRIMARY KEY(roleid) ); /*创建菜单表*/ CREATE TABLE menu( menuid VARCHAR(32), menuname VARCHAR(32), menuurl VARCHAR(32), CONSTRAINT pk_menuid PRIMARY KEY (menuid) ); /*开始创建中间表*/ /*创建userrole中间表*/ CREATE TABLE userrole( userid VARCHAR(32), roleid VARCHAR(32), CONSTRAINT pk_userrole PRIMARY KEY(userid,roleid), CONSTRAINT fk_userid FOREIGN KEY(userid) REFERENCES USER(userid), CONSTRAINT fk_roleid FOREIGN KEY(roleid) REFERENCES role(roleid) ); /*创建rolemenu中间表*/ CREATE TABLE rolemenu( menuid VARCHAR(32), roleid VARCHAR(32), CONSTRAINT rm_pk PRIMARY KEY(menuid,roleid), CONSTRAINT rm_fk1 FOREIGN KEY(menuid) REFERENCES menu(menuid), CONSTRAINT rm_fk2 FOREIGN KEY(roleid) REFERENCES role(roleid) ) INSERT INTO USER VALUES('U001','Jack','1234'); INSERT INTO USER VALUES('U002','张三','4321'); INSERT INTO USER VALUES('U003','Tom','1111'); INSERT INTO role VALUES('R001','管理员',''); INSERT INTO role VALUES('R002','教师',''); INSERT INTO userrole VALUES('U001','R001'); INSERT INTO userrole VALUES('U002','R002'); INSERT INTO menu VALUES('M001','系统管理','/sys.jsp'); INSERT INTO menu VALUES('M002','用户管理','/user.jsp'); INSERT INTO menu VALUES('M003','角色管理','/role.jsp'); INSERT INTO rolemenu VALUES('M001','R001'); INSERT INTO rolemenu VALUES('M002','R001'); INSERT INTO rolemenu VALUES('M003','R001'); INSERT INTO rolemenu VALUES('M003','R002');
ER图:
2.sql查询准备
(1)查找所有用户对应的角色信息
SELECT u.username,r.rolename FROM USER u INNER JOIN userrole ur ON u.userid=ur.userid INNER JOIN role r ON r.roleid=ur.roleid;
(2)查找所有角色对应的管理菜单
SELECT r.rolename,m.menuname FROM role r INNER JOIN rolemenu rm ON r.roleid=rm.roleid INNER JOIN menu m ON rm.menuid=m.menuid;
(3)查找所有用户对应的管理菜单
SELECT u.username,m.menuname FROM USER u INNER JOIN userrole ur ON u.userid=ur.userid INNER JOIN role r ON ur.roleid=r.roleid INNER JOIN rolemenu rm ON r.roleid=rm.roleid INNER JOIN menu m ON rm.menuid=m.menuid;
3.注意:在使用过滤器进行权限认证的时候最重要的是sql的写法,即最重要的是数据库查询部分
4.过滤器
(1)登陆验证过滤器:首先验证是否已经登陆,如果没有登录的话,首先需要登陆,在web.xml文件中配置的时候要放在所有认证过滤器的前面。
示例代码:
import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class LoginFilter implements Filter { public void init(FilterConfig filterConfig) throws ServletException { // TODO Auto-generated method stub } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; if(req.getSession().getAttribute("user")==null){ System.err.println("用户还没有登录"); HttpServletResponse resp = (HttpServletResponse) response; resp.sendRedirect(req.getContextPath()+"/index.jsp?error=2"); }else{ chain.doFilter(req, response); } } public void destroy() { // TODO Auto-generated method stub } }
(2)权限认证过滤器:如果用户已经登陆(登陆验证过滤器负责),而且想要访问权限不够的资源,比如教师想要进行系统管理,因为教师没有系统管理的权限,所以系统应当拦截该请求并警醒错误提示。
示例代码:
package cn.itcast.filter; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import org.apache.commons.dbutils.QueryRunner; import org.apache.commons.dbutils.handlers.ScalarHandler; import cn.itcast.domain.User; import cn.itcast.utils.DataSourceUtils; public class AuthFilter implements Filter { public void init(FilterConfig filterConfig) throws ServletException { // TODO Auto-generated method stub } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { //获取uri HttpServletRequest req = (HttpServletRequest) request; String uri = req.getRequestURI();//Http://localhost:8080/day20/jsps/role.jsp->day20/jsps/role.jsp uri = uri.replace(req.getContextPath(), ""); //组成sql String sql = "SELECT COUNT(1)"+ " FROM menus m INNER JOIN rolemenu rm ON m.id=rm.mid"+ " INNER JOIN roles r ON r.id=rm.rid"+ " INNER JOIN roleuser ru ON r.id=ru.rid"+ " WHERE ru.uid=? AND url=?"; //取到用户的id User user = (User) req.getSession().getAttribute("user"); try{ QueryRunner run = new QueryRunner(DataSourceUtils.getDatasSource()); Object o = run.query(sql,new ScalarHandler(),user.getId(),uri); int size = Integer.parseInt(o.toString()); if(size==0){ System.err.println("你没有权限...."); }else{ chain.doFilter(req, response); } }catch(Exception e){ } } public void destroy() { // TODO Auto-generated method stub } }
二、观察者模式
1.监听器存在三个对象
(1)监听者:XxxxListener
一般是一个接口
(2)被监听者
任意对象都能成为被监听者
(3)监听到的事件:XxxxEvent
永远是一个具体类,用来放监听到的数据,永远都会有一个方法getSource,该方法返回被监听的对象
2.观察者模式
(1)java Web中的所有监听器使用的都是观察者模式
(2)观察者模式模拟
awt编程模拟:
public class MyFrame extends JFrame { public MyFrame() { JButton btn = new JButton("你好"); System.err.println("btn: is:"+btn.hashCode()); btn.addActionListener(new MyListener()); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //获取容器 Container con= getContentPane(); //设置布局 con.setLayout(new FlowLayout()); con.add(btn); setSize(300, 300); setVisible(true); } public static void main(String[] args) { new MyFrame(); } //实现一个监听者 class MyListener implements ActionListener{ //监听方法 public void actionPerformed(ActionEvent e) { System.err.println("我监听到了:"+e.getSource().hashCode()); } } }
(3)观察者模式分析:自己模拟一个观察者模式
模拟监听某人跑步的动作,一旦监听到被监听者有跑步的动作就要有通知告知被监听者跑步了!
package com.kdyzm.ObserverModel; //测试观察者模式 public class Main { public static void main(String args[]) { Person p=new Person(); p.addPersonListener(new PersonListener(){ @Override public void doListen(PersonEvent e) { System.out.println("被监听到正在跑步!"); throw new RuntimeException("他正在跑步!!!"); } }); p.run(); } } interface PersonListener { void doListen(PersonEvent e); } class PersonEvent { Person p=null; public PersonEvent(Person p) { this.p=p; } public Person getSource() { return p; } } class Person { PersonListener listener=null; public void addPersonListener(PersonListener listener) { //完成传递监听器的过程。 this.listener=listener; } public void run() { //被监听者只要有动作,监听器也跟着一起动 if(listener!=null)//如果不加监听器的话也要能够正常“跑步” { listener.doListen(new PersonEvent(this)); } System.out.println("我正在跑步!"); } }
三、监听器
1.java web中存在三个被监听对象
(1)HttpServletRequest
(2)HttpSession
(3)ServletContext
2.监听者
监听者 |
被监听者 |
监听到事件对象 |
HttpSession – 监听HttpSession活化和顿化。 |
||
HttpSession – 监听session的属性变化的。S.setAttributee(); |
||
HttpSession - 监听哪一个对象,绑定到了session上。S.setAtrri(name,User); |
|
|
HttpSesion – 监听sessioin创建销毁 |
||
ServletContext – 属性变化的 |
|
|
servletContext 创建销毁 |
|
|
ServletRequestListener - serlvetRequestAttibuteListner |
Rrequest -创建销毁 |
|
四、监听HttpServletRequest的创建和销毁
1.一个基本的监听器实现
第一步:实现一个接口ServletRequestListener,该接口提供了两个方法可以监控Request的创建和销毁。
Method Summary |
|
|
|
|
|
创建的类:
package com.kdyzm.listener; import javax.servlet.ServletRequestEvent; import javax.servlet.ServletRequestListener; public class RequestListener implements ServletRequestListener { @Override public void requestDestroyed(ServletRequestEvent sre) { Object request=sre.getSource(); System.out.println(request+"被创建!"); } @Override public void requestInitialized(ServletRequestEvent sre) { Object request=sre.getSource(); System.out.println(request+"被销毁!"); } }
第二步:在web.xml文件中进行配置
<?xml version="1.0" encoding="UTF-8"?> <web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"> <listener> <listener-class>com.kdyzm.listener.RequestListener</listener-class> </listener> </web-app>
第三步:通过浏览器向服务器发起请求,如请求默认的index.jsp页面,可以得到该请求对象被创建的时间和该请求对象被销毁的时间。
说明:
(1):配置一个Listener只要求提供类名就可以了。
(2):在tomcat启动时,会自动的初始化这个监听器类。
(3):tomcat创建的Listner,Serlvet,Filter都是单列的.
五、通过HttpSessionListener监视器实现记录在线人数的功能
1.API
Method Summary |
|
|
|
|
|
2.实现记录在线人数的功能,不仅显示人数,还要将所有在线的人ip地址输出出来,通过配置web.xml设置session的存活时间。
具体小练习:https://github.com/kdyzm/day20_2
小练习结果:
org.apache.catalina.session.StandardSessionFacade@19815db session被创建! C35F7D7FE6B48813E79FD6DB2DC23386 退出! org.apache.catalina.session.StandardSessionFacade@f6ae8f session被创建! org.apache.catalina.session.StandardSessionFacade@10ecfa session被创建! org.apache.catalina.session.StandardSessionFacade@1362188 session被创建! org.apache.catalina.session.StandardSessionFacade@d38409 session被创建! org.apache.catalina.session.StandardSessionFacade@818d25 session被创建! org.apache.catalina.session.StandardSessionFacade@1599f60 session被创建! org.apache.catalina.session.StandardSessionFacade@ee7f4c session被创建! E4FEA09EC547D15DEF3C4B13B1327985 退出! org.apache.catalina.session.StandardSessionFacade@86aa0c session被创建! 21FE11469F2F5C2DECF8AB32F0F18B96 退出! 6BEB338728B16829FB39B693238ADDE3 退出! F71C51D49A900B3B9C5819D467136970 退出! CA402FF768C14972F6E3358B27E4DA2D 退出! ACE87998C0EAA62B2C9332FFBC31EA60 退出! 53A1FFC5F2A80A1EFDD38172C0632CAA 退出! C6BF0DF4E7042FBEBDC51192ABA3BD7F 退出!
浏览器显示示例:
六、使用HttpSessionAttributeListener监听HttpSession中的属性变化。
1.API
Method Summary |
|
|
|
|
|
|
|
2.具体用法配置略(小练习七会用到该接口)。
七、小练习:
要求:
1.显示所有登陆人数
2.登录人的ip、还要现实创建session的时间、最后访问的时间
3.管理员:可以踢出某个用户
1.项目源代码:https://github.com/kdyzm/day20_3
2.核心类:
(1)监听器1个(实现HttpSessionAttributeListener接口)
package com.kdyzm.listener; import java.util.HashMap; import java.util.Map; import javax.servlet.ServletContext; import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSessionAttributeListener; import javax.servlet.http.HttpSessionBindingEvent; public class SessionAttributeListener implements HttpSessionAttributeListener { @Override public void attributeAdded(HttpSessionBindingEvent event) { String attributename=event.getName(); if(attributename.equals("user")) { System.out.println(event.getName()+"新属性被添加!"+event.getValue()); String attributevalue=event.getValue().toString(); ServletContext sc=event.getSession().getServletContext(); Map<String,HttpSession>map=(Map<String, HttpSession>) sc.getAttribute("map"); if(map==null) { map=new HashMap<String,HttpSession>(); sc.setAttribute("map", map); } map.put(attributevalue, event.getSession()); } } @Override public void attributeRemoved(HttpSessionBindingEvent event) { String name=event.getName(); System.out.println("有属性被移除!"+name+":"+event.getValue()); if(name.equals("user")) { ServletContext sc=event.getSession().getServletContext(); String value=event.getValue().toString(); Map<String,HttpSession> map=(Map<String, HttpSession>) sc.getAttribute("map"); map.remove(value); } } @Override public void attributeReplaced(HttpSessionBindingEvent event) { } }
(2)过滤器两个(实现Filter接口)
[1]登陆过滤器:
package com.kdyzm.filter; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class LoginFilter implements Filter { @Override public void destroy() { } @Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request=(HttpServletRequest) req; HttpServletResponse respose=(HttpServletResponse) resp; if(request.getSession().getAttribute("user")==null) { System.out.println("用户还没有登录!"); respose.sendRedirect(request.getContextPath()+"/index.jsp"); } else { chain.doFilter(request, resp); } } @Override public void init(FilterConfig arg0) throws ServletException { } }
[2]获取请求ip的过滤器
package com.kdyzm.filter; //拦截请求并从请求信息中获取ip信息将信息保存到session中 import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; public class SessionSetIpFilter implements Filter { @Override public void destroy() { } @Override public void doFilter(ServletRequest req, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest request=(HttpServletRequest) req; if(request.getSession().getAttribute("ip")==null) { String ip=request.getRemoteAddr(); System.out.println("有新用户访问!ip="+ip); HttpSession session=request.getSession(); session.setAttribute("ip", ip); } chain.doFilter(request, response); } @Override public void init(FilterConfig arg0) throws ServletException { } }
(3)Servlet2个:
[1]将存放Map<String,HttpSession>对象转换成List<Map<String,String>>对象的转换Servlet
package com.kdyzm.servlet; import java.io.IOException; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import com.kdyzm.utils.BaseServlet; public class ShowAll extends BaseServlet { @Override public void defaultMethod(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ServletContext sc=getServletContext(); Map<String,HttpSession>map=(Map<String, HttpSession>) sc.getAttribute("map"); List<Map<String,String>>list=new ArrayList<Map<String,String>>(); if(map!=null) { Set<Map.Entry<String, HttpSession>>set=map.entrySet(); Iterator <Map.Entry<String, HttpSession>> it=set.iterator(); while(it.hasNext()) { Map.Entry<String, HttpSession>entry=it.next(); Map<String,String>hashMap=new HashMap<String,String>(); hashMap.put("name",entry.getValue().getAttribute("user").toString()); hashMap.put("ip",entry.getValue().getAttribute("ip").toString()); hashMap.put("createtime",new Date(entry.getValue().getCreationTime()).toString()); hashMap.put("lastaccesstime",new Date(entry.getValue().getLastAccessedTime()).toString()); list.add(hashMap); } } request.setAttribute("userlist", list); request.getRequestDispatcher("/showAll.jsp").forward(request, response); } }
[2]处理登陆和踢人请求的Servlet
package com.kdyzm.servlet; import java.io.IOException; import java.util.Map; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import com.kdyzm.utils.BaseServlet; public class UserServlet extends BaseServlet { //默认是登陆 @Override public void defaultMethod(HttpServletRequest request, HttpServletResponse response) throws IOException { String name=request.getParameter("name"); String password=request.getParameter("password"); //暂时不验证 HttpSession session=request.getSession(); session.setAttribute("user", name); response.sendRedirect(request.getContextPath()+"/index.jsp"); } public void remove(HttpServletRequest request,HttpServletResponse response) throws IOException { String name=request.getParameter("name"); name=new String(name.getBytes("iso-8859-1"),"utf-8"); ServletContext sc=getServletContext(); Map<String,HttpSession>map=(Map<String, HttpSession>) sc.getAttribute("map"); HttpSession session=map.get(name); session.removeAttribute("user"); response.sendRedirect(request.getContextPath()+"/showAll"); } }
3.配置文件web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"> <filter> <filter-name>sessionSetIpFilter</filter-name> <filter-class>com.kdyzm.filter.SessionSetIpFilter</filter-class> </filter> <filter> <filter-name>loginFilter</filter-name> <filter-class>com.kdyzm.filter.LoginFilter</filter-class> </filter> <filter-mapping> <filter-name>loginFilter</filter-name> <url-pattern>/showAll.jsp</url-pattern> <dispatcher>REQUEST</dispatcher> <dispatcher>FORWARD</dispatcher> </filter-mapping> <filter-mapping> <filter-name>sessionSetIpFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <servlet> <servlet-name>UserServlet</servlet-name> <servlet-class>com.kdyzm.servlet.UserServlet</servlet-class> </servlet> <servlet> <servlet-name>ShowAll</servlet-name> <servlet-class>com.kdyzm.servlet.ShowAll</servlet-class> </servlet> <servlet-mapping> <servlet-name>UserServlet</servlet-name> <url-pattern>/userServlet</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>ShowAll</servlet-name> <url-pattern>/showAll</url-pattern> </servlet-mapping> <listener> <listener-class>com.kdyzm.listener.SessionAttributeListener</listener-class> </listener> </web-app>
4.jsp文件
(1)登陆JSP
<%@ page language="java" import="java.util.*" pageEncoding="utf-8" contentType="text/html; charset=utf-8" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <meta http-equiv="Content-Type" content="text/html;charset=utf-8"> <title>Insert title here!</title> <style type="text/css"> table { border: 1px solid black; border-collapse: collapse; margin: 0 auto; } td { border: 1px solid black; text-align: center; padding: 3px; } </style> </head> <body> <!-- 默认是登陆界面 --> <c:choose> <c:when test="${empty sessionScope.user}"> <form action="<c:url value='/userServlet'/>" method="post"> <table> <tr> <td>用户名:</td> <td><input type="text" name="name"></td> </tr> <tr> <td>密码:</td> <td><input type="text" name="password"></td> </tr> <tr> <td colspan="2"><input type="submit" value="登陆"></td> </tr> </table> </form> </c:when> <c:otherwise> ${sessionScope.user}已登录!<br/> <a href="<c:url value='/showAll'/>">查看登陆用户列表</a> </c:otherwise> </c:choose> </body> </html>
(2)显示用户列表JSP
<%@ page language="java" import="java.util.*" pageEncoding="utf-8" contentType="text/html; charset=utf-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <meta http-equiv="Content-Type" content="text/html;charset=utf-8"> <title>Insert title here!</title> <style type="text/css"> table { border: 1px solid black; border-collapse: collapse; margin: 0 auto; } td { border: 1px solid black; text-align: center; padding: 3px; } </style> </head> <body> 这里是在线用户列表! <table> <tr> <td>用户名</td> <td>ip地址</td> <td>首次访问时间</td> <td>最后一次访问时间</td> <td>踢人</td> </tr> <c:forEach items="${requestScope.userlist}" var="map"> <tr> <td>${map.name}</td> <td>${map.ip}</td> <td>${map.createtime }</td> <td>${map.lastaccesstime}</td> <td><a href="<c:url value='/userServlet?cmd=remove&name=${map.name}'/>">踢人</a></td> </tr> </c:forEach> </table> </body> </html>
5.注意事项:
(1)Map对象使用put(key,value)方法之后,可以在JSP页面中使用EL表达式${map对象.key)的方式将映射的值取出。
(2)将IP放到session作用域中之后不能在监视器中直接将该ip拿出,拿到的一定是NULL。
(3)使用登陆过滤器的时候一定要注意避免循环重定向错误
6.出现的错误
循环重定向错误
7.小练习结果:
(1)直接访问showAll.jsp,重定向到index.jsp页面
(2)登陆之后,再次登陆
(3)查看用户列表
(4)在其它机器登陆之后
(5)将其他机器上登陆的用户踢掉(不要踢我)
【1】我的当前页面
【2】其他机器不能继续访问该页面,重定向到登陆页面(刷新之后)