经过一些努力,我能够在Symfony中实现自定义用户提供程序,但是,它无法按预期运行,并且我很难发现自己去了哪里。 我遵循此Guide 但是我有一些我无法解决的问题。
1-为什么我必须同时使用Entity和WebSErviceuser编码?
AppBundle\Entity\User: bcrypt
AppBundle\Security\User\WebserviceUser:
algorithm: bcrypt
cost: 12
2-为什么Symfony不会抛出任何错误?
3-还有一个主要问题:如何访问用户ID(我在BlogController上需要用户ID)
3-并且当尝试从Controller访问$this->getUser()
时,我得到的是WebserviceUser
。
4-和salt.i将salt设置为随机字符串..所有这些最佳做法是吗?
我所有的代码:
Security.yml
security:
encoders:
# Our user class and the algorithm we'll use to encode passwords
# http://symfony.com/doc/current/book/security.html#encoding-the-user-s-password
AppBundle\Entity\User: bcrypt
AppBundle\Security\User\WebserviceUser:
algorithm: bcrypt
cost: 12
providers:
# in this example, users are stored via Doctrine in the database
# To see the users at src/AppBundle/DataFixtures/ORM/LoadFixtures.php
# To load users from somewhere else: http://symfony.com/doc/current/cookbook/security/custom_provider.html
webservice:
#id: AppBundle\Security\User\WebserviceUserProvider
id: app.webservice_user_provider
#database_users:
#entity: { class: AppBundle:User, property: username }
# http://symfony.com/doc/current/book/security.html#firewalls-authentication
firewalls:
secured_area:
# this firewall applies to all URLs
pattern: ^/
# but the firewall does not require login on every page
# denying access is done in access_control or in your controllers
anonymous: true
# This allows the user to login by submitting a username and password
# Reference: http://symfony.com/doc/current/cookbook/security/form_login_setup.html
form_login:
# The route name that the login form submits to
check_path: security_login
# The name of the route where the login form lives
# When the user tries to access a protected page, they are redirected here
login_path: security_login
# Secure the login form against CSRF
# Reference: http://symfony.com/doc/current/cookbook/security/csrf_in_login_form.html
csrf_token_generator: security.csrf.token_manager
# The page users are redirect to when there is no previous page stored in the
# session (for example when the users access directly to the login page).
default_target_path: blog_index
logout:
# The route name the user can go to in order to logout
path: security_logout
# The name of the route to redirect to after logging out
target: homepage
access_control:
# this is a catch-all for the admin area
# additional security lives in the controllers
- { path: '^/(%app_locales%)/admin', roles: ROLE_ADMIN }
services.yml
services:
app.webservice_user_provider:
class: AppBundle\Security\User\WebserviceUserProvider
autowire: true
# First we define some basic services to make these utilities available in
# the entire application
slugger:
class: AppBundle\Utils\Slugger
markdown:
class: AppBundle\Utils\Markdown
# These are the Twig extensions that create new filters and functions for
# using them in the templates
app.twig.app_extension:
public: false
class: AppBundle\Twig\AppExtension
arguments: ['@markdown', '%app_locales%']
tags:
- { name: twig.extension }
app.twig.intl_extension:
public: false
class: Twig_Extensions_Extension_Intl
tags:
- { name: twig.extension }
# Defining a form type as a service is only required when the form type
# needs to use some other services, such as the entity manager.
# See http://symfony.com/doc/current/best_practices/forms.html
app.form.type.tagsinput:
class: AppBundle\Form\Type\TagsInputType
arguments: ['@doctrine.orm.entity_manager']
tags:
- { name: form.type }
# Event Listeners are classes that listen to one or more specific events.
# Those events are defined in the tags added to the service definition.
# See http://symfony.com/doc/current/event_dispatcher.html#creating-an-event-listener
app.redirect_to_preferred_locale_listener:
class: AppBundle\EventListener\RedirectToPreferredLocaleListener
arguments: ['@router', '%app_locales%', '%locale%']
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
app.comment_notification:
class: AppBundle\EventListener\CommentNotificationListener
arguments: ['@mailer', '@router', '@translator', '%app.notifications.email_sender%']
# The "method" attribute of this tag is optional and defaults to "on + camelCasedEventName"
# If the event is "comment.created" the method executed by default is "onCommentCreated()".
tags:
- { name: kernel.event_listener, event: comment.created, method: onCommentCreated }
# Event subscribers are similar to event listeners but they don't need service tags.
# Instead, the PHP class of the event subscriber includes a method that returns
# the list of events listened by that class.
# See http://symfony.com/doc/current/event_dispatcher.html#creating-an-event-subscriber
app.requirements_subscriber:
class: AppBundle\EventListener\CheckRequirementsSubscriber
arguments: ['@doctrine.orm.entity_manager']
tags:
- { name: kernel.event_subscriber }
# To inject the voter into the security layer, you must declare it as a service and tag it with security.voter.
# See http://symfony.com/doc/current/security/voters.html#configuring-the-voter
app.post_voter:
class: AppBundle\Security\PostVoter
public: false
tags:
- { name: security.voter }
# Uncomment the following lines to define a service for the Post Doctrine repository.
# It's not mandatory to create these services, but if you use repositories a lot,
# these services simplify your code:
#
# app.post_repository:
# class: Doctrine\ORM\EntityRepository
# factory: ['@doctrine.orm.entity_manager', getRepository]
# arguments: [AppBundle\Entity\Post]
#
# // traditional code inside a controller
# $entityManager = $this->getDoctrine()->getManager();
# $posts = $entityManager->getRepository('AppBundle:Post')->findAll();
#
# // same code using repository services
# $posts = $this->get('app.post_repository')->findAll();
WebserviceUserProvider.php
<?php
// src/AppBundle/Security/User/WebserviceUserProvider.php
namespace AppBundle\Security\User;
use AppBundle\Entity\User;
use AppBundle\Security\User\WebserviceUser;
//use Doctrine\DBAL\Connection;
use Doctrine\ORM\EntityManager;
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;
class WebserviceUserProvider implements UserProviderInterface
{
private $em;
public function __construct(EntityManager $entityManager)
{
$this->em = $entityManager;
}
public function loadUserByUsername($username)
{
$userData = $this->em->getRepository(User::class)->findOneBy(array('username' => $username));
$row = $userData;
if (!$row->getUsername())
{
$exception = new UsernameNotFoundException(sprintf('Username "%s" not found in the database.', $row->getUsername()));
$exception->setUsername($username);
throw $exception;
}
else
{
$salt = '54hg5g4hfjh4g5sdgf45gd4h84gjhdf54gf4g2f2gfdhggfdg';
return new WebserviceUser($row->getUsername(), $row->getPassword(),$salt , $row->getRoles());
}
}
public function refreshUser(UserInterface $user)
{
if (!$user instanceof WebserviceUser) {
throw new UnsupportedUserException(
sprintf('Instances of "%s" are not supported.', get_class($user))
);
}
return $this->loadUserByUsername($user->getUsername());
}
public function supportsClass($class)
{
return WebserviceUser::class === $class;
}
}
?>
WebserviceUser.php
<?php
// src/AppBundle/Security/User/WebserviceUser.php
namespace AppBundle\Security\User;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\EquatableInterface;
class WebserviceUser implements UserInterface, EquatableInterface
{
private $username;
private $password;
private $salt;
private $roles;
//
public function __construct(string $username, string $password, string $salt, array $roles)
{
if (empty($username))
{
throw new \InvalidArgumentException('No username provided.');
}
$this->username = $username;
$this->password = $password;
$this->salt = $salt;
$this->roles = $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 eraseCredentials()
{
}
public function isEqualTo(UserInterface $user)
{
if (!$user instanceof WebserviceUser) {
return false;
}
if ($this->password !== $user->getPassword()) {
return false;
}
if ($this->salt !== $user->getSalt()) {
return false;
}
if ($this->username !== $user->getUsername()) {
return false;
}
return true;
}
}
?>
BlogController.php
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AppBundle\Controller\Admin;
use AppBundle\Entity\Post;
use AppBundle\Form\PostType;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* Controller used to manage blog contents in the backend.
*
* Please note that the application backend is developed manually for learning
* purposes. However, in your real Symfony application you should use any of the
* existing bundles that let you generate ready-to-use backends without effort.
*
* See http://knpbundles.com/keyword/admin
*
* @Route("/admin/post")
* @Security("has_role('ROLE_ADMIN')")
*
* @author Ryan Weaver <weaverryan@gmail.com>
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
*/
class BlogController extends Controller
{
/**
* Lists all Post entities.
*
* This controller responds to two different routes with the same URL:
* * 'admin_post_index' is the route with a name that follows the same
* structure as the rest of the controllers of this class.
* * 'admin_index' is a nice shortcut to the backend homepage. This allows
* to create simpler links in the templates. Moreover, in the future we
* could move this annotation to any other controller while maintaining
* the route name and therefore, without breaking any existing link.
*
* @Route("/", name="admin_index")
* @Route("/", name="admin_post_index")
* @Method("GET")
*/
public function indexAction()
{
dump($this->getUser());
$entityManager = $this->getDoctrine()->getManager();
$posts = $entityManager->getRepository(Post::class)->findBy(['author' => $this->getUser()], ['publishedAt' => 'DESC']);
return $this->render('admin/blog/index.html.twig', ['posts' => $posts]);
}
/**
* Creates a new Post entity.
*
* @Route("/new", name="admin_post_new")
* @Method({"GET", "POST"})
*
* NOTE: the Method annotation is optional, but it's a recommended practice
* to constraint the HTTP methods each controller responds to (by default
* it responds to all methods).
*/
public function newAction(Request $request)
{
$post = new Post();
$post->setAuthor($this->getUser());
// See http://symfony.com/doc/current/book/forms.html#submitting-forms-with-multiple-buttons
$form = $this->createForm(PostType::class, $post)
->add('saveAndCreateNew', SubmitType::class);
$form->handleRequest($request);
// the isSubmitted() method is completely optional because the other
// isValid() method already checks whether the form is submitted.
// However, we explicitly add it to improve code readability.
// See http://symfony.com/doc/current/best_practices/forms.html#handling-form-submits
if ($form->isSubmitted() && $form->isValid()) {
$post->setSlug($this->get('slugger')->slugify($post->getTitle()));
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($post);
$entityManager->flush();
// Flash messages are used to notify the user about the result of the
// actions. They are deleted automatically from the session as soon
// as they are accessed.
// See http://symfony.com/doc/current/book/controller.html#flash-messages
$this->addFlash('success', 'post.created_successfully');
if ($form->get('saveAndCreateNew')->isClicked()) {
return $this->redirectToRoute('admin_post_new');
}
return $this->redirectToRoute('admin_post_index');
}
return $this->render('admin/blog/new.html.twig', [
'post' => $post,
'form' => $form->createView(),
]);
}
/**
* Finds and displays a Post entity.
*
* @Route("/{id}", requirements={"id": "\d+"}, name="admin_post_show")
* @Method("GET")
*/
public function showAction(Post $post)
{
// This security check can also be performed
// using an annotation: @Security("is_granted('show', post)")
$this->denyAccessUnlessGranted('show', $post, 'Posts can only be shown to their authors.');
return $this->render('admin/blog/show.html.twig', [
'post' => $post,
]);
}
/**
* Displays a form to edit an existing Post entity.
*
* @Route("/{id}/edit", requirements={"id": "\d+"}, name="admin_post_edit")
* @Method({"GET", "POST"})
*/
public function editAction(Post $post, Request $request)
{
$this->denyAccessUnlessGranted('edit', $post, 'Posts can only be edited by their authors.');
$entityManager = $this->getDoctrine()->getManager();
$form = $this->createForm(PostType::class, $post);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$post->setSlug($this->get('slugger')->slugify($post->getTitle()));
$entityManager->flush();
$this->addFlash('success', 'post.updated_successfully');
return $this->redirectToRoute('admin_post_edit', ['id' => $post->getId()]);
}
return $this->render('admin/blog/edit.html.twig', [
'post' => $post,
'form' => $form->createView(),
]);
}
/**
* Deletes a Post entity.
*
* @Route("/{id}/delete", name="admin_post_delete")
* @Method("POST")
* @Security("is_granted('delete', post)")
*
* The Security annotation value is an expression (if it evaluates to false,
* the authorization mechanism will prevent the user accessing this resource).
*/
public function deleteAction(Request $request, Post $post)
{
if (!$this->isCsrfTokenValid('delete', $request->request->get('token'))) {
return $this->redirectToRoute('admin_post_index');
}
$entityManager = $this->getDoctrine()->getManager();
// Delete the tags associated with this blog post. This is done automatically
// by Doctrine, except for SQLite (the database used in this application)
// because foreign key support is not enabled by default in SQLite
$post->getTags()->clear();
$entityManager->remove($post);
$entityManager->flush();
$this->addFlash('success', 'post.deleted_successfully');
return $this->redirectToRoute('admin_post_index');
}
}