我有一个Symfony 2.7.6项目,其中包含自定义简单表单身份验证提供程序,并支持记住我功能以及非个性化功能。一切都按预期工作。
但是,我想引入另一个身份验证提供程序,该提供程序将允许使用两个HTTP标头进行身份验证的会话状态(例如API-Client-Id
和API-Client-Token
)与第三方应用程序无关的请求。
我创建了一个简单的Pre-Auth身份验证提供程序,用于验证这些头字段,并在成功时创建带有空用户实例的身份验证令牌。
但是,看起来Symfony正在尝试使用会话记住这些API身份验证,因此我在第二个请求中收到以下错误:“您无法从EntityUserProvider刷新不包含标识符的用户用户对象必须使用Doctrine映射的自己的标识符进行序列化。“。
我可以在防火墙配置中设置stateless: true
标志以禁用会话支持,但它会为两个身份验证提供程序禁用它。
那么,如何使用我的Simple Form身份验证器保留现有功能,然后创建另一层身份验证以用于单个无状态API请求?
我不确定我的方法在概念上是否正确。我很乐意接受任何建议,并会在第一次请求时提供任何相关信息。
这是我的security.yml
配置:
security:
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
pattern: ^/
anonymous: ~
form_login:
login_path: app.login
check_path: app.session.sign_in
username_parameter: username
password_parameter: password
success_handler: app.security.login_handler
failure_handler: app.security.login_handler
require_previous_session: false
logout:
path: app.session.sign_out
invalidate_session: false
success_handler: app.security.logout_success_handler
# Simple form auth provider
simple_form:
authenticator: app.security.authenticator.out_service
# Token provider
simple_preauth:
authenticator: app.security.authenticator.api_client
remember_me:
name: "%app.session.remember_me.name%"
key: "%secret%"
lifetime: 1209600 # 14 days
path: /
domain: ~
always_remember_me: true
switch_user: { role: ROLE_ADMIN }
access_control:
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/recover-password, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: /, roles: IS_AUTHENTICATED_REMEMBERED }
providers:
main:
entity:
class: App\AppBundle\Model\User
property: id
encoders:
App\AppBundle\Model\User: plaintext
role_hierarchy:
ROLE_ADMIN: [ROLE_USER, ROLE_ACTIVE]
ROLE_API_CLIENT: ~
ROLE_USER: ~
ROLE_ACTIVE: ~
ApiClientAuthenticator.php :
<?php
namespace App\AppBundle\Security;
use Symfony\Component\Security\Core\Authentication\SimplePreAuthenticatorInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use App\AppBundle\Model\User;
class ApiClientAuthenticator implements SimplePreAuthenticatorInterface
{
/** @var LoggerInterface */
protected $logger;
/** @var array */
protected $clients;
/**
* @param array $clients
*/
public function __construct(array $clients)
{
$this->clients = $clients;
}
public function createToken(Request $request, $providerKey)
{
$clientId = $request->headers->get('Api-Client-Id');
$clientSecret = $request->headers->get('Api-Client-Secret');
if (!$clientId || !$clientSecret) {
return null;
}
return new PreAuthenticatedToken(
'anon.',
[$clientId, $clientSecret],
$providerKey
);
}
public function authenticateToken(TokenInterface $token, UserProviderInterface $userProvider, $providerKey)
{
list ($clientId, $clientSecret) = $token->getCredentials();
$foundClient = null;
foreach ($this->clients as $client) {
if ($client['id'] == $clientId) {
if ($client['secret'] == $clientSecret) {
$foundClient = $client;
break;
}
}
}
if (!$foundClient) {
throw new AuthenticationException;
}
$user = new User;
$user->setApiClient(true);
return new PreAuthenticatedToken(
$user,
$foundClient,
$providerKey,
['ROLE_API_CLIENT']
);
}
public function supportsToken(TokenInterface $token, $providerKey)
{
return ($token instanceof PreAuthenticatedToken && $token->getProviderKey() === $providerKey);
}
}