Java Web基础入门第七十二讲 Filter(过滤器)——Filter(过滤器)常见应用(二):实现用户自动登录

本文介绍了Java Web中实现用户自动登录的流程和思路。在用户成功登录后,服务器发送包含用户名和加密密码的Cookie。过滤器(AutoLoginFilter)会检查Cookie,匹配数据库中的信息,从而实现在下次访问时自动登录。文章详细讲解了LoginServlet和AutoLoginFilter的代码实现,以及如何处理Cookie的安全性问题。
摘要由CSDN通过智能技术生成

用户自动登录的流程

你访问某一个网站,使用表单登录,当你一点提交时,一个Servlet会处理登录请求,这个Servlet只要发现你成功登录了,它在你登录的同时会给你回写一个Cookie,这个Cookie里面就包含了你的登录信息,那么你下次再去找服务器时,就会带着Cookie过来,这时候就会有一个过滤器拦截所有的访问,过滤器拦截下来之后,看你有没有带Cookie,如果你有带Cookie过来,就会帮你自动登录。

实现用户自动登录的思路

实现用户自动登录的思路是这样的:

  • 在用户登录成功后,发送一个名称为autologin的Cookie给客户端,Cookie的值为用户名和md5加密后的密码;
  • 编写一个AutoLoginFilter,这个Filter检查用户是否带有名称为autologin的Cookie来,如果有,则调用dao层的方法查询Cookie的用户名和密码是否和数据库匹配,匹配则向session中存入User对象(即用户登录标记),以实现程序完成自动登录。

编写代码实现用户自动登录

现在我们来写代码实现用户自动登录。为了实现这个需求,我们大可不必搞得那么复杂,设计成用户一旦登录,就让其跳转到网站首页。首先,在Eclipse中新建一个动态Web项目——FilterDemo。然后,在cn.liayun.domain包下创建一个JavaBean——User.java。

package cn.liayun.domain;

public class User {
	private String username;
	private String password;
	
	public User() {
		super();
		// TODO Auto-generated constructor stub
	}
	
	public User(String username, String password) {
		super();
		this.username = username;
		this.password = password;
	}

	public String getUsername() {
		return username;
	}
	public void setUsername(String username) {
		this.username = username;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
}

接着,在cn.liayun.service包下创建一个Java类——BusinessService.java,用于给Web层提供业务服务。

package cn.liayun.service;

import java.util.ArrayList;
import java.util.List;

import cn.liayun.domain.User;

public class BusinessService {
	
	private static List<User> list = new ArrayList<User>();
	
	static {
		list.add(new User("liayun", "111"));
		list.add(new User("叶美美", "123"));
		list.add(new User("燕子", "123321"));
	}
	
	public User login(String username, String password) {
		for (User user : list) {
			if (user.getUsername().equals(username) && user.getPassword().equals(password)) {
				return user;
			} 
		}
		return null;
	}
	
	public User findUser(String username) {
		for (User user : list) {
			if (user.getUsername().equals(username)) {
				return user;
			} 
		}
		return null;
	}
	
}	

接着,在WebRoot根目录下新建一个用户登录页面——login.jsp。该页面内容如下:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>用户登录页面</title>
</head>
<body>
	<form action="${pageContext.request.contextPath }/LoginServlet" method="post">
		用户名:<input type="text" name="username" /><br/>
		密码:<input type="password" name="password" /><br/>
		有效期:
			1分钟<input type="radio" name="time" value="${1*60 }" />
			5分钟<input type="radio" name="time" value="${5*60 }" />
			10分钟<input type="radio" name="time" value="${10*60 }" /><br/>
		<input type="submit" value="登录" />
	</form>
</body>
</html>

紧接着,在cn.liayun.web.servlet包下创建一个LoginServlet,用于处理用户登录的请求。若我们不使用自动登录,而是使用传统的方式登录,那么LoginServlet的具体代码如下:
在这里插入图片描述
现在我们要改造LoginServlet,使其能实现自动登录的功能。下面我们慢慢来,不急!
我们知道这个LoginServlet只要发现用户成功登录了,它就会在用户登录的同时给用户的客户端回写一个Cookie,所以可以将LoginServlet的代码修改为:
在这里插入图片描述
我们现在来思考这样一个问题:给客户机发送自动登录的Cookie,Cookie的名称是autologin(Cookie名称是什么无所谓啦),那么Cookie的值又该设为什么呢?Cookie的值可以设为username:password,虽然可以这么做,但是没有什么安全性,用户打开你的缓存目录,把你的Cookie文件夹打开,就会看到你的用户名和密码了,密码极易泄露。所以我们会将密码MD5一把,即给客户机发送自动登录的Cookie的值为username:md5(password)。
接下来,我们就要设置Cookie的有效期了,这时又会产生一个问题:假设人家带过来的是5分钟,当人家只要一访问这个程序之后,5分钟之内,再不用手动登录了,过了5分钟,就不能自动登录了,因为过了5分钟,他就不会带Cookie过来了。结果,这哥们发现自动登录不了,他有可能会将计算机的时间值改一下,使用浏览器访问服务器的时候,发现没过Cookie的有效期,又会带Cookie过去,但这是不行的!也就是说Cookie的有效期,不应该浏览器(用户)来控制。
为了解决这个问题,所以我们在给用户发送Cookie的时候,我们通常会在Cookie的值里面带一个失效时间(expirestime),即Cookie的值为username:expirestime:md5(password),然后把这个Cookie发送给用户。假设我现在设的是5分钟有效,你过了5分钟,你把计算机的时间值一改,虽说你改完之后,浏览器还会带Cookie过来,但是没关系啊,我会在程序里面得到Cookie的失效时间,并且判断当前时间和失效时间谁大谁小,如果发现失效时间超出了,那我就会拒绝这个Cookie的登录。
所以,现在我们要发送给用户的Cookie,名称是autologin,值就是username:expirestime:md5(password),这样贸然将Cookie发过去,还是有点不安全。考虑到别有用心的人将你的Cookie文件夹一打开,就会看到你密码的MD5码,这样还是很容易猜出你的原始密码的(暴力破解),为了避免别人暴力破解你的密码,你在把密码MD5一把的时候,最好把password:expirestime:username这一整个组合MD5一把,即md5(password:expirestime:username),那别人破解的难度就大了。这么一路分析下来,我们最后要发送给用户的Cookie的值是username:expirestime:md5(password:expirestime:username)。
考虑清楚以上问题之后,我们再来改造LoginServlet,修改完LoginServlet之后,具体的代码如下:

package cn.liayun.web.servlet;

import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import cn.liayun.domain.User;
import cn.liayun.service.BusinessService;
import sun.misc.BASE64Encoder;

@WebServlet("/LoginServlet")
public class LoginServlet extends HttpServlet {
	
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		//传统方式的登录
		String username = request.getParameter("username");
		String password = request.getParameter("password");
		
		BusinessService service = new BusinessService();
		User user = service.login(username, password);
		if (user == null) {
			request.setAttribute("message", "用户名或密码错误!!");
			request.getRequestDispatcher("/message.jsp").forward(request, response);
			return;
		}
		
		request.getSession().setAttribute("user", user);
		
		int expirestime = Integer.parseInt(request.getParameter("time"));
		
		//给客户机发送自动登录的Cookie
		//autologin=username:expirestime:MD5(password:expirestime:username)
		Cookie cookie = makeCookie(user, expirestime);
		response.addCookie(cookie);
		
		response.sendRedirect("/FilterDemo/index.jsp");
	}
	
	public Cookie makeCookie(User user, int expirestime) {
		long currentTime = System.currentTimeMillis();
		//构建出Cookie的值
		String cookieValue = user.getUsername() + ":" + (currentTime + expirestime * 1000) + ":" + md5(user.getUsername(), user.getPassword(), (currentTime + expirestime * 1000));
		Cookie cookie = new Cookie("autologin", cookieValue);
		cookie.setMaxAge(expirestime);//设置Cookie的有效期
		cookie.setPath("/FilterDemo");//希望客户机访问Web应用下所有资源的时候,都带着Cookie过来。
		return cookie;
	}
	
	private String md5(String username, String password, long expirestime) {
		try {
			String value = password + ":" + expirestime + ":" + username;
			MessageDigest md = MessageDigest.getInstance("md5");
			byte[] md5 = md.digest(value.getBytes());
			
			BASE64Encoder encoder = new BASE64Encoder();
			return encoder.encode(md5);
		} catch (NoSuchAlgorithmException e) {
			throw new RuntimeException(e);
		}
	}
	
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		doGet(request, response);
	}

}

温馨提示:有关MD5算法和BASE64编码可以参见我的笔记《Java Web基础入门第二十讲 Servlet开发——客户端防表单重复提交和服务器端Session防表单重复提交》
最后,在cn.liayun.web.filter.example包下编写一个处理用户自动登录的过滤器——AutoLoginFilter.java。该过滤器的具体代码如下:

package cn.liayun.web.filter.example;

import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

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.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import cn.liayun.domain.User;
import cn.liayun.service.BusinessService;
import sun.misc.BASE64Encoder;

public class AutoLoginFilter implements Filter {

	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
		// TODO Auto-generated method stub

	}

	@Override
	public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) resp;
		
		//1.先检查用户是否已登录,没登录才自动登录
		User user = (User) request.getSession().getAttribute("user");
		if (user != null) {
			chain.doFilter(request, response);
			return;
		}
		
		//2.用户没有登录,这时再执行自动登录逻辑
		
		//看用户有没有带过来自动登录的Cookie
		Cookie autoLoginCookie = null;
		Cookie[] cookies = request.getCookies();
		//小心:如果用户是第一次访问,那么得到的cookies将是null
		for (int i = 0; cookies != null && i < cookies.length; i++) {
			if (cookies[i].getName().equals("autologin")) {
				autoLoginCookie = cookies[i];
			}
		}
		
		//用户没有带过来自动登录的Cookie,还是放行
		if (autoLoginCookie == null) {
			chain.doFilter(request, response);//用户没有带自动登录的Cookie,就不帮其自动登录了,所以要放行。
			return;
		}
		
		//用户带过来自动登录的Cookie,则先检查Cookie的有效期
		String[] values = autoLoginCookie.getValue().split("\\:");
		if (values.length != 3) {
			chain.doFilter(request, response);
			return;
		}
		long expirestime = Long.parseLong(values[1]);
		if (System.currentTimeMillis() > expirestime) {//当前时间值大于Cookie失效时间值,即用户带过来的Cookie已失效
			chain.doFilter(request, response);
			return;
		}
		
		//代表Cookie的时间有效,再检查Cookie的有效性
		//Cookie的值:liayun:60:eMjBYnfs+/W5JDbWmCFjwQ==
		String username = values[0];
		String client_md5 = values[2];
		
		BusinessService service = new BusinessService();
		user = service.findUser(username);
		if (user == null) {
			chain.doFilter(request, response);
			return;
		}
		
		//autologin=username:expirestime:MD5(password:expirestime:username)
		String server_md5 = md5(user.getUsername(), user.getPassword(), expirestime);
		if (!server_md5.equals(client_md5)) {
			chain.doFilter(request, response);
			return;
		}
		
		//执行自动登录
		request.getSession().setAttribute("user", user);
		chain.doFilter(request, response);
	}
	
	private String md5(String username, String password, long expirestime) {
		try {
			String value = password + ":" + expirestime + ":" + username;
			MessageDigest md = MessageDigest.getInstance("md5");
			byte[] md5 = md.digest(value.getBytes());
			
			BASE64Encoder encoder = new BASE64Encoder();
			return encoder.encode(md5);
		} catch (NoSuchAlgorithmException e) {
			throw new RuntimeException(e);
		}
	}

	@Override
	public void destroy() {
		// TODO Auto-generated method stub

	}

}

记得还要在web.xml文件中配置以上过滤器。
在这里插入图片描述
除此之外,不要忘了在WebRoot根目录新建一个网站首页页面——index.jsp以及一个网站全局消息显示页面——message.jsp。

  • index.jsp页面的内容如下:

    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>网站首页</title>
    </head>
    <body>
    	欢迎您:${user.username }<br/>
    </body>
    </html>
    
  • message.jsp页面的内容如下:

    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>网站全局消息显示页面</title>
    </head>
    <body>
    	${message }
    </body>
    </html>
    

至此,实现用户自动登录的功能就算完成了。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

李阿昀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值