一、帐户激活
在很多时候,在某些网站注册一个用户之后,网站会给这个用户注册时填写的email地址发送一封帐户激活邮件,这封邮件的内容就是一个激活帐户的链接和一段简短的文字描述,如果用户没有去邮箱将帐户激活,可能在使用网站的某些功能时就会受到限制,比如不能发贴、下载资料、评论等限制。这么做的原因应该是为了保证帐户的安全性和邮箱的有效性,以后网站如果有什么活动、资讯或系统安全通知等,可以在第一时间通知到用户。比如我在奇艺视频网站注册一个帐号之后,它就会往我注册时填写的邮箱中发一封帐户激活的邮件,邮件中有一个激活的链接,点击它就可以将你的帐户激活。如下图所示:
这个功能是怎么做到的呢,其实实现也是很简单的,说白了就是一个Update操作。
实现思路:当一个用户注册成功之后,系统向用户注册时提供的email地址发送一封邮件,邮件的内容是一个激活帐户的链接和一段简短的文字描述,这个链接的功能就是将这个帐户的状态修改成激活状态。这个功能的重点在于如何生成这个激活帐户的链接。直接可以联想到的方法就是建一个Servlet,Servlet接收一个用户名参数,Servlet拿到用户名参数之后,到数据库把这条记录load出来,做一次update操作即可。但是这样做会有风险,比如有人想在你的网站上搞破坏,注册时他填的email地址就是乱填的,这个Email地址可能根本就收不到邮件(但符合email格式),注册完成之后,然后在浏览器地址栏输入网站之前发送的帐户激活链接,并加上新注册的用户名参数,这样一来就跳过了去邮箱激活的步聚,时间久了,库中可能会留下许多无效的email地址和帐户。为了避免这个问题,解决方案是在用户注册成功之后,在发送激活帐户的链接中加一个随机码,这个随机码是将用户名和一个随机数用md5加密后的一串16进制字符串,每次用户在激活帐户的时候,都验证这个随机码是否和注册时发送的一致,这样就可以避免人为的修改激活链接达到帐户激活的功能。
二、忘记密码
当你在某网站注册一个帐户之后,发现这个帐户很久没用了,密码也忘记了,如果还记得之前注册时填写的Email,那就可以向你的email中发一个重设密码的链接,就可以重新设置你的密码。大部份网站一般都提供了根据Email找回密码的功能。实现此功能的思路:在登录界面添加一个“忘记密码“的链接,用户点击进去之后,输入用户名或Email,系统根据用户名或Email找出用户信息,并生成一个重新设置密码的链接发送到用户的邮箱中(生成链接的方式和生成帐户激活链接的方式一样),用户在邮箱中点击重设密码的链接,根据提示进入密码重设页面,重新输入新的密码即可。
功能实现步聚(帐号激活):
1、用户注册页面
<form action="${pageContext.request.contextPath}/register" method="post"> 用户名:<input type="text" name="userName" value="${param.userName}"><span class="error">${errors.userName}</span><br/> 密码:<input type="password" name="password" ><span class="error">${errors.password}</span><br/> 确认密码:<input type="password" name="password2"><span class="error">${errors.password2}</span><br/> email:<input type="text" name="email" value="${param.email}"><span class="error">${errors.email}</span><br/> <input type="submit" value="注册"> </form>
2、处理注册请求的Servlet(RegisterServlet)
package org.study.accountactivate.web.servlet; import java.io.IOException; import java.util.HashMap; import java.util.Map; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.study.accountactivate.dao.UserDao; import org.study.accountactivate.dao.impl.UserDaoImpl; import org.study.accountactivate.domail.User; import org.study.accountactivate.util.EmailUtils; /** * 用户注册 */ public class RegisterServlet extends HttpServlet { private static final long serialVersionUID = 1L; private UserDao userDao = UserDaoImpl.getInstance(); protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String userName = request.getParameter("userName"); String password = request.getParameter("password"); String password2 = request.getParameter("password2"); String email = request.getParameter("email"); Map<String, String> errors = new HashMap<String,String>(); if (userName == null || "".equals(userName)) { errors.put("userName", "用户名不能为空!"); } else if (userName != null && userDao.findUserByName(userName) != null) { errors.put("userName", "该用户已注册!"); } if (password == null || "".equals(password)) { errors.put("password","密码不能为空!"); } else if (password != null && password.length() < 3) { errors.put("password","密码长度不能低于3位!"); } if (password2 == null || "".equals(password2)) { errors.put("password2", "确认密码不能为空!"); } else if (password2 != null && !password2.equals(password)) { errors.put("password2", "两次输入的密码不一致!"); } if (email == null || "".equals(email)) { errors.put("email", "email不能为空!"); } else if (email != null && !email.matches("[0-9a-zA-Z_-]+@[0-9a-zA-Z_-]+\\.[0-9a-zA-Z_-]+(\\.[0-9a-zA-Z_-])*")) { errors.put("email", "email格式不正确!"); } if (!errors.isEmpty()) { request.setAttribute("errors", errors); request.getRequestDispatcher("/registerUI").forward(request, response); return; } User user = new User(); user.setUserName(userName); user.setPassword(password); user.setEmail(email); user.setActivated(false); userDao.addUser(user); // 注册成功后,发送帐户激活链接 EmailUtils.sendAccountActivateEmail(user); // 注册成功直接将当前用户保存到session中 request.getSession().setAttribute("user", user); request.getRequestDispatcher("/WEB-INF/pages/registerSuccess.jsp").forward(request,response); } }
3、激活帐户的Servlet(ActivateAccountServle)
package org.study.accountactivate.web.servlet; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.study.accountactivate.dao.UserDao; import org.study.accountactivate.dao.impl.UserDaoImpl; import org.study.accountactivate.domail.User; import org.study.accountactivate.util.GenerateLinkUtils; /** * 帐户激活 */ public class ActivateAccountServlet extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String idValue = request.getParameter("id"); int id = -1; try { id = Integer.parseInt(idValue); } catch (NumberFormatException e) { throw new RuntimeException("无效的用户!"); } UserDao userDao = UserDaoImpl.getInstance(); User user = userDao.findUserById(id);// 得到要激活的帐户 user.setActivated(GenerateLinkUtils.verifyCheckcode(user, request));// 校验验证码是否和注册时发送的一致,以此设置是否激活该帐户 userDao.updateUser(user); request.getSession().setAttribute("user", user); request.getRequestDispatcher("/accountActivateUI").forward(request, response); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
4、操作用户的DAO接口与实现类(UserDao和UserDaoImpl类)
package org.study.accountactivate.dao; import org.study.accountactivate.domail.User; public interface UserDao { void addUser(User user); void updateUser(User user); User findUserById(int id); User findUserByName(String userName); User findUserByNameOrEmail(String nameOrEmail); } package org.study.accountactivate.dao.impl; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.UUID; import org.study.accountactivate.dao.UserDao; import org.study.accountactivate.domail.User; public class UserDaoImpl implements UserDao { private static UserDaoImpl instance = new UserDaoImpl(); private UserDaoImpl() {} public static UserDaoImpl getInstance() { return instance; } Map<Integer,User> users = new HashMap<Integer, User>(); int nextId = 1; @Override public void addUser(User user) { user.setId(nextId++); user.setRandomCode(UUID.randomUUID().toString()); users.put(user.getId(), user); } @Override public void updateUser(User user) { users.put(user.getId(), user); } @Override public User findUserById(int id) { return users.get(id); } @Override public User findUserByName(String userName) { Collection<User> userValues = users.values(); for (Iterator<User> iterator = userValues.iterator();iterator.hasNext();) { User user = iterator.next(); if (user.getUserName().equals(userName)) { return user; } } return null; } @Override public User findUserByNameOrEmail(String nameOrEmail) { Collection<User> userValues = users.values(); for(Iterator<User> iterator = userValues.iterator();iterator.hasNext();) { User user = iterator.next(); if (user.getEmail().equals(nameOrEmail) || user.getUserName().equals(nameOrEmail)) { return user; } } return null; } }
5、发送Email的工具类(EmailUtils,用于发送帐户激活链接和密码重置链接)
package org.study.accountactivate.util; import java.io.IOException; import java.io.InputStream; import java.util.Date; import java.util.Properties; import javax.mail.Authenticator; import javax.mail.Message.RecipientType; import javax.mail.PasswordAuthentication; import javax.mail.Session; import javax.mail.Transport; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; import org.study.accountactivate.domail.User; public class EmailUtils { private static final String FROM = "[email protected]"; /** * 注册成功后,向用户发送帐户激活链接的邮件 * @param user 未激活的用户 */ public static void sendAccountActivateEmail(User user) { Session session = getSession(); MimeMessage message = new MimeMessage(session); try { message.setSubject("帐户激活邮件"); message.setSentDate(new Date()); message.setFrom(new InternetAddress(FROM)); message.setRecipient(RecipientType.TO, new InternetAddress(user.getEmail())); message.setContent("<a href='" + GenerateLinkUtils.generateActivateLink(user)+"'>点击激活帐户</a>","text/html;charset=utf-8"); // 发送邮件 Transport.send(message); } catch (Exception e) { e.printStackTrace(); } } /** * 发送重设密码链接的邮件 */ public static void sendResetPasswordEmail(User user) { Session session = getSession(); MimeMessage message = new MimeMessage(session); try { message.setSubject("找回您的帐户与密码"); message.setSentDate(new Date()); message.setFrom(new InternetAddress(FROM)); message.setRecipient(RecipientType.TO, new InternetAddress(user.getEmail())); message.setContent("要使用新的密码, 请使用以下链接启用密码:<br/><a href='" + GenerateLinkUtils.generateResetPwdLink(user) +"'>点击重新设置密码</a>","text/html;charset=utf-8"); // 发送邮件 Transport.send(message); } catch (Exception e) { e.printStackTrace(); } } public static Session getSession() { Properties props = new Properties(); props.setProperty("mail.transport.protocol", "smtp"); props.setProperty("mail.smtp.host", "smtp.163.com"); props.setProperty("mail.smtp.port", "25"); props.setProperty("mail.smtp.auth", "true"); Session session = Session.getInstance(props, new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { String password = null; InputStream is = EmailUtils.class.getResourceAsStream("password.dat"); byte[] b = new byte[1024]; try { int len = is.read(b); password = new String(b,0,len); } catch (IOException e) { e.printStackTrace(); } return new PasswordAuthentication(FROM, password); } }); return session; } }
6、生成帐户激活、密码重设链接的工具类(GenerateLinkUtils)
package org.study.accountactivate.util; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import javax.servlet.ServletRequest; import org.study.accountactivate.domail.User; /** * 生成帐户激活、重新设置密码的链接 */ public class GenerateLinkUtils { private static final String CHECK_CODE = "checkCode"; /** * 生成帐户激活链接 */ public static String generateActivateLink(User user) { return "http://localhost:8080/AccountActivate/activateAccount?id=" + user.getId() + "&" + CHECK_CODE + "=" + generateCheckcode(user); } /** * 生成重设密码的链接 */ public static String generateResetPwdLink(User user) { return "http://localhost:8080/AccountActivate/resetPasswordUI?userName=" + user.getUserName() + "&" + CHECK_CODE + "=" + generateCheckcode(user); } /** * 生成验证帐户的MD5校验码 * @param user 要激活的帐户 * @return 将用户名和密码组合后,通过md5加密后的16进制格式的字符串 */ public static String generateCheckcode(User user) { String userName = user.getUserName(); String randomCode = user.getRandomCode(); return md5(userName + ":" + randomCode); } /** * 验证校验码是否和注册时发送的验证码一致 * @param user 要激活的帐户 * @param checkcode 注册时发送的校验码 * @return 如果一致返回true,否则返回false */ public static boolean verifyCheckcode(User user,ServletRequest request) { String checkCode = request.getParameter(CHECK_CODE); return generateCheckcode(user).equals(checkCode); } private static String md5(String string) { MessageDigest md = null; try { md = MessageDigest.getInstance("md5"); md.update(string.getBytes()); byte[] md5Bytes = md.digest(); return bytes2Hex(md5Bytes); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return null; } private static String bytes2Hex(byte[] byteArray) { StringBuffer strBuf = new StringBuffer(); for (int i = 0; i < byteArray.length; i++) { if(byteArray[i] >= 0 && byteArray[i] < 16) { strBuf.append("0"); } strBuf.append(Integer.toHexString(byteArray[i] & 0xFF)); } return strBuf.toString(); } }
7、实体类(User)
package org.study.accountactivate.domail; public class User { // 编号 private int id; // 用户名 private String userName; // 密码 private String password; // email private String email; // 是否激活 private boolean activated; // 随机码(激活帐户与生成重设密码链接时使用) private String randomCode; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public boolean isActivated() { return activated; } public void setActivated(boolean activated) { this.activated = activated; } public String getRandomCode() { return randomCode; } public void setRandomCode(String randomCode) { this.randomCode = randomCode; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } }
功能实现步骤(忘记密码):
1、登录页面
<form action="${pageContext.request.contextPath}/login" method="post"> <span class="error" style="display: block;">${errors.loginError}</span> 用户名:<input type="text" name="userName" value="${param.userName}"><span class="error">${errors.userName}</span><br/> 密码:<input type="password" name="password"><span class="error">${errors.password}</span><br/> <input type="submit" value="登录"> <a href="${pageContext.request.contextPath}/forgotPwdUI">忘记密码?</a> <a href="${pageContext.request.contextPath}/registerUI">注册</a> </form>
2、发送重设密码申请的页面
<form action="${pageContext.request.contextPath}/forgotPwd" method="post"> <span style="color: red">${requestScope.sendMailMsg}</span> 用户名/邮箱:<input type="text" name="userNameOrEmail" /><span style="color: red">${requestScope.errorMsg}</span><br/> <input type="submit" value="提交" /><a href=""></a> </form>
3、处理“发送重设密码链接“请求的Servlet(ForgotPwdServlet)
package org.study.accountactivate.web.servlet; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.study.accountactivate.dao.UserDao; import org.study.accountactivate.dao.impl.UserDaoImpl; import org.study.accountactivate.domail.User; import org.study.accountactivate.util.EmailUtils; /** * 发送重设密码申请的链接 */ public class ForgotPwdServlet extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String userNameOrEmail = request.getParameter("userNameOrEmail"); UserDao userDao = UserDaoImpl.getInstance(); User user = userDao.findUserByNameOrEmail(userNameOrEmail); if (user == null) { request.setAttribute("errorMsg", userNameOrEmail + ",不存在!"); request.getRequestDispatcher("/forgotPwdUI").forward(request, response); return; } // 发送重新设置密码的链接 EmailUtils.sendResetPasswordEmail(user); request.setAttribute("sendMailMsg", "您的申请已提交成功,请查看您的"+user.getEmail()+"邮箱。"); request.getRequestDispatcher("/WEB-INF/pages/forgotPwdSuccess.jsp").forward(request, response); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
4、重新设置密码的页面
<form action="${pageContext.request.contextPath}/resetPassword" method="post"> <span class="error" style="display: block;">${errors.passwordError}</span> 用户名:<input type="text" name="userName" value="${userName}" readonly="readonly"/><br/> 新密码:<input type="password" name="newPassword" /><span class="error">${errors.newPassword }</span><br/> 确认新密码:<input type="password" name="newPassword2"/><span class="error">${errors.newPassword2 }</span><br/> <input type="submit" value="修改" /> </form>
5、处理重新设置密码请求的Servlet(ResetPasswordServlet)
package org.study.accountactivate.web.servlet; import java.io.IOException; import java.util.HashMap; import java.util.Map; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.study.accountactivate.dao.UserDao; import org.study.accountactivate.dao.impl.UserDaoImpl; import org.study.accountactivate.domail.User; /** * 重新设置密码 */ public class ResetPasswordServlet extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String userName = request.getParameter("userName"); String newPassword = request.getParameter("newPassword"); String newPassword2 = request.getParameter("newPassword2"); Map<String,String> errors = new HashMap<String, String>(); if (newPassword == null || "".equals(newPassword)) { errors.put("newPassword", "新密码不能为空!"); } if (newPassword2 == null || "".equals(newPassword2)) { errors.put("newPassword2", "确认新密码不能为空!"); } if (!newPassword.equals(newPassword2)) { errors.put("passwordError", "两次输入的密码不一致!"); } if (!errors.isEmpty()) { request.setAttribute("errors", errors); request.getRequestDispatcher("/resetPasswordUI?userName=" + userName).forward(request, response); return; } UserDao userDao = UserDaoImpl.getInstance(); User user = userDao.findUserByName(userName); user.setPassword(newPassword); request.getRequestDispatcher("/WEB-INF/pages/resetPasswordSuccess.jsp").forward(request, response); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
帐户激活操作流程:
1)、注册页面
2)、注册成功页面
3)、帐户未激活,登录后的界面
4)、前往邮箱中激活帐户
5)、激活成功
6)、帐户已激活,登录后的界面
忘记密码操作流程:
1)、登录页面(点击“忘记密码”链接,进入重设密码页面)
2、重设密码申请页面(输入用户名或邮箱,点击“提交”按钮)
3、重设密码申请成功页面
4、邮箱中重设密码的链接
5、重新设置密码页面
6、密码重新设置成功页面
转自:http://blog.csdn.net/xyang81/article/details/7727141#comments