所在位置:

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

第6步:创建 UserForms.py

下一步就是定义我们的表单验证逻辑。数据检查是我们工作的中心。每当我们收到用户的一些信息。我们希望确定我们有创建用户和登录的所有数据。

from flask_wtf import Form
from wtforms import (BooleanField, TextField, HiddenField, PasswordField,
   DateTimeField, validators, IntegerField, SubmitField)
import UserConstants

class LoginForm(Form):
   login = TextField('user_name', [validators.Required()])
   password  = TextField('password',  [validators.Required()])
   remember_me = BooleanField('remember_me', default = False)

class SignupForm(Form):
   user_name   = TextField('user_name',   [
      validators.Length(
         min = UserConstants.MIN_USERNAME_LEN,
         max = UserConstants.MAX_USERNAME_LEN
      ),
      validators.Regexp(
         "^[a-zA-Z0-9]*$",
         message="Username can only contain letters and numbers"
      )
   ])
   first_name  = TextField('first_name', [validators.Required()])
   last_name   = TextField('last_name', [validators.Required()])
   email       = TextField('email', [validators.Required(), validators.Email()])
   password    = PasswordField(
      'New Password',
      [validators.Length(min=UserConstants.MIN_PASSWORD_LEN,max=UserConstants.MAX_PASSWORD_LEN)]
   )
   confirm     = PasswordField('Repeat Password', [
      validators.Required(),
      validators.EqualTo('password', message='Passwords must match')
   ])

这是相当直接的。我们定义了一个登录单表和另外一个注册表单。Flask-WTF 允许我们定义一些字段和指定特定的字段来验证。有些默认的验证是提供的,但是我们也可能自己定义。请参见用户名的逻辑验证,我们使用正则表达式限制了只能是字母和数字。

第7步:创建验证终端 api/auth.py

vim app/api/auth.py
# Users API for authentication
'''  
   Users API for authentication
'''  
from flask import (Blueprint, render_template, current_app, request,
                   flash, url_for, redirect, session, abort, jsonify)

from flask.ext.login import login_required, login_user, current_user, logout_user, confirm_login, login_fresh
from ..common import Response
from ..extensions import db

from ..users import User, SignupForm, LoginForm

auth = Blueprint('auth', __name__, url_prefix='/api/auth')

@auth.route('/verify_auth', methods=['GET'])
@login_required
def verify_auth():  
   return Response.make_data_resp(data=current_user.to_json())

@auth.route('/login', methods=['POST'])
def login():  
   """ POST only operation. check login form. Log user in """
   # 这里应该是 current_user.is_authenticated:
   if current_user.is_authenticated():
      return Response.make_success_resp(msg="You are already logged in")

   form = LoginForm()
   if form.validate_on_submit():
      user, authenticated = User.authenticate(form.login.data,
                                              form.password.data)
      if user :
         if authenticated:
            login_user(user, remember=form.remember_me.data)
            return Response.make_data_resp(data=current_user.to_json(), msg="You have successfully logged in")
         else:
            return Response.make_error_resp(msg="Invalid username or password", type="Wrong Authentication", code=422)
      else:
         return Response.make_error_resp(msg="Username does not exist", type="Wrong Authentication", code=422)

   return Response.make_form_error_resp(form=form)


@auth.route('/logout', methods=['POST'])
@login_required
def logout():
   """ logout user """
   session.pop('login', None)
   logout_user()
   return Response.make_success_resp(msg="You have successfully logged out")

@auth.route('/signup', methods=['POST'])
def signup():
   # 这里应该是 current_user.is_authenticated:
   if current_user.is_authenticated():
      # 这里应该是 return Response.make_success_resp("You're already signed up")
      return make_success_resp("You're already signed up")

   form = SignupForm()

   if form.validate_on_submit():
      # check if user_name or email is taken
      if User.is_user_name_taken(form.user_name.data):
         return Response.make_error_resp(msg="This username is already taken!", code=409)
      if User.is_email_taken(form.email.data):
         return Response.make_error_resp(msg="This email is already taken!", code=409)

      try:
         # create new user
         user = User()
         form.populate_obj(user)

         db.session.add(user)
         db.session.commit()

      except Exception as e:
         return Response.make_exception_resp(exception=e)

      # log the user in
      login_user(user)
      return Response.make_success_resp(msg="You successfully signed up! Please check your email for further verification.")

   return Response.make_form_error_resp(form=form)

这里我们定义了4个终端,将使用它们来进行身份验证。你可能想知道为什么我们需要 /api/auth/verify_auth。就像我之前提到的,我们创建一个纯API后端和用javascript驱动的前端。因此,作为javascript应用程序的第一次启动,我们需要确定用户是否已经登录了。这个终端不只是允许前端来启动应用程序状态。你将会在下面的UI部分看到一个快速的示例。

这个 Form.validate_on_submit 就像它的名字建议那样 - 验证输入的表单。它将确保每个字段都会传递我们为它定义的验证规则,否则就失败。

默认情况下,由于设置了 Flask-WTF,每个POST请示都会受到 csrf 的保护。跨站点请求伪造不同于其他黑客技术,恶意用户伪造他们的证书并代表你的行为。相反,黑客会在你已经的登录的网站上插入恶意的脚本来欺骗你的浏览器。在网站方面,这个行为是真实的,值得信任的,因为是直接来自你的,虽然你的浏览器不希望这样做。这里有很多常见的修复方式 - 其中一种就是 CSRF 保护。Flask-WTF 将会对每个 POST 请求都会验证一个散列令牌。

更新 app.py 包含我们的 API 蓝图认证。

# in app/app.py

from .api import helloworld, auth

DEFAULT_BLUEPRINTS = [
   helloworld,
   auth,
]

第8步:基本前端

在最后这一部分,我将简短地说一下实现简单的前端,和通过jquery实现的后端。

第一步,用一个新的控制器来服务静态 HTML。

$ vim app/frontend/controller.py
from flask import (Flask, Blueprint, render_template, current_app, request,
                   flash, url_for, redirect, session, abort, jsonify, send_from_directory)

frontend = Blueprint('frontend', __name__)

@frontend.route('/')
@frontend.route('/<path:path>')
def index(path=None):  
   return render_template('app.html')

这个控制器负责服务静态页面,在这种情况下, app/template/app.html

现在从 github 复制 app/templatesapp/static 目录。打开这个文件, 注意我们如何加载这两个 javascript 文件,jquery 和 我们自定义的用来跟后端通信的 javascript 文件。我们将会使用 bootstrap 样式。

// app/templates/app.html

{% block js_btm %}
<!-- Put javascript here !-->

   <script src="/static/desktop/vendors/jquery.js"></script>
   <script src="/static/desktop/js/main.js"></script>

{% endblock %}

因此我们所有的游戏计划都在 /static/desktop/js/main.js

// setup csrf token for all ajax calls
var csrftoken = $('meta[name=csrf-token]').attr('content');  
$.ajaxSetup({
    beforeSend: function(xhr, settings) {
        if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type)) {
         xhr.setRequestHeader("X-CSRFToken", csrftoken);
      }
    }
});

$(document).ready(function(){
   // initial check to see if user is logged in or not
   updateAuthStatus();


   // setup logged in view
   $('#logged-in-view button').click(function(){
      logout()
         .done(function(response){
            showLoggedout()
         }).fail(function(response){
            showLoggedIn();
         });
   });

   // setup signup view
   $('#signup-view #signup-form').submit(function(e) {
      e.preventDefault();
      var form = $(this);
      var signupData = extractFormInput(form);

      signup(signupData)
         .done(function(response){
            alert('You just created a new user');
            form.trigger('reset');
            updateAuthStatus();
         }).fail(function(response){
            alert('Something went wrong');
         });

   });
});

// helpers
// 这里应该是 function updateAuthStatus()
function updateAuth() {
   verifyAuth()
      .done(function(response){
         showLoggedIn(response.data.user_name)
      }).fail(function(response){
         showLoggedout()
      });
}
function extractFormInput(form) {
   var inputs = form.serializeArray();
   var data = {};
   $.each(inputs, function(index, input) {
      data[input.name] = input.value;
   });
   return data;
}

function showLoggedIn(username) {
   // show logged in view and show username
   $("#logged-in-view span").text(username);
   $("#logged-out-view").addClass('hidden');
   $("#logged-in-view").removeClass('hidden');
}

function showLoggedout() {
   // show logged out view
   $("#logged-out-view").removeClass('hidden');
   $("#logged-in-view").addClass('hidden');
}

// API calls
function verifyAuth(callback) {
   var url = '/api/auth/verify_auth';
   return $.get(url);
}

function login(loginData){
   var url = '/api/auth/login';
   return $.post(url, loginData);
}

function logout() {
   var url = '/api/auth/logout';
   return $.post(url);
}

function signup(signupData) {
   var url = '/api/auth/signup';
   return $.post(url, signupData);
}

登录视图

登录之后的视图

注册视图

就这些了!现在你已经在Flask中创建了一个简单的用户系统,它能够创建新用户和用已经存在的用户来登录。在下面的文章,我将会讨论UI方面的工作和展示一个用Backbone创建用户验证的示例。

注意:我用 python2.6去运行下载的代码会报错,只要把 current_user.is_authenticated() 改成 current_user.is_authenticated 就可以了,因为 is_authenticatedcurrent_user 的一个属性,不是 current_user 的一个方法

参考网站

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

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

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

【下一篇】Mac常用软件