通过邮箱找回密码简单实现

基本步骤:

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;
    }
}
posted @ 2014-03-19 12:51  ciaos  阅读(2148)  评论(0编辑  收藏  举报