专注细节
努力进步

Flask-User Login

User Logins

Configuration

As in previous chapters, we start by configuring the Flask extensions that we will use. For the login system we will use two extensions. Flask-Login and Flask-OpenID. (configured as fllows in init.py)

import os
from flask.ext.login import LoginManager
from flask.ext.openid import OpenID
from config import basedir

lm = LoginManager()
lm.init_app(app)
oid = OpenID(app, os.path.join(basedir, 'tmp'))

The Flask-OpenID extension requires a path to a temp folder(tmp) where files can be stored.

Revisiting our User model

The Flask-Login extension expects certain methods to be implemented in our User class.

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    nickname = db.Column(db.String(64), index=True, unique=True)
    email = db.Column(db.String(120), index=True, unique=True)
    posts = db.relationship('Post', backref='author', lazy='dynamic')

    def is_authenticated(self):
        return True

    def is_active(self):
        return True

    def is_anonymous(self):
        return False

    def get_id(self):
        try:
            return unicode(self.id)  # python 2
        except NameError:
            return str(self.id)  # python 3

    def __repr__(self):
        return '<User %r>' % (self.nickname)

These methods have misleading names.

User loader callback

Now we are ready to start implementing the login system using the Flask-Login and Flask-OpenID extensions.

First, we have to write a function that loads a user from the database.

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

This function is registered with Flask-Login through the lm.user_loader decorator.Also, the user ids in Flask-Login are always unicode strings, When we send the id to Flask-SQLAlchemy, we need to make a conversion to a integer.

The login view function

from flask import render_template, flash, redirect, session, url_for, request, g
from flask.ext.login import login_user, logout_user, current_user, login_required
from app import app, db, lm, oid
from forms import LoginForm
from models import User

@app.route('/login', methods=['GET', 'POST'])
@oid.loginhandler
def login():
    if g.user is not None and g.user.is_authenticated():
        return redirect(url_for('index'))
    form = LoginForm()
    if form.validate_on_submit():
        session['remember_me'] = form.remember_me.data
        return oid.try_login(form.openid.data, ask_for=['nickname', 'email'])
    return render_template('login.html', 
                           title='Sign In',
                           form=form,
                           providers=app.config['OPENID_PROVIDERS'])

The oid.loginhandler tells Flask-OpenID that this is our login view function. Frist, we check if g.user is set to an authenticated user, in that case we just redirect to the index page. The idea here is that if there is a logged in user already we will not do a second login.
The g global is setup by Flask as a place to store and share data during the life of a request.
The url_for function that we used in the redirect call is defined as a clean way and better way to obtain the URL like redirect(‘/index’).

When we get a data back from the login form, we need to do two things. First we store the value of the remenber_me boolean in the flask session,, not to be confused with the db.session from Flask-SQLAlchemy. We’ve seen that the flask.g object stores and shares data though the life of a request. The flask.session provides a much more complex service along those lines. Once data is stored in the session object it will be available during that request and any future requests made by the same client.

The oid.try_login call is the call that riggers the user authentication through Flask-OpenID. This function takes two arguments, the openid given by the user and a list of data items that we want from the OpenID provider.

The OpenID authentication happens asynchronously. Flask-OpenID will call a function that is registered with the oid.after_login decorator if the authentication is sucessful. if the authentication fails the user will be taken back to the login page.

The Flask-OpenID login callback

As mention in the last, here is our implementation fo the after_login function.

@oid.after_login
def after_login(resp):
    if resp.email is None or resp.email == "":
        flash('Invalid login. Please try again.')
        return redirect(url_for('login'))
    user = User.query.filter_by(email=resp.email).first()
    if user is None:
        nickname = resp.nickname
        if nickname is None or nickname == "":
            nickname = resp.email.split('@')[0]
        user = User(nickname=nickname, email=resp.email)
        db.session.add(user)
        db.session.commit()
    remember_me = False
    if 'remember_me' in session:
        remember_me = session['remember_me']
        session.pop('remember_me', None)
    login_user(user, remember = remember_me)
    return redirect(request.args.get('next') or url_for('index'))

The resp argument contains information returned by the OpenID provider.
The first if is just for validation, we require a valid email, so if a email was not provided we cannot log the user in.
Next, we search our database whether the email is found in our database. If not, we add new user to our database. Some providers may not provide the nickname, so if not provided we use the part of the email before ‘@’.
Also, we load the remember_me value from the Flask session to know whether we should stored in the login view function.
Then we call Flask-Login’s login_user function to register a valid login.
Finally, in the last we redirect to the next page, or the index page if the next page is not provided.
The Flask-Login will login the original URL as the next page, but in our project, we defined in init.py:

lm = LoginManager()
lm.init_app(app)
lm.login_view = 'login'

The g.user global

In the login view function, we check g.user to determine if a user is already logged in. To implement this, we will use the before_request event from Flask.

@app.before_request
def before_request():
    g.user = current_user

The current_user global is set by Flask_Login, so we just put a copy in the g object to have better access to it.

The index view

We have left our index view function using fake objects, because at the time we did not have users or posts in our system. Well, we have users now, so let’s hook that up:

@app.route('/')
@app.route('/index')
@login_required
def index():
    user = g.user
    posts = [
        { 
            'author': {'nickname': 'John'}, 
            'body': 'Beautiful day in Portland!' 
        },
        { 
            'author': {'nickname': 'Susan'}, 
            'body': 'The Avengers movie was so cool!' 
        }
    ]
    return render_template('index.html',
                           title='Home',
                           user=user,
                           posts=posts)

Only two changes to this function. First, we have added the login_required decorator. This will ensure that this page is only seen logged in users.
The second change is that we pass g.user down to the template, instead of the fake object.

Logging out

The view function for logging out is extremely simple:

@app.route('/logout')
def logout():
    logout_user()
    return redirect(url_for('index'))

Also we need to modify the template to add a logout link.

<html>
  <head>
    {% if title %}
    <title>{{ title }} - microblog</title>
    {% else %}
    <title>microblog</title>
    {% endif %}
  </head>
  <body>
    <div>Microblog:
        <a href="{{ url_for('index') }}">Home</a>
        {% if g.user.is_authenticated() %}
        | <a href="{{ url_for('logout') }}">Logout</a>
        {% endif %}
    </div>
    <hr>
    {% with messages = get_flashed_messages() %}
    {% if messages %}
    <ul>
    {% for message in messages %}
        <li>{{ message }} </li>
    {% endfor %}
    </ul>
    {% endif %}
    {% endwith %}
    {% block content %}{% endblock %}
  </body>
</html>
分享到:更多 ()