“忘记密码“功能过程及其实现细节 您所在的位置:网站首页 华硕TUFb660可以cpu超频吗 “忘记密码“功能过程及其实现细节

“忘记密码“功能过程及其实现细节

2023-12-27 07:17| 来源: 网络整理| 查看: 265

更多2019年的技术文章,欢迎关注我的微信公众号:跃码前行(微信号:code_forward),也可扫下方二维码关注获取最新文章哦~

对于忘记密码功能,一般都是通过2种方式找回:一种是通过预留电话号码发送验证码找回,另一个是通过设定邮箱找回。对于具体的找回流程,参见:http://www.yixieshi.com/ucd/9207.html。

这里结合目前做的项目,详细的说明一下密码找回的过程。这里用的是邮箱找回。根据一般的忘记密码的流程来说明。我采用的流程是:

流程

【登录】 --> 【点击忘记密码】 --> 【输入个人邮箱和验证码】 --> 【系统发送邮箱验证】 --> 【用户在限定时间内登录邮箱,点击链接,进入重置密码页面】 --> 【重置密码完毕,点击进入登录界面】。

登录页面

先给出我做的登录界面,其中包含"忘记密码"功能

一般登录界面都有“忘记密码”选项,这里不多说。

"忘记密码"页面

点击"忘记密码"选项后,这时,页面会跳向一个新页面,即“忘记密码”页面。首先给出"忘记密码"按钮对应的跳转代码片段:

忘记密码?

在点击后,跳到新页面:

这里的界面设计是模仿的网页163邮箱的忘记密码,给出链接:网易邮箱帐号安全中心。

该界面中,有几个关键点:一个是验证码的生成,一个是邮箱发送的功能,还一个就是点击确定后的反馈界面。主要包含这3个方面的内容,这3点以下将会详细介绍。

1、验证码生成功能

参考文章:J2EE动态生成验证码可刷新-CSDN论坛。

关键是jsp和servlet的交互。上面的链接中,主要使用了作者贴出的serlvet代码片段,即随机生成四位0~9的数字,然后生成一些干扰线,最后将生成的图片发送到jsp页面。

下面给出部分后台servlet代码片段:

public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("生成验证码"); // 清空缓冲区 response.reset(); // 注意这里的MIME类型 response.setContentType("image/png"); // 设置页面不缓存 response.setHeader("Pragma", "No-cache"); response.setHeader("Cache-Control", "no-cache"); response.setDateHeader("Expires", 0); // 创建一个图像,验证码显示的图片大小 BufferedImage image = new BufferedImage(width, height,BufferedImage.TYPE_INT_RGB); // 获取图形上下文 Graphics g = image.getGraphics(); // 设置背景 g.setColor(getRandColor(200,250)); g.fillRect(0, 0, width, height); for (int i = 0; i < 4; i++) { drawCode(g, i); } //添加干扰线 drawNoise(g, 12); // 绘制边框 //g.setColor(Color.gray); //g.drawRect(0, 0, width - 1, height - 1); // 将验证码内容保存进session中,用于验证用户输入是否正确时使用 HttpSession session = request.getSession(true); session.removeAttribute("rand"); session.setAttribute("rand", codeNumbers); // 重设字符串 codeNumbers = ""; // 利用ImageIO类的write方法对图像进行编码 ServletOutputStream sos = response.getOutputStream(); ImageIO.write(image, "PNG", sos); sos.close(); }

从上面的代码可以看出,基本步骤是:生成4位0~9的随机数,然后设置数字的大小,颜色等;接着设置干扰线,根数,颜色等,然后创建出一个BufferedImage对象,就是验证码的背景图片大小、颜色等;最后将生成好的验证码传送到前台。

注意:这里要将验证码(即图片)传送到前台的jsp界面,需要三个方面的设置:

①设置传送到前台的内容类型,可以是text/html,也可以是image/png。由于这里传送的是验证码,所以设置为后一种。

// 注意这里的MIME类型 response.setContentType("image/png");

②在servlet中,还要考虑用什么方法对图像进行编码。

// 利用ImageIO类的write方法对图像进行编码 ServletOutputStream sos = response.getOutputStream(); ImageIO.write(image, "PNG", sos); sos.close();

③在jsp页面部分:

对应的js函数:

function refreshcode(){ document.getElementById("code").src="codeMakerServlet?a="+Math.random()+100; return true; }

其实就是每次点击,都运行一次对应的servlet,servlet在运行后,都会发送一个验证码图片到前台,也就是所谓的"刷新"。

2、填写内容检查

这里用到了一个js库:jquery.validate.min.js。这个只能判断输入的内容是否符合规则。

由于要输入用户的登录邮箱,规则正确只是第一步,还要查看输入的邮箱在数据库中是否存在,如果不存在,是不能允许发送密码重置邮件的。所以对邮箱正确性的检查,需要调用一次$.getJSON();进行验证;

对于验证码的验证,由于在验证码生成的servlet中,已经设置了

// 将验证码内容保存进session中,用于验证用户输入是否正确时使用 HttpSession session = request.getSession(true); session.removeAttribute("rand"); session.setAttribute("rand", codeNumbers);

所以这时候还要进行一次后台通讯,查看是否验证码输入正确。但是,有个问题我还没来得急了解:这个验证码的作用是什么?这个也是我在后期需要进行追加的内容。

3、邮件发送功能

参考文章:http://www.iteye.com/topic/352753。 需要用到的jar包:mail.jar。

在上面的参考文章中,作者详细的给出了3段主要的代码,这里也不多说了,说实话,我还真没怎么看其内部的实现原理,只是把别人写好的代码,直接调用接口使用。

注意:作者在最后给出了几个经验,其中一个就是不能用新申请的邮箱,否则无法发送成功,我试了下,确实是这样,但是这个是不是所有的邮箱都是这样,我也没有试过。

另外,需要特别提出的是,发送内容的方式分为两种,一个是按照文本发送,一个是按照html发送。

先给出示例代码:

public static void main(String[] args){ //这个类主要是设置邮件 MailSenderInfo mailInfo = new MailSenderInfo(); mailInfo.setMailServerHost("smtp.163.com"); mailInfo.setMailServerPort("25"); mailInfo.setValidate(true); mailInfo.setUserName("[email protected]"); mailInfo.setPassword("********");//您的邮箱密码 mailInfo.setFromAddress("[email protected]"); mailInfo.setToAddress("[email protected]"); mailInfo.setSubject("设置邮箱标题,sfsfdfdff"); mailInfo.setContent("http://write.blog.csdn.net/postlist"); //这个类主要来发送邮件 SimpleMailSender sms = new SimpleMailSender(); sms.sendTextMail(mailInfo);//发送文体格式 SimpleMailSender.sendHtmlMail(mailInfo);//发送html格式 }

在代码的最后,有两种发送方式,一般都会采用第二种方式,如果要给对方邮箱发送一些超链接等,就需要用html格式发送,这样,就能自动识别成超链接。下图是用文本方式发送的上面代码中的内容: 从上图中,我们可以看出,对应的标签是当做文本处理的,下图是用html格式发送的内容: 两种效果还是不同的,由于发送验证邮件需要用到链接,所以项目中采用html格式。

4、邮件中的链接

对于这个链接,还有一个重要的部分,那就是链接要链向哪里?需要哪些参数?这个链接怎么保证时效性(每个密码找回链接都是有时效性的)?

咱们首先看一下成熟的例子。学信网的密码找回链接如下:

现在能够确定的一点是:链接是指向某个servlet的,但是对应的参数就是一串没有规律的数字。这个key中隐含着什么样的信息?我认为包括两个部分:

①一部分是用户的用户名等可以辨识用户的内容,否则在用户点击链接后无法得知用户是谁;

②还一部分是邮件发送时的时间,因为只有参数中含有时间信息,才能使得该链接具有时效性(具体时效性可以是多 少,指向     的后台servlet中需要设定的部分)。

知道了以上的内容,还剩下最后一个问题,这个key值的乱码是怎么得来的?首先想到的就是加密。对的!就是加密得来的。否则以明文传送,不太安全。

至于这个加密的部分,就放在下面去讲。这里先来大体说一下加密:输入就是一串数,出来的就是一堆乱码,而且,这里的加密必须要是可逆的,否则点击链接后,不能还原成明文还是不顶用啊。既然知道了输入就是一个字符串,那么可以把时间和用户名串起来,这样就形成了一个字符串,然后在后台再解析出来。

5、点击确定后的信息回馈

首先来看一下【学信网】是怎么回馈的:

用户点击"确定"按钮,当邮件发送成功后,会显示"邮件发送成功",然后下面跟出一些提示性的语言blablabla....但是,若由于某些原因,导致邮件发送失败时,也要给出相应的提醒。

模仿上述反馈界面,我用了如下界面:

内部实现原理如下:

①在用户点击"确认"按钮后,后台首先获得当前的系统时间(按照ms计算),然后从前台得到了用户输入的登录邮箱,在两者之间加入一个"@"符连接起来为一个字符串(目的:便于在转变为明文的时候将两者区分开来,因为邮箱的第一个字母是不允许使用"@"符的,所以加入该符号不会引起歧义)。然后将该字符串加密成一串密文,这就是key值。前面再配上某个servlet的固定连接,就是完成了link链接;

②调用邮箱发送接口,实现邮箱转发。本系统中使用的邮箱接口如下:

/** * 发送信息到用户的登录邮箱 * * @param userEmail * @return */ public Boolean sendMail(String userEmail) { Boolean flag = false; try { // 获取当前系统时间 Date now = new Date(); String currentTime = "" + now.getTime(); String urlString = "http://localhost:8080/EVM/forgetPasswordAction?method=resetPassword&key="; CodeEncryption cEncryption = new CodeEncryption(); String encryptedCode = cEncryption.encryptCode(currentTime + "@" + userEmail); String link = urlString + encryptedCode; String adminMailAddress = "[email protected]"; String contents = this.setContents(userEmail, link, adminMailAddress); //邮箱配置 String serverHost = "smtp.163.com"; String serverPort = "25"; Boolean isValidate = true; String userName = "[email protected]"; String password = "blablabla"; String toMailAddress = userEmail; String subtitle = "科研数据管理平台注册账号密码找回 "; this.setMail(serverHost, serverPort, isValidate, userName, password, toMailAddress, subtitle, contents); flag = true; } catch (Exception e) { e.printStackTrace(); } return flag; }

上面的函数会返回一个布尔值,如果邮箱发送成功,则返回true,否则返回false。该函数是在service层,得到的布尔值将会返回到调用该函数的servlet层。这里面有个setMail函数,里面有很多参数,下面也给出对应的setMail函数:

/** * 邮箱发送前的配置 * @param serverHost * @param serverPort * @param isValidate * @param userName * @param password * @param toMailAddress * @param subtitle * @param contents */ public void setMail(String serverHost, String serverPort, Boolean isValidate, String userName, String password, String toMailAddress, String subtitle, String contents) { // 这个类主要是设置邮件 MailSenderInfo mailInfo = new MailSenderInfo(); mailInfo.setMailServerHost(serverHost); mailInfo.setMailServerPort(serverPort); mailInfo.setValidate(isValidate); mailInfo.setUserName(userName); mailInfo.setPassword(password);// 您的邮箱密码 mailInfo.setFromAddress(userName); mailInfo.setToAddress(toMailAddress); mailInfo.setSubject(subtitle); mailInfo.setContent(contents); // 这个类主要来发送邮件 SimpleMailSender sms = new SimpleMailSender(); // sms.sendTextMail(mailInfo);// 发送文体格式 SimpleMailSender.sendHtmlMail(mailInfo);// 发送html格式 }

若邮件发送失败,servlet会接收到false的信息。先来看一下对应的servlet层是怎么做的:

/** * 密码忘记,发送信息到邮箱 * @param request * @param response */ public void passwordForgotten(HttpServletRequest request, HttpServletResponse response) { String userEmail = request.getParameter("userEmailInput"); ForgetPasswordService fPasswordService = new ForgetPasswordService(); Boolean flag = fPasswordService.sendMail(userEmail); try { if (flag) { RequestDispatcher rDispatcher = request.getRequestDispatcher("fpEmailSended.jsp"); String str1 = userEmail.substring(0, 3); String str2 = userEmail.substring(userEmail.indexOf("@")); String str3 = str1 + "***" + str2; request.setAttribute("userEmail", str3); request.setAttribute("flag", "true"); rDispatcher.forward(request, response); } else { RequestDispatcher rDispatcher = request.getRequestDispatcher("fpEmailSended.jsp"); String str1 = userEmail.substring(0, 3); String str2 = userEmail.substring(userEmail.indexOf("@")); String str3 = str1 + "***" + str2; request.setAttribute("userEmail", str3); request.setAttribute("flag", false); rDispatcher.forward(request, response); } } catch (Exception e) { e.printStackTrace(); } }

这里,flag出现了2个分支:在正确的情况下,给出"邮箱发送成功"的反馈界面,并由 RequestDispatcher指向一个新的jsp页面,即点击确认的那个页面和信息回馈的页面不是同一个页面。

若flag为true,则走第一个分支,结果如下:

flag为false,则走第二个分支,结果如下:

下面我们来看一下发送成功的情况,这样在邮箱285***@qq.com中就出现了一封邮件:

6、点击邮箱链接触发的操作

点击链接后,会进入到指定的servlet。从链接中可以看到,进入到的是一个名称叫forgetPasswordAction的servlet中。后面跟着两个参数:method----指定servlet触发什么操作,key----包含有发送时间(ms计算)和用户名信息,在servlet中会转化为明文。其中,"发送时间"用于判断用户点击链接的时候,该链接是否已经过时,"用户名"用于servlet跳转到jsp页面的前端显示。

下面看一下指定的servlet中的相关内容:

/** * 用户在点击邮箱里的链接后,进入该函数 * @param request * @param response */ public void resetPassword(HttpServletRequest request, HttpServletResponse response) { String key = request.getParameter("key"); ForgetPasswordService fService = new ForgetPasswordService(); List datasList = new ArrayList(); datasList = fService.getExplicitCode(key); String time = datasList.get(0); String userEmail = datasList.get(1); Boolean timeFlag = fService.judgeOfTime(time); //判断用户点击链接的时间是否过期 String flag = "false"; if (timeFlag) { flag = "true"; } List resultList = new ArrayList(); resultList.add(flag); resultList.add(userEmail); try { RequestDispatcher rDispatcher = request.getRequestDispatcher("resetPassword.jsp"); request.setAttribute("datasList", resultList); rDispatcher.forward(request, response); } catch (Exception e) { e.printStackTrace(); } } 7、重置密码页面

首先判断时间是否过期,并将标记发送到jsp中。下面给出resetPassword.jsp中的部分内容:

首先获得后台传过来的参数:

得到flag,将flag设置到隐藏的input中:

在本jsp页面中,有两个大的div,分别对应flag的不同标记。若flag的标记为true,则显示一个div的内容,隐藏另一个div,若flag为false,则反过来。

flag为true时显示的内容:

用户名 密 码   注:密码长度至少6位 确认密码 确定

对应的页面如下:

若flag为false,即链接已过期,对应的html代码如下;

  重置密码链接已过期

       没有收到重置密码邮件,您可以到邮件垃圾箱里找找。        或者点击:【重新发送重置密码邮件】。

对应的页面如下:

jsp中的js控制代码:

var flag = $("#hiddenInput").val(); if(flag == "true") { $("#failureDivId").hide(); $("#resetPasswordForm").show(); } else { $("#resetPasswordForm").hide(); $("#failureDivId").show(); }

重置界面

进入重置页面,用户页面已自动填充(根据邮箱链接中的key值可得到用户名),用户这时候可进行密码重置。这里需要说明的一点是:一般的找回密码流程,最后都会让用户重新设置密码,不会把原来用户忘记的密码发送给用户。

其实,在重置密码页面,主要需要说明的就是密码的加密过程。

1、加密算法

我这里用到的是3DES密码加密,相关链接可以直接搜索3DES百度百科,里面有示例代码。这里我贴出相关的代码片段:

byte[] encoded = encryptMode(keyBytes, szSrc.getBytes());  System.out.println("加密后的字符串:" + new String(encoded));  byte[] srcBytes = decryptMode(keyBytes, encoded);  System.out.println("解密后的字符串:" + (new String(srcBytes)));

这里可以看出来,加密之后的密文是一个byte数组,难就难在这个数组上,虽然可以使用new String(),转化为字符串,但是这个字符串是乱码,我试过,无论使用什么编码格式,转化出来的字符串都是乱码,这是一个问题,另一个问题是,转化后的字符串是无法再回退到byte数组的,即这个过程是不可逆的。基于以上两点缺陷,不能将得到的密文byte数组直接转化为字符串。

为了解决这个问题,我想到了另外一种方式,很巧妙的化解了byte数组的显示问题,而且,此种方式,在一定程度上还算是对byte数组的再次加密,方法如下:

/** * 将明文字符串加密成密文,然后以字符串的形式返回. * 说明:在将字符串明文加密后,得到的是一个byte[]数组,这时候如果将byte[]数组转化为字符串,是乱码。解决方式如下: * 步骤1:对于byte[]数组中的每个元素,因为范围都在-128~127之间,所以为了方便表达,统一加上128,都转化为0或者正数; * 步骤2: 利用Integer.toBinaryString()函数,将每个元素转化为16进制,结果若为单个的,用g在右侧补成2位的; * 步骤3:将所有的结果统一串成一个整个的字符串,作为结果返回。 * @param explicitPassword * @return */ public String encryptCode(String explicitString) { Security.addProvider(new com.sun.crypto.provider.SunJCE()); String implicitString = ""; byte[] encoded = encryptMode(keyBytes, explicitString.getBytes()); List codeArrTemp = new ArrayList(); for (int i = 0; i < encoded.length; i++) { codeArrTemp.add(String.valueOf(Integer.toHexString((int) encoded[i] + 128))); } List codeArr = new ArrayList(); for (int i = 0; i < codeArrTemp.size(); i++) { if (codeArrTemp.get(i).length() == 1) { String temp = codeArrTemp.get(i) + "g"; codeArr.add(temp); } else { codeArr.add(codeArrTemp.get(i)); } } for (int i = 0; i < codeArr.size(); i++) { implicitString += codeArr.get(i); } return implicitString; }

注释中,已经给出了我的转化步骤,下面再给出与上面加密配套的解密代码:

/** * 对密文解密,返回明文 * @param implicitString * @return */ public String deEncryptCode(String implicitString) { Security.addProvider(new com.sun.crypto.provider.SunJCE()); String explicitString = null; byte[] encoded = null; //根据implicitString得到encoded List codeArrTemp = new ArrayList(); for (int i = 0; i < implicitString.length(); i += 2) { codeArrTemp.add(implicitString.substring(i, i + 2)); } List codeArr = new ArrayList(); for (int i = 0; i < codeArrTemp.size(); i++) { if (codeArrTemp.get(i).contains("g")) { codeArr.add(codeArrTemp.get(i).substring(0, 1)); } else { codeArr.add(codeArrTemp.get(i)); } } encoded = new byte[codeArr.size()]; for (int i = 0; i < codeArr.size(); i++) { encoded[i] = (byte) (Integer.parseInt(codeArr.get(i), 16) - 128); } byte[] srcBytes = decryptMode(keyBytes, encoded); explicitString = new String(srcBytes); return explicitString; }

以上,就完成了字符串的加/解密过程,上面的代码可以直接拿过来用。其中,以上两个函数中,用到了原来的加/解密接口,这里顺便也把代码贴出来:

3DES加密:

// DES,DESede,Blowfish // keybyte为加密密钥,长度为24字节 // src为被加密的数据缓冲区(源) public static byte[] encryptMode(byte[] keybyte, byte[] src) { try { // 生成密钥 SecretKey deskey = new SecretKeySpec(keybyte, Algorithm); // 加密 Cipher c1 = Cipher.getInstance(Algorithm); c1.init(Cipher.ENCRYPT_MODE, deskey); return c1.doFinal(src); } catch (java.security.NoSuchAlgorithmException e1) { e1.printStackTrace(); } catch (javax.crypto.NoSuchPaddingException e2) { e2.printStackTrace(); } catch (java.lang.Exception e3) { e3.printStackTrace(); } return null; }

3DES解密:

// keybyte为加密密钥,长度为24字节 // src为加密后的缓冲区 public static byte[] decryptMode(byte[] keybyte, byte[] src) { try { // 生成密钥 SecretKey deskey = new SecretKeySpec(keybyte, Algorithm); // 解密 Cipher c1 = Cipher.getInstance(Algorithm); c1.init(Cipher.DECRYPT_MODE, deskey); return c1.doFinal(src); } catch (java.security.NoSuchAlgorithmException e1) { e1.printStackTrace(); } catch (javax.crypto.NoSuchPaddingException e2) { e2.printStackTrace(); } catch (java.lang.Exception e3) { e3.printStackTrace(); } return null; }

以上3段代码就是整个加密、解密的过程。在我们数据库中的密码加密,也是用到了以上的方法。

2、"重置密码"点击"确定"后的信息反馈页面

这一步类似于前面的发送邮箱的确认,也是分为两支:成功还是失败。这里再次贴出重置密码的页面:

在用户点击"确定"后,后台将密码加密,然后存入数据库,等操作完成后,后台的servlet跳转到一个新页面:resetPassSuccOrFail.jsp,即给出信息反馈,表示是重置成功还是失败。

这里的操作和前面都是类似的,也就不多说了。下面给出两张分支的页面:

当修改密码失败时:

至此,"忘记密码"功能已实现完毕。

更多2019年的技术文章,欢迎关注我的微信公众号:跃码前行(微信号:code_forward),也可扫下方二维码关注获取最新文章哦~



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有