现在很多网站都有为用户保存登陆信息(即保存 Cookie )的功能,当用户下一次进入网站时,可以帮助用户自动登陆,使网站显得更加友好。笔者通过研究 ACEGI 项目的自动登陆源码,编写了一个安全有效的实现两星期自动登陆功能的 JAVA 工具类, 。下面是具体的实现流程和实现代码。
先说一下流程:
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] & 0x0f, 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 属于持久层相关的程序,这里就不贴出来了,读者可根据自己需要选择不同的持久层框架,在本程序中只要实现查询用户的功能就可以了
来源:http://blog.csdn.net/lyhapple/archive/2007/10/09/1817308.aspx