Flask 学习 七 用户认证
使用werkzeug 实现密码散列
from werkzeug.security import generate_password_hash,check_password_hash class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True,index=True)
role_id=db.Column(db.Integer,db.ForeignKey('roles.id'))
password_hash=db.Column(db.String(128))
@property
def password(self):
raise AttributeError('密码不是一个可读属性') #只写属性
@password.setter
def password(self,password):
self.password_hash = generate_password_hash(password) def verify_password(self,password):
return check_password_hash(self.password_hash,password) def __repr__(self):
return '<User %r>' % self.username
密码散列化测试tests/test_url_model.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import unittest
from app.models import User
class UserModelTestCase(unittest.TestCase):
def test_password_setter(self):
u = User(password='cat')
self.assertTrue(u.password_hash is not None)
def test_no_password_getter(self):
u = User(password='cat')
with self.assertRaises(AttributeError):
u.password
def test_password_verification(self):
u = User(password='cat')
self.assertTrue(u.verify_password('cat'))
self.assertFalse(u.verify_password('dog'))
def test_password_salts_are_random(self):
u =User(password='cat')
u2=User(password='cat')
self.assertTrue(u.password_hash != u2.password_hash)
创建用户认证蓝本
app/auth/__init__.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from flask import Blueprint
auth = Blueprint('auth',__name__)
from . import views
app/auth/views.py 蓝本路由和视图函数
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from flask import render_template
from . import auth @auth.route('/login')
def login():
return render_template('auth/login.html')
app/__init__.py 注册蓝本
from .auth import auth as auth_blueprint
app.register_blueprint(auth_blueprint,url_prefix='/auth')
安装flask-login插件
pip install flask-login
修改User模型,支持用户登陆 app/models.py
from flask_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)
role_id=db.Column(db.Integer,db.ForeignKey('roles.id'))
password_hash=db.Column(db.String(128))
app/__init__.py 初始化Flask_Login
from flask_login import LoginManager
#初始化Flask-Login
login_manager=LoginManager()
login_manager.session_protection='strong' # 记录客户端ip,用户代理信息,发现异动,登出用户,可以设置不同等级None,'basic','strong'
login_manager.login_view='auth.login' # 设置登录页面端点,蓝本的名字也要加到前面 def create_app(config_name):
#....
login_manager.init_app(app) #初始化登陆
#....
app/models.py 加载用户的回掉函数
from . import login_manager # 加载用户的回调函数
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
# 加载用户回掉函数,接收以Unicode字符串表示的用户标识符,如果能找到这个用户必须返回用户对象,否则返回None
保护路由
为了只让认证的用户访问,未认证的用户会拦截请求,发往登陆页面
#保护路由
@app.route('/secret')
@login_required
def secret():
return '只有认证后的用户才能登陆'
添加登陆表单
app/auth/forms.py
from flask_wtf import FlaskForm
from wtforms import StringField,PasswordField,BooleanField,SubmitField
from wtforms.validators import DataRequired,Length,Email class LoginForm(FlaskForm):
email=StringField('邮箱',validators=[DataRequired(),Length(1,64),Email()])
password=PasswordField('密码',validators=[DataRequired()])
remember_me=BooleanField('保持登陆')
submit = SubmitField('登陆')
auth/login.html 用wtf.quick_form()渲染表单
{% extends 'base.html' %}
{% import'bootstrap/wtf.html' as wtf %}
{% block title %}Flask-登陆{% endblock %}
{% block page_content %}
<div class="page-header">
<h1>登陆</h1>
</div>
<div class="col-md-4">
{{ wtf.quick_form(form) }}
</div>
{% endblock %}
base.html # current_user.is_authenticated 如果是匿名用户登陆is_authenticated返回False,这个方法可以判断当前用户是否登陆
<ul class="nav navbar-nav navbar-right">
{% if current_user.is_authenticated %}
<li><a href="{{ url_for('auth.logout') }}">退出登陆</a></li>
{% else %}
<li><a href="{{ url_for('auth.login') }}">立即登陆</a></li>
{% endif %}
</ul>
登入用户
app/auth/views.py #登陆路由
from flask import render_template,redirect,request,url_for,flash
from flask_login import login_user,login_required,logout_user,current_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): # verify_password会验证表单数据
login_user(user,form.remember_me.data) # 如果为True则为用户生成长期有效的cookies
return redirect(request.args.get('next') or url_for('main.index'))# next保存原地址(从request.args字典中读取)
flash('用户名或密码无效')
return render_template('auth/login.html',form=form)
更新登陆模板auth/login.html
{% extends 'base.html' %}
{% import'bootstrap/wtf.html' as wtf %}
{% block title %}Flask-登陆{% endblock %}
{% block page_content %}
<div class="page-header">
<h1>登陆</h1>
</div>
<div class="col-md-4">
{{ wtf.quick_form(form) }}
</div>
{% endblock %}
登出用户
auth/views.py
from flask_login import login_user,login_required,logout_user,current_user @auth.route('/logout')
@login_required
def logout():
logout_user()
flash('你已经退出')
return redirect(url_for('main.index'))
测试登陆 index.html
<h1>Hello,
{% if current_user.is_authenticated %}
{{ current_user.username }}
{% else %}
访客
{% endif %}!
</h1>
注册新用户
添加用户注册表单
from flask_wtf import FlaskForm
from wtforms import StringField,PasswordField,BooleanField,SubmitField
from wtforms.validators import DataRequired,Length,Email,Regexp,EqualTo
from ..models import User class RegistrationForm(FlaskForm):
email = StringField('邮箱', validators=[DataRequired(), Length(1, 64), Email()])
username= StringField('用户名', validators=[DataRequired(), Length(1, 64), Regexp('^[A-Za-z][A-Za-z0-9_.]*$',0,'用户名必须是字母,数字,点号,下划线')])
password = PasswordField('密码', validators=[DataRequired(),EqualTo('password2',message='密码不一致')])
password2 = PasswordField('再次输入密码', validators=[DataRequired()])
submit = SubmitField('注册')
def validate_email(self,filed):
if User.query.filter_by(email=filed.data).first():
raise ValidationError('邮箱已被注册')
def validate_username(self,filed):
if User.query.filter_by(username=filed.data).first():
raise ValidationError('用户名已被使用')
auth/register.html
{% extends 'base.html' %}
{% import'bootstrap/wtf.html' as wtf %}
{% block title %}Flask-注册{% endblock %}
{% block page_content %}
<div class="page-header">
<h1>注册</h1>
</div>
<div class="col-md-4">
{{ wtf.quick_form(form) }}
</div>
{% endblock %}
auth/login.html 添加链接到注册页面
<p>新用户?<a href="{{ url_for('auth.register') }}">点击这里注册</a></p>
app/auth/views.py
@auth.route('/register',methods=['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)
flash('你现在已经登陆了')
return redirect(url_for('auth.login'))
return render_template('auth/register.html',form=form)
确认账户
使用isdangerous生成确认令牌
app/models.py 确认用户账户
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): #生成令牌,有效期1小时
s = Serializer(current_app.config['SECRET_KEY'],expiration)
return s.dumps({'confirm':self.id})
def confirm(self,token): # 检验令牌 如果通过把confirmed字段设为True
s=Serializer(current_app.config['SECRET_KEY'])
try:
data = s.loads(token)
except:
return False
if data.get('confirm') !=self.id: # 检查令牌id是否存储是已登录用户进行匹配
return False
self.confirmed=True
db.session.add(self)
return True
发送确认邮件
auth/views.py
from ..email import send_mail @auth.route('/register',methods=['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)
db.session.commit()
token = user.generate_confirmation_token()
send_mail(user.email,'确认你的账户','auth/email/confirm',user=user,token=token)
flash('我们已经给你发送了一封确认邮件')
return redirect(url_for('main.index'))
return render_template('auth/register.html',form=form)
auth/email/cinfirm.txt
亲爱的,{{ user.username }}
欢迎来到 Flasky
为了确认您的账户,请点击以下链接: {{ url_for('auth.confirm',token=token,_external=True) }} #返回绝对路径 Flasky 团队
auth/views.py
from flask_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('你已经确认了你的账户,谢谢')
else:
flash('确认链接失效或过期')
return redirect(url_for('main.index'))
app/auth/views.py 使用before_app_request来全局使用处理程序中未确认的用户
@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')
auth/views.py 重新发送确认邮件
@auth.route('/confirm')
@login_required
def resend_confirmation():
token=current_user.generate_confirmation_token()
send_mail(current_user.email,'确认你的账户','auth/email/confirm',user=current_user,token=token)
flash('一封新的确认邮件已经发送到您的邮箱')
return redirect(url_for('main.index'))
管理账户:
修改密码 重设密码 修改电子邮件地址
https://github.com/Erick-LONG/flask_blog/commit/3748e70d9f986b066328185bcf417d8b8205ed8c https://github.com/Erick-LONG/flask_blog/commit/ce4a67d31f0d7d5f5c6719eebb193b2949c77b0c https://github.com/Erick-LONG/flask_blog/commit/f89ef3dac3819129165be4d9b2a67a2bb092cf34
Flask 学习 七 用户认证的更多相关文章
- Flask学习之五 用户登录
英文博客地址:http://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-v-user-logins 中文翻译地址:http:// ...
- 「Django」rest_framework学习系列-用户认证
用户认证:1.项目下utils文件写auth.py文件 from rest_framework import exceptions from api import models from rest_f ...
- Django 学习之用户认证组件auth与User对象
一.auth模块 from django.contrib import auth django.contrib.auth中提供了许多方法,这里主要介绍其中的三个. 1 .authenticate() ...
- Flask 学习 九 用户资料
资料信息 app/models.py class User(UserMixin,db.Model): #...... name = db.Column(db.String(64)) location ...
- Flask 学习 八 用户角色
角色在数据库中表示 app/models.py class Role(db.Model): __tablename__='roles' id = db.Column(db.Integer,primar ...
- flask学习(七):URL反转
1. 什么叫反转URL:从视图函数到url的转换叫做反转url 2. 反转url的用处: 1) 在页面重定向的时候,会使用url反转 2) 在模板中,也会使用url反转 3. 实例: 打印出了url
- Flask学习之六 个人资料和头像
英文博客地址:http://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-vi-profile-page-and-avatars ...
- flask设置cookie,设置session,模拟用户认证、模拟管理后台admin、模拟用户logout
设置cookie HTTP协议是无状态的,在一次请求响应结束后,服务器不会留下关于客户端状态的信息.但是对于某些web程序来说,客户端的信息有必要被记住,比如用户的登录状态,这样就可以根据用户的状态来 ...
- Django学习笔记(13)——Django的用户认证(Auth)组件,视图层和QuerySet API
用户认证组件的学习 用户认证是通过取表单数据根数据库对应表存储的值做比对,比对成功就返回一个页面,不成功就重定向到登录页面.我们自己写的话当然也是可以的,只不过多写了几个视图,冗余代码多,当然我们也可 ...
随机推荐
- (luogu1704)寻找最优美做题曲线 [TPLY]
寻找最优美做题曲线 题目链接:https://www.luogu.org/problemnew/show/P1704 题目大意: 求包含指定点的最长不降子序列(严格递增) 题解 首先我们发现 一个序列 ...
- [JSOI2008]球形空间产生器sphere
Sol 设一个dis,就有n+1个方程,消掉dis,就只有n个方程,组成一个方程组,高斯消元就好(建议建立方程时推一下,很简单) # include <bits/stdc++.h> # d ...
- [SCOI2016]美味
按位从高往低贪心,枚举到第i位,只需要判断这2^i长度的区间是否有菜,用主席树就可以了 # include <bits/stdc++.h> # define RG register # d ...
- [BZOJ3668] [Noi2014] 起床困难综合症 (贪心)
Description 21 世纪,许多人得了一种奇怪的病:起床困难综合症,其临床表现为:起床难,起床后精神不佳.作为一名青春阳光好少年,atm 一直坚持与起床困难综合症作斗争.通过研究相关文献,他找 ...
- Spring【DAO模块】就是这么简单
前言 上一篇Spring博文主要讲解了如何使用Spring来实现AOP编程,本博文主要讲解Spring的DAO模块对JDBC的支持,以及Spring对事务的控制... 对于JDBC而言,我们肯定不会陌 ...
- 【机器学习】正则化的线性回归 —— 岭回归与Lasso回归
注:正则化是用来防止过拟合的方法.在最开始学习机器学习的课程时,只是觉得这个方法就像某种魔法一样非常神奇的改变了模型的参数.但是一直也无法对其基本原理有一个透彻.直观的理解.直到最近再次接触到这个概念 ...
- .net core 2使用ef core 2.0以db first方法创建实体类
先安装以下三个包: Install-Package Microsoft.EntityFrameworkCore.SqlServer Install-Package Microsoft.EntityFr ...
- JDK1.8源码(二)——java.lang.Integer 类
上一篇博客我们介绍了 java.lang 包下的 Object 类,那么本篇博客接着介绍该包下的另一个类 Integer.在前面 浅谈 Integer 类 博客中我们主要介绍了 Integer 类 和 ...
- 题目1031:xxx定律
题目描述: 对于一个数n,如果是偶数,就把n砍掉一半:如果是奇数,把n变成 3*n+ 1后砍掉一半,直到该数变为1为止. 请计算需要经过几步才能将n变到1,具体可见样例. 输入: 测试包含多个用例,每 ...
- 【.NetCore】基于jenkins以及gitlab的持续编译及发布
前沿 其实本来是想把标题叫做持续集成的,只是后来看看研究出的内容,就只有发布这一个动作,自动化测试等内容也未涉及到,所以改名叫持续编译及发布应该更加贴切吧? 问题背景 其实目前我们传统方式上的发布方式 ...