用户自动登录的流程
你访问某一个网站,使用表单登录,当你一点提交时,一个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>
至此,实现用户自动登录的功能就算完成了。