API 中的双因素身份验证
本指南描述了如何在不生成前端但提供 API 端点的 Symfony 应用程序中设置双因素身份验证。
先决条件
为了使双因素身份验证在 API 中工作,您正在进行身份验证的防火墙**必须是有状态的**(防火墙配置中 stateless: false
或根本未配置,因为它默认是有状态的)。这意味着 API 正在启动一个会话,该会话由客户端在每次调用时传递。会话对于双因素身份验证存储登录状态是必要的 - 用户是否已完成双因素身份验证。
如果您使用自定义身份验证器(您可能已遵循 Symfony 的指南 使用 Guard 的自定义身份验证系统(API 令牌示例)),请确保您的身份验证器不会在每个请求上进行身份验证,而仅在调用身份验证路由时进行身份验证。有关示例,请查看 Symfony 指南中的 避免在每个请求上验证浏览器身份 部分。
设置
注意
为简单起见,本指南假设您正在构建 JSON API,并且您正在使用 Symfony 自带的 json_login
身份验证机制。对于任何其他身份验证机制,它都应该以相同或至少类似的方式工作,只要它允许您配置自定义成功处理程序即可。
您需要实现 4 个类
- 身份验证机制的自定义成功处理程序
- 双因素身份验证的自定义“需要双因素身份验证”处理程序
- 双因素身份验证的自定义成功处理程序
- 双因素身份验证的自定义失败处理程序
配置
请确保您的防火墙上设置了以下配置选项
1 2 3 4 5 6 7 8
# config/packages/security.yaml
security:
firewalls:
your_firewall_name:
# ...
two_factor:
prepare_on_login: true
prepare_on_access_denied: true
1) 登录时的响应
用户登录后返回第一个响应。如果没有双因素身份验证,它将返回“登录成功”或“登录失败”响应。使用双因素身份验证,您最终需要返回第三种类型的响应,以告知客户端身份验证尚未完成,并且需要双因素身份验证。然后客户端应显示双因素身份验证表单。
如果您为用户提供多种身份验证机制来识别自己,则必须为每种机制执行此操作。
要实现这样的响应,您需要创建一个自定义成功处理程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
<?php
namespace App\Security;
use Scheb\TwoFactorBundle\Security\Authentication\Token\TwoFactorTokenInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
class AuthenticationSuccessHandler implements AuthenticationSuccessHandlerInterface
{
public function onAuthenticationSuccess(Request $request, TokenInterface $token): Response
{
if ($token instanceof TwoFactorTokenInterface) {
// Return the response to tell the client two-factor authentication is required.
return new Response('{"login": "success", "two_factor_complete": false}');
}
// Otherwise return the default response for successful login. You could do this by decorating
// the original authentication success handler and calling it here.
}
}
将其注册为服务并将其配置为身份验证方法的自定义 success_handler
1 2 3 4 5 6
# config/packages/security.yaml
security:
firewalls:
your_firewall_name:
json_login: # The authentication mechanism you're using
success_handler: your_api_success_handler
2) 需要双因素身份验证的响应
您需要一个响应,当用户请求路径时返回该响应,但该路径(尚未)不可访问,因为用户必须先完成双因素身份验证。这可能与您的“拒绝访问”响应相同。
创建一个实现 Scheb
的类以返回响应。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
<?php
namespace App\Security;
use Scheb\TwoFactorBundle\Security\Http\Authentication\AuthenticationRequiredHandlerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
class TwoFactorAuthenticationRequiredHandler implements AuthenticationRequiredHandlerInterface
{
public function onAuthenticationRequired(Request $request, TokenInterface $token): Response
{
// Return the response to tell the client that authentication hasn't completed yet and
// two-factor authentication is required.
return new Response('{"error": "access_denied", "two_factor_complete": false}');
}
}
将其注册为服务并将其配置为 two_factor
身份验证方法的 required_handler
1 2 3 4 5 6
# config/packages/security.yaml
security:
firewalls:
your_firewall_name:
two_factor:
authentication_required_handler: your_api_2fa_required_handler
3) 双因素身份验证成功时的响应
您需要一个响应,当双因素身份验证成功完成并且用户现在已完全通过身份验证时返回该响应。为其实现另一个成功处理程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
<?php
namespace App\Security;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
class TwoFactorAuthenticationSuccessHandler implements AuthenticationSuccessHandlerInterface
{
public function onAuthenticationSuccess(Request $request, TokenInterface $token): Response
{
// Return the response to tell the client that authentication including two-factor
// authentication is complete now.
return new Response('{"login": "success", "two_factor_complete": true}');
}
}
将其注册为服务并将其配置为 two_factor
身份验证方法的 success_handler
1 2 3 4 5 6
# config/packages/security.yaml
security:
firewalls:
your_firewall_name:
two_factor:
success_handler: your_api_2fa_success_handler
4) 双因素身份验证失败时的响应
您需要一个响应,当尝试进行双因素身份验证但由于某种原因身份验证失败时返回该响应。为其实现一个失败处理程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
<?php
namespace App\Security;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
class TwoFactorAuthenticationFailureHandler implements AuthenticationFailureHandlerInterface
{
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): Response
{
// Return the response to tell the client that 2fa failed. You may want to add more details
// from the $exception.
return new Response('{"error": "2fa_failed", "two_factor_complete": false}');
}
}
将其注册为服务并将其配置为 two_factor
身份验证方法的 failure_handler
1 2 3 4 5 6
# config/packages/security.yaml
security:
firewalls:
your_firewall_name:
two_factor:
failure_handler: your_api_2fa_failure_handler
发送 2fa 代码
POST 数据
在 API 用例中,您通常会将双因素身份验证代码发送到您在防火墙设置中配置的“2fa 检查”路径。代码的发送方式与您从 2fa 表单发送的方式相同 - 带有有效负载中 post 数据的 POST
请求。
默认的 POST 参数名称是 _auth_code
,尽管可以在防火墙配置中自定义它
1 2 3 4 5 6 7
# config/packages/security.yaml
security:
firewalls:
your_firewall_name:
# ...
two_factor:
auth_code_parameter_name: _auth_code # Name of the parameter for the two-factor authentication code
JSON 数据
为了更好地与 JSON 风格的 API 集成,该捆绑包还接受带有 JSON 有效负载的 POST
请求。确保您发送带有 JSON 内容类型的 JSON 编码有效负载,例如 application/json
。
例如,如果您想使用以下类型的有效负载
1
{"data": {"authCode": "1234"}}
您必须告诉捆绑包,身份验证代码位于 data
中的 authCode
属性中。因此,在防火墙配置中,您必须设置以下内容
1 2 3 4 5 6 7
# config/packages/security.yaml
security:
firewalls:
your_firewall_name:
# ...
two_factor:
auth_code_parameter_name: data.authCode
如您所见,可以使用 symfony/property-access 表示法来定义参数名称,从而允许您从复杂的数据结构中读取数据。
请注意,由于您正在处理 JSON **对象**,因此您必须使用点 .
表示法来访问对象属性。对于上面的示例,data.authCode
是正确的属性路径。数组样式表示法 data[authCode]
将不起作用。