我想实现以下目标:
在我的安装中有两个捆绑包,ApiBundle和BackendBundle。用户在BackendBundle中定义,但我可以稍后将它们放在UserBundle中。
ApiBundle基本上为控制器提供api方法,例如getSomething()
。
BackendBundle具有用户实体,服务和一些视图,如登录表单和后端视图。从后端控制器我想访问某些api方法。
将从外部请求其他api方法。将通过curl请求Api方法。
我希望为这两个目的拥有不同的用户。 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?例如。如果有人请求{ 'error': 'No access' }
,我想返回/api/getSomething
而不是登录表单的html,当然如果有人请求/backend/someroute
我想要显示登录表单。
非常感谢每一位帮助! :)
<小时/> symfony文档说:
防火墙的主要工作是配置用户的身份验证方式。他们会使用登录表吗? HTTP基本认证? API令牌?以上所有?
我认为我的问题基本上是,如何同时进行登录表单和api令牌身份验证。
所以也许,我需要这样的东西:http://symfony.com/doc/current/security/guard_authentication.html#frequently-asked-questions
答案 0 :(得分:2)
问题1 :当您只想通过apiKey对用户进行身份验证时,那么最好的解决方案就是实现自己的用户提供商。解决方案在Symfony doc:http://symfony.com/doc/current/security/api_key_authentication.html
中有详细描述编辑 - 您可以拥有任意数量的用户提供商,如果一个用户提供商失败,那么另一个用户提供商就会发挥作用 - 此处描述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);
}
}
}