通过邮箱找回密码简单实现
基本步骤:
1,用户访问网站公开的页面,输入注册邮箱,点击"发送找回密码邮件"按钮
2,服务器检测该邮箱正确性及是否有注册,如果已注册,生成一个时间戳+邮箱名的随机数加密后使用Javamail类发送邮件
3,用户登录自己的邮箱点击回调url
4,服务器检测回调url中参数时效性和正确性,如果正确则进入重置密码环节。
依赖包:
mysql-connector-java-5.0.8-bin.jar
java-mail-1.4.jar
源代码如下:
A,入口(注册/forgot到AccountHelperServlet上面)
<?xml version="1.0" encoding="UTF-8"?> <web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"> <display-name></display-name> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> <servlet> <servlet-name>ForgotPwdServlet</servlet-name> <servlet-class>com.mycompany.account.AccountHelperServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>ForgotPwdServlet</servlet-name> <url-pattern>/forgot</url-pattern> </servlet-mapping> </web-app>
B,Servlet业务逻辑(主要是发送邮件)
package com.mycompany.account; import java.io.IOException; import java.io.PrintWriter; import java.sql.SQLException; import javax.servlet.RequestDispatcher; import javax.servlet.Servlet; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class AccountHelperServlet extends HttpServlet implements Servlet { /** * */ private static final long serialVersionUID = -643722721720417036L; private static final int validtime = 1000 * 60 * 10; // 10 minutes @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // TODO Auto-generated method stub String action = req.getParameter("action"); // // show forget password page // if(action == null){ RequestDispatcher rd = req.getRequestDispatcher("/forgot.jsp"); rd.forward(req, resp); return; } // // show reset password page // else if(action.equals("reset")){ String token = req.getParameter("token"); try{ EncryptDecryptData des = new EncryptDecryptData(); String rawtoken = null; try{ rawtoken = des.decrypt(token); }catch(Exception ex){ resp.sendError(400,"bad request"); return; } long currTimeMillis = System.currentTimeMillis(); long prevTimeMillis = Long.parseLong(rawtoken.split(" ")[0]); if(currTimeMillis - prevTimeMillis > validtime){ resp.sendError(400,"bad request"); return; } req.setAttribute("token", token); RequestDispatcher rd = req.getRequestDispatcher("/reset.jsp"); rd.forward(req, resp); return; } catch(Exception ex){ resp.sendError(500,"internal server error"); return; } } else{ resp.sendError(400,"bad request"); return; } } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String action = req.getParameter("action"); // // send mail action // if(action.equals("sendmail")){ String mailAddress = req.getParameter("mailaddress"); String ret = null; try { ret = AccountHelperUtils.SelectMail(mailAddress); } catch (SQLException e) { resp.sendError(500,"internal server error"); return; } if(ret == null){ resp.sendError(400,"bad request"); return; } String rawtoken = System.currentTimeMillis() + " " + ret; String token = ""; try{ EncryptDecryptData des = new EncryptDecryptData();// 使用默认密钥 token = des.encrypt(rawtoken); } catch(Exception ex){ resp.sendError(500,"internal server error"); return; } String genurl = "http://www.mycompany.com/forgot?action=reset&token="+token; String mailContent = "<div>" + "<div>亲爱的mycompany用户</div>" + "<div>您已经在mycompany申请了找回密码,请点击下面链接,重新设置您的密码:</div>" + "<div><a href=\""+genurl+"\">"+genurl+"</a></div>" + "<div>此信是由mycompany系统发出,系统不接受回信,请勿直接回复。</div>" + "<div>致礼!</div>" + "</div>"; MailHelperUtils.sendEmail("mycompany@mycompany.com", "mailpassword", new String[]{mailAddress}, "请重置您的mycompany账号密码", mailContent, null, "text/html", "UTF8"); PrintWriter out=resp.getWriter(); out.println("mail sent, please login mail to check!"); out.close(); return; } // // reset password action // else if(action.equals("resetpwd")){ String newpwd = req.getParameter("newpwd"); String token = req.getParameter("token"); try{ EncryptDecryptData des = new EncryptDecryptData(); String rawtoken = null; try{ rawtoken = des.decrypt(token); }catch(Exception ex){ resp.sendError(400,"bad request"); return; } long currTimeMillis = System.currentTimeMillis(); long prevTimeMillis = Long.parseLong(rawtoken.split(" ")[0]); if(currTimeMillis - prevTimeMillis > validtime){ resp.sendError(400,"bad request"); return; } String mailAddress = rawtoken.split(" ")[1]; boolean ret = AccountHelperUtils.UpdatePwd(mailAddress, newpwd); if(ret){ PrintWriter out=resp.getWriter(); out.println("password reset,please relogin"); out.close(); return; }else{ resp.sendError(500,"internal server error"); return; } } catch(Exception ex){ resp.sendError(500,"internal server error"); return; } } else{ resp.sendError(400,"bad request"); return; } } }
forgot.jsp
<%@ page language="java" import="java.util.*" pageEncoding="ISO-8859-1"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>Forgot Password</title> </head> <body> <form action="/forgot?action=sendmail" method="post"> <input type="text" name="mailaddress" /> <input type="submit" value="send mail" /> </form> </body> </html>
reset.jsp
<%@ page language="java" import="java.util.*" pageEncoding="ISO-8859-1"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>find password demo</title> </head> <body> <form action="/forgot?action=resetpwd" method="post"> <input type="hidden" name="token" value="<%=(String)request.getAttribute("token")%>" /> <input type="text" name="newpwd" /> <input type="submit" value="update password" /> </form> </body> </html>
C,工具类
a,发送邮件工具(通过设置smtp发送邮件)
package com.mycompany.mail; import java.io.File; import java.util.Date; import java.util.Properties; import javax.activation.DataHandler; import javax.activation.FileDataSource; import javax.mail.Authenticator; import javax.mail.Message; import javax.mail.Multipart; import javax.mail.PasswordAuthentication; import javax.mail.Session; import javax.mail.Transport; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMultipart; import javax.mail.internet.MimeUtility; public class MailHelperUtils { public static void sendEmail(final String sender,final String password,String[] receivers, String title, String mailContent, File[] attachements, String mimetype, String charset) { Properties props = new Properties(); //设置smtp服务器地址 //这里使用QQ邮箱,记得关闭独立密码保护功能和在邮箱中设置POP3/IMAP/SMTP服务 props.put("mail.smtp.host", "smtp.exmail.qq.com"); //需要验证 props.put("mail.smtp.auth", "true"); //创建验证器 Authenticator authenticator = new Authenticator() { protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(sender, password); } }; //使用Properties创建Session Session session = Session.getDefaultInstance(props, authenticator); //Set the debug setting for this Session //session.setDebug(true); try { //使用session创建MIME类型的消息 MimeMessage mimeMessage = new MimeMessage(session); //设置发件人邮件 mimeMessage.setFrom(new InternetAddress(sender)); //获取所有收件人邮箱地址 InternetAddress[] receiver = new InternetAddress[receivers.length]; for (int i=0; i<receivers.length; i++) { receiver[i] = new InternetAddress(receivers[i]); } //设置收件人邮件 mimeMessage.setRecipients(Message.RecipientType.TO, receiver); //设置标题 mimeMessage.setSubject(title, charset); //设置邮件发送时间 //SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); //mimeMessage.setSentDate(format.parse("2011-12-1")); mimeMessage.setSentDate(new Date()); //创建附件 Multipart multipart = new MimeMultipart(); //创建邮件内容 MimeBodyPart body = new MimeBodyPart(); //设置邮件内容 body.setContent(mailContent, (mimetype!=null && !"".equals(mimetype) ? mimetype : "text/plain")+ ";charset="+ charset); multipart.addBodyPart(body);//发件内容 //设置附件 if(attachements!=null){ for (File attachement : attachements) { MimeBodyPart attache = new MimeBodyPart(); attache.setDataHandler(new DataHandler(new FileDataSource(attachement))); String fileName = getLastName(attachement.getName()); attache.setFileName(MimeUtility.encodeText(fileName, charset, null)); multipart.addBodyPart(attache); } } //设置邮件内容(使用Multipart方式) mimeMessage.setContent(multipart); //发送邮件 Transport.send(mimeMessage); } catch (Exception e) { e.printStackTrace(); } } private static String getLastName(String fileName) { int pos = fileName.lastIndexOf("\\"); if (pos > -1) { fileName = fileName.substring(pos + 1); } pos = fileName.lastIndexOf("/"); if (pos > -1) { fileName = fileName.substring(pos + 1); } return fileName; } }
b,操作数据库工具(检测邮件地址正确性及更新密码)
package com.mycompany.mail; import java.sql.*; public class AccountHelperUtils { final private static String MysqlHost = "127.0.0.1"; final private static String TableName = "account"; final private static String DbName = "testdb"; final private static String UserName = "username"; final private static String PassWord = "password"; static Connection conn; public static String SelectMail(String mailAddress) throws SQLException{ String userMail = null; PreparedStatement ps = null; ResultSet rs = null; try{ conn = getConnection(); String sql = "select * from "+TableName+" where name = ?"; ps = conn.prepareStatement(sql); ps.setString(1, mailAddress); rs = ps.executeQuery(); while(rs.next()){ userMail = rs.getString("name"); break; } }catch(Exception ex){ return null; }finally{ if(rs!=null){rs.close();} if(ps!=null){ps.close();} if(conn!=null){conn.close();conn = null;}; } return userMail; } public static Boolean UpdatePwd(String mailAddress, String newpwd) throws SQLException{ boolean ret = true; PreparedStatement ps = null; ResultSet rs = null; try{ conn = getConnection(); String sql = "update "+TableName+" set pwd = ? where name = ?"; ps = conn.prepareStatement(sql); ps.setString(1, newpwd); ps.setString(2, mailAddress); ps.executeUpdate(); }catch(Exception ex){ ret = false; }finally{ if(rs!=null){rs.close();} if(ps!=null){ps.close();} if(conn!=null){conn.close();conn = null;}; } return ret; } private static Connection getConnection() throws Exception { Connection con = null; try { Class.forName("com.mysql.jdbc.Driver"); con = DriverManager.getConnection( "jdbc:mysql://"+MysqlHost+"/"+DbName, UserName, PassWord); } catch (Exception e) { throw e; } return con; } }
c,des加密解密工具类
package com.mycompany.mail; import java.security.Key; import javax.crypto.Cipher; /** * DES加密和解密工具,可以对字符串进行加密和解密操作 。 */ public class EncryptDecryptData { /** * 默认构造方法,使用默认密钥 */ public EncryptDecryptData() throws Exception { this(strDefaultKey); } /** * 指定密钥构造方法 * @param strKey 指定的密钥 * @throws Exception */ public EncryptDecryptData(String strKey) throws Exception { // Security.addProvider(new com.sun.crypto.provider.SunJCE()); Key key = getKey(strKey.getBytes()); encryptCipher = Cipher.getInstance("DES"); encryptCipher.init(Cipher.ENCRYPT_MODE, key); decryptCipher = Cipher.getInstance("DES"); decryptCipher.init(Cipher.DECRYPT_MODE, key); } /** 字符串默认键值 */ private static String strDefaultKey = "mysecretkey"; /** 加密工具 */ private Cipher encryptCipher = null; /** 解密工具 */ private Cipher decryptCipher = null; /** * 将byte数组转换为表示16进制值的字符串, 如:byte[]{8,18}转换为:0813, 和public static byte[] * hexStr2ByteArr(String strIn) 互为可逆的转换过程 * @param arrB 需要转换的byte数组 * @return 转换后的字符串 * @throws Exception 本方法不处理任何异常,所有异常全部抛出 */ public static String byteArr2HexStr(byte[] arrB) throws Exception { int iLen = arrB.length; // 每个byte用两个字符才能表示,所以字符串的长度是数组长度的两倍 StringBuffer sb = new StringBuffer(iLen * 2); for (int i = 0; i < iLen; i++) { int intTmp = arrB[i]; // 把负数转换为正数 while (intTmp < 0) { intTmp = intTmp + 256; } // 小于0F的数需要在前面补0 if (intTmp < 16) { sb.append("0"); } sb.append(Integer.toString(intTmp, 16)); } return sb.toString(); } /** * 将表示16进制值的字符串转换为byte数组, 和public static String byteArr2HexStr(byte[] arrB) * 互为可逆的转换过程 * @param strIn 需要转换的字符串 * @return 转换后的byte数组 */ public static byte[] hexStr2ByteArr(String strIn) throws Exception { byte[] arrB = strIn.getBytes(); int iLen = arrB.length; // 两个字符表示一个字节,所以字节数组长度是字符串长度除以2 byte[] arrOut = new byte[iLen / 2]; for (int i = 0; i < iLen; i = i + 2) { String strTmp = new String(arrB, i, 2); arrOut[i / 2] = (byte) Integer.parseInt(strTmp, 16); } return arrOut; } /** * 加密字节数组 * @param arrB 需加密的字节数组 * @return 加密后的字节数组 */ public byte[] encrypt(byte[] arrB) throws Exception { return encryptCipher.doFinal(arrB); } /** * 加密字符串 * @param strIn 需加密的字符串 * @return 加密后的字符串 */ public String encrypt(String strIn) throws Exception { return byteArr2HexStr(encrypt(strIn.getBytes())); } /** * 解密字节数组 * @param arrB 需解密的字节数组 * @return 解密后的字节数组 */ public byte[] decrypt(byte[] arrB) throws Exception { return decryptCipher.doFinal(arrB); } /** * 解密字符串 * @param strIn 需解密的字符串 * @return 解密后的字符串 */ public String decrypt(String strIn) throws Exception { return new String(decrypt(hexStr2ByteArr(strIn))); } /** * 从指定字符串生成密钥,密钥所需的字节数组长度为8位 不足8位时后面补0,超出8位只取前8位 * @param arrBTmp 构成该字符串的字节数组 * @return 生成的密钥 */ private Key getKey(byte[] arrBTmp) throws Exception { // 创建一个空的8位字节数组(默认值为0) byte[] arrB = new byte[8]; // 将原始字节数组转换为8位 for (int i = 0; i < arrBTmp.length && i < arrB.length; i++) { arrB[i] = arrBTmp[i]; } // 生成密钥 Key key = new javax.crypto.spec.SecretKeySpec(arrB, "DES"); return key; } }