所在位置:

在Flask中设置用户身份验证(上)【翻译】

介绍

前面的教程,我演示了如何创建一个最简单的Flask应用程序 - hello world。不幸的是,示例应用程序太简单而不能显示 应用程序结构 的全部潜力,我想在这篇文章中展示一些东西。

在本教程中,我将继续使用相同的代码并开发一个简单的用户身份验证系统。用户是每个现代应用程序的核心,创建用户系统无疑是每个开发人员实现下一个世界级改变应用的第一步。你可以从这里开始。

为了获得完整意义上的代码之前,请查看Github里的 源代码

源代码

你可以从 github 里下载 源代码

最后我得到什么?

好问题。我将演示如何用 Flask-SQLAlchemy 来创建模型,用 Flask-WTF 来验证表单,用 Flask-Login 来处理登录会话和我将会演示 Flask-Script 的用法。也会有一些UI的代码,但将会非常少,因为这个教程最主要涉及的是Flask开发。在最后,你应该可以注册一个新用户,登录这个用户,刷新页面,你仍然是登录状态,最后当你退出的时候,将会从服务器中清除用户的会话。

更具体地说,在这个教程里将会出现下面的内容:

  • 在数据里创建用户表(使用sqlite的情况),这个表包含姓名,姓氏,电子邮件地址,用户名和密码等字段。密码为了安全原因将被散列。
  • 创建2个WTForms,用户登录和注册。它们将被用于登录和注册的数据验证。
  • 创建4个验证终端:/api/auth/verify_auth, /api/auth/login, /api/auth/logout, 和 /api/auth/signup
  • jquery 创建一个轻量级的UI来演示前面提到的 APIs 验证。

好吧,谈论的太多而且没有足够的编码。让我们开始吧。

第1步。安装依赖

Flask 是一个非常灵活的框架,它为日志记录,表单验证和数据库抽象(ORM)提供了许多不同的扩展。在这个教程,我将会演示如何使用这4个常用的Flask扩展。

$ pip install Flask-Script
$ pip install Flask-SQLAlchemy
$ pip install Flask-WTF
$ pip install Flask-Login
$ pip freeze > requirements.txt

第2步:创建文件

创建一个用户验证系统,我们将为html页面和用户模块(文件夹)创建一个前端模块来处理用户验证和数据库访问。然后我们为验证终端像登录和退出添加另外一个应用程序接口 auth.py 。为了使所有东西协同工作,我们将在使用过程中创建一些有用的函数。

$ cd DoubleDibz
$ touch manage.py
$ mkdir app/users
$ mkdir app/static
$ mkdir app/frontend
$ touch app/frontend/__init__.py
$ touch app/frontend/controller.py
$ touch app/models.py
$ touch app/common/helpers.py
$ touch app/common/response.py
$ touch app/users/UserModels.py
$ touch app/users/UserConstants.py
$ touch app/users/UserForms.py
$ touch app/users/__init__.py
$ touch app/api/auth.py

我们当前的结构:

DoubleDibz  
├── app
│   ├── __init__.py
│   ├── api
│   │   ├── __init__.py
│   │   ├── auth.py
│   │   └── helloworld.py
│   ├── app.py
│   ├── common
│   │   ├── __init__.py
│   │   ├── constants.py
│   │   ├── helpers.py
│   │   └── response.py
│   ├── config.py
│   ├── extensions.py
│   ├── frontend
│   │   ├── __init__.py
│   │   └── controller.py
│   ├── models.py
│   ├── static
│   ├── templates
│   └── users
│       ├── UserConstants.py
│       ├── UserForms.py
│       ├── UserModels.py
│       └── __init__.py
├── manage.py
├── requirements.txt
└── run.py

我不会讨论添加或者修改的每一行代码,例如 app/common/helpers.pyapp/common/response.py,但他们在构建系统的时候是至关重要的。为了了解大局,去看一下最后的代码

第3步: 编辑 extensions.py 和 app.py

初始化 Flask-SQLAlchemyFlask-WTFFlask-Login 扩展。

$ vim app/extensions.py

输入下面的内容

# Flask-SQLAlchemy extension instance
from flask.ext.sqlalchemy import SQLAlchemy
db = SQLAlchemy()

# Flask-Login
from flask.ext.login import LoginManager
login_manager = LoginManager()

# Flask-WTF csrf protection
from flask_wtf.csrf import CsrfProtect
csrf = CsrfProtect()

这些代码将是对象的扩展。下一步,我们将会在我们的Flask应用程序中初始化这些扩展。

$ vim app/app.py

添加这些到最上面

from .models import User
from .extensions import db, login_manager, csrf

添加下面的内容到 configure_extensions函数

def configure_extensions(app):
   # flask-sqlalchemy
   db.init_app(app)

   # flask-login
   login_manager.login_view = 'frontend.index'
   login_manager.refresh_view = 'frontend.index'

   @login_manager.user_loader
   def load_user(id):
      return User.query.get(id)

   login_manager.setup_app(app)

   # flask-wtf
   csrf.init_app(app)

第4步:创建 userModels.py

好了,现在我们已经初始化了扩展。我们能够从用户模型开始。 在 SQLAlchemy 中,每一个数据模型对应到数据库的表: 用户模型将包含所有与用户有关的信息和作为最重要的信息记录在网站上。

from werkzeug import generate_password_hash, check_password_hash

from flask.ext.login import UserMixin  
from ..common.helpers import JsonSerializer, get_current_time
from ..extensions import db

import UserConstants

class UserJsonSerializer(JsonSerializer):
    __json_public__ = ['id', 'email', 'user_name']
    __json_modifiers__ = {
      'role_code' : ['role', (lambda code : UserConstants.USER_ROLE[code])]
    }

class User(db.Model, UserMixin, UserJsonSerializer):

   __tablename__ = "user"
   def __repr__(self):
      return '<User %r>' % (self.user_name)

   id            = db.Column(db.Integer, primary_key = True)
   first_name    = db.Column(db.String(UserConstants.STRING_LEN), nullable=False)
   last_name     = db.Column(db.String(UserConstants.STRING_LEN), nullable=False)
   user_name     = db.Column(db.String(UserConstants.STRING_LEN),  index = True, unique = True, nullable=False)
   email         = db.Column(db.String(UserConstants.STRING_LEN), index = True, unique = True, nullable=False)
   created_on    = db.Column(db.DateTime, nullable=False, default = get_current_time)
   role_code = db.Column(db.SmallInteger, default=UserConstants.USER, nullable=False)

   # User Password
   _password = db.Column('password', db.String(UserConstants.PW_STRING_LEN), nullable=False)

   def _get_password(self):
      return self._password

   def _set_password(self, password):
      self._password = generate_password_hash(password)

   password = db.synonym('_password',
                          descriptor=property(_get_password,
                                              _set_password))

   def check_password(self, password):
      if self.password is None:
         return False
      return check_password_hash(self.password, password)

   # methods
   @classmethod
   def authenticate(cls, user_name, password):
      user = User.query.filter(db.or_(User.user_name == user_name)).first()

      if user:
         authenticated = user.check_password(password)
      else:
         authenticated = False
      return user, authenticated

   @classmethod
   def is_user_name_taken(cls, user_name):
      return db.session.query(db.exists().where(User.user_name==user_name)).scalar()

   @classmethod
   def is_email_taken(cls, email_address):
      return db.session.query(db.exists().where(User.email==email_address)).scalar()

有几件事值得在这里说一下

  • 密码字段:注意如何用 SQLAlchemy 的 同义词 和 描述器来定义密码字段的。密码本质上是 _password 属性的镜像和我们使用描述器去定义如何得到密码和设置密码的行为,为 _get_password_set_password 定义。当设置密码的时候,我们使用 generate_password_hash 来保存散列密码来替换用户的密码字符串。

  • 混合 UserJsonSerializer:继承自 JsonSerializerr (见 app/common/helpers 的定义)。它提供了 to_json 函数将反序列化模型实例转为 json 格式。示例(在第五步设置了 Flask-Script 之后):

$ python manage.py shell
>>> user = models.User.query.all()[0];
>>> user.to_json()
{'role': 'user', 'user_name': u'spchuang', 'id': 1, 'email': u"test@gmail.com"}
  • 混合 UserMixin : 为Flask-Login所需要的提供默认的实现

第5.1步:用 Flask-script 创建数据库

现在我们有了用户模型,可以创建一个实际的数据库表。为了做到这一点,我们将使用 Flask-script 。这个扩展允许我们创建方便的脚本,像初始化数据库和使用应用程序上下文来运行一个shell脚本。

vim manager.py
from flask.ext.script import Manager, Shell, Server
from flask import current_app
from app import create_app
from app.extensions import db
import app.models as Models
from app.config import DefaultConfig
import os

def create_my_app(config=DefaultConfig):
  return create_app(config)

manager = Manager(create_my_app)

# runs Flask development server locally at port 5000
manager.add_command("runserver", Server(host="0.0.0.0", port=5000))

# start a Python shell with contexts of the Flask application
@manager.shell
def make_shell_context():
   return dict(app=current_app, db=db, models=Models)

# init/reset database
@manager.command
def initdb():
    db.drop_all(bind=None)
    db.create_all(bind=None)

    # add sample user
    user = Models.User(
            first_name=u'Sam',
            last_name=u'Chuang',
            user_name=u'spchuang',
            password=u'123456',
            email=u"test@gmail.com")
    db.session.add(user)
    db.session.commit()


if __name__ == "__main__":
    manager.run()

现在,通过运行下面的命令,你能够初始化数据库来创建用户表和示例用户,后面我们将使用它来测试和登录。

$ python manage.py initdb

你应该会看到一个文件在 /tmp/test.db 创建,因为我们配置了SQLAlchemy 的sqlite db 路径。为了确定用户是否真的创建了,你能够使用 Flask-Script's 脚本命令。

$ python manage.py shell
>>> users = models.User.query.all()
>>> print users
[<User u'spchuang'>]
>>> print users[0].password
pbkdf2:sha1:1000$x8kBSPNw$92b15d93720e001a1cf6343f2b589cff0b198024

注意如何显示用户的用户名。 这是在用户模型对象里定义了 __repr__ 方法。同样,看一下密码字段如何存储为散列。除了使用管理脚本,你也可以使用 sqlite3 接口来验证 db 数据。

$ sqlite3 /tmp/test.db
SQLite version 3.8.3 2014-02-03 14:04:11
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> select user_name,email from user;
spchuang|test@gmail.com'
sqlite> select * from sqlite_master where type='table';
table|user|user|2|CREATE TABLE user (
    id INTEGER NOT NULL,
    first_name VARCHAR(64) NOT NULL,
    last_name VARCHAR(64) NOT NULL,
    user_name VARCHAR(64) NOT NULL,
    email VARCHAR(64) NOT NULL,
    created_on DATETIME NOT NULL,
    password VARCHAR(80) NOT NULL,
    role_code SMALLINT NOT NULL,
    PRIMARY KEY (id)
)

第5.2步:测试数据库和模型

目前我们定义了用户模型和创建了用户表。让我们退一步,做一些测试以确保一切工作都是正常的。把一个内容充实的项目分成小的并且是可测试的组件是好的。这允许我们建立我们所走的基线。

$ python manage.py shell
>>> user, authenticated = models.User.authenticate('random_user', 'test')
>>> print user, authenticated
None False
>>> user, authenticated = models.User.authenticate('spchuang', 'test')
>>> print user, authenticated
<User u'spchuang'> False
>>> user, authenticated = models.User.authenticate('spchuang', '123456')
>>> print user, authenticated
<User u'spchuang'> True
>>> print models.User.is_user_name_taken('spchuang')
True
>>> print models.User.is_email_taken('test@gmail.com')
True

亲爱的,看起来所有的功能像我们预期地工作着。一般来说,在shell中验证函数是非常方便的,而且是一种快速迭代代码的方式。但接下来,我们可能想添加单元测试来覆盖那些逻辑,因为我们不能每次都能测试它们。自动化测试能保证所有测试的覆盖范围和显示是否有任何新的代码(打破测试用例)。测试不在这个教程的范围,但是我将可能会找个时间来给出一个示例来展示如何在Flask中测试。

注意: 剩下的内容请看 在Flask中设置用户身份验证(下)【翻译】

参考网站

http://blog.sampingchuang.com/setup-user-authentication-in-flask/

原文:http://blog.sampingchuang.com/setup-user-authentication-in-flask/

【上一篇】建立一个简单hello world的Flask API 应用【翻译】

【下一篇】在Flask中设置用户身份验证(下)【翻译】