好的,在深入研究了捆绑包的代码之后,我想通了。这个问题是由 Symfony 的 HTTP Basic Authentication impementation 处理错误凭据的方式造成的。 401 Bad Credentials 响应是由BasicAuthenticationEntryPoint 创建的自定义响应,由BasicAuthenticationListener 的handle 函数调用,在同一函数中抛出AuthenticationException 后立即调用。所以没有办法用监听器捕捉到这个异常:
public function handle(GetResponseEvent $event)
{
$request = $event->getRequest();
if (false === $username = $request->headers->get('PHP_AUTH_USER', false)) {
return;
}
if (null !== $token = $this->securityContext->getToken()) {
if ($token instanceof UsernamePasswordToken && $token->isAuthenticated() && $token->getUsername() === $username) {
return;
}
}
if (null !== $this->logger) {
$this->logger->info(sprintf('Basic Authentication Authorization header found for user "%s"', $username));
}
try {
$token = $this->authenticationManager->authenticate(new UsernamePasswordToken($username, $request->headers->get('PHP_AUTH_PW'), $this->providerKey));
$this->securityContext->setToken($token);
} catch (AuthenticationException $failed) {
$this->securityContext->setToken(null);
if (null !== $this->logger) {
$this->logger->info(sprintf('Authentication request failed for user "%s": %s', $username, $failed->getMessage()));
}
if ($this->ignoreFailure) {
return;
}
$event->setResponse($this->authenticationEntryPoint->start($request, $failed));
}
}
入口点的start 函数创建自定义响应,不涉及任何异常:
public function start(Request $request, AuthenticationException $authException = null)
{
$response = new Response();
$response->headers->set('WWW-Authenticate', sprintf('Basic realm="%s"', $this->realmName));
$response->setStatusCode(401, $authException ? $authException->getMessage() : null);
return $response;
}
上面handle 函数中的第一个if-clause 也解释了为什么它在“根本没有用户凭据”的情况下工作,因为在这种情况下,侦听器只是停止尝试对用户进行身份验证,并且因此,Symfony 的防火墙侦听器会抛出异常(不太确定确切的位置),因此 FOSRestBundle 的 AccessDeniedListener 能够捕获 AuthenticationException 并执行其操作。