将fos_oauth身份验证器与单密钥身份验证器相结合

时间:2015-03-06 11:03:01

标签: symfony authentication oauth-2.0 api-key

我为我的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 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);
    }
}

但我找不到原因。

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

感谢您的帮助。

1 个答案:

答案 0 :(得分:0)

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

不要犹豫,建议改进评论! ;)

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

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 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令牌都得到了正确管理。