我关注了Symfony doc。教程here并在我的应用程序中开发了API密钥认证机制。
我的security.yml
如下所示,我需要链提供商回退到http basic auth,以防用户找不到API密钥。
security:
encoders:
FOS\UserBundle\Model\UserInterface: sha512
Symfony\Component\Security\Core\User\User:
algorithm: bcrypt
encode_as_base64: false
iterations: 4
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: ROLE_ADMIN
providers:
api_key_user_provider:
id: AppBundle\Security\ApiKeyUserProvider
fos_userbundle:
id: fos_user.user_provider.username
chain_provider:
chain:
providers: [api_key_user_provider, fos_userbundle]
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
user:
pattern: ^/users/
anonymous: true
stateless: true
logout: true
api:
anonymous: false
stateless: true
simple_preauth:
authenticator: AppBundle\Security\ApiKeyAuthenticator
provider: chain_provider
当我尝试访问系统的安全区域(' api')时,会抛出上述错误。我对Symfony防火墙配置很安静,有人可以告诉我这方面的正确方向吗?
我的ApiKeyUserProvider
课程如下:
class ApiKeyUserProvider implements UserProviderInterface
{
/** @var EntityManager $entityManager */
protected $entityManager;
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
/**
* Get the username of a user for a given API key
*
* @param $apiKey
* @return string|null
*/
public function getUsernameForApiKey($apiKey)
{
$apiKeyRepository = $this->entityManager->getRepository('UserBundle:ApiKey');
$username = $apiKeyRepository->getUserNameForApiKey($apiKey);
return $username;
}
/**
* Loads the user for the given username.
*
* This method must throw UsernameNotFoundException if the user is not
* found.
*
* @param string $username The username
*
* @return UserInterface
*
* @throws UsernameNotFoundException if the user is not found
*/
public function loadUserByUsername($username)
{
$userRepository = $this->entityManager->getRepository('UserBundle:User');
$user = $userRepository->findOneByUsername($username);
if (!$user instanceof User) {
throw new UsernameNotFoundException();
}
return $user;
}
/**
* Refreshes the user.
*
* It is up to the implementation to decide if the user data should be
* totally reloaded (e.g. from the database), or if the UserInterface
* object can just be merged into some internal array of users / identity
* map.
*
* @return UserInterface
*
* @throws UnsupportedUserException if the user is not supported
*/
public function refreshUser(UserInterface $user)
{
// this is used for storing authentication in the session
// but in this case, the token is sent in each request,
// so authentication can be stateless. Throwing this exception
// is proper to make things stateless
throw new UnsupportedUserException();
}
/**
* Whether this provider supports the given user class.
*
* @param string $class
*
* @return bool
*/
public function supportsClass($class)
{
return User::class === $class;
}
}
我的ApiKeyAuthenticator
课程如下所示:
class ApiKeyAuthenticator implements SimplePreAuthenticatorInterface
{
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 supportsToken(TokenInterface $token, $providerKey)
{
return $token instanceof PreAuthenticatedToken && $token->getProviderKey() === $providerKey;
}
public function createToken(Request $request, $providerKey)
{
// look for an apikey query parameter
// $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) {
// throw new BadCredentialsException();
// or to just skip api key authentication
return null;
}
return new PreAuthenticatedToken(
'anon.',
$apiKey,
$providerKey
);
}
}