Security is a two-step process whose goal is to prevent a user from accessing a resource that they should not have access to.php
In the first step of the process, the security system identifies who the user is by requiring the user to submit some sort of identification. This is called authentication, and it means that the system is trying to find out who you are.html
1. 認證: 判斷用戶是不是合法用戶。ios
Once the system knows who you are, the next step is to determine if you should have access to a given resource. This part of the process is called authorization, and it means that the system is checking to see if you have privileges to perform a certain action.git
2. 權限:在本系統中有多大權限,能夠作何種操做。github
Since the best way to learn is to see an example, start by securing your application with HTTP Basic authentication.web
Symfony's security component is available as a standalone PHP library for use inside any PHP project.express
symfony安全組件能夠做爲一個php庫用在任何一個php項目中。api
The Security component can be configured via your application configuration. In fact, most standard security setups are just a matter of using the right configuration. The following configuration tells Symfony to secure any URL matching /admin/* and to ask the user for credentials using basic HTTP authentication (i.e. the old-school username/password box):安全
安全組件經過配置信息配置。cookie
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
# app/config/security.yml
security:
firewalls:
secured_area:
pattern: ^/
anonymous: ~
http_basic:
realm: "SecuredDemoArea"
access_control:
- { path: ^/admin, roles: ROLE_ADMIN }
providers:
in_memory:
memory:
users:
ryan: { password: ryanpass, roles: 'ROLE_USER' }
admin: { password: kitten, roles: 'ROLE_ADMIN' }
encoders:
Symfony\Component\Security\Core\User\User: plaintext
|
A standard Symfony distribution separates the security configuration into a separate file (e.g. app/config/security.yml). If you don't have a separate security file, you can put the configuration directly into your main config file (e.g. app/config/config.yml).
The end result of this configuration is a fully-functional security system that looks like the following:
Let's look briefly at how security works and how each part of the configuration comes into play.
In this section, you'll learn how to create a basic login form that continues to use the hard-coded users that are defined in the security.yml file.
To load users from the database, please read How to load Security Users from the Database (the Entity Provider). By reading that article and this section, you can create a full login form system that loads users from the database.
So far, you've seen how to blanket your application beneath a firewall and then protect access to certain areas with roles. By using HTTP Authentication, you can effortlessly tap into the native username/password box offered by all browsers. However, Symfony supports many authentication mechanisms out of the box. For details on all of them, see the Security Configuration Reference.
In this section, you'll enhance this process by allowing the user to authenticate via a traditional HTML login form.
First, enable form login under your firewall:
Now, when the security system initiates the authentication process, it will redirect the user to the login form (/login by default). Implementing this login form visually is your job. First, create the two routes you used in the security configuration: the login route will display the login form (i.e. /login) and the login_check route will handle the login form submission (i.e. /login_check):
You will not need to implement a controller for the /login_check URL as the firewall will automatically catch and process any form submitted to this URL. However, you must have a route (as shown here) for this URL, as well as one for your logout path (see Logging Out).
Notice that the name of the login route matches the login_path config value, as that's where the security system will redirect users that need to login.
Next, create the controller that will display the login form:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
// src/Acme/SecurityBundle/Controller/SecurityController.php;
namespace Acme\SecurityBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\SecurityContext;
class SecurityController extends Controller
{
public function loginAction(Request $request)
{
$session = $request->getSession();
// get the login error if there is one
if ($request->attributes->has(SecurityContext::AUTHENTICATION_ERROR)) {
$error = $request->attributes->get(
SecurityContext::AUTHENTICATION_ERROR
);
} else {
$error = $session->get(SecurityContext::AUTHENTICATION_ERROR);
$session->remove(SecurityContext::AUTHENTICATION_ERROR);
}
return $this->render(
'AcmeSecurityBundle:Security:login.html.twig',
array(
// last username entered by the user
'last_username' => $session->get(SecurityContext::LAST_USERNAME),
'error' => $error,
)
);
}
}
|
Don't let this controller confuse you. As you'll see in a moment, when the user submits the form, the security system automatically handles the form submission for you. If the user had submitted an invalid username or password, this controller reads the form submission error from the security system so that it can be displayed back to the user.
In other words, your job is to display the login form and any login errors that may have occurred, but the security system itself takes care of checking the submitted username and password and authenticating the user.
Finally, create the corresponding template:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
{# src/Acme/SecurityBundle/Resources/views/Security/login.html.twig #}
{% if error %}
<div>{{ error.message }}</div>
{% endif %}
<form action="{{ path('login_check') }}" method="post">
<label for="username">Username:</label>
<input type="text" id="username" name="_username" value="{{ last_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>
|
This login form is currently not protected against CSRF attacks. Read Using CSRF in the Login Form on how to protect your login form.
The error variable passed into the template is an instance of AuthenticationException
. It may contain more information - or even sensitive information - about the authentication failure, so use it wisely!
The form has very few requirements. First, by submitting the form to /login_check (via thelogin_check route), the security system will intercept the form submission and process the form for you automatically. Second, the security system expects the submitted fields to be called _usernameand _password (these field names can be configured).
And that's it! When you submit the form, the security system will automatically check the user's credentials and either authenticate the user or send the user back to the login form where the error can be displayed.
Let's review the whole process:
By default, if the submitted credentials are correct, the user will be redirected to the original page that was requested (e.g. /admin/foo). If the user originally went straight to the login page, he'll be redirected to the homepage. This can be highly customized, allowing you to, for example, redirect the user to a specific URL.
For more details on this and how to customize the form login process in general, see How to customize your Form Login.
In the previous sections, you learned how you can protect different resources by requiring a set ofroles for a resource. This section explores the other side of authorization: users.
During authentication, the user submits a set of credentials (usually a username and password). The job of the authentication system is to match those credentials against some pool of users. So where does this list of users come from?
In Symfony2, users can come from anywhere - a configuration file, a database table, a web service, or anything else you can dream up. Anything that provides one or more users to the authentication system is known as a "user provider". Symfony2 comes standard with the two most common user providers: one that loads users from a configuration file and one that loads users from a database table.
The easiest way to specify your users is directly in a configuration file. In fact, you've seen this already in the example in this chapter.
This user provider is called the "in-memory" user provider, since the users aren't stored anywhere in a database. The actual user object is provided by Symfony (User
).
Any user provider can load users directly from configuration by specifying the usersconfiguration parameter and listing the users beneath it.
If your username is completely numeric (e.g. 77) or contains a dash (e.g. user-name), you should use that alternative syntax when specifying users in YAML:
1 2 3 |
users:
- { name: 77, password: pass, roles: 'ROLE_USER' }
- { name: user-name, password: pass, roles: 'ROLE_USER' }
|
For smaller sites, this method is quick and easy to setup. For more complex systems, you'll want to load your users from the database.
If you'd like to load your users via the Doctrine ORM, you can easily do this by creating a User class and configuring the entity provider.
A high-quality open source bundle is available that allows your users to be stored via the Doctrine ORM or ODM. Read more about the FOSUserBundle on GitHub.
With this approach, you'll first create your own User class, which will be stored in the database.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// src/Acme/UserBundle/Entity/User.php
namespace Acme\UserBundle\Entity;
use Symfony\Component\Security\Core\User\UserInterface;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class User implements UserInterface
{
/**
* @ORM\Column(type="string", length=255)
*/
protected $username;
// ...
}
|
As far as the security system is concerned, the only requirement for your custom user class is that it implements the UserInterface
interface. This means that your concept of a "user" can be anything, as long as it implements this interface.
The user object will be serialized and saved in the session during requests, therefore it is recommended that you implement the Serializable interface in your user object. This is especially important if your User class has a parent class with private properties.
Next, configure an entity user provider, and point it to your User class:
With the introduction of this new provider, the authentication system will attempt to load a Userobject from the database by using the username field of that class.
This example is just meant to show you the basic idea behind the entity provider. For a full working example, see How to load Security Users from the Database (the Entity Provider).
For more information on creating your own custom provider (e.g. if you needed to load users via a web service), see How to create a custom User Provider.
So far, for simplicity, all the examples have stored the users' passwords in plain text (whether those users are stored in a configuration file or in a database somewhere). Of course, in a real application, you'll want to encode your users' passwords for security reasons. This is easily accomplished by mapping your User class to one of several built-in "encoders". For example, to store your users in memory, but obscure their passwords via sha1, do the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# app/config/security.yml
security:
# ...
providers:
in_memory:
memory:
users:
ryan: { password: bb87a29949f3a1ee0559f8a57357487151281386, roles: 'ROLE_USER' }
admin: { password: 74913f5cd5f61ec0bcfdb775414c2fb3d161b620, roles: 'ROLE_ADMIN' }
encoders:
Symfony\Component\Security\Core\User\User:
algorithm: sha1
iterations: 1
encode_as_base64: false
|
By setting the iterations to 1 and the encode_as_base64 to false, the password is simply run through the sha1 algorithm one time and without any extra encoding. You can now calculate the hashed password either programmatically (e.g. hash('sha1', 'ryanpass')) or via some online tool likefunctions-online.com
Supported algorithms for this method depend on your PHP version. A full list is available calling the PHP function hash_algos
.
If you're creating your users dynamically (and storing them in a database), you can use even tougher hashing algorithms and then rely on an actual password encoder object to help you encode passwords. For example, suppose your User object is Acme\UserBundle\Entity\User (like in the above example). First, configure the encoder for that user:
In this case, you're using the stronger sha512 algorithm. Also, since you've simply specified the algorithm (sha512) as a string, the system will default to hashing your password 5000 times in a row and then encoding it as base64. In other words, the password has been greatly obfuscated so that the hashed password can't be decoded (i.e. you can't determine the password from the hashed password).
New in version 2.2: As of Symfony 2.2 you can also use the PBKDF2 and BCrypt password encoders.
If you have some sort of registration form for users, you'll need to be able to determine the hashed password so that you can set it on your user. No matter what algorithm you configure for your user object, the hashed password can always be determined in the following way from a controller:
1 2 3 4 5 6 |
$factory = $this->get('security.encoder_factory');
$user = new Acme\UserBundle\Entity\User();
$encoder = $factory->getEncoder($user);
$password = $encoder->encodePassword('ryanpass', $user->getSalt());
$user->setPassword($password);
|
When you allow a user to submit a plaintext password (e.g. registration form, change password form), you must have validation that guarantees that the password is 4096 characters or less. Read more details in How to implement a simple Registration Form.
After authentication, the User object of the current user can be accessed via the security.contextservice. From inside a controller, this will look like:
1 2 3 4 |
public function indexAction()
{
$user = $this->get('security.context')->getToken()->getUser();
}
|
In a controller this can be shortcut to:
1 2 3 4 |
public function indexAction()
{
$user = $this->getUser();
}
|
Anonymous users are technically authenticated, meaning that the isAuthenticated()method of an anonymous user object will return true. To check if your user is actually authenticated, check for the IS_AUTHENTICATED_FULLY role.
In a Twig Template this object can be accessed via the app.user key, which calls theGlobalVariables::getUser()
method:
Each authentication mechanism (e.g. HTTP Authentication, form login, etc) uses exactly one user provider, and will use the first declared user provider by default. But what if you want to specify a few users via configuration and the rest of your users in the database? This is possible by creating a new provider that chains the two together:
Now, all authentication mechanisms will use the chain_provider, since it's the first specified. Thechain_provider will, in turn, try to load the user from both the in_memory and user_db providers.
You can also configure the firewall or individual authentication mechanisms to use a specific provider. Again, unless a provider is specified explicitly, the first provider is always used:
In this example, if a user tries to log in via HTTP authentication, the authentication system will use the in_memory user provider. But if the user tries to log in via the form login, the user_db provider will be used (since it's the default for the firewall as a whole).
For more information about user provider and firewall configuration, see the SecurityBundle Configuration ("security").
The idea of a "role" is key to the authorization process. Each user is assigned a set of roles and then each resource requires one or more roles. If the user has any one of the required roles, access is granted. Otherwise access is denied.
Roles are pretty simple, and are basically strings that you can invent and use as needed (though roles are objects internally). For example, if you need to start limiting access to the blog admin section of your website, you could protect that section using a ROLE_BLOG_ADMIN role. This role doesn't need to be defined anywhere - you can just start using it.
All roles must begin with the ROLE_ prefix to be managed by Symfony2. If you define your own roles with a dedicated Role class (more advanced), don't use the ROLE_prefix.
Instead of associating many roles to users, you can define role inheritance rules by creating a role hierarchy:
In the above configuration, users with ROLE_ADMIN role will also have the ROLE_USER role. TheROLE_SUPER_ADMIN role has ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH and ROLE_USER (inherited fromROLE_ADMIN).
Now that you have a User and Roles, you can go further than URL-pattern based authorization.
Protecting your application based on URL patterns is easy, but may not be fine-grained enough in certain cases. When necessary, you can easily force authorization from inside a controller:
1 2 3 4 5 6 7 8 9 10 11 |
// ...
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
public function helloAction($name)
{
if (false === $this->get('security.context')->isGranted('ROLE_ADMIN')) {
throw new AccessDeniedException();
}
// ...
}
|
A firewall must be active or an exception will be thrown when the isGranted() method is called. It's almost always a good idea to have a main firewall that covers all URLs (as is shown in this chapter).
New in version 2.4: The expression functionality was introduced in Symfony 2.4.
In addition to a role like ROLE_ADMIN, the isGranted method also accepts an Expression
object:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\ExpressionLanguage\Expression;
// ...
public function indexAction()
{
if (!$this->get('security.context')->isGranted(new Expression(
'"ROLE_ADMIN" in roles or (user and user.isSuperAdmin())'
))) {
throw new AccessDeniedException();
}
// ...
}
|
In this example, if the current user has ROLE_ADMIN or if the current user object's isSuperAdmin()method returns true, then access will be granted (note: your User object may not have anisSuperAdmin method, that method is invented for this example).
This uses an expression and you can learn more about the expression language syntax, see The Expression Syntax.
Inside the expression, you have access to a number of variables:
user The user object (or the string anon if you're not authenticated);
roles The array of roles the user has, including from the role hierarchy but not including the IS_AUTHENTICATED_* attributes (see the functions below);
object: The object (if any) that's passed as the second argument to isGranted ;
token The token object;
AuthenticationTrustResolverInterface
,
object: you'll probably use the is_* functions below instead.
Additionally, you have access to a number of functions inside the expression:
In fact, anything in Symfony can be protected using a strategy similar to the one seen in the previous section. For example, suppose you have a service (i.e. a PHP class) whose job is to send emails from one user to another. You can restrict use of this class - no matter where it's being used from - to users that have a specific role.
For more information on how you can use the Security component to secure different services and methods in your application, see How to secure any Service or Method in your Application.
If you want to check if the current user has a role inside a template, use the built-in helper function:
If you use this function and are not at a URL behind a firewall active, an exception will be thrown. Again, it's almost always a good idea to have a main firewall that covers all URLs (as has been shown in this chapter).
New in version 2.4: The expression functionality was introduced in Symfony 2.4.
You can also use expressions inside your templates:
For more details on expressions and security, see Complex Access Controls with Expressions.
Imagine you are designing a blog system where your users can comment on your posts. Now, you want a user to be able to edit their own comments, but not those of other users. Also, as the admin user, you yourself want to be able to edit all comments.
The Security component comes with an optional access control list (ACL) system that you can use when you need to control access to individual instances of an object in your system. Without ACL, you can secure your system so that only certain users can edit blog comments in general. But withACL, you can restrict or allow access on a comment-by-comment basis.
For more information, see the cookbook article: How to use Access Control Lists (ACLs).
Usually, you'll also want your users to be able to log out. Fortunately, the firewall can handle this automatically for you when you activate the logout config parameter:
Once this is configured under your firewall, sending a user to /logout (or whatever you configure thepath to be), will un-authenticate the current user. The user will then be sent to the homepage (the value defined by the target parameter). Both the path and target config parameters default to what's specified here. In other words, unless you need to customize them, you can omit them entirely and shorten your configuration:
Note that you will not need to implement a controller for the /logout URL as the firewall takes care of everything. You do, however, need to create a route so that you can use it to generate the URL:
Once the user has been logged out, they will be redirected to whatever path is defined by the targetparameter above (e.g. the homepage). For more information on configuring the logout, see theSecurity Configuration Reference.
By default, Symfony2 relies on a cookie (the Session) to persist the security context of the user. But if you use certificates or HTTP authentication for instance, persistence is not needed as credentials are available for each request. In that case, and if you don't need to store anything else between requests, you can activate the stateless authentication (which means that no cookie will be ever created by Symfony2):
If you use a form login, Symfony2 will create a cookie even if you set stateless to true.
New in version 2.2: The StringUtils and SecureRandom classes were added in Symfony 2.2
The Symfony Security component comes with a collection of nice utilities related to security. These utilities are used by Symfony, but you should also use them if you want to solve the problem they address.
The time it takes to compare two strings depends on their differences. This can be used by an attacker when the two strings represent a password for instance; it is known as a Timing attack.
Internally, when comparing two passwords, Symfony uses a constant-time algorithm; you can use the same strategy in your own code thanks to the StringUtils
class:
1 2 3 4 |
use Symfony\Component\Security\Core\Util\StringUtils;
// is password1 equals to password2?
$bool = StringUtils::equals($password1, $password2);
|
Whenever you need to generate a secure random number, you are highly encouraged to use the Symfony SecureRandom
class:
1 2 3 4 |
use Symfony\Component\Security\Core\Util\SecureRandom;
$generator = new SecureRandom();
$random = $generator->nextBytes(10);
|
The nextBytes()
methods returns a random string composed of the number of characters passed as an argument (10 in the above example).
The SecureRandom class works better when OpenSSL is installed but when it's not available, it falls back to an internal algorithm, which needs a seed file to work correctly. Just pass a file name to enable it:
1 2 |
$generator = new SecureRandom('/some/path/to/store/the/seed.txt');
$random = $generator->nextBytes(10);
|
You can also access a secure random instance directly from the Symfony dependency injection container; its name is security.secure_random.
Security can be a deep and complex issue to solve correctly in your application. Fortunately, Symfony's Security component follows a well-proven security model based around authenticationand authorization. Authentication, which always happens first, is handled by a firewall whose job is to determine the identity of the user through several different methods (e.g. HTTP authentication, login form, etc). In the cookbook, you'll find examples of other methods for handling authentication, including how to implement a "remember me" cookie functionality.
Once a user is authenticated, the authorization layer can determine whether or not the user should have access to a specific resource. Most commonly, roles are applied to URLs, classes or methods and if the current user doesn't have that role, access is denied. The authorization layer, however, is much deeper, and follows a system of "voting" so that multiple parties can determine if the current user should have access to a given resource. Find out more about this and other topics in the cookbook.