Symfony 身份验证与 guard always return "Username could not be found."

Symfony authentication with guard always return "Username could not be found."

我知道有很多关于这个主题的话题,但是 none 其中对我有帮助...

我正在开发一个应用程序,您必须在 headers 中为每个请求发送一个访问令牌。 我使用 Guard 管理此安全性。

对于我的测试,当我发送虚假令牌时,或者当我不发送它时,必须根据需要调用 start() 或 onAuthenticationFailure() 方法。 但它不起作用。我每次都有同样的错误。 看起来这些方法从未被调用过。

未发送授权

GET /BileMo/web/app_dev.php/api/products/2 HTTP/1.1
Host: localhost:8888
Content-Type: application/json

{
     "message": "Username could not be found."
}

无效的访问令牌

GET /BileMo/web/app_dev.php/api/products/2 HTTP/1.1
Host: localhost:8888
Content-Type: application/json
Authorization: *Fake Facebook Token*

{
     "message": "Username could not be found."
}

而不是:

{
       "message": "Authorization required"
}

{
       "message": "The facebook access token is wrong!"
}

有了正确的访问令牌,请求就会正确返回给用户。

请求示例:

GET /BileMo/web/app_dev.php/api/products/2 HTTP/1.1
Host: localhost:8888
Content-Type: application/json
Authorization: *Facebook Token*

以下是我的代码的重要部分:

security.yml

security:
    encoders:
        FOS\UserBundle\Model\UserInterface: sha512

    role_hierarchy:
        ROLE_ADMIN:       ROLE_USER
        ROLE_SUPER_ADMIN: ROLE_USER

    providers:
        fos_userbundle:
            id: fos_user.user_provider.username_email

        api_key_user_provider:
            entity:
                class: FacebookTokenBundle:User
                property: facebook_access_token

    firewalls:
        api:
            pattern: ^/api
            stateless: true
            anonymous: false
            guard:
                authenticators:
                    - AppBundle\Security\FacebookAuthenticator

        dev:
            pattern:  ^/(_(profiler|wdt)|css|images|js)/
            security: false

        main:
            pattern: ^/
            form_login:
                provider: fos_userbundle
                csrf_token_generator: security.csrf.token_manager
                login_path: /login
                check_path: /login_check

            oauth:
                resource_owners:
                    facebook:           "/login/check-facebook"
                login_path:        /login
                failure_path:      /login

                oauth_user_provider:
                    #this is my custom user provider, created from FOSUBUserProvider - will manage the
                    #automatic user registration on your site, with data from the provider (facebook. google, etc.)
                    service: my_user_provider
            logout:       true
            anonymous:    true


        login:
            pattern:  ^/login$
            security: false

            remember_me:
                secret: "%secret%"
                lifetime: 31536000 # 365 days in seconds
                path: /
                domain: ~ # Defaults to the current domain from $_SERVER

    access_control:
        - { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/admin/, role: ROLE_ADMIN }
        - { path: ^/api, role: ROLE_USER }

FacebookAuthenticator.php

namespace AppBundle\Security;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Guard\AbstractGuardAuthenticator;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use HWI\Bundle\OAuthBundle\OAuth\Response\UserResponseInterface;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Security\Core\User\UserProviderInterface;

class FacebookAuthenticator extends AbstractGuardAuthenticator
{
    public function __construct(EntityManager $em)
    {
        $this->em = $em;
    }

    /**
     * Called when authentication is needed, but it's not sent
     */
    public function start(Request $request, AuthenticationException $authException = null)
    {
        $data = array(
            // you might translate this message
            'message' => 'Authentication Required'
        );

        return new JsonResponse($data, Response::HTTP_UNAUTHORIZED);
    }
    /**
     * Called on every request. Return whatever credentials you want to
     * be passed to getUser(). Returning null will cause this authenticator
     * to be skipped.
     */
    public function getCredentials(Request $request)
    {
        if (!$token = $request->headers->get('Authorization')) {
            // No token?
            $token = null;
        }

        // What you return here will be passed to getUser() as $credentials
        return array(
            'token' => $token,
        );
    }

    public function getUser($credentials, UserProviderInterface $userProvider)
    {
        $user = $this->em->getRepository('FacebookTokenBundle:User')
            ->findOneBy(array('facebook_access_token' => $credentials));
        return $user;

    }

    public function checkCredentials($credentials, UserInterface $user)
    {
        if ($user->getFacebookAccessToken() === $credentials['token']) {
            return true;
        }
            return new JsonResponse(array('message' => 'The facebook access token is wrong!', Response::HTTP_FORBIDDEN));
    }

    public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
    {
        // on success, let the request continue
        return null;
    }

    public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
    {
        $data = array(
            'message' => strtr($exception->getMessageKey(), $exception->getMessageData())

            // or to translate this message
            // $this->translator->trans($exception->getMessageKey(), $exception->getMessageData())
        );

        return new JsonResponse($data, Response::HTTP_FORBIDDEN);
    }

    public function supportsRememberMe()
    {
        return false;
    }
}

此行为是预期的。 AbstractGuardAuthenticator 的接口过于通用,如果需要,您需要根据自己的需要对其进行定制。

例如,如果出现错误 "Authorization required" - 您可能会在 getCredentials() 方法中抛出 AuthenticationException。异常将在 symfony 核心中捕获,方法 start() 将最终被调用。

public function getCredentials(Request $request)
{
    if (!$request->headers->has('Authorization')) {
        throw new AuthenticationException();
    }
    ...
}

方法 onAuthenticationFailure() 通常用于在凭据错误的情况下将用户重定向到登录页面。如果 API 键入 header,则不需要此功能。同样在当前的实现中,当 "API key not correct" 和 "user is not found" 时如何分开?

以上答案有些正确,并进行了一些更正:

验证器(守卫)本身中抛出的任何异常都将触发onAuthenticationFailure()

public function onAuthenticationFailure(Request $request, AuthenticationException $exception): JsonResponse
{
    return new JsonResponse(['message' => 'Forbidden'], Response::HTTP_FORBIDDEN);
}

public function start(Request $request, AuthenticationException $authException = null): JsonResponse
{
    return new JsonResponse(['message' => 'Authentication Required'], Response::HTTP_UNAUTHORIZED);
}

方法 start() 被调用,例如,当您像在控制器中一样从您的应用程序中抛出一个 AccessDeniedException 时。也许一个很好的用例是,比如说,您想将某个特定用户列入一条特定路线的黑名单,并且您不想用不必要的膨胀来填充您的守卫身份验证器。

/** 
 * @Route("/something-special")
 */
public function somethingSpecial()
{
    if ($this->getUser()->getUsername() === 'a_banned_user') {
        throw new AccessDeniedException();
    }

    // ...
}

然后测试它:

$ curl -H "Auth-Header-Name: the_auth_token" http://site.local/something-special
{"message":"Authentication Required"}

但是,另一方面,如果由于缺少标记 header 而抛出异常,那么 onAuthenticationFailure() 将 运行 改为:

public function getCredentials(Request $request): array
{
    if (!$request->headers->has('Authorization')) {
        throw new AuthenticationException('Authentication header missing.');
    }

    // ...
}

然后使用 (注意:AuthenticationException 消息在 onAuthenticationFailure() 中被忽略,只有 returns 通用 "Forbidden" 消息,如上所示):

$ curl http://site.local/something-special
{"message":"Forbidden"}