Warning: file_get_contents(/data/phpspider/zhask/data//catemap/7/symfony/6.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Authentication Symfony 3.2:通过不同属性对用户进行身份验证_Authentication_Symfony - Fatal编程技术网

Authentication Symfony 3.2:通过不同属性对用户进行身份验证

Authentication Symfony 3.2:通过不同属性对用户进行身份验证,authentication,symfony,Authentication,Symfony,我希望实现以下目标: 在我的安装中有两个包,ApiBundle和BackendBundle。用户是在BackendBundle中定义的,不过我可以稍后将它们放在UserBundle中 ApiBundle基本上为控制器提供了api方法,例如getSomething() BackendBundle包含用户实体、服务和一些视图,如登录表单和后端视图。我希望从后端控制器访问某些api方法 其他api方法将从外部请求。Api方法将通过curl请求 我希望两种用途都有不同的用户。User类实现了UserIn

我希望实现以下目标:

在我的安装中有两个包,ApiBundle和BackendBundle。用户是在BackendBundle中定义的,不过我可以稍后将它们放在UserBundle中

ApiBundle基本上为控制器提供了api方法,例如
getSomething()

BackendBundle包含用户实体、服务和一些视图,如登录表单和后端视图。我希望从后端控制器访问某些api方法

其他api方法将从外部请求。Api方法将通过curl请求

我希望两种用途都有不同的用户。
User
类实现了
UserInterface
,并具有
$username
$password
$apiKey
等属性

现在基本上我想通过用户名和密码的登录表单提供一种身份验证方法,以及通过外部的curl为api调用提供另一种身份验证方法,这只需要apiKey

在这两种情况下,经过身份验证的用户应该可以访问不同的资源

到目前为止,我的security.yml如下所示:

providers:
    chain_provider:
        chain:
            providers: [db_username, db_apikey]
    db_username:
        entity:
            class: BackendBundle:User
            property: username
    db_apikey:
        entity:
            class: BackendBundle:User
            property: apiKey

encoders:
    BackendBundle\Entity\User:
        algorithm: bcrypt
        cost: 12

firewalls:
    dev:
        pattern: ^/(_(profiler|wdt)|css|images|js)/
        security: false
    main:
        anonymous: ~
        form_login:
            login_path: login
            check_path: login
            default_target_path: backend
            csrf_token_generator: security.csrf.token_manager
        logout:
            path:   /logout
            target: /login
        provider: chain_provider

access_control:
    - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
    - { path: ^/api, roles: ROLE_API }
    - { path: ^/backend, roles: ROLE_BACKEND } 
问题1:我如何实现来自同一实体的用户可以进行不同的身份验证并访问某些资源?所需的行为是使用用户名/密码或仅使用apikey进行身份验证

问题2:如果请求者未正确验证,我如何实现api方法返回json,而不是返回登录表单的视图?例如,如果有人请求
/api/getSomething
,我想返回类似
{'error':'No access'}
的内容,而不是html作为登录表单,当然,如果有人请求
/backend/someroute
,我想显示登录表单

非常感谢您的帮助!:)
symfony文档说:

防火墙的主要工作是配置用户的身份验证方式。他们会使用登录表单吗?HTTP基本身份验证?API令牌?所有这些

我想我的问题基本上是,我如何能够同时拥有登录表单和api令牌身份验证


所以,也许我需要这样的东西:

问题1:当您只想通过apiKey对用户进行身份验证时,最好的解决方案是实现自己的用户提供者。解决方案在Symfony文档中有详细描述:

编辑-您可以拥有任意数量的用户提供程序,如果其中一个失败,则另一个将成为可供使用的用户提供程序-如下所述

下面是ApiKeyAuthenticator的代码,它获取令牌并调用ApiKeyUserProvider为其查找/获取用户。如果找到用户,则提供给Symfony security。ApiKeyUserProvider需要UserRepository来执行用户操作——我肯定您有一个,否则就编写它

代码没有经过测试,所以可能需要稍加调整

让我们开始工作吧:

src/BackendBundle/Security/ApiKeyAuthenticator.php

namespace BackendBundle\Security;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Http\Authentication\SimplePreAuthenticatorInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
use Symfony\Component\Security\Http\Authentication\SimplePreAuthenticatorInterface;
use Symfony\Component\HttpFoundation\JsonResponse;

class ApiKeyAuthenticator implements SimplePreAuthenticatorInterface, AuthenticationFailureHandlerInterface
{
    protected $httpUtils;

    public function __construct(HttpUtils $httpUtils)
    {
        $this->httpUtils = $httpUtils;
    }   

    public function createToken(Request $request, $providerKey)
    {
        //use this only if you want to limit apiKey authentication only for certain url
        //$targetUrl = '/login/check';
        //if (!$this->httpUtils->checkRequestPath($request, $targetUrl)) {
        //    return;
        //}

        // get an apikey from authentication request
        $apiKey = $request->query->get('apikey');
        // or if you want to use an "apikey" header, then do something like this:
        // $apiKey = $request->headers->get('apikey');

        if (!$apiKey) {
            //You can return null just skip the authentication, so Symfony
            // can fallback to another authentication method, if any.
            return null;
            //or you can return BadCredentialsException to fail the authentication
            //throw new BadCredentialsException();
        }

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

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

    public function authenticateToken(TokenInterface $token, UserProviderInterface $userProvider, $providerKey)
    {
        if (!$userProvider instanceof ApiKeyUserProvider) {
            throw new \InvalidArgumentException(
                sprintf(
                    'The user provider must be an instance of ApiKeyUserProvider (%s was given).',
                    get_class($userProvider)
                )
            );
        }

        $apiKey = $token->getCredentials();
        $username = $userProvider->getUsernameForApiKey($apiKey);

        if (!$username) {
            // CAUTION: this message will be returned to the client
            // (so don't put any un-trusted messages / error strings here)
            throw new CustomUserMessageAuthenticationException(
                sprintf('API Key "%s" does not exist.', $apiKey)
            );
        }

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

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

    public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
    {
        // this contains information about *why* authentication failed
        // use it, or return your own message
        return new JsonResponse(//$exception, 401);
    }
}
src/BackendBundle/Security/ApiKeyUserProvider.php

namespace BackendBundle\Security;

use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\UserInterface;

use BackendBundle\Entity\User;
use BackendBundle\Entity\UserORMRepository;

class ApiKeyUserProvider implements UserProviderInterface
{
    private $userRepository;

    public function __construct(UserORMRepository $userRepository)
    {
        $this->userRepository = $userRepository;
    }

    public function getUsernameForApiKey($apiKey)
    {
        //use repository method for getting user from DB by API key
        $user = $this->userRepository->...
        if (!$user) {
            throw new UsernameNotFoundException('User with provided apikey does not exist.');
        }

        return $username;
    }

    public function loadUserByUsername($username)
    {
        //use repository method for getting user from DB by username
        $user = $this->userRepository->...
        if (!$user) {
            throw new UsernameNotFoundException(sprintf('User "%s" does not exist.', $username));
        }
        return $user;
    }

    public function refreshUser(UserInterface $user)
    {
        if (!$user instanceof User) {
            throw new UnsupportedUserException(sprintf('Expected an instance of ..., but got "%s".', get_class($user)));
        }
        if (!$this->supportsClass(get_class($user))) {
            throw new UnsupportedUserException(sprintf('Expected an instance of %s, but got "%s".', $this->userRepository->getClassName(), get_class($user)));
        }
        //use repository method for getting user from DB by ID
        if (null === $reloadedUser = $this->userRepository->findUserById($user->getId())) {
            throw new UsernameNotFoundException(sprintf('User with ID "%s" could not be reloaded.', $user->getId()));
        }
        return $reloadedUser;
    }

    public function supportsClass($class)
    {
        $userClass = $this->userRepository->getClassName();
        return ($userClass === $class || is_subclass_of($class, $userClass));
    }
} 
服务定义:

services:
    api_key_user_provider:
        class: BackendBundle\Security\ApiKeyUserProvider
    apikey_authenticator:
        class: BackendBundle\Security\ApiKeyAuthenticator
        arguments: ["@security.http_utils"]
        public:    false
最后是安全提供程序配置:

providers:
    chain_provider:
        chain:
            providers: [api_key_user_provider, db_username]
    api_key_user_provider:
        id: api_key_user_provider
    db_username:
        entity:
            class: BackendBundle:User
            property: username
我鼓励你更多地学习Symfony文档,对于认证过程、用户实体、用户提供者等都有很好的解释

问题2:通过定义自己的拒绝访问处理程序,可以为拒绝访问事件实现不同的响应类型:

namespace BackendBundle\Security;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Http\Authorization\AccessDeniedHandlerInterface;

class AccessDeniedHandler implements AccessDeniedHandlerInterface
{
    public function handle(Request $request, AccessDeniedException $accessDeniedException)
    {
        $route = $request->get('_route');
        if ($route == 'api')) {
            return new JsonResponse($content, 403);
        } elseif ($route == 'backend')) {
            return new Response($content, 403);
        } else {
            return new Response(null, 403);
        }
    }
}

谢谢我已经更新了我的问题,修正了输入错误,并添加了:所需的行为是使用用户名/密码或仅使用apikey进行身份验证。那么,您是否尝试过此操作,或者是否需要任何进一步的帮助?我进行了更新,以使用apikey身份验证示例回答更多信息。代码未经测试,因此可能需要稍作调整。