您不能设置令牌的角色属性。如您所见,TokenInterface 不包含角色的设置器。
你不能setAuthenticated(false),因为那样你会清除第一个身份验证因素,然后归零。
但我相信你以错误的方式解决问题。
保留令牌原样,并使用onAuthenticationSuccess 的侦听器检查User::organization::getMfa() 以决定是否需要:
- 向用户发送二次验证码,
- 将您所在的位置存储在身份验证未完成的位置(例如,在会话中、在用户属性的数据库中、为此单独存储等)。
(就我个人而言,我会将 MFA 的要求设为另一个角色,以便可以将其存储在令牌上,您无需为此检查其他内容)。
然后,为每个请求创建一个event subscriber。
在该侦听器上,您将注入use Symfony\Component\Security\Core\Security 以访问用户并查看User::organization::getMfa() === true;以及检查身份验证是否未完成的任何方法(例如,如果您存储在会话中,或者直接检查用户实体等)。
如果你没有令牌,你return,这是一个未经身份验证的视图,不是监听器的问题。
如果用户不需要MFA,你只需return;在这里无事可做。
如果用户需要 MFA,但未处于身份验证过程中,您可以使用 return。这意味着用户已经完全通过身份验证,他们可以继续。
如果用户需要 MFA 并且正处于身份验证过程的中间,您将显示完成它的任何方法。通常是一个表格。
如果用户需要 MFA 并且正在进行身份验证过程并且他们正在向您发送检查此身份验证的方法(例如,带有适当字段的 POST 请求),您验证身份验证是否良好。
验证好不好?您清除“正在进行的身份验证”位,然后返回。不会拦截更多请求。
验证不好吗?您要么让他们再次检查,重新发送验证码,将用户完全注销,以便他们重新开始......究竟什么在很大程度上取决于您的要求。
完整的代码实现取决于您,因为它不可能涵盖所有可能的场景。
但您的订阅者可能是这样开始的(请注意,还有几个未实现的方法)。
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\Security;
class MfaSubscriber implements EventSubscriberInterface
{
private $security;
public function __construct(Security $security)
{
$this->security = $security;
}
public function onRequest(RequestEvent $event)
{
if ($event->getRequestType() === HttpKernelInterface::SUB_REQUEST) {
return;
}
$request = $event->getRequest();
if ( null == $token = $this->security->getToken()) {
return;
}
if ( ! in_array('ROLE_NEEDS_MFA', $token->getRoleNames(), true)) {
return;
}
if ( ! $request->hasPreviousSession() || ! $request->getSession()->get('AUTHENTICATION_IN_PROCESS')) {
return;
}
if (
$request->getMethod() === Request::METHOD_POST
&& $request->get('verification_code')
&& $this->verifyCode($request->get('verification_code')) // verify code needs to be implemented
) {
$request->getSession()->set('AUTHENTICATION_IN_PROCESS', null);
return;
}
$event->setResponse($this->displayMfaVerificationMethod($request, $token));
}
public static function getSubscribedEvents(): array
{
return [
KernelEvents::REQUEST => 'onRequest',
];
}
}