我正在尝试创建一个自定义安全包。
我可以设法让一些工作(漫长而艰难:-))。
除了当我在某处出现身份验证错误时,我想重新路由到“注销”页面而不是登录页面。
我认为如果抛出异常,以这种方式更改 security.yml 会发送到失败路径
security:
firewalls:
logout:
pattern: ^/logout$
security: false
secured_area:
pattern: ^/
my_user_factory: true
form_login:
login_path: /login
check_path: /login_check
failure_path: /logout
由于我的基本代码一直在工作(没有真正的控制),我添加了一个异常抛出来测试身份验证错误。
class AuthProvider implements AuthenticationProviderInterface
{
//...
public function authenticate(TokenInterface $token)
{
throw new BadCredentialsException('Bad credentials :)');
}
//...
但我回到了登录页面。
这是跟踪:
// I arrive on the site
UserFactory.getPosition
UserFactory.getKey
UserFactory.getKey
UserFactory.create
CnamtsSecurityExtension.load
Configuration.getConfigTreeBuilder
AcmeSecurityExtension.load
Configuration.getConfigTreeBuilder
AuthProvider.__construct
AuthListener.__construct
AuthProvider.__construct
// Let's login
SecurityController.loginAction
AuthProvider.__construct
AuthListener.__construct
AuthListener.attemptAuthentication
UserToken.__construct
AuthProvider.supports
AuthProvider.authenticate
// The exception is thrown
UserToken.serialize
UserToken.unserialize
UserToken.serialize
AuthProvider.__construct
SecurityController.loginAction
// Back to the login page. I'd like to be on the logout one.
这是整个代码。如果删除“throw new BadCredentialsException('Bad credentials :)');” 行它应该工作。
捆绑树:
|~src/
| |~Acme/
| | `~SecurityBundle/
| | |~Controller/
| | | |-DefaultController.php
| | | `-SecurityController.php
| | |~DependencyInjection/
| | | |~Security/
| | | | `~Factory/
| | | | `-UserFactory.php
| | | |-AcmeSecurityExtension.php
| | | `-Configuration.php
| | |~Resources/
| | | |~config/
| | | | |-routing.yml
| | | | |-security_factories.yml
| | | | `-services.yml
| | | `~views/
| | | |~Default/
| | | | `-index.html.twig
| | | | `-logout.html.twig
| | | `~Login/
| | | `-login.html.twig
| | |~Security/
| | | |~Authentication/
| | | | |~Firewall/
| | | | | `-AuthListener.php
| | | | |~Provider/
| | | | | `-AuthProvider.php
| | | | `~Token/
| | | | `-UserToken.php
| | | `~User/
| | | |-User.php
| | | `-UserProvider.php
| | |+Tests/
| | `-AcmeSecurityBundle.php
工厂 :
<?php
// src/Acme/SecurityBundle/DependencyInjection/Security/Factory/UserFactory.php
namespace Acme\SecurityBundle\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 UserFactory implements SecurityFactoryInterface
{
public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint)
{
$providerId = 'security.authentication.provider.user.'.$id;
$container
->setDefinition($providerId, new DefinitionDecorator('user.security.authentication.provider'))
->replaceArgument(0, new Reference($userProvider))
;
$listenerId = 'security.authentication.listener.user.'.$id;
$listener = $container->setDefinition($listenerId, new DefinitionDecorator('user.security.authentication.listener'));
return array($providerId, $listenerId, $defaultEntryPoint);
}
public function getPosition()
{
return 'pre_auth';
}
public function getKey()
{
return 'my_user_factory';
}
public function addConfiguration(NodeDefinition $node)
{}
}
听众:
<?php
// Acme/SecurityBundle/Security/Firewall/AuthListener.php
namespace Acme\SecurityBundle\Security\Authentication\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 Symfony\Component\Security\Http\Firewall\AbstractAuthenticationListener;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface;
use Symfony\Component\Security\Http\HttpUtils;
use Acme\SecurityBundle\Security\Authentication\Token\UserToken;
class AuthListener extends AbstractAuthenticationListener
{
protected $securityContext;
protected $authenticationManager;
protected $httpUtils;
public function __construct(SecurityContextInterface $securityContext, AuthenticationManagerInterface $authenticationManager,
SessionAuthenticationStrategyInterface $sessionStrategy, HttpUtils $httpUtils, $options = array())
{
parent::__construct($securityContext, $authenticationManager, $sessionStrategy, $httpUtils, "user", array_merge(array(
'username_parameter' => '_username',
'password_parameter' => '_password',
'intention' => 'authenticate',
'post_only' => true,
), $options));
}
/**
* Performs authentication.
*
* @param Request $request A Request instance
*
* @return TokenInterface The authenticated token, or null if full authentication is not possible
*
* @throws AuthenticationException if the authentication fails
*/
protected function attemptAuthentication(Request $request)
{
$username = trim($request->get($this->options['username_parameter'], null, true));
$password = $request->get($this->options['password_parameter'], null, true);
//$request->getSession()->set(SecurityContextInterface::LAST_USERNAME, $username);
return $this->authenticationManager->authenticate(new UserToken($username, $password, $this->providerKey));
}
public function getHttpUtils()
{
return $this->httpUtils;
}
public function setHttpUtils($httpUtils)
{
$this->httpUtils = $httpUtils;
}
}
身份验证提供者:
<?php
// Acme/SecurityBundle/Security/Authentication/Provider/AuthProvider.php
namespace Acme\SecurityBundle\Security\Authentication\Provider;
use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\NonceExpiredException;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Acme\SecurityBundle\Security\Authentication\Token\UserToken;
class AuthProvider implements AuthenticationProviderInterface
{
private $userProvider;
private $cacheDir;
public function __construct(UserProviderInterface $userProvider, $cacheDir)
{
$this->userProvider = $userProvider;
$this->cacheDir = $cacheDir;
}
public function authenticate(TokenInterface $token)
{
// EXCEPTION TO MAKE AN AUTHENTICATION ERROR
throw new BadCredentialsException('Bad credentials :)');
$user = $this->userProvider->loadUserByUsername($token->getUsername());
// $userToken = new UserToken();
// $userToken->setUser($user);
// echo "it worked"; exit;
$newToken = new UserToken($token->getUser(), $token->getCredentials(), "user", array("ROLE_ADMIN"));
$username = $newToken->getUser();
if (empty($username)) {
throw new BadCredentialsException('Bad credentials :)');
}
//return $newToken;
if ($user && $this->validate()) {
$authenticatedToken = new UserToken($token->getUser(), $token->getCredentials(), "user", $user->getRoles());
$authenticatedToken->setUser($user);
return $authenticatedToken;
}
}
public function supports(TokenInterface $token)
{
return $token instanceof UserToken;
}
public function validate()
{
return true;
}
}
用户令牌:
<?php
// Acme/SecurityBundle/Security/Authenticaion/Token/UserToken.php
namespace Acme\SecurityBundle\Security\Authentication\Token;
use Symfony\Component\Security\Core\Authentication\Token\AbstractToken;
/**
* UsernamePasswordToken implements a username and password token.
*
*/
class UserToken extends AbstractToken
{
private $credentials;
private $providerKey;
/**
* Constructor.
*
* @param string $user The username (like a nickname, email address, etc.)
* @param string $credentials This usually is the password of the user
* @param string $providerKey The provider key
* @param array $roles An array of roles
*
* @throws \InvalidArgumentException
*/
public function __construct($user, $credentials, $providerKey, array $roles = array())
{
parent::__construct($roles);
if (empty($providerKey)) {
throw new \InvalidArgumentException('$providerKey must not be empty.');
}
$this->setUser($user);
$this->credentials = $credentials;
$this->providerKey = $providerKey;
parent::setAuthenticated(count($roles) > 0);
}
/**
* {@inheritdoc}
*/
public function setAuthenticated($isAuthenticated)
{
if ($isAuthenticated) {
throw new \LogicException('Cannot set this token to trusted after instantiation.');
}
parent::setAuthenticated(false);
}
public function getCredentials()
{
return $this->credentials;
}
public function getProviderKey()
{
return $this->providerKey;
}
/**
* {@inheritdoc}
*/
public function eraseCredentials()
{
parent::eraseCredentials();
$this->credentials = null;
}
public function serialize()
{
return serialize(array($this->credentials, $this->providerKey, parent::serialize()));
}
public function unserialize($str)
{
list($this->credentials, $this->providerKey, $parentStr) = unserialize($str);
parent::unserialize($parentStr);
}
}
用户提供者:
<?php
// src/Acme/SecurityBundle/Security/User/UserProvider.php
namespace Acme\SecurityBundle\Security\User;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Acme\SecurityBundle\Security\User\User;
class UserProvider implements UserProviderInterface
{
public function loadUserByUsername($username)
{
// make a call to your webservice here
// $userData = ...
// pretend it returns an array on success, false if there is no user
$user = new User();
$user->setUsername($username);
$user->setPassword("1234");
$user->setRoles(array("ROLE_ADMIN"));
return $user;
}
public function refreshUser(UserInterface $user)
{
if (!$user instanceof User) {
throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_class($user)));
}
return $this->loadUserByUsername($user->getUsername());
}
public function supportsClass($class)
{
return $class === 'Acme\SecurityBundle\Security\User';
}
}
用户 :
<?php
// src/Acme/SecurityBundle/Security/User/User.php
namespace Acme\SecurityBundle\Security\User;
use Symfony\Component\Security\Core\User\UserInterface;
class User implements UserInterface
{
private $username;
private $password;
private $salt;
private $roles;
public function getRoles()
{
return $this->roles;
}
public function getPassword()
{
return $this->password;
}
public function getSalt()
{
return $this->salt;
}
public function getUsername()
{
return $this->username;
}
public function setRoles($roles)
{
$this->roles = $roles;
}
public function setPassword($password)
{
$this->password = $password;
}
public function setSalt($salt)
{
$this->salt = $salt;
}
public function setUsername($username)
{
$this->username = $username;
}
public function eraseCredentials()
{
}
public function equals(UserInterface $user)
{
if (!$user instanceof User) {
return false;
}
if ($this->password !== $user->getPassword()) {
return false;
}
if ($this->getSalt() !== $user->getSalt()) {
return false;
}
if ($this->username !== $user->getUsername()) {
return false;
}
return true;
}
}
参数
捆绑路由:
# Acme/SecurityBundle/Resources/config/routing.yml
AcmeSecurityBundle_homepage:
pattern: /
defaults: { _controller: AcmeSecurityBundle:Default:index }
AcmeSecurityBundle_logout:
pattern: /logout
defaults: { _controller: AcmeSecurityBundle:Default:logout }
login:
pattern: /login
defaults: { _controller: AcmeSecurityBundle:Security:login }
login_check:
pattern: /login_check
工厂申报
# Acme/SecurityBundle/Resources/config/security_factories.yml
services:
security.authentication.factory.user:
class: Acme\SecurityBundle\DependencyInjection\Security\Factory\UserFactory
tags:
- { name: security.listener.factory }
服务 :
# Acme/SecurityBundle/Resources/config/services.yml
services:
user.security.authentication.provider:
class: Acme\SecurityBundle\Security\Authentication\Provider\AuthProvider
arguments: ["", %kernel.cache_dir%/security/nonces]
user.security.authentication.listener:
class: Acme\SecurityBundle\Security\Authentication\Firewall\AuthListener
arguments: [@security.context, @security.authentication.manager, @security.authentication.session_strategy, @security.http_utils]
tags:
- { name: monolog.logger, channel: security }
user_provider_service:
class: Acme\SecurityBundle\Security\User\UserProvider
应用参数
一般路由
# /app/config/routing.yml
AcmeSecurityBundle:
resource: "@AcmeSecurityBundle/Resources/config/routing.yml"
prefix: /
安全...
# /app/config/security.yml
security:
factories:
- "%kernel.root_dir%/../src/Acme/SecurityBundle/Resources/config/security_factories.yml"
encoders:
Symfony\Component\Security\Core\User\User: plaintext
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
providers:
user_provider:
id: user_provider_service
firewalls:
login:
pattern: ^/login$
security: false
logout:
pattern: ^/logout$
security: false
secured_area:
pattern: ^/
my_user_factory: true
form_login:
login_path: /login
check_path: /login_check
failure_path: /logout
access_control:
#- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY, requires_channel: https }
#- { path: ^/_internal, roles: IS_AUTHENTICATED_ANONYMOUSLY, ip: 127.0.0.1 }
控制器和模板
控制器、主页和注销
<?php
// Acme/SecurityBundle/Controller/DefaultController.php
namespace Acme\SecurityBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class DefaultController extends Controller
{
public function indexAction()
{
return $this->render('AcmeSecurityBundle:Default:index.html.twig',
array('parametres' => print_r($this->container->getParameterBag()->all(), true),
'request' => print_r($this->getRequest(),true)));
}
public function logoutAction()
{
$this->get('security.context')->setToken(null);
$this->getRequest()->getSession()->invalidate();
return $this->render('CnamtsSecurityBundle:Default:logout.html.twig',
array('parametres' => print_r($this->container->getParameterBag()->all(), true),
'request' => print_r($this->getRequest(),true)));
}
}
主页模板
{# Acme/SecurityBundle/Resources/views/Default/index.html.twig #}
Hello ! <a href = "{{ path('AcmeSecurityBundle_logout') }}">logout</a>
<pre>
Parametres
{{ parametres }}
Request
{{ request }}
</pre>
注销模板
Goodbye !
<pre>
Parametres
{{ parametres }}
Request
{{ request }}
</pre>
控制器和登录页面
<?php
// src/Acme/SecurityBundle/Controller/SecurityController.php
namespace Acme\SecurityBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Security\Core\SecurityContext;
class SecurityController extends Controller
{
public function loginAction()
{
$request = $this->getRequest();
$session = $request->getSession();
// get the login error if there is one
$error = $session->get(SecurityContext::AUTHENTICATION_ERROR);
$session->remove(SecurityContext::AUTHENTICATION_ERROR);
return $this->render('AcmeSecurityBundle:Login:login.html.twig', array(
// last username entered by the user
'last_username' => $session->get(SecurityContext::LAST_USERNAME),
'error' => $error,
));
}
}
关联模板
{# Acme/SecurityBundle/Resources/views/Security/login.html.twig #}
{% if error %}
<div>{{ error.message }}</div>
{% endif %}
<form name="loginForm" action="{{ path('login_check') }}" method="post">
<label for="username">Username:</label>
<input type="text" id="username" name="_username" />
<label for="password">Password:</label>
<input type="password" id="password" name="_password" />
{#
If you want to control the URL the user is redirected to on success (more details
below)
<input type="hidden" name="_target_path" value="/account" />
#}
<button type="submit">login</button>
</form>