赞
踩
现在web系统基本都会有用户功能,一个良好的用户认证框架可以很轻松的实现一个轻巧、安全、可扩展的用户认证功能。Flask按照一般的用户认证流程,主要使用三个扩展模块进行用户的认证管理。
Flask-Login:管理已经认证的用户信息
Werkzeug:计算密码的散列值以及用户认证处理
itsdangerous:生成和核对加密token,主要用来实现用户注册邮件确认,密码找回,密码重置
对于用户的密码字段,保存原始密码是一个最忌讳的。因为一旦后台服务器被攻破,很容易导致用户的信息遭到泄露。而且很多用户多个网站通常喜欢使用同一个密码,因此风险就更加大了。所以常用的方法是保存免得的散列值。
Flask使用Werkzeug计算和核对密码的散列值。Werkzeug提供两个方法实现这一功能。
generate_password_hash(password, method = pbkdf2:sha1, salt_length = 8):该方法是散列原始密码的,接受三个参数,第一个为待散列的原始密码,必传项,第二个为采用的散列方法,第三个为散列时候的加盐字符串,这两个为非必传项,通常采用默认的就足够了。返回值为密码的散列值。
check_password_hash(hash, password):该方法是校验数据库存储的散列值和原始密码是否一致的,用来校验用户密码是否正确的。该方法接受两个参数,第一个为数据库取出来的hash值,第二个为带校验的密码。如果返回True,则表明密码正确。
为了是User数据库模型支持密码的校验,修改User如下:
- from werkzeug.security import generate_password_hash, check_password_hash
-
- class User(db.Model):
- #...
- password_hash = db.Column(db.String(128))
-
- @property #该注释可以使字段直接通过方法名访问
- def password(self):
- raise AttributeError('password is not a readable attribute')
-
- @password.setter #字段password,直接赋值然后调用该方法
- def password(self, password):
- self.password_hash = genrate_password_hash(password)
-
- def verify_password(self, password): //校验密码
- return check_password_hash(self.password_hash, password)
把蓝本放在不同的包中方便,可以保证项目结构清晰。创建单独的用户认证蓝本,在app中创建auth包, 编辑初始文件创建蓝本app/auth/__init__.py
from flask import Blueprint
auth = Blueprint('auth', __name__)
from . import views
- from flask import render_template
- from . import auth
-
- @auth.route('/login')
- def login():
- return render_template('auth/login.html')
Flask-Login 是个非常有用的小型扩展,专门用来管理用户认证系统中的认证状态,且不依赖特定的认证机制。首先安装flask-login
(venv) F:\python\flasky>pip install flask-login
is_authenticated() 如果用户已经登录,必须返回True,否则返回False
is_active() 如果允许用户登录,必须返回True,否则返回False。如果要禁用账户,可以返回False
is_anonymous() 对普通用户必须返回False
get_id() 必须返回用户的唯一标识符,使用Unicode 编码字符串
当然Flask-Login提供了一个更简单的方法,直接继承UserMixin即可,UserMixin中已经实现了这些默认方法。修改app/modes.py
- from flask.ext.login import UserMixin
-
- class User(UserMixin, db.Model):
- __tablename__ = 'users'
- id = db.Column(db.Integer, primary_key = True)
- email = db.Column(db.String(64), unique = True, index = True) #值唯一且建立索引
- username = db.Column(db.String(64), unique = True, index = True)
- password_hash = db.Column(db.String(128))
- role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
修改玩User模型之后,新增登录表单app/auth/forms.py
- from flask.ext.wtf import Form
- from wtforms import StringField, PasswordField, BooleanField, SubmitField
- from wtforms.validators import Required, Length, Email
-
- class LoginForm(Form):
- email = StringField('Email', validators = [Required(), Length(1, 64), Email()])
- password = PasswordField('Password', validators=[Required()])
- remember_me = BooleanField('Keep me logged in')
- submit = SubmitField('Log In')
- <ul class = "nav navbar-nav navbar -right">
- {% if current_user.is_authenticated() %} //current_user 有框架Flask-Login 定义
- <li><a href="{{ url_for('auth.logout') }}">退出</a></li>
- {% else %}
- <li><a href="{{ url_for('auth.login') }}">登录</a></li>
- {% endif %}
- </ul>
- from flask import render_template, redirect, request, url_for, flash
- from flask.ext.login import login_user
- from . import auth
- from ..models import User
- from .forms import LoginForm
-
- @auth.route('/login', methods=['GET', 'POST'])
- def login():
- form = LoginForm()
- if form.validate_on_submit():
- user = User.query.filter_by(email=form.email.data).first()
- if user is not None and user.verify_password(form.password.data):
- login_user(user, form.remember_me.data)
- return redirect(request.args.get('next') or url_for('main.index'))
- flash('Invalid username or password.')
- return render_template('auth/login.html', form=form)
- {% extends "base.html" %}
- {% import "bootstrap/wtf.html" as wtf %}
-
- {% block title %}登录{% endblock %}
-
- {%block page_content %}
- <div class = "page-header">
- <h1>登录</h1>
- </div>
- <div class = "col-md-4">
- {{ wtf.quick_form(form) }}
- </div>
退出登录
- from flask.ext.login import logout_user, login_required
-
- @auth.route('/logout')
- @login_required
- def logout():
- logout_user()
- flask('已经退出登录')
- return redirect(url_for('main.index'))
- from flask.ext.wtf import Form
- from wtforms import StringField, PasswordField, BooleanField, SubmitField
- from wtforms.validators import Requried, Length, Email, Regexp, EqualTo
- from wtforms import ValidationError
- from ..models import User
-
- class RegistrationForm(Form):
- email = StringField('Email', validators = [Required(), Length(1,64), Email()])
- username = StringField('Username', validators = [Required(), Length(1, 64), Regexp('^[A-Za-z][A-Za-z0-9_.]*$', 0, '用户名必须以字母开头,','并且只能包含字母数字下划线')])
- password = PasswordField('Password', validators = [Required(), EqualTo('password2', message = '两次密码必须一样')])
- password2 = PasswordField('Confirm password', validators = [Required()])
- submit = SubmitField('Register')
-
- #这种已validate_+字段名为名字的方法,跟validators里面的验证函数作用一样
- def validate_email(self, field):
- if User.query.filter_by(email=field.data).first()
- raise ValidationError('邮箱已经被注册')
-
- def validate_username(self, field):
- if User.query.filter_by(username=field.data).first()
- raise ValidationError('用户名已经存在')
路由定义 app/auth/views.py
- @auth.route('register', method=['GET','POST'])
- def register():
- form = RegistrationForm()
- if form.validate_on_submit():
- user = User(email = form.email.data, username = form.username.data, password = form.password.data)
- db.session.add(user)
- flask('注册成功')
- return redirect(url_for('auth.login'))
- return render_template(url_for('auth/register.html', form = form))
修改User模型,让其具备生成和校验令牌的功能
- from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
- from flask import current_app
- from . import db
- class User(UserMixin, db.Model):
- # ...
- confirmed = db.Column(db.Boolean, default=False)
-
- def generate_confirmation_token(self, expiration=3600):
- s = Serializer(current_app.config['SECRET_KEY'], expiration) #生成Serilizer方法实例
- return s.dumps({'confirm': self.id}) #生成令牌
-
- def confirm(self, token): #确认令牌
- s = Serializer(current_app.config['SECRET_KEY'])
- try:
- data = s.loads(token)
- except:
- return False
- if data.get('confirm') != self.id://令牌中id和当前session中的id是否一直,防止恶意认证
- return False
- self.confirmed = True
- db.session.add(self)
- return True
- from ..email import send_email
- @auth.route('/register', methods = ['GET', 'POST'])
- def register():
- form = RegistrationForm()
- if form.validate_on_submit():
- # ...
- db.session.add(user)
- db.session.commit()
- token = user.generate_confirmation_token()
- send_email(user.email, 'Confirm Your Account',
- 'auth/email/confirm', user=user, token=token)
- flash('A confirmation email has been sent to you by email.')
- return redirect(url_for('main.index'))
- return render_template('auth/register.html', form=form)
Dear {<!-- -->{ user.username }},
Welcome to Flasky!
To confirm your account please click on the following link:
{<!-- -->{ url_for('auth.confirm', token=token, _external=True) }}
Sincerely,
The Flasky Team
Note: replies to this email address are not monitored.
- from flask.ext.login import current_user
- @auth.route('/confirm/<token>')
- @login_required
- def confirm(token):
- if current_user.confirmed:
- return redirect(url_for('main.index'))
- if current_user.confirm(token):
- flash('You have confirmed your account. Thanks!')
- else:
- flash('The confirmation link is invalid or has expired.')
- return redirect(url_for('main.index'))
app/auth/views.py:重新发送账户确认邮件
- @auth.route('/confirm')
- @login_required
- def resend_confirmation():
- token = current_user.generate_confirmation_token()
- send_email(current_user.email, 'Confirm Your Account',
- 'auth/email/confirm', user=current_user, token=token)
- flash('A new confirmation email has been sent to you by email.')
- return redirect(url_for('main.index'))
- @auth.before_app_request
- def before_request():
- if current_user.is_authenticated() \
- and not current_user.confirmed \
- and request.endpoint[:5] != 'auth.':
- and request.endpoint != 'static':
- return redirect(url_for('auth.unconfirmed'))
-
- @auth.route('/unconfirmed')
- def unconfirmed():
- if current_user.is_anonymous() or current_user.confirmed:
- return redirect(url_for('main.index'))
- return render_template('auth/unconfirmed.html')
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。