symfony后台系统_Symfony2预注册和邀请系统

symfony后台系统

We have discussed Symfony 2 development in previous SitePoint articles and we built a clone of my own personal Symfony app (Part 1, 2 and 3). But Symfony 2 is a gigantic framework and there are plenty more topics we can cover.

我们已经在先前的SitePoint文章中讨论了Symfony 2的开发,并且构建了我自己的个人Symfony应用的副本(第1部分,第2部分和第3部分 )。 但是Symfony 2是一个巨大的框架,还有很多我们可以讨论的主题。

symfony-logo

In this article series of two parts, we are going to talk about a very important area of web application development: Authentication and Authorization. Symfony2 has a very detailed elaboration of these two topics in its official documentation. Anyone who is seriously looking into this is encouraged to read through that official document after absorbing the essentials from this tutorial.

在这个由两部分组成的系列文章中,我们将讨论Web应用程序开发的一个非常重要的领域: Authentication和Authorization 。 Symfony2在其官方文档中对这两个主题进行了非常详细的阐述。 鼓励认真研究此问题的任何人在吸收了本教程的要点后,通读该正式文档。

A typical User Management flow may have the following tasks:

典型的用户管理流程可能具有以下任务:

  • A built-in user will be generated upon application installation and will be granted the root equivalent privilege.

    内置用户将在应用程序安装时生成,并将被授予root等效特权。

  • Any new user can either register via a form or can only register via invitation (which is the approach discussed in this article).

    任何新用户都可以通过表单注册,也只能通过邀请注册(这是本文讨论的方法)。
  • After registration, a user record is stored into the underlying database/table.

    注册后,用户记录将存储到基础数据库/表中。
  • Optionally, the app will put this new user in a “pending” status and send out a confirmation email. A user will only be “activated” when they click the link in the email with a confirmation token. This approach is not used in this article because we are inviting users and the site is a “closed circle” site.

    (可选)该应用会将新用户设置为“待处理”状态,并发送确认电子邮件。 仅当用户单击电子邮件中带有确认令牌的链接时,该用户才会被“激活”。 本文中未使用此方法,因为我们正在邀请用户,并且该站点是“封闭圈子”站点。
  • A user logs in. The app will verify the user name and password.

    用户登录。应用程序将验证用户名和密码。
  • Optionally, the app can do some post-login activities. In this case, we will update the user’s last login date/time in the database, and redirect them.

    该应用程序可以选择执行一些登录后活动。 在这种情况下,我们将更新用户在数据库中的上次登录日期/时间,并对其进行重定向。
  • The user can explicitly choose to logout.

    用户可以明确选择注销。

基础user(The underlying user table)

Although Symfony supports in-memory user authentication, that is not recommended in a real-world application. Most of the time, we’ll tap into other resources (database, LDAP, etc) to persist the user credentials. We will use an SQL database in our app.

尽管Symfony支持内存中用户身份验证,但在实际应用程序中不建议这样做。 大多数时候,我们会利用其他资源(数据库, LDAP等)来保留用户凭据。 我们将在应用程序中使用SQL数据库。

First, let’s create a table to store user information:

首先,让我们创建一个表来存储用户信息:

CREATE TABLE `user` ( 
	`id` INT( 255 ) AUTO_INCREMENT NOT NULL, 
	`username` VARCHAR( 255 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, 
	`password` VARCHAR( 255 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, 
	`email` VARCHAR( 255 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, 
	`created` DATETIME NOT NULL, 
	`logged` DATETIME NULL, 
	`roles` VARCHAR( 25 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, 
	`gravatar` VARCHAR( 255 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, 
	`active` TINYINT( 1 ) NOT NULL, 
	`homepage` VARCHAR( 255 ) CHARACTER SET utf8 COLLATE utf8_general_ci NULL,
	 PRIMARY KEY ( `id` )

Out of these defined fields, username and password are used to grant/deny a login request. This is called authentication.

在这些定义的字段中, usernamepassword用于授予/拒绝登录请求。 这称为身份验证。

After a successful login, the user will be granted a ROLE, which is defined by the roles field. Different roles will have different access rights when visiting URIs. This is called authorization.

成功登录后,将为用户授予ROLE ,该rolesroles字段定义。 访问URI时,不同的角色将具有不同的访问权限。 这称为授权。

username, password and roles together form the cornerstones of our security system.

usernamepasswordroles共同构成了我们安全系统的基石。

配置: security.yml (Configuration: security.yml)

Symfony uses a security.yml file to hold all the settings and configuration related to app security.

Symfony使用security.yml文件保存与应用程序安全性相关的所有设置和配置。

Below are the contents of our security.yml file (located in app/config):

以下是我们的security.yml文件的内容(位于app/config ):

security:
    providers:
        administrators: 
            entity: { class: AppBundle:User, property: username }
    encoders:
        AppBundle\Entity\User: 
            algorithm: bcrypt
            cost: 12    
    
    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt|error)|css|images|js)/
            security: false

        default:
            anonymous: ~
            http_basic: ~
            form_login:
                login_path: /login
                check_path: /login_check    
            logout:    
                path: /logout
                target: /login
    
    access_control:
        - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/register, roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/preregister, roles: IS_AUTHENTICATED_ANONYMOUSLY }            
        - { path: ^/create, roles: IS_AUTHENTICATED_ANONYMOUSLY }                
        - { path: ^/invite, roles: [ROLE_ADMIN] }
        - { path: ^/, roles: [ROLE_USER, ROLE_ADMIN] }

Under security, we have four sub-sections:

security ,我们有四个小节:

providers :负责提供用户凭证的人员 (providers: those responsible for providing user credentials)

We will store user information in a table, reflected in Symfony as an entity (a PHP class) with necessary getters/setters to manipulate the properties (table fields).

我们会将用户信息存储在一个表中,该表反映在Symfony中作为一个实体(PHP类),并带有必要的获取器/设置器来操纵属性(表字段)。

In this case, the entity is AppBundle:User, which comes from src/AppBundle/Entity/User.php and the property we use to verify a login request is coming from the username.

在这种情况下,实体是AppBundle:User ,它来自src/AppBundle/Entity/User.php ,我们用来验证登录请求的属性来自username

Symfony supports using other fields or one of several fields as the identifier of a user. A common scenario is the user being able to log in using either the username or the email field. Please refer to Symfony’s official documentation if you want to know more on this.

Symfony支持使用其他字段或多个字段之一作为用户的标识符。 常见的情况是用户能够使用usernameemail字段登录。 如果您想了解更多有关此信息,请参考Symfony的官方文档。

密码的编码方式: encoders (How the password is encoded: encoders)

Storing passwords in plain text form is never a good idea. In our case, we specify that we will use the bcrypt method with 12 loops. This will produce a reasonably strong hash with reasonable cost (of machine calculation time – the longer it takes for a password hash to be generated, the harder it is for a brute force attack to succeed).

以纯文本格式存储密码绝不是一个好主意。 在本例中,我们指定将使用带有12个循环的bcrypt方法。 这将以合理的成本生成合理的强哈希(机器计算时间-生成密码哈希所需的时间越长, 暴力攻击成功的难度就越大)。

登录和注销功能: firewalls/default (Login and logout functionality: firewalls/default)

We must specify how we log in. Symfony can use HTTP Basic login (that pops up a dialog for username/password input) or login forms. In our case, we use a form:

我们必须指定登录方式。Symfony可以使用HTTP Basic登录(弹出一个用于输入用户名/密码的对话框)或登录表单。 在我们的情况下,我们使用以下形式:

form_login:
	login_path: /login
    check_path: /login_check

Of course, we let the user have the option to explicitly log out:

当然,我们让用户可以选择显式注销:

logout:    
	path: /logout
    target: /login

The target setting here tells the app where to go (in this case back to the login page) after the user logs out.

用户注销后,此处的target设置告诉应用程序去哪里(在这种情况下,返回login页面)。

To make the above settings work properly, Symfony requires three routes in the routing.yml file:

为了使上述设置正常工作,Symfony在routing.yml文件中需要三个路由:

logout:
    path: /logout
    
login:
    path: /login
    defaults: { _controller: AppBundle:Security:login}    
        
login_check:
    path: /login_check

In particular, the /login path must be associated with a proper action in a controller (AppBundle:Security:login). The other two can be defined with a name and a path.

特别是/login路径必须与控制器中的适当操作关联( AppBundle:Security:login )。 可以使用名称和路径来定义其他两个。

access_control (access_control)

In this sub-section, we define the mapping between URI patterns and the access level required.

在本小节中,我们定义URI模式与所需访问级别之间的映射。

In general, we have 3 different levels:

通常,我们有3个不同的级别:

  • IS_AUTHENTICATED_ANONYMOUSLY – this means anybody. No access control will be applied when a URI is visited with this access right.

    IS_AUTHENTICATED_ANONYMOUSLY –这意味着任何人。 使用此访问权限访问URI时,将不会应用任何访问控制。

  • ROLE_USER – a regular user that passes the login verification can access this URI.

    ROLE_USER –通过登录验证的普通用户可以访问此URI。

  • ROLE_ADMIN – a superuser who has some special privileges can access this URI.

    ROLE_ADMIN具有某些特殊特权的超级用户可以访问此URI。

The names (ROLE_USER and ROLE_ADMIN) are artificial. Developers can apply their own set of names to be used in the app (for example, using USER and ADMIN is also fine).

名称( ROLE_USERROLE_ADMIN )是人造的。 开发人员可以应用自己的名称集来在应用程序中使用(例如,使用USERADMIN也可以)。

A common pitfall when defining the access control is making the control too strict and unusable.

定义访问控制时的一个常见陷阱是使控制过于严格且无法使用。

No matter how strict the app wants the access control to be, the app MUST at least allow anonymous access to the login page. And like Symfony’s routes mapping, we MUST define the more specific rules earlier.

无论应用程序希望访问控制的严格程度如何,应用程序至少必须允许匿名访问login页面。 就像Symfony的路由映射一样,我们必须尽早定义更具体的规则。

We have to free up access to all the routes related to user registration (register, preregister, create) and login (login) to allow anonymous access. These routes are defined first.

我们必须释放与用户注册( registerpreregistercreate )和登录( login )相关的所有路由的访问权限,以允许匿名访问。 首先定义这些路由。

To restrict the inviting functionality to a user (invite), we state that this job can only be done by an administrator.

为了将邀请功能限制为用户( invite ),我们声明此工作只能由管理员完成。

Finally, we “close” the whole site (^/, except for those URI patterns defined earlier) for everyone but “insiders” (ROLE_USER or ROLE_ADMIN is required).

最后,我们为除“内部人员”(需要ROLE_USERROLE_ADMIN )之外的所有用户“关闭”整个站点( ^/ ,除了之前定义的URI模式)。

AppBundle:User实体 (The AppBundle:User entity)

My preference is to have the AppBundle:User entity generated by importing an existing db structure. This entity by itself can’t be used by the authentication process yet. We need some further modifications:

我的偏好是通过导入现有的数据库结构来生成AppBundle:User实体。 该实体本身不能被身份验证过程使用。 我们需要进一步修改:

<?php

namespace AppBundle\Entity;

use Symfony\Component\Security\Core\User\UserInterface;

/**
 * User
 */
class User implements UserInterface, \Serializable
{

    /**
     * @var integer
     */
    private $id;

//All the table properties follow...

    public function getId()
    {
        return $this->id;
    }

//All the getters/setters follow...

    /**
     * Get roles
     *
     * @return [string]
     */
    public function getRoles()
    {
        return [$this->roles];
    }

    public function eraseCredentials()
    {
        return;
    }

    public function serialize()
    {
        ...
    }

    public function unserialize($serialized)
    {
        ...
    }
    
    public function getSalt()
    {
        ...
    }

}

The User class must implement two interfaces: Symfony\Component\Security\Core\User\UserInterface and \Serializable. This will require us to define additional methods including eraseCredentials, serialize, unserialize and getSalt.

User类必须实现两个接口: Symfony\Component\Security\Core\User\UserInterface\Serializable 。 这将要求我们定义其他方法,包括: eraseCredentialsserialize ,反unserializegetSalt

A note on the definition of the getRoles method. roles is a field in our user table so the getter is automatically generated like this:

有关getRoles方法的定义的getRolesroles是我们user表中的一个字段,因此自动生成getter,如下所示:

public function getRoles()
    {
        return $this->roles;
    }

This is because we define the roles field as simple as a VARCHAR(255), which will return the string we stored. However, Symfony requires roles in an array format, so even though we only have one string-based role saved for our user at this time, we accommodate a multiple-roles system and the roles-as-array requirement by exploding this string. That way, if we ever want a user to have multiple roles, we can save them as a comma separated list (ROLE_ADMIN, ROLE_BLOGGER, ROLE_ACCOUNTANT). Thus, we tweak the method like this:

这是因为我们将roles字段定义为像VARCHAR(255)一样简单,它将返回我们存储的字符串。 但是,Symfony需要使用数组格式的角色,因此,即使此时我们仅为用户保存了一个基于字符串的角色,我们也会通过分解此字符串来适应多角色系统和角色作为数组的要求。 这样,如果我们希望用户具有多个角色,则可以将它们另存为以逗号分隔的列表( ROLE_ADMIN, ROLE_BLOGGER, ROLE_ACCOUNTANT )。 因此,我们调整如下方法:

public function getRoles()
    {
        return explode(',', $this->roles);
    }

It’s time to move on to the logic, but before that, let’s create the very first root user with ultimate permissions.

现在该继续讲逻辑了,但是在此之前,让我们创建具有最终权限的第一个root用户。

It is easy to populate all fields in the user table for a user, except one: the password field. We are using bcrypt to hash our password so we cannot put a plain text password in it. To help us through this tedious task, we use this online tool. My sample root user’s SQL INSERT statement (hash calculated for the password “test”) follows below:

很容易为user填充user表中的所有字段,但以下一项除外: password字段。 我们正在使用bcrypt哈希密码,因此我们不能在其中输入纯文本密码。 为了帮助我们完成这项繁琐的任务,我们使用了此在线工具 。 我的示例根用户SQL INSERT语句(为密码“ test”计算出的哈希值)如下:

INSERT INTO `user` 
(`username`, `password`, `email`, `roles`, `active`) 
VALUES ('root', '$2a$12$3INF/uK0.pmZAMFW0In0uOrfpq.yaVIK0xwmWO8Yjxhs4m8CC1Ei2', 'root@somewhere.com', 'ROLE_ADMIN', '1');

您收到了ADMIN的邀请 (You have an invitation from the ADMIN)

When an administrator visits the /invite page, they can put in an email address to invite someone. The app will then build an email and send it. The email will contain a link for the recipient to register and a unique invitation code to verify the invitation is coming from a trusted source. That’s all. In the backend, the recipient’s email, invitation code and an expiration date will be stored in the database table.

管理员访问/invite页面时,可以输入电子邮件地址来邀请某人。 然后,该应用将生成一封电子邮件并将其发送。 该电子邮件将包含收件人要注册的链接和唯一的邀请码,以验证邀请来自受信任的来源。 就这样。 在后端,收件人的电子邮件,邀请代码和有效日期将存储在数据库表中。

The method code of doing so is below:

这样做的方法代码如下:

public function doinviteAction(Request $req)
    {

        $email  = $req->get('email');
        $userid = $req->get('user');

        $hash = $this->setInvite($userid, $email);
        $this->sendMail($email, $hash);

        $url = $this->generateUrl('invite');
        return $this->redirect($url);
    }

	private function setInvite($userid, $email)
    {
        $em        = $this->getDoctrine()->getManager();
        $user_repo = $em->getRepository('AppBundle:User');
        $user      = $user_repo->find($userid); //The user who initiates the invitation

        $invite = new Invite();
        $invite->setInvited($email);
        $invite->setWhoinvite($user);

        $now = new \DateTime();
        $int = new \DateInterval('P1D');
        $now->add($int);
        $invite->setExpires($now); //Set invitation expirary 

        $random = rand(10000, 99999);

        $invite->setHash($random); //A random number is used but stricter method to create a verification code can be used. 

        $em->persist($invite);
        $em->flush();

        return $random;
    }

    private function sendMail($email, $hash)
    {
        $mailer = $this->get('mailer');

        $message = $mailer->createMessage()
                ->setSubject('Someone invites you to join thisdomain.com')
                ->setFrom('root@thisdomain.com')
                ->setTo($email)
                ->setBody(
                $this->renderView('AppBundle:Default:email.html.twig', ['email' => $email, 'hash' => $hash]), 'text/html'
        );

        $mailer->send($message);
    }

We get the user who sends the invitation ($userid) and the user whom they intend to invite ($email). We then create the invitation record and generate a random number associated with this invitation as the invitation code. All the information required to send an invitation (including the invitation code) will be saved and persisted. Finally, we return the invitation code.

我们得到发送邀请的用户( $userid )和他们打算邀请的用户( $email )。 然后,我们创建邀请记录并生成与此邀请相关联的随机数作为邀请代码。 发送邀请所需的所有信息(包括邀请代码)将被保存并保留。 最后,我们返回邀请代码。

After the invitation is saved, we send the email. I am using the default Symfony mail service to do this. Please note that for the email body, I am using a Twig template to render the rich content.

邀请保存后,我们将发送电子邮件。 我正在使用默认的Symfony邮件服务来执行此操作。 请注意,对于电子邮件正文,我使用的是Twig模板来呈现丰富的内容。

Since only an administrator can invite another person, we need to control page contents based on user authorization. The default template engine used in Symfony, Twig, has a helper for that:

由于只有管理员可以邀请其他人,因此我们需要基于用户授权来控制页面内容。 Symfony Twig中使用的默认模板引擎为此提供了一个帮助器:

{% if is_granted('ROLE_ADMIN') %}
	<li><a href="{{path('invite')}}"><i class="large-icon-share"></i>&nbsp;&nbsp;Invite</a></li>
{% endif %}

In this template, we use the is_granted helper to restrict the display of a menu item for admin users only.

在此模板中,我们使用is_granted帮助器将菜单项的显示仅限于管理员用户。

开始注册 (Registration starts)

When a recipient receives an invitation from the admin, they can click the link and verify the invitation.

当收件人从管理员那里收到邀请时,他们可以单击链接并验证邀请。

A valid invitation must satisfy the following conditions:

有效邀请必须满足以下条件:

  1. The email is actually invited.

    该电子邮件实际上是受邀请的。
  2. The invitation is not expired.

    邀请未过期。
  3. The confirmation code is verified and paired with that email.

    确认码已验证并与该电子邮件配对。

All the above is done in the respective controller action:

以上所有操作均在相应的控制器操作中完成:

public function preregisterAction(Request $req)
    {
        $email = $req->get('email');
        $invitationCode   = $req->get('code');

        try
        {
            $this->verify($email, $invitationCode);
        }
        catch (\Exception $e)
        {
            $this->addFlash('error', $e->getMessage());
            return $this->redirectToRoute('error');
        }

        $registration = new User();
        $form         = $this->createForm(new RegistrationType(), $registration, ['action' => $this->generateUrl('create'), 'method' => 'POST']);


        return $this->render('AppBundle:Default:register2.html.twig', ['form' => $form->createView(), 'email' => $email]);
    }

    private function verify($email, $code)
    {
        $repo    = $this->getDoctrine()->getManager()->getRepository('AppBundle:Invite');
        $invites = $repo->findBy(['invited' => $email]);

        if (count($invites) == 0)
        {
            throw new \Exception("This email is not invited.");
        }
        $invite = $invites[0];

        $exp = $invite->getExpires();
        $now = new \DateTime();
        if ($exp < $now)
        {
            throw new \Exception("Invitation expires.");
        }

        if ($invite->getCode() !== $code)
        {
            throw new \Exception('Wrong invitation code.');
        }
    }

The verify method does the verification and throws an exception when something is wrong. In the preregisterAction method, we will catch that exception and add a flash message in the error page to alert the user. However, if the verification goes well, the registration process will go to the next step by displaying a registration form.

verify方法进行验证,并在出现问题时引发异常。 在preregisterAction方法中,我们将捕获该异常,并在错误页面中添加一条Flash消息以提醒用户。 但是,如果验证顺利,则注册过程将通过显示注册表格进入下一步。

结论 (Conclusion)

This brings us to the end of Part 1 of Symfony 2 Security Management, which covers the app setup (database and security.yml) and the pre-registration stage wherein a user verifies with the app that they are indeed invited.

这使我们进入了Symfony 2 Security Management的第1部分的结尾,该部分讨论了应用程序的设置(数据库和security.yml )以及预注册阶段,在该阶段中,用户向应用程序验证了确实邀请了他们。

In our Part 2, we will look into the next two steps: registration and login. We will also develop our post-login handler to do some application-specific actions after a user successfully logs in.

在第2部分中,我们将研究接下来的两个步骤:注册和登录。 我们还将开发登录后处理程序,以在用户成功登录后执行一些特定于应用程序的操作。

Stay tuned!

敬请关注!

翻译自: https://www.sitepoint.com/symfony2-pre-registration-invite-system/

symfony后台系统

  • 0
    点赞
  • 0
    评论
  • 1
    收藏
  • 扫一扫,分享海报

表情包
插入表情
评论将由博主筛选后显示,对所有人可见 | 还能输入1000个字符
©️2022 CSDN 皮肤主题:编程工作室 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值