4-Password Encoding and Reset Password

讲到密码加密这一点,我们应该很容易接受,我们存在数据库中的密码不可能是明文方式,以前我们都是使用md5这样的算法来加密密码或者使用sha的方式来加密,现在它们都被新宠BCryptPasswordEncoder夺取了光彩。

我们在使用加密的密码时,其实使用方式很简单,配置也很简单。先看一下我们的使用方式

就是很简单地调用了encode(),由于我们没有之间写登录的实现,所以我们没有一个直接的密码比较的过程。相反我们只需要做好配置就行了。

配置就是注入一个Bean,同时我们看到了在authProvider()我们使用到了encoder(),有了它们spring-security就“自动”完成登录验证。

上面的配置和使用,都是比较简单的,然后让我们回到“重置密码”。

先梳理一下重置密码的流程:

  1. 登录页面,用户点击“重置密码”按钮 ;
  2. 跳转到重置密码页面 ;
  3. 用户输入email地址,再点击“重置”按钮 ;
  4. 后台发送邮件给用户,生成重置密码链接 ;
  5. 用户收到邮件后点击链接,跳转到重置密码,输入新密码,提交 ;
  6. 后台处理,完成密码重置的过程。

对于用户的交互的流程是1、3、5;其余是都是程序处理。对于整个流程而言是有点长,但还是可以接受。我们写代码的思路也就清醒起来。

首先是忘记密码页面forgetPassword.html,一个输入框和一个重置按钮,比较简单。

然后就是重置按钮提交给后台的处理:

我们仍然采用事件机制,发布一个重置密码事件。

重置密码事件的处理也很简单就是发送邮件:

然后用户邮箱应该受到这样一封邮件

当用户点击上面的链接应该跳转到修改密码页面

上面使用到了SecurityContextHolder这个工具类,设置了Authentication,这是因为后面处理保存密码的时候需要使用用户信息。

最后填写好新密码及确认密码后提交给后台处理

用户密码更新完成后再次跳转到登录页面,然后使用新密码就可以登录了。

至此,我们完成了重置密码的所有编码工作。最后让我看下一共修改了哪些文件。

①IUserService中共添加4个接口

②MvcConfig中添加了2行

③OldRegistrationController添加了3个接口

④WebSecurityConfig中添加了3个放行url

⑤java文件中新增了实体类PasswordResetToken和相应的Repo,还有OnResetPasswordEvent和ResetPasswordListener

html文件中新增了forgetPassword.html和updatePassword.html

小结:总体上,完成密码重置的编码工作不是很复杂,只要上面的流程清晰,加上用到了前面java mail,spring事件处理,还有spring security中的一个工具类SecurityContextHolder和UsernamePasswordAuthenticationToken,它们完成了请求上下文中认证信息的传递。

具体的代码可以参见github.

3-Resend Verification Email

上一篇文章中,我们讲到发送邮件激活账号,同时我们还引入了一个过期时间问题,那么如果我们的用户在很久之后才去点击激活链接,显然会受到一个token已经过期的提示,但是同时我们希望可以让用户重新发送一封激活邮件,这是可以让用户重新输入一次邮箱地址,但是更好的做法是只需要用户点一下“重新发送”按钮,然后受到一封有“新”的token的邮件即可。

此时用户看到的token也变化了,如果再用以前的token,就会是一个无效的token了,和原来token关联的那条记录token的有效期延长了。好了我们看看具体怎么做。

1.先看一下我们的一共修改的文件

在OldRegistrationController中我们添加下面的代码

同时我们重构了OnRegistrationCompleteEvent,添加了一个token和构造方法

同时我们还重构了RegistrationListener

在自定义异常处理中我们添加了MailAuthenticationException

最后我们的UserServiceImpl添加了两个实现

还有MvcConfig中添加了一行代码,用于MailError.html

至此我们的改动工作全部完成了。

我们点击上面的Resend按钮后,我们可以收到新的邮件。

在你点击上面的邮件链接后,你会看到

现在正准备登录,可能你忘记了上次输入的密码,那么这时候,我们就要写一个功能“忘记密码”。这个留到下一篇文章。

具体的代码可以参见github.

透过现象看本质–编程中的错误解决

我之前在思考怎么快速地开发高质量软件,其中就有“ 快速 ”二字,我们开发中如果顺畅地写代码,时间不会占用太多,但如果写代码的过程中“不顺”,我们可能会陷入进去,这是一个很自然的结果。然后一陷入进去,意味着你可能去“探索”去了,你如果快速地解决了问题,这倒是一个很好的结果。

但我们现在回到问题解决的本身上。

解决问题,尤其是开发中的问题,有一些是,技术问题,比如我在写代码过程中遇到的:

这个错误是在badUser.html页面中使用到了
<h1 class=”alert alert-danger” th:utext=”${param.message[0]}”>error</h1>

这主要是Thymeleaf中的问题,由于我使用的较高版本的Thymeleaf 3.0.11,上面的代码在低版本中是没有问题的,如果在不会退版本的前提下——假定的项目框架环境,我们来解决这个问题。

具体的错误信息还是比较清晰的:

A problem occurred whilst attempting to access the property ‘param’: ‘Access to variable “param” is forbidden in this context. Note some restrictions apply to variable access. For example, direct access to request parameters is forbidden in preprocessing and unescaped expressions, in TEXT template mode, in fragment insertion specifications and in some specific attribute processors.’

根据错误我们看一下源码:

错误信息指出,在受限的访问上下文中,如果访问的是受限变量–>param,就会有TemplateProcessingException。

那么我们可能思考的是怎么去解决这个问题?

错误说它不允许访问param变量中的信息,它不允许是出于安全的角度来限制的,那么它不允许我们访问,我们又需要访问,那框架总要提供一种方式,让我们访问,可能我需要需要做一些调整,或者额外的努力,但是我们的功能性需求就是访问到后台传到页面中的信息。

……

上面很容易陷入到“思考怎么解决问题”这个细节出来,这是我们软件开发中可能会经常出现的技术问题。

但是在这些技术问题的背后,其本质是什么呢?

在遇到技术问题时,如果你还记得—— 当问题是一个问题的时候,那一定是

问题 > 人 

我们太关注问题本身了,所以让它难住了我们。

我们深刻地意识到问题之所以是问题的时候,那么就是开始没有问题的时候。

上面说到的技术问题,其实本质是我们对Thymeleaf框架的内部细节没有掌握,如果我们看到上面出错的源码,只是问题的局部,也就是问题森林中的一棵树。

但如果我们看到的是整个森林,我们就不会陷入到具体的一棵树上。我们如果熟悉Thymeleaf框架,我们会就理解为什么在受限的上下文中访问受限的变量需要抛出一个异常。我们会进一步理解,什么是受限的上下文。什么是受限的变量。如果需要访问受限的变量,怎么做。

所以我们需要去“探索”它产生的原因,于是我们来到了GitHub

而我们代码中真正报错的也是 th:utext=”${param.message[0]}”>error,所以我们可以换一个标签th:text=”${param.message}”,这是直觉,也是一些知识的积累。然后就是这个小小的改动就可以实现错误的解决,也同时完成了功能性需求。

我以前也是经常想,为什么我不能快速把问题解决掉呢?还总是带着一丝丝自责,觉得自己不够好,不够优秀,至少是没有快速地解决问题。其实在开发中我们基本上不可能是一番风顺的,软件的不透明性就决定了软件开发的复杂性的存在,所以积累知识是一方面,另一方面是快速定位问题的发生根本原因。

还有一种很大的可能你没有遇到上面那个问题—— 使用th:text 而不是th:utext, 因为你可能一开始就是接触到了th:text, 问题的解决就发生在了问题发生之前。有时候这是习惯的力量,有时候这是知识的力量。

但上面的问题发生在“遗留的代码”中,也就是说并不是你自己从头开始写的代码,也是维护别人的代码,这也是开发中经常存在的。我们经常会忽略一些细节,因为我们也可能不知道,比如上面的th:utext标签随着版本迭代升级而新出的问题。

如果说知识的积累是一个方面,我们可能在这一块没有什么优势–>由于我们年轻,所以积累是件好事,年轻的我们可以积累。同时在“ 积累”有所欠缺的情况下, 我们要动脑筋,想办法弄清楚问题发生的根本原因,然后可以利用一些工具,比如搜索引擎来收集信息,定位问题,然后再解决问题,当然这是个人的独立解决问题的方式,如果你的环境中有集体帮助,比如头脑风暴,也是会有帮助的。

集体中,不要害怕问题,也不要害怕问问题,只要是你经过思考的问题,提炼问题,你可以在合适的时候尝试与别人交流一下。因为解决问题本身是件快乐的过程,帮助他人也是,问高质量问题也是的。一个好的环境一定是彼此共同学习,共同成长,共同提高的。

如果要总结一下全文,编程中的确会有遇到一些不顺的时候,本文就只提到技术问题,可能还会有其它的问题,在解决技术问题的过程中,我们要透过问题本身看问题本质,比如出现这个问题的根本原因是你不知道某些技术细节导致问题发生,所以你要去发现、学习那些技术细节,然后才能解决问题。同时你也要认识到积累的力量,但更重要的是你要去想想怎么去收集那些未知的细节,这种能力对快速解决问题还是很重要的。