3

我知道关于这个主题有很多线程,但没有一个对我有帮助......

我正在开发一个应用程序,您必须在每个请求的标头中发送访问令牌。我用 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*

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

安全.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;
    }
}
4

2 回答 2

1

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

例如,要出现“需要授权”错误 - 您可以在 getCredentials() 方法中抛出 AuthenticationException。异常将在 symfony 核心中被捕获,并且方法 start() 将最终被调用。

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

方法 onAuthenticationFailure() 通常用于在凭据错误的情况下将用户重定向到登录页面。如果标头中有 API 密钥,则不需要此功能。同样在当前实现中,当“API 密钥不正确”和“未找到用户”时如何分开?

于 2017-11-14T16:46:59.897 回答
0

上面的答案有些正确,但有一些更正:

身份验证器(守卫)本身抛出的任何异常都将触发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"}

但是,另一方面,如果由于缺少令牌标头而引发异常,onAuthenticationFailure()则将改为运行:

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

    // ...
}

然后用(注意:在 onAuthenticationFailure() 中忽略 AuthenticationException 消息,只返回一个通用的“禁止”消息,如上所示):

$ curl http://site.local/something-special
{"message":"Forbidden"}
于 2019-11-11T19:43:42.173 回答