2017-02-10 8 views
1

私は次のことを達成したいと考えています:Symfony 3.2:異なるプロパティでユーザを認証する

私のインストールでは、2つのバンドルApiBundleとBackendBundleがあります。ユーザはBackendBundleで定義されていますが、あとでUserBundleに入れることができます。

ApiBundleは、基本的に、コントローラに、たとえばgetSomething()のようなAPIメソッドを提供します。

BackendBundleには、ユーザーエンティティ、サービス、ログインフォームやバックエンドビューなどのビューがあります。バックエンドコントローラからは、特定のAPIメソッドにアクセスしたいと思うでしょう。

その他のAPIメソッドは、外部からリクエストされます。 Apiメソッドは、カールを介して要求されます。

私は両方の目的で異なるユーザーが必要です。 UserクラスはUserInterfaceを実装し、$username,$passwordおよび$apiKeyのようなプロパティを持っています。

基本的に、ユーザー名とパスワードを使用したログインフォームによる認証方法と、apiKeyのみが必要な外部からのcurlによるapi呼び出しの別の認証方法を提供します。

どちらの場合も、認証されたユーザーは異なるリソースにアクセスできる必要があります。

は私の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:はどのように私は同じエンティティからのユーザーは、異なる認証し、特定のressourcesにアクセスできることを達成することができますか?望ましい動作は、ユーザー名/パスワードまたはapikeyのみによる認証です。

質問2:ログインフォームのビューを返すのではなく、リクエスタが正しく認証されていないと、apiメソッドがjsonを返す方法を教えてください。例えば。誰かが/api/getSomethingを要求し、誰かが/backend/somerouteを要求した場合はログインフォームを表示したい場合は、ログインフォームのhtmlの代わりに{ 'error': 'No access' }のようなものを返したいと思います。

非常に助かります。 :)


symfonyのドキュメントは言う:ファイアウォールの

主な仕事は、ユーザーが認証方法を設定することです。彼らはログインフォームを使用しますか? HTTP基本認証? APIトークン?上記のすべて?

私の質問は基本的に、ログインフォームとapiトークン認証を同時に持つことができると思います。 http://symfony.com/doc/current/security/guard_authentication.html#frequently-asked-questions

+0

私はapikey認証の例で詳細な情報を更新しました。コードはテストされていないので、微調整が必​​要な場合があります。 –

答えて

1

質問1:あなただけAPIKEYでユーザーを認証する場合は、可能な限り最良の解決策は、独自のユーザー・プロバイダを実装するだろう

だから多分、私はこのようなものが必要。解決策はよくsymfonyのドキュメントにdecribedさ:http://symfony.com/doc/current/security/api_key_authentication.html

EDIT - あなたが必要な数のユーザープロバイダを持つことができ、1つが故障した場合、その後、別のプレイするなり - ここで説明https://symfony.com/doc/current/security/multiple_user_providers.html

ダウン以下でありますApiKeyAuthenticatorのコードで、トークンを取得し、ApiKeyUserProviderを呼び出して、そのユーザを検索/取得します。ユーザーが見つかった場合、Symfonyセキュリティに提供されます。 ApiKeyUserProviderはUserRepositoryをユーザ操作に必要とします。あなたが持っていると確信しています。

コードはテストされていないため、微調整が必​​要な場合があります。

だから、仕事に取得することができます:

のsrc/BackendBundle /セキュリティ/ 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 /セキュリティ/ 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); 
     } 
    } 
} 
+0

ありがとう!私は自分の質問を更新し、入力ミスを修正して追加しました。希望する動作は、ユーザー名/パスワードまたは認証のみです。 – meandeveloper111

+0

だから、これを試しましたか、それとももっと援助が必要でしたか? –

関連する問題