将 fos_oauth 验证器与单密钥验证器组合

Combining fos_oauth authenticator with single key authenticator

我为我的api设置了两种身份验证方法:

两者都各有千秋。

我尝试将这些系统与此安全配置相结合:

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

    role_hierarchy:
        ROLE_ALLOWED_TO_SWITCH: ~
        ROLE_SUPPORT:           ~
        ROLE_ADMIN:             [ROLE_SONATA_ADMIN]
        ROLE_SUPER_ADMIN:       [ROLE_ADMIN, ROLE_SUPPORT, ROLE_ALLOWED_TO_SWITCH]

    providers:
        fos_userbundle:
            id: fos_user.user_provider.username_email
        api_key_user:
            id: security.user.provider.api_key

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

        oauth_token:
            pattern:    ^/oauth/v2/token
            security:   false

        api:
            pattern: ^/api
            stateless: true
            simple_preauth:
                authenticator: security.authentication.authenticator.api_key
            fos_oauth: true

        main:
            pattern: ^/
            form_login:
                provider: fos_userbundle
                csrf_provider: form.csrf_provider
                login_path: /login
                check_path: /login_check

            anonymous: ~
            logout:
                path: /logout
            switch_user: true

如果我尝试使用此 curl 命令获取访问令牌:

curl "http://localhost:8000/oauth/v2/token?client_id=1_2rqa1al0trwgso8g8co4swsks48cwsckgc8cokswkcgos4csog&client_secret=25a78plm6c2ss044k4skckkwoo8kw4kcoccg8sg0skook4sgwg&grant_type=password&username=test&password=test

我得到了一个 access_token,但当我尝试使用它时:

curl -X GET http://localhost:8000/api/changelogs.json -H "Authorization: Bearer MmI2OWNkNjhjMGYwOTUyNDA2OTdlMDBjNjA1YmI3MjVhNTBiMTNhMjI0MGE1YmM3NzgwNjVmZWZmYWNhM2E4YQ" | json_pp

我得到:

{
   "error" : "invalid_grant",
   "error_description" : "The provided access token is invalid."
}

通过停用我的 single_preauth api 密钥验证器,它可以工作,我可以访问我的 API.

我的 api 密钥验证器似乎阻止了所有其他系统。

在这里,我的 ApiKeyAuthenticator class:

class ApiKeyAuthenticator implements SimplePreAuthenticatorInterface, AuthenticationFailureHandlerInterface
{
    private $userProvider;

    /**
     * @param ApiKeyUserProvider $userProvider
     */
    public function __construct(ApiKeyUserProvider $userProvider)
    {
        $this->userProvider = $userProvider;
    }

    /**
     * {@inheritdoc}
     */
    public function createToken(Request $request, $providerKey)
    {
        $apiKey = str_replace('Bearer ', '', $request->headers->get('Authorization', ''));

        if (!$apiKey) {
            throw new BadCredentialsException('No API key given.');
        }

        return new PreAuthenticatedToken('anon.', $apiKey, $providerKey);
    }

    /**
     * {@inheritdoc}
     */
    public function authenticateToken(TokenInterface $token, UserProviderInterface $userProvider, $providerKey)
    {
        $apiKey = $token->getCredentials();
        $username = $this->userProvider->getUsernameForApiKey($apiKey);

        if (!$username) {
            throw new AuthenticationException('The provided access token is invalid.');
        }

        $user = $this->userProvider->loadUserByUsername($username);

        return new PreAuthenticatedToken(
            $user,
            $apiKey,
            $providerKey,
            $user->getRoles()
        );
    }

    /**
     * {@inheritdoc}
     */
    public function supportsToken(TokenInterface $token, $providerKey)
    {
        return $token instanceof PreAuthenticatedToken && $token->getProviderKey() === $providerKey;
    }

    /**
     * {@inheritdoc}
     */
    public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
    {
        return new JsonResponse([
            'error'             => 'invalid_grant',
            'error_description' => $exception->getMessage()
        ], 401);
    }
}

但是我找不到原因。

如何结合这两种验证方法?

感谢您的帮助。

终于找到了如何处理它,但不确定这是更好和正确的方法。

不要犹豫,在评论中提出改进建议! ;)

首先,从 security.yml 文件中删除 fos_oauth 密钥。它应该看起来像:

security:
    firewalls:
        # [...]
        api:
            pattern: ^/api
            stateless: true
            # This will handle both oauth access token and simple private token
            simple_preauth:
                authenticator: security.authentication.authenticator.api_key
        # [...]

ApiKeyUserProvider::getUsernameForApiKey 方法中,您将搜索自定义 api 密钥管理器和 OAuth 访问令牌管理器。

完整的 class 应该是这样的。

class ApiKeyUserProvider implements UserProviderInterface
{
    /**
     * @var UserManagerInterface
     */
    private $userManager;

    /**
     * @var ApiKeyManager
     */
    private $apiKeyManager;

    /**
     * @var AccessTokenManagerInterface
     */
    private $accessTokenManager;

    /**
     * @param UserManagerInterface        $userManager
     * @param ApiKeyManager               $apiKeyManager
     * @param AccessTokenManagerInterface $accessTokenManager
     */
    public function __construct(UserManagerInterface $userManager, ApiKeyManager $apiKeyManager, AccessTokenManagerInterface $accessTokenManager)
    {
        $this->userManager = $userManager;
        $this->apiKeyManager = $apiKeyManager;
        $this->accessTokenManager = $accessTokenManager;
    }

    /**
     * @param string $apiKey
     *
     * @return string|null
     */
    public function getUsernameForApiKey($apiKey)
    {
        // FOSOAuth system
        $token = $this->accessTokenManager->findTokenByToken($apiKey);
        if ($token) {
            return $token->getUser()->getUsername();
        }

        // Private key system
        return $this->apiKeyManager->getUsernameForToken($apiKey);
    }

    /**
     * {@inheritdoc}
     */
    public function loadUserByUsername($username)
    {
        return $this->userManager->findUserByUsername($username);
    }

    /**
     * {@inheritdoc}
     */
    public function refreshUser(UserInterface $user)
    {
        throw new UnsupportedUserException();
    }

    /**
     * {@inheritdoc}
     */
    public function supportsClass($class)
    {
        return 'FOS\UserBundle\Model\User' === $class;
    }
}

瞧!私人和 OAuth 令牌都得到正确管理。