为网站提供流畅的登录体验需要一种微妙的平衡。一方面,我们希望不同计算机水平的用户都能尽快完成登录。另一方面,我们需要确保访问我们系统的人的身份,否则可能会发生灾难性的安全事故。
在本教程中,我们将展示如何在基于 Spring Boot 的应用程序中 使用一次性令牌登录。此机制在易用性和安全性之间取得了良好的平衡,并且从 Spring Boot 版本 3.4 开始,在使用Spring Security 6.4或更高版本时即可开箱即用。
什么是一次性令牌登录?
在计算机应用程序中识别用户的传统方法是提供一个表单,让用户提供用户名和密码。现在,如果用户忘记了密码怎么办?常见的方法是提供“忘记密码”按钮。
当用户点击此按钮时,后端会向用户发送一条消息,其中包含一个限时令牌,允许用户重新定义其密码。
然而,对于一系列应用程序来说,用户不需要经常访问网站和/或费心保存密码。在这些情况下,用户往往会不断使用重置密码功能,这会导致用户感到沮丧,有时甚至会导致客户支持电话发怒。以下是属于此类的一些应用程序:
- 社区场所(俱乐部、学校、教堂、游戏场)
- 文件分发/签名服务
- 弹出式营销网站
- 用户告知其用户名,通常与其电子邮件地址相对应
- 系统生成一个有时间限制的令牌,并使用带外机制发送,可以是电子邮件、短信、移动通知或类似方式
- 用户在电子邮件/消息应用程序中打开消息并点击提供的链接,其中包含一次性令牌
- 用户的设备浏览器打开该链接,将其带回系统的 OTT 登录位置
- 系统检查链接中嵌入的令牌值。如果有效,则授予访问权限,用户可以继续。或者,显示令牌提交表单,提交后,完成登录过程
何时应使用OTT?
在考虑给定应用程序的一次性登录机制之前,最好先了解一下它的优缺点:
优点
- 无需管理用户密码,这也消除了安全风险
- 即使不懂技术的用户也可以轻松使用和理解
- 我们现在可能会想:为什么不使用社交登录?从技术角度来看,社交登录通常基于 OAuth2/OIDC,比 OTT 更安全。
缺点
- 基于单因素的身份验证,至少从应用程序的端点开始
- 易受中间人攻击
然而,启用它需要更多的操作工作(例如,为每个提供商请求和维护客户端 ID),并且考虑到人们对共享个人数据的意识的提高,可能会导致参与度下降。
使用 Spring Boot 和 Spring Security 实现 OTT
让我们创建一个简单的 Spring Boot 应用程序,该应用程序使用自 3.4 版以来提供的 OTT 支持。与往常一样,我们首先添加所需的 Maven 依赖项:
<dependency> |
OTT配置
在当前版本中,为应用程序启用 OTT 需要我们提供SecurityFilterChain bean:
@Bean |
这里的关键点是使用6.4 版中作为 DSL 配置的一部分引入的新oneTimeTokenLogin()方法。与往常一样,此方法允许我们自定义机制的所有方面。但是,在我们的例子中,我们仅使用Customizer.withDefaults()来接受默认值。
另外,请注意,我们在配置中添加了formLogin() 。如果没有它,Spring Security 将默认使用基本身份验证,这无法与 OTT 很好地兼容。
最后,在authorizeHttpRequests()部分,我们刚刚添加了一个要求对所有请求进行身份验证的配置。
发送令牌token
OTT 机制没有内置方法来实现向用户实际交付令牌。如文档中所述,这是一个经过深思熟虑的设计决定,因为实现此功能的方法实在太多了。
相反,OTT 将此责任委托给应用程序代码,该代码必须公开实现OneTimeTokenGenerationSuccessHandler接口的bean。或者,我们可以直接通过配置 DSL 传递此接口的实现。
此接口只有一个方法handle(),该方法接受当前 servlet 请求、响应以及最重要的OneTimeToken对象。后者具有以下属性:
- tokenValue:我们需要发送给用户的生成的令牌
- username:已获知的用户名
- expiresAt:生成的令牌过期的时间
- 使用提供的用户名作为键来查找所需的配送详情。例如,这些详情可能包括电子邮件地址或电话号码以及用户的区域设置
- 构建一个 URL,将用户引导至 OTT 登录页面
- 准备一条带有 OTT 链接的消息并发送给用户
- 向客户端发送重定向响应,将浏览器发送到 OTT 登录页面
对于步骤 4,我们将重定向详细信息委托给 Spring Security 的RedirectOneTimeTokenGenerationSuccessHandler。 这是最终的实现:
public class OttLoginLinkSuccessHandler implements OneTimeTokenGenerationSuccessHandler { |
请注意传递给RedirectOneTimeTokenGenerationSuccessHandler 的“/login/ott” 构造函数参数。 这对应于令牌提交表单的默认位置,可以使用 OTT DSL 将其配置为其他位置。
至于OttSenderService,我们将使用一个虚假的发送方实现,将令牌存储在由用户名索引的 Map 中并记录其值:
public class FakeOttSenderService implements OttSenderService { |
请注意,OttSenderService有一个可选方法,允许我们恢复用户名的令牌。此方法的主要目的是简化单元测试的实现。