基本步骤:
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;
}
}