一.任务描述
相信很多小伙伴都使用QQ聊天工具,那是否遇到过这样的场景呢?当在一台电脑上已经登录QQ,此时因为某些原因需要在另一台电脑再登录相同号码的QQ,登录成功后会发现之前电脑上的QQ下线了。这就是QQ限制了同一个号码在电脑上不能重复登录,我们的Web程序也可以进行重复登录的限制,那么本次任务就是用过滤器和监听器来解决重复登录问题。具体任务如下:
1、未登录时不能访问主界面。
2、登录后,登录信息存储到session中。
3、监听器监听session属性值变化。
4、一个浏览器中已经登录,如果在另一个浏览器中重复登录,则清除前次登录信息。
二.效果演示
1.运行web应用程序,进入谷歌浏览器登录界面
2.此时为第一次进入程序,输入一个用户名密码。(这里输入用户名为haiexijun)
3.点击提交按钮登录,显示登录成功。
4.我如果用另外一个客户端登录,模拟异地登陆。上面第一次用的是谷歌浏览器,这次用edge浏览器输入用户名。
5.在edge浏览器上点击提交,则会在edge上成功登陆.
6.返回谷歌浏览器,刷新登陆界面后。会显示账号被异地登录了,并要求重新登录了。
7.点击确定会让你重新登录
8.并且可以在谷歌浏览器重新登录,并成功登陆
9.然后再一次回到edge浏览器再刷新则会被提醒账号被异地登录,并提醒重新登陆:
很简单吧!
10.之前相同用户名异端登录提醒的功能算是实现了,最后测试一下不同用户名则不会出现提示。
在edge浏览器输入用户名为zcbad,和谷歌浏览器的haiexijun不是一个用户了,回到谷歌浏览器刷新则不会出现异端登录的提醒。完美实现!
三.代码实现
1.在idea中用maven创建一个webapp项目,项目结构如图:
2.然后在webapp目录下创建一个login.html的用户登录界面:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>login</title> </head> <body> <form method="post" action="/login"> 用户名:<input type="text" name="username"/> 密码:<input type="password" name="password"/> <input type="submit" name="登录"/> </form> </body> </html>
用post请求传到的名为login的servlet处理请求。
3.设置一个过滤器loginFilter,对url为/login的请求进行过滤:
1.package org.example.filter; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @WebFilter(filterName = "loginFilter",urlPatterns = "/login") public class loginFilter implements Filter { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { //获取httpServletRequest对象 HttpServletRequest request=(HttpServletRequest) servletRequest; //获取全局对象 ServletContext context=request.getServletContext(); //获取userName String userName=request.getParameter("username"); //设置一个用户列表,用于记录用户登录 if (context.getAttribute("userList")==null){ //如果第一次登录这个客户端,就创建列表,加入用户 List<String> userList = new ArrayList<String>(); userList.add(userName); context.setAttribute("userList",userList); }else { //如果不是第一次登录 List<String> userList= (List<String>) context.getAttribute("userList"); //就判断用户列表中是否有此用户 if (!userList.contains(userName)){ //如果不包含该用户,就添加进去 userList.add(userName); } } //获取此客户端的session //session列表 HttpSession session= request.getSession(); if (context.getAttribute("sessionMap")==null){ Map<String,HttpSession> sessionMap=new HashMap<String,HttpSession>(); sessionMap.put(userName,session); context.setAttribute("sessionMap",sessionMap); }else { Map<String,HttpSession> sessionMap= (Map<String, HttpSession>) context.getAttribute("sessionMap"); if (!sessionMap.containsKey(userName)){ sessionMap.put(userName,session); } //测试sessionMap System.out.println("======sessionMap======"); for (Map.Entry<String,HttpSession> entry:sessionMap.entrySet()){ System.out.println(entry.getKey()+":"+entry.getValue()); } System.out.println("======================="); } //给session设置username session.setAttribute("userName",userName); //判断是否为同一个session Map<String,HttpSession> sessionMap = (Map<String, HttpSession>) context.getAttribute("sessionMap"); HttpSession session1=sessionMap.get(userName); if (session1==session){ filterChain.doFilter(servletRequest,servletResponse); }else { HttpServletResponse response=(HttpServletResponse) servletResponse; response.sendRedirect("/logout.html"); //用于销毁session session.invalidate(); } } }
我先把用户名加入一个arraylist列表中,其实这一步可有可无啦(一开始写的,忘了删)。创建名为sessionMap的map<String,HttpSession>集合,把每次登录所创建的不同session存进去,键为userName,值为当前应用的session。以便后续监听和判断。
网上很多人是通过sessionid来判断是否是同一个客户端上的登录,但我直接比较不同客户端登录时服务器创建的session是否为同一个对象(不同客户端登录,服务器创建的session就是不同的,直接比较是否为同一个httpsession对象就行了)。
如果判断当前session和sessionMap中保存的同用户名的session为同一个session,则为同一个客户端同一个用户登录。否则异地登录,则刷新就要重新登陆。
4.同时,还要写一个监听器sessionListener:
package org.example.listener; import javax.servlet.ServletContext; import javax.servlet.http.*; import java.util.Map; public class sessionListener implements HttpSessionAttributeListener, HttpSessionListener { @Override public void attributeAdded(HttpSessionBindingEvent event) { //获取session HttpSession session=event.getSession(); //通过session来获取context上下文对象 ServletContext context=session.getServletContext(); //获得用户名 String userName= (String) session.getAttribute("userName"); //判断sessionId是否于之前登录时sessionMap里存的相同 Map<String,HttpSession> sessionMap = (Map<String, HttpSession>) context.getAttribute("sessionMap"); String sessionId=sessionMap.get(userName).getId(); if (!session.getId().equals(sessionId)){ sessionMap.remove(userName); } sessionMap.put(userName,session); } @Override public void sessionDestroyed(HttpSessionEvent se) { System.out.println("destory"); } }
这个监听器的作用是监听session的属性的变化,在session属性发生改变时触发该监听器。第一次开启应用时会触发一次。后来每在已登录的客户端以外的客户端上登录也会产生新的session,也就是会有session的属性被设置,从而也触发监听器,,进行判断sessionid,然后更改sessionMap。
5.login.java的servlet:
package org.example.servlet; 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 java.io.IOException; @WebServlet("/login") public class login extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.getRequestDispatcher("/index.html").forward(req,resp); } }
通过了过滤器后,到login.java的servlet这里,这一步也就是简简单单的把请求转发到index.html页面了,此时就登录成功了!
6.index.html代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>index</title> </head> <body> <h1>登陆成功!</h1> </body> </html>
7.异地登录时跳转到logout.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>logout</title> </head> <body> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>logout</title> </head> <body> <script type="text/javascript"> alert("你尚未登录,或者账号在异地登陆,请重新登陆!"); window.location.href="http://localhost:8888/login.html"; </script> </body> </html> </body> </html>
8.web.xml的配置:
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <display-name>Archetype Created Web Application</display-name> <listener> <listener-class>org.example.listener.sessionListener</listener-class> </listener> </web-app>
其他配置:
9.maven依赖配置:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>mylogin</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <name>mylogin Maven Webapp</name> <!-- FIXME change it to the project's website --> <url>http://www.example.com</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.7</maven.compiler.source> <maven.compiler.target>1.7</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>2.3.3</version> </dependency> <dependency> <groupId>javax.servlet.jsp.jstl</groupId> <artifactId>jstl-api</artifactId> <version>1.2</version> <exclusions> <exclusion> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> </exclusion> <exclusion> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.glassfish.web</groupId> <artifactId>jstl-impl</artifactId> <version>1.2</version> <exclusions> <exclusion> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> </exclusion> <exclusion> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> </exclusion> <exclusion> <groupId>javax.servlet.jsp.jstl</groupId> <artifactId>jstl-api</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <build> <finalName>mylogin</finalName> <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) --> <plugins> <plugin> <artifactId>maven-clean-plugin</artifactId> <version>3.1.0</version> </plugin> <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging --> <plugin> <artifactId>maven-resources-plugin</artifactId> <version>3.0.2</version> </plugin> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.0</version> </plugin> <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>2.22.1</version> </plugin> <plugin> <artifactId>maven-war-plugin</artifactId> <version>3.2.2</version> </plugin> <plugin> <artifactId>maven-install-plugin</artifactId> <version>2.5.2</version> </plugin> <plugin> <artifactId>maven-deploy-plugin</artifactId> <version>2.8.2</version> </plugin> </plugins> </pluginManagement> </build> </project>