现在很多网站都有为用户保存登陆信息(即保存Cookie)的功能,当用户下一次进入网站时,可以帮助用户自动登陆,使网站显得更加友好。笔者通过研究ACEGI项目的自动登陆源码,编写了一个安全有效的实现两星期自动登陆功能的JAVA工具类,在此要感谢传智播客公司教学总监张孝祥老师向笔者推荐了ACEGI项目。下面是具体的实现流程和实现代码。
先说一下流程:
1. 保存用户信息阶段:
当用户登陆网站时,在登陆页面填写完用户名和密码后,如果用户在提交时还选择了“两星期内自动登陆”复选框,那么在后台程序中验证用户名和密码全都正确后,还要为用户保存这些信息,以便用户下一次可以直接进入网站;如果用户没有勾选“两星期内自动登陆”复选框,则不必为用户保存信息,那么用户在下一次登陆网站时仍需要填写用户名和密码。
在保存用户信息阶段,主要的工作是对用户的信息进行加密并保存到客户端。加密用户的信息是较为繁琐的,大致上可分 为以下几个步聚:
① 得到用户名、经MD5加密后的用户密码、cookie有效时间(本文设置的是两星期,可根据自己需要修改)
② 自定义的一个webKey,这个Key是我们为自己的网站定义的一个字符串常量,这个可根据自己需要随意设置
③ 将上两步得到的四个值得新连接成一个新的字符串,再进行MD5加密,这样就得到了一个MD5明文字符串
④ 将用户名、cookie有效时间、MD5明文字符串使用“:”间隔连接起来,再对这个连接后的新字符串进行Base64编码
⑤ 设置一个cookieName,将cookieName和上一步产生的Base64编码写入到客户端。
2. 读取用户信息:
其实弄明白了保存原理,读取及校验原理就很容易做了。读取和检验可以分为下面几个步骤:
① 根据设置的cookieName,得到cookieValue,如果值为空,就不帮用户进行自动登陆;否则执行读取方法
② 将cookieValue进行Base64解码,将取得的字符串以split(“:”)进行拆分,得到一个String数组cookieValues(此操作与保存阶段的第4步正好相反),这一步将得到三个值:
cookieValues[0] ---- 用户名
cookieValues[1] ---- cookie有效时间
cookieValues[2] ---- MD5明文字符串
③ 判断cookieValues的长度是否为3,如果不为3则进行错误处理。
④ 如果长度等于3,取出第二个,即cookieValues[1],此时将会得到有效时间(long型),将有效时间与服务器系统当前时间比较,如果小于当前时间,则说明cookie过期,进行错误处理。
⑤ 如果cookie没有过期,就取cookieValues[0],这样就可以得到用户名了,然后去数据库按用户名查找用户。
⑥ 如果上一步返回为空,进行错误处理。如果不为空,那么将会得到一个已经封装好用户信息的User实例对象user
⑦ 取出实例对象user的用户名、密码、cookie有效时间(即cookieValues[1])、webKey,然后将四个值连接起来,然后进行MD5加密,这样做也会得到一个MD5明文字符串(此操作与保存阶段的第3步类似)
⑧ 将上一步得到MD5明文与cookieValues[2]进行equals比较,如果是false,进行错误处理;如果是true,则将user对象添加到session中,帮助用户完成自动登陆
完整的代码,用途请参见注释
CookieUtil.java
处理cookie的工具类,包括读取,保存,清除三个主要方法。
package cn.itcast.util;


import java.io.IOException;

import java.io.PrintWriter;

import java.io.UnsupportedEncodingException;


import javax.servlet.FilterChain;

import javax.servlet.ServletException;

import javax.servlet.http.Cookie;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import javax.servlet.http.HttpSession;

import java.security.MessageDigest;

import java.security.NoSuchAlgorithmException;


import cn.itcast.bean.User;

import cn.itcast.dao.UserDAO;

import cn.itcast.factory.DaoImplFactory;

import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;



/**//*

* 2007.09.21 by lyhapple

* */



public class CookieUtil
{

//保存cookie时的cookieName

private final static String cookieDomainName = “cn.itcast”;


//加密cookie时的网站自定码

private final static String webKey = “itcast”;


//设置cookie有效期是两个星期,根据需要自定义

private final static long cookieMaxAge = 60 * 60 * 24 * 7 * 2;


//保存Cookie到客户端--------------------------------------------------------------------------------------------------------

//在CheckLogonServlet.java中被调用

//传递进来的user对象中封装了在登陆时填写的用户名与密码


public static void saveCookie(User user, HttpServletResponse response)
{


//cookie的有效期

long validTime = System.currentTimeMillis() + (cookieMaxAge * 1000);


//MD5加密用户详细信息

String cookieValueWithMd5 =getMD5(user.getUserName() + ":" + user.getPassword()

+ ":" + validTime + ":" + webKey);


//将要被保存的完整的Cookie值

String cookieValue = user.getUserName() + ":" + validTime + ":" + cookieValueWithMd5;


//再一次对Cookie的值进行BASE64编码

String cookieValueBase64 = new String(Base64.encode(cookieValue.getBytes()));


//开始保存Cookie

Cookie cookie = new Cookie(cookieDomainName, cookieValueBase64);

//存两年(这个值应该大于或等于validTime)

cookie.setMaxAge(60 * 60 * 24 * 365 * 2);

//cookie有效路径是网站根目录

cookie.setPath("/");

//向客户端写入

response.addCookie(cookie);

}


//读取Cookie,自动完成登陆操作--------------------------------------------------------------------------------------------

//在Filter程序中调用该方法,见AutoLogonFilter.java

public static void readCookieAndLogon(HttpServletRequest request, HttpServletResponse response,


FilterChain chain) throws IOException, ServletException,UnsupportedEncodingException
{


//根据cookieName取cookieValue

Cookie cookies[] = request.getCookies();

String cookieValue = null;


if(cookies!=null)
{


for(int i=0;i<cookies.length;i++)
{


if (cookieDomainName.equals(cookies[i].getName()))
{

cookieValue = cookies[i].getValue();

break;

}

}

}


//如果cookieValue为空,返回,


if(cookieValue==null)
{

return;

}


//如果cookieValue不为空,才执行下面的代码

//先得到的CookieValue进行Base64解码

String cookieValueAfterDecode = new String (Base64.decode(cookieValue),"utf-8");


//对解码后的值进行分拆,得到一个数组,如果数组长度不为3,就是非法登陆

String cookieValues[] = cookieValueAfterDecode.split(":");


if(cookieValues.length!=3)
{

response.setContentType("text/html;charset=utf-8");

PrintWriter out = response.getWriter();

out.println("你正在用非正常方式进入本站
");

out.close();

return;

}


//判断是否在有效期内,过期就删除Cookie

long validTimeInCookie = new Long(cookieValues[1]);


if(validTimeInCookie < System.currentTimeMillis())
{

//删除Cookie

clearCookie(response);

response.setContentType("text/html;charset=utf-8");

PrintWriter out = response.getWriter();

out.println("<a href=’logon.jsp’>你的Cookie已经失效,请重新登陆</a>");

out.close();

return;

}


//取出cookie中的用户名,并到数据库中检查这个用户名,

String username = cookieValues[0];


//根据用户名到数据库中检查用户是否存在

UserDAO ud = DaoImplFactory.getInstance();

User user = ud.selectUserByUsername(username);


//如果user返回不为空,就取出密码,使用用户名+密码+有效时间+ webSiteKey进行MD5加密


if(user!=null)
{

String md5ValueInCookie = cookieValues[2];

String md5ValueFromUser =getMD5(user.getUserName() + ":" + user.getPassword()

+ ":" + validTimeInCookie + ":" + webKey);

//将结果与Cookie中的MD 5码 相比较,如果相同,写入Session,自动登陆成功,并继续用户请求


if(md5ValueFromUser.equals(md5ValueInCookie))
{

HttpSession session = request.getSession(true);

session.setAttribute("user", user);

chain.doFilter(request, response);

}


}else
{

//返回为空执行

response.setContentType("text/html;charset=utf-8");

PrintWriter out = response.getWriter();

out.println("cookie验证错误!");

out.close();

return;

}

}


//用户注销时,清除Cookie,在需要时可随时调用------------------------------------------------------------


public static void clearCookie( HttpServletResponse response)
{

Cookie cookie = new Cookie(cookieDomainName, null);

cookie.setMaxAge(0);

cookie.setPath("/");

response.addCookie(cookie);

}


//获取Cookie组合字符串的MD 5码 的字符串----------------------------------------------------------------------------


public static String getMD5(String value)
{

String result = null;


try
{

byte[] valueByte = value.getBytes();

MessageDigest md = MessageDigest.getInstance("MD5");

md.update(valueByte);

result = toHex(md.digest());


} catch (NoSuchAlgorithmException e2)
{

e1.printStackTrace();

}

return result;

}


//将传递进来的字节数组转换成十六进制的字符串形式并返回


private static String toHex(byte[] buffer)
{

StringBuffer sb = new StringBuffer(buffer.length * 2);


for (int i = 0; i < buffer.length; i++)
{

sb.append(Character.forDigit((buffer[i] & 0xf0) >> 4, 16));

sb.append(Character.forDigit(buffer[i] & 0x 0f , 16));

}

return sb.toString();

}

}

下面的是对CookieUtil工具类各方法的调用演示:
User.java
封装用户信息的JavaBean对象模型
package com.itcast.bean;



public class User
{

private int id;


private String userName;


private String password;



public String getPassword()
{

return password;

}



public void setPassword(String password)
{

this.password = password;

}



public String getUserName()
{

return userName;

}



public void setUserName(String userName)
{

this.userName = userName;

}



public int getId()
{

return id;

}



public void setId(int id)
{

this.id = id;

}

}

AutoLogonFilter.java
过滤器程序,可在WEB-INF/web.xml中设置过滤规则,本文对过滤规则不作介绍,此程序主要作用是检查用户在上一次登陆时是否保存了Cookie,如果保存了,就处理Cookie信息,并帮助用户自动登陆
本程序主要调用了CookieUtil.java中的读取与自动登陆方法,即readCookieAndLogon方法
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.Cookie;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import javax.servlet.http.HttpSession;


import cn.itcast bean.User;

import cn.itcast.util.CookieUtil;



public class AutoLogonFilter implements Filter
{



public void destroy()
{

}


//保存cookie时的cookieName,与CookieUtil.java中的设置相同

private final static String cookieDomainName = “cn.itcast”;


public void doFilter(ServletRequest req, ServletResponse resp,


FilterChain chain) throws IOException, ServletException
{

HttpServletRequest request = (HttpServletRequest)req;

HttpServletResponse response = (HttpServletResponse)resp;

HttpSession session = request.getSession(true);

User user = (User)session.getAttribute("user");


//如果封装的user不为空,说明已经登陆,则继续执行用户的请求.下面的就不处理了


if(user!=null)
{

chain.doFilter(request,response);

return;

}


//user为空,说明用户还没有登陆,就尝试得到浏览器传送过来的Cookie

Cookie cookies[] = request.getCookies();

String cookieValue = null;


if(cookies!=null)
{


for(int i=0;i<cookies.length;i++)
{


if (cookieDomainName.equals(cookies[i].getName()))
{

cookieValue = cookies[i].getValue();

break;

}

}

}


//如果cookieValue为空,也继续执行用户请求


if(cookieValue==null)
{

chain.doFilter(request,response);

return;

}


//cookieValue不为空执行下面的方法,调用CookieUtil.java中的readCookieAndLogon方法


try
{

CookieUtil.readCookieAndLogon(cookieValue, request, response, chain);


}catch(Exception e)
{

e.printStackTrace();

}

}



public void init(FilterConfig arg0) throws ServletException
{

}

}

CheckLogonServlet.java
验证用户登陆信息的Servlet,此程序调用了CookieUtil.java中的saveCookie方法
package cn.itcast.servlet;



/**//*

* update 2007.09.23 by lyhapple

* 检查用户登陆

* */


import java.io.IOException;


import javax.servlet.ServletException;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import javax.servlet.http.HttpSession;


import cn.itcast.bean.User;

import cn.itcast.dao.UserDAO;

import cn.itcast.factory.DaoImplFactory;

import cn.itcast.util.CookieUtil;



public class CheckLogonServlet extends HttpServlet
{


public void doGet(HttpServletRequest request, HttpServletResponse response)


throws ServletException, IOException
{

doPost(request, response);

}


public void doPost(HttpServletRequest request, HttpServletResponse response)


throws ServletException, IOException
{

request.setCharacterEncoding("utf-8");

String username = request.getParameter("username").trim();

String password = CookieUtil.getMD5(request.getParameter("password"));

String remeberMe = request.getParameter("remeberMe");

HttpSession session = request.getSession(false);


// 将接收到的用户名传递到UserDao的checkUser方法中,检查用户

// 返回一个User类型的对象

UserDAO ud = DaoImplFactory.getInstance();

User user = ud.selectUserByUsername(username);


if (user == null)
{

request.setAttribute("checkUserError","<a href='register.jsp'><font color=red>用户名不存在,请先注册</font></a>");

request.getRequestDispatcher("index.jsp").forward(request, response);

return;

}



if(!password.equals(user.getPassword()))
{

request.setAttribute("checkPasswordError","<font color=red>密码输入错误,请重新输入</font>");

request.getRequestDispatcher("index.jsp").forward(request, response);

return;

}


//保存Cookie,这里调用了CookieUtil.java中的saveCookie方法,将上面的user对象作为参数传递


if ("on".equals(remeberMe))
{

CookieUtil.saveCookie(user, response);

}

//在Session中保存用户信息,并转向用户的个人信息页面

session.setAttribute("user", user);

request.getRequestDispatcher("User/userInfo.jsp").forward(request,response);

}

}

UserDAO.java与DaoImplFactory.java属于持久层相关的程序,这里就不贴出来了,读者可根据自己需要选择不同的持久层框架,在本程序中只要实现查询用户的功能就可以了