Tôi đang làm việc trên ứng dụng Symfony2 có API có sẵn cho các ứng dụng khác. Tôi muốn bảo mật quyền truy cập vào API. Đối với phần này tôi không có vấn đề gì.Sử dụng nhà cung cấp xác thực tùy chỉnh trong Symfony2
Nhưng tôi phải làm cho kết nối này khả dụng không phải với cặp đăng nhập/mật khẩu thông thường mà chỉ với một khóa API.
Vì vậy, tôi đã đi đến trang web chính thức và sách dạy nấu ăn tuyệt vời của nó cho creating a custom authentication provider, chỉ là những gì tôi cần tôi tự nói với bản thân mình.
Ví dụ không phải là những gì tôi cần nhưng tôi quyết định điều chỉnh nó theo nhu cầu của mình.
Thật không may là tôi đã không thành công.
Tôi sẽ cung cấp cho bạn mã của tôi và sau này tôi sẽ giải thích vấn đề của mình.
Đây là Nhà máy của tôi để tạo các nhà cung cấp chứng thực và người nghe:
<?php
namespace Pmsipilot\UserBundle\DependencyInjection\Security\Factory;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\DefinitionDecorator;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface;
class ApiFactory implements SecurityFactoryInterface
{
/**
* @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
* @param string $id
* @param aray $config
* @param string $userProvider
* @param string $defaultEntryPoint
* @return array
*/
public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint)
{
$providerId = 'security.authentification.provider.api.'.$id;
$container
->setDefinition($providerId, new DefinitionDecorator('api.security.authentification.provider'))
->replaceArgument(0, new Reference($userProvider))
;
$listenerId = 'security.authentification.listener.api.'.$id;
$listener = $container->setDefinition($listenerId, new DefinitionDecorator('api.security.authentification.listener'));
return array($providerId, $listenerId, $defaultEntryPoint);
}
/**
* @return string
*/
public function getPosition()
{
return 'http';
}
/**
* @return string
*/
public function getKey()
{
return 'api';
}
/**
* @param \Symfony\Component\Config\Definition\Builder\NodeDefinition $node
* @return void
*/
public function addConfiguration(NodeDefinition $node)
{
}
}
Tiếp đang nghe tôi:
<?php
namespace Pmsipilot\UserBundle\Security\Firewall;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\Security\Http\Firewall\ListenerInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Pmsipilot\UserBundle\Security\WsseUserToken;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
class ApiListener implements ListenerInterface
{
protected $securityContext;
protected $authenticationManager;
/**
* Constructor for listener. The parameters are defined in services.xml.
*
* @param \Symfony\Component\Security\Core\SecurityContextInterface $securityContext
* @param \Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface $authenticationManager
*/
public function __construct(SecurityContextInterface $securityContext, AuthenticationManagerInterface $authenticationManager)
{
$this->securityContext = $securityContext;
$this->authenticationManager = $authenticationManager;
}
/**
* Handles login request.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
* @return void
*/
public function handle(GetResponseEvent $event)
{
$request = $event->getRequest();
$securityToken = $this->securityContext->getToken();
if($securityToken instanceof AuthenticationToken)
{
try
{
$this->securityContext->setToken($this->authenticationManager->authenticate($securityToken));
}
catch(\Exception $exception)
{
$this->securityContext->setToken(null);
}
}
}
}
đang cung cấp dịch vụ chứng thực của tôi:
<?php
namespace Pmsipilot\UserBundle\Security\Authentication\Provider;
use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface;
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\UserCheckerInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\Exception\AuthenticationServiceException;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
class ApiProvider implements AuthenticationProviderInterface
{
private $userProvider;
/**
* Constructor.
*
* @param \Symfony\Component\Security\Core\User\UserProviderInterface $userProvider An UserProviderInterface instance
*/
public function __construct(UserProviderInterface $userProvider)
{
$this->userProvider = $userProvider;
}
/**
* @param string $username
* @param \Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken $token
* @return mixed
* @throws \Symfony\Component\Security\Core\Exception\AuthenticationServiceException|\Symfony\Component\Security\Core\Exception\UsernameNotFoundException
*/
protected function retrieveUser($username, UsernamePasswordToken $token)
{
$user = $token->getUser();
if($user instanceof UserInterface)
{
return $user;
}
try
{
$user = $this->userProvider->loadUserByApiKey($username, $token->getCredentials());
if(!$user instanceof UserInterface)
{
throw new AuthenticationServiceException('The user provider must return a UserInterface object.');
}
return $user;
}
catch (\Exception $exception)
{
throw new AuthenticationServiceException($exception->getMessage(), $token, 0, $exception);
}
}
/**
* @param TokenInterface $token
* @return null|\Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken
* @throws \Symfony\Component\Security\Core\Exception\AuthenticationServiceException|\Symfony\Component\Security\Core\Exception\BadCredentialsException|\Symfony\Component\Security\Core\Exception\UsernameNotFoundException
*/
function authenticate(TokenInterface $token)
{
$username = $token->getUsername();
if(empty($username))
{
throw new AuthenticationServiceException('No username given.');
}
try
{
$user = $this->retrieveUser($username, $token);
if(!$user instanceof UserInterface)
{
throw new AuthenticationServiceException('retrieveUser() must return a UserInterface.');
}
$authenticatedToken = new UsernamePasswordToken($user, null, 'api', $user->getRoles());
$authenticatedToken->setAttributes($token->getAttributes());
return $authenticatedToken;
}
catch(\Exception $exception)
{
throw $exception;
}
}
/**
* @param TokenInterface $token
* @return bool
*/
public function supports(TokenInterface $token)
{
return true;
}
}
Để sử dụng hai đối tượng này tôi đã sử dụng tệp yml để định cấu hình chúng:
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="pmsipilot.api.security.authentication.factory" class="Pmsipilot\UserBundle\DependencyInjection\Security\Factory\ApiFactory" public="false">
<tag name="security.listener.factory" />
</service>
</services>
</container>
Bây giờ mã nhà cung cấp chứng thực:
<?php
namespace Pmsipilot\UserBundle\Security\Authentication\Provider;
use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface;
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\UserCheckerInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\Exception\AuthenticationServiceException;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
class ApiProvider implements AuthenticationProviderInterface
{
private $userProvider;
/**
* Constructor.
*
* @param \Symfony\Component\Security\Core\User\UserProviderInterface $userProvider An UserProviderInterface instance
*/
public function __construct(UserProviderInterface $userProvider)
{
$this->userProvider = $userProvider;
}
/**
* @param string $username
* @param \Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken $token
* @return mixed
* @throws \Symfony\Component\Security\Core\Exception\AuthenticationServiceException|\Symfony\Component\Security\Core\Exception\UsernameNotFoundException
*/
protected function retrieveUser($username, UsernamePasswordToken $token)
{
$user = $token->getUser();
if($user instanceof UserInterface)
{
return $user;
}
try
{
$user = $this->userProvider->loadUserByApiKey($username, $token->getCredentials());
if(!$user instanceof UserInterface)
{
throw new AuthenticationServiceException('The user provider must return a UserInterface object.');
}
return $user;
}
catch (\Exception $exception)
{
throw new AuthenticationServiceException($exception->getMessage(), $token, 0, $exception);
}
}
/**
* @param TokenInterface $token
* @return null|\Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken
* @throws \Symfony\Component\Security\Core\Exception\AuthenticationServiceException|\Symfony\Component\Security\Core\Exception\BadCredentialsException|\Symfony\Component\Security\Core\Exception\UsernameNotFoundException
*/
function authenticate(TokenInterface $token)
{
$username = $token->getUsername();
if(empty($username))
{
throw new AuthenticationServiceException('No username given.');
}
try
{
$user = $this->retrieveUser($username, $token);
if(!$user instanceof UserInterface)
{
throw new AuthenticationServiceException('retrieveUser() must return a UserInterface.');
}
$authenticatedToken = new UsernamePasswordToken($user, null, 'api', $user->getRoles());
$authenticatedToken->setAttributes($token->getAttributes());
return $authenticatedToken;
}
catch(\Exception $exception)
{
throw $exception;
}
}
/**
* @param TokenInterface $token
* @return bool
*/
public function supports(TokenInterface $token)
{
return true;
}
}
Just FYI cung cấp người dùng của tôi:
<?php
namespace Pmsipilot\UserBundle\Security\Provider;
use Propel\PropelBundle\Security\User\ModelUserProvider;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use \Symfony\Component\Security\Core\Encoder\MessageDigestPasswordEncoder;
class ApiProvider extends ModelUserProvider
{
/**
* Constructeur
*/
public function __construct()
{
parent::__construct('Pmsipilot\UserBundle\Model\User', 'Pmsipilot\UserBundle\Proxy\User', 'username');
}
/**
* @param string $apikey
* @return mixed
* @throws \Symfony\Component\Security\Core\Exception\UsernameNotFoundException
*/
public function loadUserByApiKey($apikey)
{
$queryClass = $this->queryClass;
$query = $queryClass::create();
$user = $query
->filterByApiKey($apikey)
->findOne()
;
if(null === $user)
{
throw new UsernameNotFoundException(sprintf('User with "%s" api key not found.', $apikey));
}
$proxyClass = $this->proxyClass;
return new $proxyClass($user);
}
}
Và đối với phần cấu hình security.yml tôi:
security:
factories:
PmsipilotFactory: "%kernel.root_dir%/../src/Pmsipilot/UserBundle/Resources/config/security_factories.xml"
providers:
interface_provider:
id: pmsipilot.security.user.provider
api_provider:
id: api.security.user.provider
encoders:
Pmsipilot\UserBundle\Proxy\User: sha512
firewalls:
assets:
pattern: ^/(_(profiler|wdt)|css|images|js|favicon.ico)/
security: false
api:
provider: api_provider
access_denied_url: /unauthorizedApi
pattern: ^/api
api: true
http_basic: true
stateless: true
interface:
provider: interface_provider
access_denied_url: /unauthorized
pattern: ^/
anonymous: ~
form_login:
login_path: /login
check_path: /login_check
use_forward: true
default_target_path:/
logout: ~
access_control:
- { path: ^/api, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/, roles: SUPER_ADMIN }
Wow nó rất nhiều mã, tôi hy vọng nó không quá nhàm chán.
Vấn đề của tôi ở đây là cung cấp dịch vụ chứng thực tùy chỉnh của tôi được gọi bằng hai bức tường lửa api và giao diện thay vì chỉ bởi api một. Và tất nhiên họ không cư xử như tôi muốn.
Tôi không tìm thấy bất kỳ điều gì về vấn đề như vậy. Tôi biết tôi đã phạm sai lầm, nếu không nó sẽ hoạt động, nhưng ở đâu và tại sao tôi không biết.
Tôi cũng tìm thấy this tutorial nhưng không giúp được gì nhiều.
Tất nhiên, đừng ngại đề xuất tôi nếu có giải pháp khác để sử dụng nhà cung cấp xác thực khác với nhà cung cấp mặc định.
Tại sao bạn cần các nhà cung cấp api và giao diện? Bạn ApiProvider không có bất kỳ mã xác thực nào (ví dụ: so sánh khóa được người dùng thông qua) –
Vì phần api của ứng dụng của tôi, tôi chỉ muốn người dùng xác thực bằng khóa api, bất kể mật khẩu của mình. Nếu khóa api khớp với người dùng, người đó sẽ tự động được xác thực. Nhà cung cấp xác thực mặc định kiểm tra mật khẩu với tên người dùng và tôi muốn kiểm tra này được thực hiện. – Maxime
Tôi tìm thấy một cách khác để làm những gì tôi muốn, nhưng nó không hoạt động. Tôi đã tạo một dịch vụ với "security.authentication.provider.dao.api" làm id và bên trong nhà cung cấp xác thực này, tôi đặt cùng mã như trên. Nhưng tôi gặp vấn đề tương tự, nó vẫn được sử dụng ngay cả với tường lửa giao diện. Tôi chỉ muốn quá tải Symfony \ Component \ Security \ Core \ Authentication \ Provider \ DaoAuthenticationProvider để tránh việc xác minh mật khẩu, nó không nên quá phức tạp, phải không? – Maxime