整体实现逻辑
前端在登录页面时,自动从后台获取最新的验证码图片
服务器接收获取生成验证码请求,生成验证码和对应的图片,图片响应回前端,验证码保存一份到服务器的 session 中
前端用户登录时携带当前验证码
服务器校验验证码是否合法(验证码存在并未过期),继续后续的用户名和密码校验逻辑
通过一个时序图来表述如下图,图中细化了一下各个控制器和过滤器之间的功能职责,还不是很正规,只为了更好表达上述的流程描述,所以读者们将就一下:
时序图 plantUML 代码
plantUML 的使用教程请移步至之前发布过的文章:PlantUML 语法之时序图
1 | @startuml |
将上述的逻辑进行任务拆分:随机验证码和图片生成,生成验证码请求Controller
,session存储器就临时使用spring-social-web
包中的SessionStrategy
来存储,验证码过滤器,配置过滤器到spring scuerity
过滤器中的用户密码验证过滤器之前。
前端控制器+生成验证码
服务器 session 容器
采用小步快走的开发模式,前端控制器和生成验证码的代码都写在一起,后期再进行代码重构, 这里主要引用了spring-social-web
依赖:
1 | <dependency> |
这个包里面有个很小巧的session
管理工具:SessionStrategy
考虑到这个 session 在验证码过滤器中还得使用,所以自定义了一个配置,直接注入到了spring中:
1 | import org.springframework.context.annotation.Bean; |
这样,在 Controller 层直接通过@Autowired
引用即可。
图片验证码容器
图片验证码对象需要至少三个属性:图片,验证码,过期时间。
后期可能还有其他的验证形式,但是其中公共的部分:验证码和过期时间是可以抽象出来,这里为了演示不做重构。
1 | import java.awt.image.BufferedImage; |
公共常量
在图片生成代码中,图片的尺寸,验证码的随机随机数长度和过期时间,都设计在了静态常量类中,当然也可以做成配置文件。验证码的 session 的唯一标识也做成了公共的,以便在验证码过滤器中进行校验时使用:
1 | public class MyConstants { |
生成验证码请求Controller
源码:
1 | import static org.woodwhale.king.commons.MyConstants.EXPIRE_SECOND; |
为了提升代码的可扩展性,随机验证码的生成方法generate()
的方法最好是抽成接口,后期可能还有短信验证码,三方登录的验证码,这里仅做演示。上述验证码图片效果:
验证码过滤器
SpringSecurity
是通过过滤器链来进行校验的,我们想要验证图形验证码,所以可以在认证流程之前,也就是UsernamePasswordAuthenticationFilter
之前进行校验。
那么自定义的验证码过滤器也需要实现j2EE
的过滤器接口,同时验证方法validate()
只做了内部方法抽象,后期可以做成可扩展的抽象接口,这个void
方法可能会抛出异常,这里的异常设计成了spring security
框架的AuthenticationException
高级抽象异常的子类,为了就是保证和安全认证的异常同步,后期使用同一个失败处理器抓取AuthenticationException
类型的异常即可:
1 | import org.springframework.security.core.AuthenticationException; |
而上述异常的接收者就是springboot + spring security 学习笔记(一)自定义基本使用及个性化登录配置里提到的自定义认证失败处理器。
1 | import static org.woodwhale.king.commons.MyConstants.SESSION_KEY; |
细节注意:这个过滤器继承了OncePerRequestFilter
,目的在于接受 spring 的管理,它能保证我们的过滤器在一次请求中只被调用一次。
验证码过滤器配置
验证码的过滤应该在用户认证过滤之前,所以需要配置在UsernamePasswordAuthenticationFilter
过滤器之前,自定义的ValidateCodeFilter
过滤器由于配置了@Component("validateCodeFilter")
,所以已经注入到了 spring 中,安全认证配置中直接@Autowired
引用即可。
注意:因为在验证码Controller 中设置了这个
/code/image
请求路径,因此要做不需验证配置,将其加入到.antMatchers()
中。
1 |
|
到此,整个图片验证码的安全认证流程设计就结束了,可以再回头看看笔者最开始画的时序图,感觉还是非常不专业规范的,这里辅助说明的草稿,如笔者有设计更好的时序图,欢迎交流。