【问题标题】:Combining fos_oauth authenticator with single key authenticator结合 fos_oauth 认证器和单密钥认证器
【发布时间】:2015-03-06 11:03:01
【问题描述】:

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

但我找不到原因。

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

感谢您的帮助。

【问题讨论】:

    标签: symfony authentication oauth-2.0 api-key


    【解决方案1】:

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

    毫不犹豫地提出改进 cmets 的建议! ;)

    首先,从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 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;
        }
    }
    

    瞧! Private 和 OAuth 令牌都得到了正确管理。

    【讨论】:

    • 感谢分享您的解决方案。您能否也分享一下服务security.authentication.authenticator.api_key 的完整声明以及与用户提供者相关的声明。谢谢美人
    • 还有一个问题:ApiKeyManager是怎么注入的??我在服务容器中搜索,我没有找到它!你自己创造的吗?
    猜你喜欢
    • 1970-01-01
    • 2012-02-05
    • 2015-03-02
    • 2015-01-16
    • 2018-02-14
    • 2010-11-19
    • 2021-03-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多