传统的用户名和密码认证方式存在被破解或盗用的风险。未来的身份认证趋势将更加强调多因素身份认证,结合多个不同的身份验证要素,如生物识别技术(指纹、面部识别等)、硬件安全令牌等,提供更强的安全性。
多因素身份认证(MFA)使用两个或多个因素的任意组合来验证身份并保护重要资产免受欺诈性访问。目前广泛采用的短信验证码,就是多因素身份认证的一个具体形式:双因素身份验证(2FA)。它将密码与发送到移动设备的 SMS 代码结合起来。如果有一个因素受到影响,系统仍然是安全的。
可以使用三种因素来确认身份:

  • 你所拥有的东西—比如银行卡,钥匙或 U 盘。
  • 你所知道的东西—比如密码或 PIN。
  • 你本身就是生物识别因素—比如指纹,语音,虹膜扫描和其他物理特征。

image.png
上图展示了 authing.cn 的多因素认证界面,其中短信验证码、电子邮箱验证以及 OTP 口令验证就是利用了“你所拥有的东西”,而“人脸识别验证”和“指纹验证”就是将你本身做为了生物识别因素。“密码验证”则是利用了“你所知道的东西”,在当前版本的 authing.cn 的个人中心,“密码”在“账号安全”标签页里。
MFA 一般都会从“你本身是识别因素”、“你所知道的东西”和“你所拥有的东西”中选择两个或多个因素,更多地是选择两个因素的组合。

你所拥有的东西

手机(做为令牌)

手机作为令牌的优点是,它们是大多数人都拥有的设备,因此不需要额外的硬件或软件。缺点是,手机可以丢失或被盗,而且不是所有人都有手机。另外,实现起来也比较复杂,因为需要开发原生应用,并且要有推送消息的能力。

通过短信发送的一次性密码


服务器端生成一个一次性密码(OTP),并将其发送到用户注册的手机。用户在登录时输入 OTP,服务器端验证 OTP 是否正确。这种方法的优点是,它不需要任何额外的硬件或软件,因为它使用用户已经拥有的手机。缺点是,它需要用户在登录时输入 OTP,这可能会导致用户体验不佳。另外,短信验证码的接收可能存在延迟或者被拦截,可能需要准备好客户支持或者技术支持进行排查。

案例:在 Keycloak 里集成短信验证码登录


在 Keycloak 里,可以通过集成第三方的短信服务商,来实现短信验证码登录。这里以阿里云为例,来演示如何在 Keycloak 里集成阿里云的短信服务。
尽管国内的互联网生态里使用短信验证码登录十分常见,采用 Keycloak 做为身份认证解决方案的公司也很多,然而 Keycloak 官方却没有现成的短信验证码登录解决方案,这里通过提供一种建议的解决方案,希望对大家有所帮助。 下图展示了最终的效果。
image.png

如何开通了阿里云短信服务不在本文讨论范围,可以参考官网。这里假设已经开通了阿里云短信服务,并且已经有了一个可用的短信模板。接下来我们详细讨论技术实现。

(1)引用开源的 Keycloak 短信验证码登录插件

推荐使用 https://github.com/cooperlyt/keycloak-phone-provider ,从其 release 页面下载最新的 release 包,然后解压到 Keycloak 的 /opt/keycloak/providers/ 目录下,就完成了引用。推荐使用 Dockerfile 来实现这一步,这样可以在容器启动时自动完成这一步,一个示例的 Dockerfile 如下: dockerfile ARG KEYCLOAK_VERSION=21.0.1 FROM docker.io/library/keycloak-builder as mvn_builder FROM quay.io/keycloak/keycloak:${KEYCLOAK_VERSION} as builder COPY --from=mvn_builder /tmp/target/.jar /opt/keycloak/providers/ COPY --from=mvn_builder /tmp/target/.jar /opt/keycloak/deployments/

COPY idps/phone/keycloak-phone-provider.jar /opt/keycloak/providers/ COPY idps/phone/keycloak-phone-provider.resources.jar /opt/keycloak/providers/ COPY idps/phone/keycloak-sms-provider-aliyun.jar /opt/keycloak/providers/

ENV KC_HOSTNAME_STRICT=false ENV KC_HOSTNAME_STRICT_HTTPS=false ENV KC_HTTP_ENABLED=true

USER 1000

RUN /opt/keycloak/bin/kc.sh build --health-enabled=true

FROM quay.io/keycloak/keycloak:$KEYCLOAK_VERSION COPY --from=builder /opt/keycloak/ /opt/keycloak/ WORKDIR /opt/keycloak

ENTRYPOINT [/opt/keycloak/bin/kc.sh, start-dev, --spi-phone-default-service=aliyun, --spi-message-sender-service-aliyun-region=cn-hangzhou, --spi-message-sender-service-aliyun-key=LTAZVd, --spi-message-sender-service-aliyun-secret=e6uIwd, --spi-message-sender-service-aliyun-auth-template=SMS_13000053, --hostname-strict=false, --features=preview,scripts, --log-level=INFO]


注意,一共需要复制 3 个 jar 包到 /opt/keycloak/providers/ 目录下。最后,在启动 Keycloak 时需要指定一些参数,这些参数可以通过环境变量来传递,也可以通过命令行参数来传递,这里使用了命令行参数的方式。这里需要注意的是,--spi-phone-default-service=aliyun 这个参数,它指定了默认的短信服务商,这里指定为阿里云;另外,一定要用 --spi-message-sender-service-aliyun-auth-template 来指定短信模板,模板内容必须包含一个名为 code 的参数。下图展示了一个可供参考的短信模板。
image.png
有了上述的 Dockerfile 之后,可以再配置一个 Docker Compose 文件,来方便地启动 Keycloak,一个示例的 Docker Compose 文件如下: yaml version: 3

services: keycloak: build: context: . dockerfile: Dockerfile environment: KEYCLOAK_ADMIN: admin KEYCLOAK_ADMIN_PASSWORD: admin PORT: 8080 DB_VENDOR: h2 ports: - 8080:8080 - 9990:9990 volumes: - ~/.m2/repository:/root/.m2/repository

通过 docker compose up --build 命令,就可以启动 Keycloak 了。

(2)配置登录流程

在 Keycloak 启动后,通过账号 admin、密码 admin 登录后台,创建一个安全领域 Realm,给它取一个名字。如下图所示。
image.png
然后,在验证(Authentication)的 流程(Flow)下面,将 Browser 复制并命名为 Browser with phone 的流程。过程如下面两张截图所示。
image.png
image.png

接着,点击刚才复制的 Browser with phone 流程,然后使用 Phone Username Password Form 替换 Username Password Form。在 Phone Username Password Form 的旁边有一个设置按钮,点击它进行配置。
image.png
然后回到验证(Authentication)的流程列表页,点击 Browser with phone 的 Bind flow,将其绑定至 Browser 流程。过程如下图所示。
image.png
最后,回到 Realm 的设置页,在主题(Theme)选项卡里,将 Login 的主题设置为 phone 即可,如下图所示:
image.png
至此,Keycloak 的短信验证码登录配置就完成了。在登录页,可以看到短信验证码登录的入口,如本示例开头的最终效果所示。
image.png

(3)导出配置


在上面我们看到,整个配置有点繁琐,特别是关于验证流程的配置步骤很多。可以通过将配置好的安全领域导出来,这样,在其他环境的 Keycloak 实例上,再次导入配置,做一些相应的修改(比如安全领域的名称等等)即可,而不用再重复验证流程的配置。导出配置的步骤如下图所示。
image.png

(4)导入配置


在上述配置导出后,在新的 Keycloak 实例上,可以选择上一步保存好的 json 文件进行导入操作,如下图所示。
image.png
该 json 文件的关键相关内容如下: json { ..., loginTheme: phone, authenticationFlows: { [ ..., { id: 070902f7-e4dc-43ee-8156-abc9d7a021e6, alias: Browser with phone, description: browser based authentication, providerId: basic-flow, topLevel: true, builtIn: false, authenticationExecutions: [ { authenticator: auth-cookie, authenticatorFlow: false, requirement: ALTERNATIVE, priority: 10, autheticatorFlow: false, userSetupAllowed: false }, { authenticator: auth-spnego, authenticatorFlow: false, requirement: DISABLED, priority: 20, autheticatorFlow: false, userSetupAllowed: false }, { authenticator: identity-provider-redirector, authenticatorFlow: false, requirement: ALTERNATIVE, priority: 25, autheticatorFlow: false, userSetupAllowed: false }, { authenticatorFlow: true, requirement: ALTERNATIVE, priority: 30, autheticatorFlow: true, flowAlias: Browser with phone forms, userSetupAllowed: false } ] }, { id: 0ea1be2f-285e-453b-80f5-f365e8c31a0e, alias: Browser with phone Browser - Conditional OTP, description: Flow to determine if the OTP is required for the authentication, providerId: basic-flow, topLevel: false, builtIn: false, authenticationExecutions: [ { authenticator: conditional-user-configured, authenticatorFlow: false, requirement: REQUIRED, priority: 10, autheticatorFlow: false, userSetupAllowed: false }, { authenticator: auth-otp-form, authenticatorFlow: false, requirement: REQUIRED, priority: 20, autheticatorFlow: false, userSetupAllowed: false } ] }, { id: 7bb0c556-7ca1-438c-895c-ff387b14b3c2, alias: Browser with phone forms, description: Username, password, otp and other auth forms., providerId: basic-flow, topLevel: false, builtIn: false, authenticationExecutions: [ { authenticatorConfig: phone, authenticator: auth-phone-username-password-form, authenticatorFlow: false, requirement: REQUIRED, priority: 20, autheticatorFlow: false, userSetupAllowed: false }, { authenticatorFlow: true, requirement: CONDITIONAL, priority: 21, autheticatorFlow: true, flowAlias: Browser with phone Browser - Conditional OTP, userSetupAllowed: false } ] }, ... ] }, ... }

通过电子邮件发送一次性密码


服务器端生成一个一次性密码(OTP),并将其发送到用户注册的电子邮箱。用户在登录时输入 OTP,服务器端验证 OTP 是否正确。这种方法的优点是,它不需要任何额外的硬件或软件,因为它使用用户已经拥有的电子邮箱。但是需要更多的客户支持或者技术支持,因为用户可能会遇到电子邮件延迟或者电子邮件被拦截的问题。

通过原生应用生成一次性密码


这需要用户在自己的设备上安装一个支持 OAuth 一次性密码生成算法的原生应用程序,比如 Google Authenticator。在登录时,用户侧和服务器端会使用共享密钥生成同样的一次性密码。这个共享密钥是在注册时添加到生成器中的——通常是通过扫描二维码来实现的。 是 authing.cn 的 OTP 口令验证界面。
image.png

通过硬件生成一次性密码

需要非常高安全的交易系统比如银行可能会要求通过硬件令牌来生成一次性密码。

智能卡

智能卡是一种带有内置集成电路芯片的塑料卡,可以用于存储和处理数据。智能卡可以用于存储用户的身份信息,比如数字证书、私钥等。

硬件Fob

硬件 Fob 是一种小型硬件设备,可以生成一次性密码。它们通常是通过 USB、蓝牙或者NFC 连接到计算机或者手机的。硬件 Fob 通常是由用户自己购买的,但也可以由公司提供。

你所知道的东西

“你所知道的东西”有非常多的选项,通常都是用来取代用户的密码的——特别是在手机上,因为输入密码可能会很麻烦。如果一个系统已经采用了用户名密码做为身份认证的基本方式,那么仅仅将“你所知道的东西”做为身份认证的第二因素,经常会被质疑这根本就不是第二因素,因为它和第一因素(用户名密码)是同一类型的。但是,如果你将“你所知道的东西”和“你所拥有的东西”结合起来,那么它就是一个真正的第二因素了。

用户名和密码

用户名和密码是最常见的身份验证方式。它们的优点是,它们是最简单的身份验证方式,因为它们是用户已经知道的东西。缺点是,它们容易被破解,因为用户通常会选择简单的密码,或者在多个网站上使用相同的密码。

PIN

PIN 是一种简单的密码,通常由 4 或者 6 位数字组成。PIN 通常用于 ATM 机和信用卡。PIN 的优点是,它们比密码更容易记忆,因为它们通常只有 4 或者 6 位数字。缺点是,它们容易被破解,因为用户通常会选择简单的 PIN,或者在多个网站上使用相同的 PIN。

单一采用 PIN 作为身份认证因素的场景比较少见,因为这样的话,安全性非常低。如果是 4 位 PIN 码,那么一共只有 10000 种可能的组合,即攻击者有万分之一的机会第一次就猜中了 PIN 码。如果是 6 位 PIN 码,那么一共只有 1000000 种可能的组合,即攻击者有百万分之一的机会第一次就猜中了 PIN 码。由于暴力破解比较容易,所以需要有严格的错误尝试锁定策略,比如连续输入错误 PIN 码 3 次就锁定账号,需要管理员解锁。或者需要再等待一定时间之后才能开始再试,以此来让暴力破解的成本变得更高。

安全问题

安全问题通常由用户在注册时选择。安全问题的优点是,它们是用户已经知道的东西。缺点是,它们容易被破解,因为用户通常会选择简单的安全问题,或者在多个网站上使用相同的安全问题。

你本身就是生物识别因素

这种验证方法经常在科幻电影里看到——比如通过扫描虹膜来打开宇宙飞船的门、或者通过指纹识别来发身核弹。但是,这种验证方法在现实生活中也是存在的,比如指纹识别、面部识别等。

指纹

要么应用光学,要么应用电阻的方法来测量摩擦纹理等细节。

声纹

声纹识别是一种生物识别技术,它使用声音特征来识别个人。声纹识别可以用于识别个人的身份,比如微信登录时就可以选择朗读4位数字来登录。

面部识别

面部识别是一种生物识别技术,它使用面部特征来识别个人。面部识别可以用于识别个人的身份,比如很多原生应用都支持对接 Face ID 来实现人脸识别登录。

虹膜扫描

虹膜在眼睛的前方,管理着进入眼睛的光线。虹膜扫描是一种生物识别技术,它使用虹膜特征来识别个人,但不如视网膜扫描精确。

视网膜扫描

视网膜在眼睛的后半部分,每个人的视网膜都是独一无二的。使用视网膜特征来识别个人需要特别的技术。

小结

本文探讨了多因素身份认证,分别介绍了三种因素,即“你所知道的东西”、“你所拥有的东西”和“你本身就是生物识别因素”。并且还拿 Keycloak 做为身份认证平台举例,详细描述了如何实现常见的短信验证码登录。
多因素身份认证是未来身份认证的趋势,结合多个不同的身份验证要素,如生物识别技术(指纹、面部识别等)、硬件安全令牌等,提供更强的安全性。