flask-login和flask-wtf

一、表单验证
前言
在Flask项目开发中针对提交表单的校验,可以使用Flask-WTF扩展库进行快速的字段校验,也可以进行页面快速渲染,并提供跨站请求伪造的保护功能。

1. 安装Flask-WTF

pip install flask-wtf

注意:
可以新建一个requirement.txt文档,里面写入将要导入的包

flask
flask-script
flask-blueprint
flask-sqlalchemy
pymysql
redis
flask-session

在Terminal中输入:pip install -r requirement.txt

2. 实现注册功能

2.1 注册表单模型定义
在定义的表单类中定义需要验证的username、password和password2字段,并实现如下校验:

校验密码password2和password相等
校验用户名是否存在
校验用户名的长度是否符合规范

users->forms.py
“`
# 导入扩展类
from flask_wtf import FlaskForm
# 导入验证字段
from wtforms import StringField, SubmitField, ValidationError
# 导入表单验证
from wtforms.validators import DataRequired, EqualTo

 from user.models import User

 class UserForm(FlaskForm):
     """
     登录注册表单验证
     """
     username = StringField('用户名', validators=[DataRequired()])
     password = StringField('密码', validators=[DataRequired()])
     password2 = StringField('确认密码', validators=[DataRequired(),
                                                 EqualTo('password', '密码不一致')]
                             )
     submit = SubmitField('提交')

     def validate_username(self, field):
         # 验证用户名是否重复
         if User.query.filter(User.username == field.data).first():
             raise ValidationError('用户名已存在')

         # 对用户名的长度进行判断
         if len(field.data) < 3:
             raise ValidationError('用户名长度不能少于3个字符')

         if len(field.data) > 6:
             raise ValidationError('用户名长度不能大于6个字符')

```

注意: 验证字段的方法名为: validate_字段(self, field)

2.2 定义注册视图函数
当HTTP请求为GET时,将表单验证对象返回给页面。
当HTTP请求为POST时,通过方法validate_on_submit()方法进行字段校验和提交判断,如果校验失败,则可以从form.errors中获取错误信息。
如果验证通过,则从form.字段.data中获取到字段的值。
users->views

@blue.route('/register/', methods=['GET', 'POST'])
def register():
    form = UserForm()
    if request.method == 'GET':
        return render_template('register.html', form=form)

    if request.method == 'POST':
        # 判断表单中的数据是否通过验证
        if form.validate_on_submit():
            # 获取验证通过后的数据
            username = form.username.data
            password = form.password.data
            # 保存
            user = User()
            user.username = username
            #加密 generate_password_hash(password)
            user.password = generate_password_hash(password)
            user.save()
            return redirect(url_for('user.login'))
        return render_template('register.html', form=form)

2.3 模板展示
注册模板采用继承父模板base.html的形式。在register.html模压中分析如下:

1\. 定义字段名: {{ form.字段.label }}

2\. 定义input输入框: {{ form.字段 }}

3\. 展示错误信息: {{ form.errors.字段 }}

4\. 跨站请求伪造: {{ form.csrf_token }}

注册register.html页面如下:

{% extends 'base.html' %}

{% block title %}
    注册页面
{% endblock %}

{% block content %}
    <form action="" method="post">
        {{ form.csrf_token }}
        {{ form.username.label }}:{{ form.username(style='color:red;', placeholder='请输入用户名', onblur="alert('123')") }}
        {{ form.password.label }}:{{ form.password }}
        {{ form.password2.label }}:{{ form.password2 }}
        {{ form.submit }}

        {% if form.errors %}
            姓名错误信息:{{ form.errors.username }}
            密码错误信息:{{ form.errors.password2 }}
        {% endif %}
    </form>
{% endblock %}

注意: 通过form.字段解析的input标签中可以自定义样式,如{{ form.字段(class=’xxx’, style=’color:red’) }}

3. 常见字段类型

字段类型    说明
StringField 普通文本字段
PasswordField   密码文本字段
SubmitField 提交按钮
HiddenField 隐藏文本字段
TextAreaField   多行文本字段
DateField   文本字段,datetime.date格式
DateTimeField   文本字段,datetime.datetime格式
IntegerField    文本字段,整数类型
FloatField  文本字段,小数类型
BooleanField    复选框,值为True或False
RadioField  单选框
SelectField 下拉列表
FileField   文件上传字段

4. 验证器

验证器 说明
DataRequired    确保字段有值(并且if判断为真)
Email   邮箱地址
IPAddress   IPv4的IP地址
Length  规定字符长度
NumberRange 输入数值的范围
EqualTo 验证两个字段的一致性
URL 有效的URL
Regexp  正则验证

二、登录注册
前言
在flask中如何快速的实现登录注册注销功能,以及登录状态验证等功能? flask的扩展库中有Flask-Login库就可快速的实现以上的功能,实现起来是非常的便利。

1. 安装Flask-Login

pip install flask-login

2. 实现登录功能

2.1 定义login.html模板

{% extends 'base.html' %}

{% block title %}
    登录页面
{% endblock %}

{% block content %}
    <form action="" method="post">
        姓名:<input type="text" name="username">
        密码:<input type="text" name="password">
        <input type="submit" value="提交">
    </form>
{% endblock %}

2.2 实现登录功能
登录方法中定义被login_manager.user_loader装饰的回调函数,回调函数在如下两个地方被调用:
1)该函数表明当前用户登录成功时调用login_user()方法时,会被回调的函数。回调函数实现的功能是向会话上下文session中存储最为中间的键值对,key为user_id, value为当前登录用户的ID值。
2)回调函数在访问任何一个路由地址时也会被调用。
注意: 因为请求上下文在每次建立连接时,都需要获取当前登录用户并将当前登录用户设置为全局上下文current_user,因此回调函数返回的是当前登录系统的用户对象。
users->views.py

from flask_login import LoginManager, login_required, login_user, logout_user,current_user

# 获取登录管理对象
login_manager = LoginManager()  

@login_manager.user_loader
def load_user(user_id):
    # 必须编写一个函数用于从数据库加载用户。
    # 这个函数在login_user(user)存储当前登录用户到session中时,会被调用
    # 在每次访问地址的时候都被被调用,用于向请求上下文中绑定当前登录的用户信息
    return User.query.get(user_id)

@blue.route('/login/', methods=['GET', 'POST'])
def login():
    if request.method == 'GET':
        return render_template('login.html')

    if request.method == 'POST':
        username = request.form.get('username')
        password = request.form.get('password')
        # 校验用户名和密码是否填写完成
        if not all([username, password]):
            return render_template('login.html')
        # 通过用户名获取用户对象
        user = User.query.filter_by(username=username).first()
        # 校验密码是否正确
        if check_password_hash(user.password, password):
            # 实现登录
            # login_user()能够将已登录并通过load_user()的用户对应的User对象保存在session中
            # 在session中会创建一个键值对,key为user_id,value为当前登录用户的id值
            # 如果希望应用记住用户的登录状态, 只需要为 login_user()的形参 remember 传入 True 实参就可以了.
            login_user(user)
            return redirect(url_for('user.index'))
        else:
            flash('用户名或者密码错误')

        return redirect(url_for('user.index'))

2.3 启动文件进行配置
session_protection: 设置存储用户登录状态的安全级别
login_view: 设置登录验证失败的跳转地址

from user.views import login_manager

app.config['SECRET_KEY'] = os.urandom(24)

# 登录管理,初始化app
# 可以设置None,'basic','strong'以提供不同的安全等级,一般设置strong,如果发现异常会登出用户
# session_protection 能够更好的防止恶意用户篡改 cookies, 当发现 cookies 被篡改时, 该用户的 session 对象会被立即删除, 导致强制重新登录。
login_manager.session_protection='strong'

# 当登录认证不通过,则跳转到该地址
login_manager.login_view='user.login'
login_manager.init_app(app)

2.4 访问首页,登录校验
使用装饰器login_required()进行登录校验。
核心思想: 校验session中是否存在key为user_id的键值对。如果校验成功,则继续访问被装饰的函数。如果校验失败,则跳转到启动文件中定义的login_manager.login_view定义的视图函数。

@blue.route('/index/')
@login_required
def index():
    return render_template('index.html')

如果登录校验成功,则渲染index.html首页,在页面中可以解析全局变量current_user参数。

{% extends 'base.html' %}

{% block title %}
    首页页面
{% endblock %}

{% block content %}
    <p>我是首页</p>
    <p>当前登录系统用户为: {{ current_user.username }}</p>
{% endblock %}

2.5 注销
使用logout_user()方法实现注销,核心功能就是删除当前会话上下文session中的user_id键值对。
# 退出

@blue.route('/logout/', methods=['GET'])
@login_required
def logout():
    logout_user()
    return redirect(url_for('user.login'))

三、文件上传
1、配置路径
utils->settings.py

import os

# 基础路径
BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

# static路径
STATIC_DIR=os.path.join(BASE_DIR,'static')

#templates路径
TEMPLATES_DIR=os.path.join(BASE_DIR,'templates')

# 上传路径
UPLOAD_DIR=os.path.join(os.path.join(STATIC_DIR,'media'),'upload')

2、保存文件

       # 获取图片
        icons = request.files.get('icons')
        #保存save(path)
        #绝对路径
        file_path=os.path.join(UPLOAD_DIR,icons.filename)
        #保存图片到绝对路径中
        icons.save(file_path)
        # 保存user对象
        user=current_user
        #将文件保存到数据库中
        user.icons=os.path.join('upload',icons.filename)
        db.session.add(user)
        db.session.commit()

3、html文件

{% extends 'base.html' %}
{% block title %}
首页

{% endblock %}
{% block content %}
首页
<p>当前用户{{current_user.username}}e</p>
<img src="/static/media/{{current_user.icons}}" alt="">
<form action="" method="post" enctype="multipart/form-data">
    头像:<input type="file" name="icons">
    <input type="submit" value="上传">
</form>

{% endblock %}

四、完整的登录注册代码
manager.py

from flask import Flask
from flask_script import Manager

from users.models import db
from users.views import users_blue, login_manager

app = Flask(__name__)

# 注册蓝图
app.register_blueprint(blueprint=users_blue, url_prefix='/users')

# 定义密钥
app.config['SECRET_KEY'] = 'secret_key'

# 配置数据库
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:123456@127.0.0.1:3306/f_login'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

# 没有登录跳转地址(装饰器)
login_manager.login_view='users.user_login'

# 绑定
db.init_app(app)
# 绑定flask-login(登录验证)
login_manager.init_app(app)

# 定义manager管理
manager = Manager(app=app)

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

users->views.py

import os

from flask import Blueprint, request, render_template, redirect, url_for
from flask_login import login_user, LoginManager, current_user, login_required, logout_user
from werkzeug.security import generate_password_hash, check_password_hash

from users.forms import UsersRegisterForm, UsersLoginForm

from users.models import db, User
from utils.settings import UPLOAD_DIR

# current_user是全局变量,可以输出user的说有信息

# 定义蓝图
users_blue = Blueprint('users', __name__)

login_manager = LoginManager()


@users_blue.route('/register/', methods=['GET', 'POST'])
def register():
    form = UsersRegisterForm()
    if request.method == 'GET':
        return render_template('register.html', form=form)
    if request.method == 'POST':
        # 验证提交的字段信息
        if form.validate_on_submit():
            username=form.username.data
            password=form.password.data
            #实现注册,保存用户信息到User模型中
            user = User()
            user.username = username
            user.password = generate_password_hash(password)
            db.session.add(user)
            db.session.commit()
            return redirect(url_for('users.user_login'))
        else:
            # 验证失败,form.errors中存在错误信息
            return render_template('register.html', form=form)


@users_blue.route('/create_table/')
def create_table():
    db.create_all()
    return '创建成功'


# 有点类似中间件
@login_manager.user_loader
def load_user(user_id):
    return User.query.get(user_id)


@users_blue.route('/login/', methods=['GET', 'POST'])
def login():
    form = UsersLoginForm()
    if request.method == 'GET':
        return render_template('login.html', form=form)
    if request.method == 'POST':
        if form.validate_on_submit():
            username=form.username.data
            password=form.password.data
            user = User.query.filter(User.username == username).first()
            # 校验用户名和密码是否填写完整
            if user:
                # 获取用户,进行密码判断
                if check_password_hash(user.password,password):
                    #密码正确
                    #实现功能
                    login_user(user)
                    return redirect(url_for('users.index'))
                else:
                    error_password = '密码错误'
                    return render_template('login.html', error_password=error_password,form=form)
            else:
                # 获取不到用户,返回错误信息给页面
                error_username = '该用户没有注册,请去注册'
                return render_template('login.html',error_username=error_username,form=form)
        else:
            return render_template('login.html', form=form)


# @users_blue.route('/user_login/', methods=['GET', 'POST'])
# def user_login():
#     if request.method == 'GET':
#         return render_template('login.html')
#     if request.method == 'POST':
#         username = request.form.get('username')
#         password = request.form.get('password')
#         user = User.query.filter(User.username == username).first()
#         # 校验用户名和密码是否填写完整
#         if not all([username,password]):
#             return render_template('login.html')
#         if user:
#             # 获取用户,进行密码判断
#             if check_password_hash(user.password,password):
#                 #密码正确
#                 #实现功能
#                 login_user(user)
#                 return redirect(url_for('users.index'))
#         else:
#             # 获取不到用户,返回错误信息给页面
#             error = '该用户没有注册,请去注册'
#             return render_template('login.html',error=error)


@users_blue.route('/index/',methods=['GET', 'POST'])
@login_required
def index():
    if request.method == 'GET':
        return render_template('index.html')
    if request.method == 'POST':
        # 获取图片
        icons = request.files.get('icons')
        #保存save(path)
        # 作业:只保存jpg,png。。。 实现中间件,
        # @user_blueprint.before_request
        # @user_blueprint.after_request
        # @user_blueprint.teardown_request
        file_path=os.path.join(UPLOAD_DIR,icons.filename)
        icons.save(file_path)
        # 保存user对象
        user=current_user
        user.icons=os.path.join('upload',icons.filename)
        db.session.add(user)
        db.session.commit()
        return redirect(url_for('users.index'))


@users_blue.route('/logout/')
@login_required
def logout():
    logout_user()
    return redirect(url_for('users.user_login'))

users->models.py

from flask_login import UserMixin
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()


class User(UserMixin,db.Model):
    id = db.Column(db.Integer,primary_key=True,autoincrement=True)
    username = db.Column(db.String(20),unique=True,nullable=False)
    password = db.Column(db.String(130),nullable=False)
    icons = db.Column(db.String(100),nullable=True)

    __tablename__='user'

users->forms.py

from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, PasswordField
from wtforms.validators import DataRequired, EqualTo, ValidationError

from users.models import User


class UsersRegisterForm(FlaskForm):
    # 定义用户名和密码都是必填项
    username = StringField('姓 名', validators=[DataRequired()])
    password = PasswordField('密 码', validators=[DataRequired()])
    password2 = PasswordField('确认密码',
                              validators=[DataRequired(), EqualTo('password', '密码不一致')])
    submit = SubmitField('注册')

    def validate_username(self, field):
        user = User.query.filter(User.username == field.data).first()
        if len(field.data) < 3:
            raise ValidationError('用户名不能少于3个字符')
        if len(field.data) > 20:
            raise ValidationError('用户名不能大于20个字符')
        if user:
            raise ValidationError('用户名已存在')

    def validate_password(self, field):
        if len(field.data) < 6:
            raise ValidationError('密码不能少于6个字符')

    def validate_password2(self, field):
        if len(field.data) < 6:
            raise ValidationError('确认密码不能少于6个字符')


class UsersLoginForm(FlaskForm):
    username=StringField('用户名',validators=[DataRequired()])
    password = PasswordField('密码', validators=[DataRequired()])
    submit=SubmitField('登录')

    def validate_username(self, field):
        if len(field.data) < 3:
            raise ValidationError('用户名不能少于3个字符')
        if len(field.data) > 20:
            raise ValidationError('用户名不能大于20个字符')

    def validate_password(self, field):
        if len(field.data) < 6:
            raise ValidationError('密码不能少于6个字符')

utils->settings.py

import os

# 基础路径
BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

# static路径
STATIC_DIR=os.path.join(BASE_DIR,'static')

#templates路径
TEMPLATES_DIR=os.path.join(BASE_DIR,'templates')

# 上传路径
UPLOAD_DIR=os.path.join(os.path.join(STATIC_DIR,'media'),'upload')

templates->base.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{% block title %}{% endblock %}</title>
    {% block css %}
    {% endblock %}

    {% block js %}
    {% endblock %}
</head>
<body>
{% block content %}
{% endblock %}
</body>
</html>

templates->base_main.html

{% extends 'base.html' %}
{% block js %}
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"type="text/javascript"></script>
{% endblock %}

templates->register.html

{% extends 'base_main.html' %}
{% block title %}
注册
{% endblock %}
{% block content %}
<form action="" method="post">
    {{ form.csrf_token }}

    {% if form.errors.username %}
        {{ form.errors.username.0 }}
    {% endif %}
    <p>{{ form.username.label }}: {{ form.username }}</p>
    {% if form.errors.password %}
        {{ form.errors.password.0 }}
    {% endif %}
    <p>{{ form.password.label }}: {{ form.password }}</p>
    {% if form.errors.password2 %}
        {{ form.errors.password2.0 }}
    {% endif %}
    <p>{{ form.password2.label }}: {{ form.password2 }}</p>
    {{ form.submit() }}
</form>

{% endblock %}

templates->login.html

{% extends 'base_main.html' %}
{% block title %}
登录
{% endblock %}
{% block content %}
<form action="" method="post">
    {{ form.csrf_token }}

    {% if form.errors.username %}
    {{ form.errors.username.0 }}
    {% endif %}

    {% if error_username %}
    {{ error_username }}
    {% endif %}

    <p>{{ form.username.label }}: {{ form.username }}</p>

    {% if form.errors.password %}
    {{ form.errors.password.0 }}
    {% endif %}

    {% if error_password %}
    {{ error_password }}
    {% endif %}

    <p>{{ form.password.label }}:{{ form.password }}</p>

    {{ form.submit() }}
    <!--<p>姓名:<input type="text" name="username"></p>-->
    <!--<p>密码:<input type="password" name="password"></p>-->
    <!--<input type="submit" value="登录">-->
</form>

{% endblock %}

templates->index.html

{% extends 'base.html' %}
{% block title %}
首页

{% endblock %}
{% block content %}
首页
<p>当前用户{{current_user.username}}e</p>
<img src="/static/media/{{current_user.icons}}" alt="">
<form action="" method="post" enctype="multipart/form-data">
    头像:<input type="file" name="icons">
    <input type="submit" value="上传">
</form>

{% endblock %}

作者:晓晓的忍儿
链接:https://www.jianshu.com/p/9e343d10f7a2
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

flask插件系列之Flask-WTF表单

lask_wtf是flask框架的表单验证模块,可以很方便生成表单,也可以当做json数据交互的验证工具,支持热插拔。

安装

pip install Flask-WTF

Flask-WTF其实是对wtforms组件的封装,使其支持对flask框架的热插拔。

简单使用

# app.py
from flask import Flask, current_app, request, render_template
from forms import MyForm

app = Flask(__name__,template_folder='static/html')
@app.route('/',methods=['GET','POST'])
def login():
    form = MyForm()
    if form.validate_on_submit():
        return 'OK'
    return render_template('forms/index.html', form=form)
if __name__ == '__main__':
    app.run(host='127.0.0.1', port=80, debug=True)

# forms.py
from flask_wtf import FlaskForm
from wtforms import StringField
from wtforms.validators import DataRequired

class MyForm(FlaskForm):
    name = StringField('name', validators=[DataRequired()])

# forms/index.html
<form method="POST" action="/">
{{ form.csrf_token }}
{{ form.name.label }} {{ form.name(size=20) }}
<input type="submit" value="Go">
</form>

flask_wtf定义字段
flask_wtf完全使用wtforms组件的字段模型,wtforms对字段的定义在fields模块;又分为core和simple,core模块定义了普通使用的字段,simple在core模块的基础上扩展了一些字段,这些字段会自动进行字段级别的校验。

字段类型

# core.py
__all__ = (
    'BooleanField', 'DecimalField', 'DateField', 'DateTimeField', 'FieldList',
    'FloatField', 'FormField', 'IntegerField', 'RadioField', 'SelectField',
    'SelectMultipleField', 'StringField',
)
常用字段说明:
BooleanField:布尔类型,如Flask,True
StringField:字符串类型
DecimalField:小数点文本字段,如:‘1.23’
DateField:日期字段,格式:'%Y-%m-%d'
DateTimeField:日期字段,格式:'%Y-%m-%d %H:%M:%S'
FieldList:统一字段类型组成列表,如:FieldList(StringField('Name', [validators.required()]))
FloatField:浮点数类型
IntegerField:整形
SelectMultipleField:多选框
RadioField:单选框


# simple.py
TextAreaField:文本域,可接受多行输入
PasswordField:密码字段,输入的不会直接在浏览器明文显示
FileField:上传文件,但不会处理验证文件,需要手动处理
HiddenField:隐藏字段
SubmitField:按钮
TextField:字符串类型的别名,弃用

表单定义

# 参数:
class UserAdminForm(FlaskForm):
    username = StringField(label='用户名', validators=[DataRequired(),Length(4,20)])
    password_hash = PasswordField(label='密码',validators=[DataRequired(),Length(4,20)])
    limit = SelectField(label='用户权限',
                        choices=[('guest', '所有权限'),
                                 ('operation', '可读可写不可删除'),
                                 ('management', '可读不可写')],
                        default='guest')  # 权限

# 字段一般的参数
# label:字段的名字
# default:默认
# validators:字段的验证序列
# description:字段的描述
# choices:SelectField和他的子类有的字段,选择框,多选一

字段的验证序列
字段的参数validators可以指定提交表单的验证序列,按照从左到右的顺序,默认的可选验证在wtforms.validators模块,已经封装的验证方法有:

__all__ = (
    'DataRequired', 'data_required', 'Email', 'email', 'EqualTo', 'equal_to',
    'IPAddress', 'ip_address', 'InputRequired', 'input_required', 'Length',
    'length', 'NumberRange', 'number_range', 'Optional', 'optional',
    'Required', 'required', 'Regexp', 'regexp', 'URL', 'url', 'AnyOf',
    'any_of', 'NoneOf', 'none_of', 'MacAddress', 'mac_address', 'UUID'
)
模块中大小写有对应的方式,如DataRequired对应data_required。

DataRequired/data_required:验证数据是否真实存在,即不能为空,必须是非空白字符串,否则触发StopValidation错误。
InputRequired/input_required:和DataRequired的区别在于可以是空白字符串;
Required/required:data_required的别名
Email/email:验证符合邮件的格式,只有最基本的验证;
EqualTo/equal_to:比较两个字段的值,比如密码和确认密码,如果不相等就触发错误,equal_to(field,message),需要输入另一个字段的名字。
IPAddress/ip_address:验证是否是ip地址,默认验证IPV4地址。
MacAddress/mac_address:验证是否符合mac格式;
UUID:是否是uuid格式;
URL/url:验证是否符合url格式;
Regexp/regexp:用提供的正则表达式验证字段;Regexp(r"")
Length/length:设置字段值的长度,Length(min,max);
NumberRange/number_range:设置一个数字字段的取值范围,可以针对浮点数和小数;NumberRange(min,max)
Optional/optional:允许字段为空并停止验证;
NoneOf/none_of:将传入的数据和无效的比较,是抛出异常;Noneof(values).
Anyof/any_of:将传入的数据和预设的数据比较,不是异常。Anyof(values).

自定义字段验证
如果默认的验证序列不满足我们的要求,我们可以通过继承的方式自定义字段。

from wtforms.validators import DataRequired,Length,StopValidation
class NewStringField(StringField):
    """
    自定义一个新的字段
    """
    def pre_validate(self, form):
        """验证方法,在validators验证序列之前"""
        x:str = form.name.data
        if not x.startswith('g'):
            raise StopValidation("your data must be startswith 'g'")

    def post_validate(self, form, validation_stopped):
        """
        验证方法,在validators验证序列之后
        :param form:该字段所属的表单对象
        :param validation_stopped:前面验证序列的结果,True表示验证通过,False表示验证失败
        :return:
        """
        if not validation_stopped:
            raise ValueError("验证失败了!")
        pass

触发StopValidation异常会停止验证链;

自定义表单验证

一般来说,如果对表单有额外需要的验证,一般自定义表单的额外的验证方法而不是重新自定义新的字段,而form已经为我们提供了这种方法。
看Form对象的源码:

def validate(self):
    """
    Validates the form by calling `validate` on each field, passing any
    extra `Form.validate_<fieldname>` validators to the field validator.
    """
    extra = {}
    for name in self._fields:
        inline = getattr(self.__class__, 'validate_%s' % name, None)
        if inline is not None:
            extra[name] = [inline]

    return super(Form, self).validate(extra)

Form对象调用validate函数时会自动寻找validate_%s的方法添加到验证序列,并在原先字段的验证序列验证完毕后执行。

class MyForm(FlaskForm):
    name = StringField('name', validators=[DataRequired(), Length(4,20)])
    def validate_name(self, field):
        print(field.data)
        if hasattr(self, 'name') and len(self.name.data) > 5:
            print(self.name.data)
            return True
        raise ValidationError('超过5个字符')

# 在自定义的验证方法中,抛出异常使用ValidationError,validate会自动捕捉。

表单对象
flask_wtf推荐使用Form对象的子类FlaskForm代替,该对象提供了所有表单需要的属性和方法。那么Form对象是如何自动实现表单功能的呢?
分析FlaskForm对象源码:

class FlaskForm(Form):
    class Meta(DefaultMeta):
        def wrap_formdata(self, form, formdata):
            pass

    def __init__(self, formdata=_Auto, **kwargs):
        csrf_enabled = kwargs.pop('csrf_enabled', None)
        pass
    def is_submitted(self):
        pass
    def validate_on_submit(self):
        pass
    def hidden_tag(self, *fields):
        pass
    def validate(self):
        pass

FlaskForm内部定义了一个Meta类,该类添加csrf保护的一些方法,所以创建表单的时候一定要导入FlaskForm而不是Form.

is_submitted:检查是否有一个活跃的request请求;

validate_on_submit:调用is_submitted和validate方法,返回一个布尔值,用来判断表单是否被提交;

validate:字段级别的验证,每个字段都有一个validate方法,FlaskForm调用validate会对所有的字段调用validate方法验证,如果所有的验证都通过返回Ture,否则抛出异常。

hidden_tag:获取表单隐藏的字段;

wrap_formdata:获取request中的form,每次form对象初始化时会执行该函数从request获取form。

重要属性

form.data:字段名字和值组成的字典;
form.errors:验证失败的信息字典,在调用validate_on_submit方法后才有效;
form.name.data:字段name的值;
form.name.type:字段name的类型

常用场景
登录验证

# froms.py
class UserPasswordForm(FlaskForm):
    """
    登录提交的表单
    """
    username = StringField('User', validators=[DataRequired()])
    password = PasswordField('Password', validators=[DataRequired()])

# form.html
<form method="POST" action="/">
{{ form.csrf_token }}
{{ form.username.label }} {{ form.username(size=20) }}
{{ form.password.label }} {{ form.password }}
<input type="submit" value="Go">
</form>

# views.py
@app.route('/login',methods=['GET','POST'])
def login():
    form = UserPasswordForm()
    if form.validate_on_submit():
        # 验证表单
        if form.username.data == "xiaoming" and form.password.data == '123':
            return 'OK'
    return render_template('forms/index.html', form=form)

ajax请求转化表单
有时候我们没有html页面的表单,只有ajax请求的数据交互,但是想借用Form来定义接口和验证接收的数据,如果ajax的请求方法是(‘POST’, ‘PUT’, ‘PATCH’, ‘DELETE’)中的一种,FlaskForm会自动从request对象中调用request.form和request.get_json()方法来接收数据,因此这种方式十分方便。注意:get方法不再其中。

# form.py
class MyForm(FlaskForm):
    name = StringField('name', validators=[DataRequired(), Length(4,20)])
# view.py
@app.route('/form',methods=['GET','POST'])
def form():
    if request.method != "GET":
        form = MyForm() # form会获取请求数据
        print(form.data)
        return 'ok'
    return ''
# test.py
import requests as req
import json

class ProTest():
    baseurl = 'http://127.0.0.1:80'
    def test_form(self):
        url = self.baseurl + '/form'
        rsp = req.post(url,json={'name':'hhhh'})
        # rsp = req.get(url,json={'name':'hhhh'})
if __name__ == '__main__':
    test = ProTest()
    test.test_form()

form启用csrf保护
默认csrf保护是开启的,只要在html文件中添加{{ form.csrf_token }},app必须设置SECRET_KEY参数。

# 禁用保护
form = Form(csrf_enabled=False)
# 或配置app时
WTF_CSRF_ENABLED = False

一般数据csrf保护
同理必须设置SECRET_KEY参数。

from flask_wtf.csrf import CsrfProtect
csrf = CsrfProtect()

def create_app():
    app = Flask(__name__)
    csrf.init_app(app)

# 模板中添加一个隐藏域
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<meta name="csrf-token" content="{{ csrf_token() }}">
# 如果是ajax请求,可以在脚本中
var csrftoken = "{{ csrf_token() }}"
# 然后每个请求添加 X-CSRFToken 头部

# 全局禁用csrf
WTF_CSRF_CHECK_DEFAULT = False

# 对一些视图跳过csrf检查
@csrf.exempt
@app.route('/foo', methods=('GET', 'POST'))
def my_handler():
    return 'ok'

参考
https://flask-wtf.readthedocs.io/en/stable/

http://www.pythondoc.com/flask-wtf/

https://www.cnblogs.com/sysnap/p/6568397.html # 表单验证器的总结

python 各种加密

背景
加密学习
对称加密
对称密钥加密 , 又叫私钥加密。即信息发送的方和接受方用一个密钥去加密和揭秘数据。 最大的优势是 加解密速度快,适合对大量数据进行加密, 对称加密的缺点是密钥的管理和分配, 换句话说就是 如何把密钥发送到需要解密你的消息的人手里的问题。在发送密钥的过程中, 密钥有很大的风险被黑客拦截。 现实中的做法是将对称加密的密钥进行非对称加密然后传给需要他的人。
非对称加密
非对称加密系统, 又称公钥密钥加密。 非对称加密为数据的加密与解密提供了一种非常安全的方式。她使用了一对密钥, 私钥和公钥。 私钥只能有一方安全保管, 不能外泄, 而公钥可以发给任何请求她的人。非对称加密使用这对密钥中的一个进行加密, 而解密却需要一个另外一个密钥。 比如你去银行 你向银行请求公钥,银行将公钥发给你,你使用公钥对消息加密,那么只有私钥的持有人–银行才能对你的消息解密。 与对称加密的不同之处是, 银行不需要将私钥通过网络发送出去。因此安全性大大提高。 目前最常用的非对称加密算法是RSA算法。公钥机制灵活,但加密和解密速度却比对称密钥加密慢得多。 公钥机制灵活, 但是加密和解密速度却要比堆成加密慢很多。
1) Alice需要在银行的网站做一笔交易,她的浏览器首先生成了一个随机数作为对称密钥。
(2) Alice的浏览器向银行的网站请求公钥。
(3) 银行将公钥发送给Alice。
(4) Alice的浏览器使用银行的公钥将自己的对称密钥加密。
(5) Alice的浏览器将加密后的对称密钥发送给银行。
(6) 银行使用私钥解密得到Alice浏览器的对称密钥。
(7) Alice与银行可以使用对称密钥来对沟通的内容进行加密与解密了。
加密1
(三)总结
(1) 对称加密加密与解密使用的是同样的密钥,所以速度快,但由于需要将密钥在网络传输,所以安全性不高。
(2) 非对称加密使用了一对密钥,公钥与私钥,所以安全性高,但加密与解密速度慢。
(3) 解决的办法是将对称加密的密钥使用非对称加密的公钥进行加密,然后发送出去,接收方使用私钥进行解密得到对称加密的密钥,然后双方可以使用对称加密来进行沟通。

base64 加密
python3 输入的都是 二进制 byte类型
注意:用于base64编码的,要么是ASCII包含的字符,要么是二进制数据
base64 是对称加密

base64 的加密和解密
———————–我是华丽的分界线,以下是代码————————–
import base64

s = ‘hello, world’
s = “你好”
# 加密
bs = base64.b64encode(s.encode(“utf8”))
print(bs)

# 解密
decode = base64.b64decode(bs)
print(decode)
print(decode.decode(“utf8”))
————————-我是华丽的分界线,以上是代码————————–
base64 是 一种用64 个字符来表示任意的二进制数据的方法。base64 可以成为密码学的基石。可以将任意二进制数据进行Base64 编码。 所有的数据都能被编码为并只有64个字符就能表示的文本文件。( 64字符:A~Z a~z 0~9 + / )编码后的数据~=编码前数据的4/3,会大1/3左右。
Base64编码的原理
加密2

1 将所有字符转化为ASCII码。
2 将ASCII码转化为8位二进制 。
3 将二进制3个归成一组(不足3个在后边补0)共24位,再拆分成4组,每组6位。
4 统一在6位二进制前补两个0凑足8位。
5 将补0后的二进制转为十进制。
6 从Base64编码表获取十进制对应的Base64编码。

Base64编码的说明

1 转换的时候,将三个byte的数据,先后放入一个24bit的缓冲区中,先来的byte占高位。
2 数据不足3byte的话,于缓冲区中剩下的bit用0补足。然后,每次取出6个bit,按照其值选择查表选择对应的字符作为编码后的输出。
3 不断进行,直到全部输入数据转换完成。
4 如果最后剩下两个输入数据,在编码结果后加1个“=”。
5 如果最后剩下一个输入数据,编码结果后加2个“=”。
6 如果没有剩下任何数据,就什么都不要加,这样才可以保证资料还原的正确性。

MD5
由于MD5模块在python3中被移除,在python3中使用hashlib模块进行md5操作
——————-我是华丽的分界线,以下是代码—————————–
import hashlib

str = “我真帅”

# 创建一个md5 对象
h1 = hashlib.md5()
# 此处必须声明encode
# 若写法为hl.update(str) 报错为: Unicode-objects must be encoded before hashing
h1.update(str.encode())
print(“加密前”, str)
print(“加密后”, h1.hexdigest())
——————–我是华丽的分界线,以上是代码——————————
sha1 加密
——————–我是华丽的分界线,以下是代码——————————
import hashlib
def str_encrypt(str):
“””
使用sha1加密算法,返回str加密后的字符串
“””
sha = hashlib.sha1(str)
encrypts = sha.hexdigest()
return encrypts
———————–我是华丽的分界线,以上是代码—————————-
简介
message-digest algorithm 5(信息-摘要算法)。经常说的“MD5加密”,就是它→信息-摘要算法。

md5,其实就是一种算法。可以将一个字符串,或文件,或压缩包,执行md5后,就可以生成一个固定长度为128bit的串。这个串,基本上是唯一的。

不可逆性
每个人都有不同的指纹,看到这个人,可以得出他的指纹等信息,并且唯一对应,但你只看一个指纹,是不可能看到或读到这个人的长相或身份等信息。

特点

1 压缩性:任意长度的数据,算出的MD5值长度都是固定的。
2 容易计算:从原数据计算出MD5值很容易。
3 抗修改性:对原数据进行任何改动,哪怕只修改1个字节,所得到的MD5值都有很大区别。
4 强抗碰撞:已知原数据和其MD5值,想找到一个具有相同MD5值的数据(即伪造数据)是非常困难的。

MD5长度

md5的长度,默认为128bit,也就是128个0和1的二进制串。这样表达是很不友好的。所以将二进制转成了16进制,每4个bit表示一个16进制,所以128/4 = 32 换成16进制表示后,为32位了。

为什么网上还有md5是16位的呢?

其实16位的长度,是从32位md5值来的。是将32位md5去掉前八位,去掉后八位得到的。

DES

Python加密库PyCryptodome
PyCrytodome 取代了 PyCrypto 。
安装与导入
Windows安装之前需要先安装Microsoft Visual c++ 2015。

下载地址:https://www.microsoft.com/en-us/download/details.aspx?id=48145

在Linux上安装,可以使用以下 pip 命令:
pip install pycryptodome
import Crypto

在Windows 系统上安装则稍有不同:
pip install pycryptodomex
import Cryptodome

DES算法为密码体制中的对称密码体制,又被称为美国数据加密标准。

DES是一个分组加密算法,典型的DES以64位为分组对数据加密,加密和解密用的是同一个算法。

DES算法的入口参数有三个:Key、Data、Mode。其中Key为7个字节共56位,是DES算法的工作密钥;Data为8个字节64位,是要被加密或被解密的数据;Mode为DES的工作方式,有两种:加密或解密。

密钥长64位,密钥事实上是56位参与DES运算(第8、16、24、32、40、48、56、64位是校验位,使得每个密钥都有奇数个1),分组后的明文组和56位的密钥按位替代或交换的方法形成密文组。

加密原理
DES 使用一个 56 位的密钥以及附加的 8 位奇偶校验位,产生最大 64 位的分组大小。这是一个迭代的分组密码,使用称为 Feistel 的技术,其中将加密的文本块分成两半。使用子密钥对其中一半应用循环功能,然后将输出与另一半进行“异或”运算;接着交换这两半,这一过程会继续下去,但最后一个循环不交换。DES 使用 16 个循环,使用异或,置换,代换,移位操作四种基本运算。

算法步骤
1)初始置换
其功能是把输入的64位数据块按位重新组合,并把输出分为L0、R0两部分,每部分各长3 2位,其置换规则为将输入的第58位换到第一位,第50位换到第2位……依此类推,最后一位是原来的第7位。L0、R0则是换位输出后的两部分,L0是输出的左32位,R0是右32位,例:设置换前的输入值为D1D2D3……D64,则经过初始置换后的结果为:L0=D58D50……D8;R0=D57D49……D7。
其置换规则见下表:
58,50,42,34,26,18,10,2,60,52,44,36,28,20,12,4,
62,54,46,38,30,22,14,6,64,56,48,40,32,24,16,8,
57,49,41,33,25,17,9,1,59,51,43,35,27,19,11,3,
61,53,45,37,29,21,13,5,63,55,47,39,31,23,15,7,
2)逆置换
经过16次迭代运算后,得到L16、R16,将此作为输入,进行逆置换,逆置换正好是初始置换的逆运算,由此即得到密文输出。
此算法是对称加密算法体系中的代表,在计算机网络系统中广泛使用.

加密和解密的过程
——————-我是华丽的分界线,以下是代码—————————–
from Cryptodome.Cipher import DES

key = b’abcdefgh’ # 密钥 8位或16位,必须为bytes
def pad(text):
“””
# 加密函数,如果text不是8的倍数【加密文本text必须为8的倍数!】,那就补足为8的倍数
:param text:
:return:
“””
while len(text) % 8 != 0:
text += ‘ ‘
return text

des = DES.new(key, DES.MODE_ECB) # 创建一个DES实例
text = ‘Python rocks!’
padded_text = pad(text)
encrypted_text = des.encrypt(padded_text.encode(‘utf-8’)) # 加密
print(encrypted_text)
# rstrip(‘ ‘)返回从字符串末尾删除所有字符串的字符串(默认空白字符)的副本
plain_text = des.decrypt(encrypted_text).decode().rstrip(‘ ‘) # 解密
print(plain_text)
———————我是华丽的分界线,以下是代码—————————
from Cryptodome.Cipher import DES
import binascii

# 这是密钥
key = b’abcdefgh’
# 需要去生成一个DES对象
des = DES.new(key, DES.MODE_ECB)
# 需要加密的数据
text = ‘python spider!’
text = text + (8 – (len(text) % 8)) * ‘=’
# 加密的过程
encrypto_text = des.encrypt(text.encode())
# 加密过后二进制转化为ASCII
encrypto_text = binascii.b2a_hex(encrypto_text)
print(encrypto_text)
# 解密需要ASCII 先转化为二进制 然后再进行解密
plaint = des.decrypt(binascii.a2b_hex(encrypto_text))
print(plaint)

———————我是华丽的分界线,以上是代码—————————
3DES
简介

3DES(或称为Triple DES)是三重数据加密算法(TDEA,Triple Data Encryption Algorithm)块密码的通称。它相当于是对每个数据块应用三次DES加密算法。

由于计算机运算能力的增强,原版DES密码的密钥长度变得容易被暴力破解。3DES即是设计用来提供一种相对简单的方法,即通过增加DES的密钥长度来避免类似的攻击,而不是设计一种全新的块密码算法。

3DES(即Triple DES)是DES向AES过渡的加密算法(1999年,NIST将3-DES指定为过渡的加密标准),加密算法,其具体实现如下:设Ek()和Dk()代表DES算法的加密和解密过程,K代表DES算法使用的密钥,M代表明文,C代表密文,这样:

3DES加密过程为:C=Ek3(Dk2(Ek1(M)))

3DES解密过程为:M=Dk1(EK2(Dk3(C)))

AES
简介

高级加密标准(英语:Advanced Encryption Standard,缩写:AES),在密码学中又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。经过五年的甄选流程,高级加密标准由美国国家标准与技术研究院(NIST)于2001年11月26日发布于FIPS PUB 197,并在2002年5月26日成为有效的标准。2006年,高级加密标准已然成为对称密钥加密中最流行的算法之一。

AES在软件及硬件上都能快速地加解密,相对来说较易于实作,且只需要很少的存储器。作为一个新的加密标准,目前正被部署应用到更广大的范围。
特点与思想
1,抵抗所有已知的攻击。
2,在多个平台上速度快,编码紧凑。
3,设计简单。

加密3

AES为分组密码,分组密码也就是把明文分成一组一组的,每组长度相等,每次加密一组数据,直到加密完整个明文。在AES标准规范中,分组长度只能是128位,也就是说,每个分组为16个字节(每个字节8位)。密钥的长度可以使用128位、192位或256位。密钥的长度不同,推荐加密轮数也不同。

一般常用的是128位
———————我是华丽的分界线,以下是代码—————————
from Cryptodome.Cipher import AES
from Cryptodome import Random

from binascii import a2b_hex

# 要加密的明文
data = ‘南来北往’
# 密钥key必须为 16(AES-128), 24(AES-192), 32(AES-256)
key = b’this is a 16 key’
# 生成长度等于AES 块大小的不可重复的密钥向量
iv = Random.new().read(AES.block_size)
print(iv)
# 使用 key 和iv 初始化AES 对象, 使用MODE_CFB模式
mycipher = AES.new(key, AES.MODE_CFB, iv)
print(mycipher)
# 加密的明文长度必须为16的倍数, 如果长度不为16的倍数, 则需要补足为16的倍数
# 将iv(密钥向量)加到加密的密钥开头, 一起传输
ciptext = iv + mycipher.encrypt(data.encode())
# 解密的话需要用key 和iv 生成的AES对象
print(ciptext)
mydecrypt = AES.new(key, AES.MODE_CFB, ciptext[:16])
# 使用新生成的AES 对象, 将加密的密钥解密
decrytext = mydecrypt.decrypt(ciptext[16:])

print(decrytext.decode())
———————我是华丽的分界线,以上是代码—————————
RSA
非对称加密
典型的非对称加密
典型的如RSA等,常见方法,使用openssl ,keytools等工具生成一对公私钥对,使用被公钥加密的数据可以使用私钥来解密,反之亦然(被私钥加密的数据也可以被公钥解密) 。

在实际使用中私钥一般保存在发布者手中,是私有的不对外公开的,只将公钥对外公布,就能实现只有私钥的持有者才能将数据解密的方法。 这种加密方式安全系数很高,因为它不用将解密的密钥进行传递,从而没有密钥在传递过程中被截获的风险,而破解密文几乎又是不可能的。

但是算法的效率低,所以常用于很重要数据的加密,常和对称配合使用,使用非对称加密的密钥去加密对称加密的密钥。

简介
RSA加密算法是一种非对称加密算法。在公开密钥加密和电子商业中RSA被广泛使用。

该算法基于一个十分简单的数论事实:将两个大素数相乘十分容易,但那时想要对其乘积进行因式分解却极其困难,因此可以将乘积公开作为加密密钥,即公钥,而两个大素数组合成私钥。公钥是可发布的供任何人使用,私钥则为自己所有,供解密之用

而且,因为RSA加密算法的特性,RSA的公钥私钥都是10进制的,但公钥的值常常保存为16进制的格式,所以需要将其用int()方法转换为10进制格式。
————————我是华丽的分界线,以下是代码—————————

import rsa

# rsa加密
def rsaEncrypt(str):
# 生成公钥、私钥
(pubkey, privkey) = rsa.newkeys(512)
print(“pub: “, pubkey)
print(“priv: “, privkey)
# 明文编码格式
content = str.encode(‘utf-8’)
# 公钥加密
crypto = rsa.encrypt(content, pubkey)
return (crypto, privkey)

# rsa解密
def rsaDecrypt(str, pk):
# 私钥解密
content = rsa.decrypt(str, pk)
con = content.decode(‘utf-8’)
return con

(a, b) = rsaEncrypt(“hello”)
print(‘加密后密文:’)
print(a)
content = rsaDecrypt(a, b)
print(‘解密后明文:’)
print(content)
———————我是华丽的分界线,以下是代码—————————
import rsa
import binascii

def rsa_encrypt(rsa_n, rsa_e, message):
key = rsa.PublicKey(rsa_n, rsa_e)
message = rsa.encrypt(message.encode(), key)
message = binascii.b2a_hex(message)
return message.decode()

pubkey_n = ‘8d7e6949d411ce14d7d233d7160f5b2cc753930caba4d5ad24f923a505253b9c39b09a059732250e56c594d735077cfcb0c3508e9f544f101bdf7e97fe1b0d97f273468264b8b24caaa2a90cd9708a417c51cf8ba35444d37c514a0490441a773ccb121034f29748763c6c4f76eb0303559c57071fd89234d140c8bb965f9725’
pubkey_e = ‘10001’
rsa_n = int(pubkey_n, 16)
rsa_e = int(pubkey_e, 16)
message = ‘南北今天很忙’
print(“公钥n值长度:”, len(pubkey_n))

aa = rsa_encrypt(rsa_n, rsa_e, message)
print(aa)

———————我是华丽的分界线,以下是代码—————————
”’

RSA算法

”’

from Cryptodome.PublicKey import RSA

from Cryptodome.Cipher import PKCS1_OAEP, PKCS1_v1_5

class MyRSA():
def create_rsa_key(self, password):
“””

创建RSA密钥

步骤说明:

1、从 Crypto.PublicKey 包中导入 RSA,创建一个密码

2、生成 1024/2048 位的 RSA 密钥

3、调用 RSA 密钥实例的 exportKey 方法,传入密码、使用的 PKCS 标准以及加密方案这三个参数。

4、将私钥写入磁盘的文件。

5、使用方法链调用 publickey 和 exportKey 方法生成公钥,写入磁盘上的文件。

“””

key = RSA.generate(1024)

encrypted_key = key.exportKey(passphrase=password.encode(“utf-8″), pkcs=8,

protection=”scryptAndAES128-CBC”)

with open(“my_private_rsa_key.bin”, “wb”) as f:
f.write(encrypted_key)

with open(“my_rsa_public.pem”, “wb”) as f:
f.write(key.publickey().exportKey())

def encrypt(self, plaintext):
# 加载公钥

recipient_key = RSA.import_key(

open(“my_rsa_public.pem”).read()

)

cipher_rsa = PKCS1_v1_5.new(recipient_key)

en_data = cipher_rsa.encrypt(plaintext.encode(“utf-8”))

return en_data

# print(len(en_data), en_data)

def decrypt(self, en_data, password):
# 读取密钥

private_key = RSA.import_key(

open(“my_private_rsa_key.bin”).read(),

passphrase=password

)

cipher_rsa = PKCS1_v1_5.new(private_key)

data = cipher_rsa.decrypt(en_data, None)

return data

# print(data)

mrsa = MyRSA()

mrsa.create_rsa_key(‘123456’)

e = mrsa.encrypt(‘hello’)

d = mrsa.decrypt(e, ‘123456’)

print(e)

print(d)
———————我是华丽的分界线,以上是代码—————————

Python + js 通过RSA算法对post表单数

HTTP采用明文传输,如果不对用户密码进行加密处理的话,会导致用户密码明文暴露在网络,通过监听抓包很容易获得。此问题处理方法一般有使用https代替http或对http 表单提交数据进行加解密处理。这里分享的是用RSA非对称加密算法对数据进行加解密,前端js使用公钥进行加密,后端python使用私钥进行解密。
HTTP采用明文传输,如果不对用户密码进行加密处理的话,会导致用户密码明文暴露在网络,通过监听抓包很容易获得,如下图:
22
此问题处理方法一般有使用https代替http或对http 表单提交数据进行加解密处理,这里分享的是用RSA非对称加密算法对数据进行加解密,前端js使用公钥进行加密,后端python使用私钥进行解密。

RSA加密算法简而言之就是服务端生成公钥私钥对,公钥给客户端,客户端拿着公钥去加密数据,然后服务端用私钥去解密数据。

RSA算法中,n、e两个参数决定公钥,n和d决定私钥。在本例的B/S应用中,来往数据如下图:
23
具体实现
生成公钥私钥对
为了避免加密后的密码重复使用,后端处理时每次请求都重新生成公钥私钥对。

当用户请求登录时,生成公钥私钥对,并将n和e写在http页面中返回给前端。需要注意的是生成e和n需要转成十六进制再传递给前端,代码如下:

import rsa
def login(request):
(pub_key, priv_key) = rsa.newkeys(256) ## 生成公钥私钥
pubkey_e = hex(pub_key.e)
pubkey_n = hex(pub_key.n)
request.session['privkey'] = priv_key
return render_to_response('login.html', {'pubkey_e': pubkey_e , 'pubkey_n': pubkey_n})

前端js获取公钥n和e,并使用公钥对用户输入的密码进行加密,然后将加密后的密码发送给服务器请求认证。需要注意的是前端需要将python传输过来的16进制数据进行处理,去掉前面的0x和后面的L。
24
后端使用私钥进行解密验证

def dologin(request):
username=request.POST.get('username','')
en_password=request.POST.get('password','')
priv_key = request.session.get('privkey')
request.session['privkey'] = None ##清除私钥
try:
##使用私钥解密得到明文密码
password = rsa.decrypt(en_password.decode('hex'),priv_key)
except Exception as error:
password = None
##这里省略密码验证500字

验证一下,通过firebug看到post数据中密码已经进行了加密,如下图:
25

Python–urllib3库详解

Python–urllib3库中文详解

User Guide(英文)

Making requests

First things first, import the urllib3 module:

>>> import urllib3

You’ll need a 

<span class="pre">PoolManager</span>

 instance to make requests. This object handles all of the details of connection pooling and thread safety so that you don’t have to:

>>> http = urllib3.PoolManager()

To make a request use 

<span class="pre">request()</span>

:

>>> r = http.request('GET', 'http://httpbin.org/robots.txt')
>>> r.data
b'User-agent: *\nDisallow: /deny\n'
<span class="pre">request()</span>

 returns a 

<span class="pre">HTTPResponse</span>

 object, the Response content section explains how to handle various responses.

You can use 

<span class="pre">request()</span>

 to make requests using any HTTP verb:

>>> r = http.request(
...     'POST',
...     'http://httpbin.org/post',
...     fields={'hello': 'world'})

The Request data section covers sending other kinds of requests data, including JSON, files, and binary data.

Response content

The 

<span class="pre">HTTPResponse</span>

 object provides 

<span class="pre">status</span>

<span class="pre">data</span>

, and 

<span class="pre">header</span>

 attributes:

>>> r = http.request('GET', 'http://httpbin.org/ip')
>>> r.status
200
>>> r.data
b'{\n  "origin": "104.232.115.37"\n}\n'
>>> r.headers
HTTPHeaderDict({'Content-Length': '33', ...})

JSON content

JSON content can be loaded by decoding and deserializing the 

<span class="pre">data</span>

 attribute of the request:

>>> import json
>>> r = http.request('GET', 'http://httpbin.org/ip')
>>> json.loads(r.data.decode('utf-8'))
{'origin': '127.0.0.1'}

Binary content

The 

<span class="pre">data</span>

 attribute of the response is always set to a byte string representing the response content:

>>> r = http.request('GET', 'http://httpbin.org/bytes/8')
>>> r.data
b'\xaa\xa5H?\x95\xe9\x9b\x11'

Note

For larger responses, it’s sometimes better to stream the response.

Request data

Headers

You can specify headers as a dictionary in the 

<span class="pre">headers</span>

 argument in 

<span class="pre">request()</span>

:

>>> r = http.request(
...     'GET',
...     'http://httpbin.org/headers',
...     headers={
...         'X-Something': 'value'
...     })
>>> json.loads(r.data.decode('utf-8'))['headers']
{'X-Something': 'value', ...}

Query parameters

For 

<span class="pre">GET</span>

<span class="pre">HEAD</span>

, and 

<span class="pre">DELETE</span>

 requests, you can simply pass the arguments as a dictionary in the 

<span class="pre">fields</span>

 argument to 

<span class="pre">request()</span>

:

>>> r = http.request(
...     'GET',
...     'http://httpbin.org/get',
...     fields={'arg': 'value'})
>>> json.loads(r.data.decode('utf-8'))['args']
{'arg': 'value'}

For 

<span class="pre">POST</span>

 and 

<span class="pre">PUT</span>

 requests, you need to manually encode query parameters in the URL:

>>> from urllib.parse import urlencode
>>> encoded_args = urlencode({'arg': 'value'})
>>> url = 'http://httpbin.org/post?' + encoded_args
>>> r = http.request('POST', url)
>>> json.loads(r.data.decode('utf-8'))['args']
{'arg': 'value'}

Form data

For 

<span class="pre">PUT</span>

 and 

<span class="pre">POST</span>

 requests, urllib3 will automatically form-encode the dictionary in the 

<span class="pre">fields</span>

 argument provided to 

<span class="pre">request()</span>

:

>>> r = http.request(
...     'POST',
...     'http://httpbin.org/post',
...     fields={'field': 'value'})
>>> json.loads(r.data.decode('utf-8'))['form']
{'field': 'value'}

JSON

You can send a JSON request by specifying the encoded data as the 

<span class="pre">body</span>

 argument and setting the 

<span class="pre">Content-Type</span>

 header when calling 

<span class="pre">request()</span>

:

>>> import json
>>> data = {'attribute': 'value'}
>>> encoded_data = json.dumps(data).encode('utf-8')
>>> r = http.request(
...     'POST',
...     'http://httpbin.org/post',
...     body=encoded_data,
...     headers={'Content-Type': 'application/json'})
>>> json.loads(r.data.decode('utf-8'))['json']
{'attribute': 'value'}

Files & binary data

For uploading files using 

<span class="pre">multipart/form-data</span>

 encoding you can use the same approach as Form data and specify the file field as a tuple of 

<span class="pre">(file_name,</span><span class="pre">file_data)</span>

:

>>> with open('example.txt') as fp:
...     file_data = fp.read()
>>> r = http.request(
...     'POST',
...     'http://httpbin.org/post',
...     fields={
...         'filefield': ('example.txt', file_data),
...     })
>>> json.loads(r.data.decode('utf-8'))['files']
{'filefield': '...'}

While specifying the filename is not strictly required, it’s recommended in order to match browser behavior. You can also pass a third item in the tuple to specify the file’s MIME type explicitly:

>>> r = http.request(
...     'POST',
...     'http://httpbin.org/post',
...     fields={
...         'filefield': ('example.txt', file_data, 'text/plain'),
...     })

For sending raw binary data simply specify the 

<span class="pre">body</span>

 argument. It’s also recommended to set the 

<span class="pre">Content-Type</span>

 header:

>>> with open('example.jpg', 'rb') as fp:
...     binary_data = fp.read()
>>> r = http.request(
...     'POST',
...     'http://httpbin.org/post',
...     body=binary_data,
...     headers={'Content-Type': 'image/jpeg'})
>>> json.loads(r.data.decode('utf-8'))['data']
b'...'

Certificate verification

Note

New in version 1.25

HTTPS connections are now verified by default (

<span class="pre">cert_reqs</span> <span class="pre">=</span><span class="pre">'CERT_REQUIRED'</span>

).

While you can disable certification verification, it is highly recommend to leave it on.

Unless otherwise specified urllib3 will try to load the default system certificate stores. The most reliable cross-platform method is to use the certifi package which provides Mozilla’s root certificate bundle:

pip install certifi

You can also install certifi along with urllib3 by using the 

<span class="pre">secure</span>

 extra:

pip install urllib3[secure]

Warning

If you’re using Python 2 you may need additional packages. See the section below for more details.

Once you have certificates, you can create a 

<span class="pre">PoolManager</span>

 that verifies certificates when making requests:

>>> import certifi
>>> import urllib3
>>> http = urllib3.PoolManager(
...     cert_reqs='CERT_REQUIRED',
...     ca_certs=certifi.where())

The 

<span class="pre">PoolManager</span>

 will automatically handle certificate verification and will raise 

<span class="pre">SSLError</span>

 if verification fails:

>>> http.request('GET', 'https://google.com')
(No exception)
>>> http.request('GET', 'https://expired.badssl.com')
urllib3.exceptions.SSLError ...

Note

You can use OS-provided certificates if desired. Just specify the full path to the certificate bundle as the 

<span class="pre">ca_certs</span>

 argument instead of

<span class="pre">certifi.where()</span>

. For example, most Linux systems store the certificates at 

<span class="pre">/etc/ssl/certs/ca-certificates.crt</span>

. Other operating systems can be difficult.

Certificate verification in Python 2

Older versions of Python 2 are built with an 

<span class="pre">ssl</span>

 module that lacks SNI support and can lag behind security updates. For these reasons it’s recommended to usepyOpenSSL.

If you install urllib3 with the 

<span class="pre">secure</span>

 extra, all required packages for certificate verification on Python 2 will be installed:

pip install urllib3[secure]

If you want to install the packages manually, you will need 

<span class="pre">pyOpenSSL</span>

,

<span class="pre">cryptography</span>

<span class="pre">idna</span>

, and 

<span class="pre">certifi</span>

.

Note

If you are not using macOS or Windows, note that cryptography requires additional system packages to compile. See building cryptography on Linuxfor the list of packages required.

Once installed, you can tell urllib3 to use pyOpenSSL by using 

<span class="pre">urllib3.contrib.pyopenssl</span>

:

>>> import urllib3.contrib.pyopenssl
>>> urllib3.contrib.pyopenssl.inject_into_urllib3()

Finally, you can create a 

<span class="pre">PoolManager</span>

 that verifies certificates when performing requests:

>>> import certifi
>>> import urllib3
>>> http = urllib3.PoolManager(
...     cert_reqs='CERT_REQUIRED',
...     ca_certs=certifi.where())

If you do not wish to use pyOpenSSL, you can simply omit the call to

<span class="pre">urllib3.contrib.pyopenssl.inject_into_urllib3()</span>

. urllib3 will fall back to the standard-library 

<span class="pre">ssl</span>

 module. You may experience several warnings when doing this.

Warning

If you do not use pyOpenSSL, Python must be compiled with ssl support for certificate verification to work. It is uncommon, but it is possible to compile Python without SSL support. See this Stackoverflow thread for more details.

If you are on Google App Engine, you must explicitly enable SSL support in your 

<span class="pre">app.yaml</span>

:

libraries:
- name: ssl
  version: latest

Using timeouts

Timeouts allow you to control how long requests are allowed to run before being aborted. In simple cases, you can specify a timeout as a 

<span class="pre">float</span>

 to 

<span class="pre">request()</span>

:

>>> http.request(
...     'GET', 'http://httpbin.org/delay/3', timeout=4.0)
<urllib3.response.HTTPResponse>
>>> http.request(
...     'GET', 'http://httpbin.org/delay/3', timeout=2.5)
MaxRetryError caused by ReadTimeoutError

For more granular control you can use a 

<span class="pre">Timeout</span>

 instance which lets you specify separate connect and read timeouts:

>>> http.request(
...     'GET',
...     'http://httpbin.org/delay/3',
...     timeout=urllib3.Timeout(connect=1.0))
<urllib3.response.HTTPResponse>
>>> http.request(
...     'GET',
...     'http://httpbin.org/delay/3',
...     timeout=urllib3.Timeout(connect=1.0, read=2.0))
MaxRetryError caused by ReadTimeoutError

If you want all requests to be subject to the same timeout, you can specify the timeout at the 

<span class="pre">PoolManager</span>

 level:

>>> http = urllib3.PoolManager(timeout=3.0)
>>> http = urllib3.PoolManager(
...     timeout=urllib3.Timeout(connect=1.0, read=2.0))

You still override this pool-level timeout by specifying 

<span class="pre">timeout</span>

 to 

<span class="pre">request()</span>

.

Retrying requests

urllib3 can automatically retry idempotent requests. This same mechanism also handles redirects. You can control the retries using the 

<span class="pre">retries</span>

 parameter to 

<span class="pre">request()</span>

. By default, urllib3 will retry requests 3 times and follow up to 3 redirects.

To change the number of retries just specify an integer:

>>> http.requests('GET', 'http://httpbin.org/ip', retries=10)

To disable all retry and redirect logic specify 

<span class="pre">retries=False</span>

:

>>> http.request(
...     'GET', 'http://nxdomain.example.com', retries=False)
NewConnectionError
>>> r = http.request(
...     'GET', 'http://httpbin.org/redirect/1', retries=False)
>>> r.status
302

To disable redirects but keep the retrying logic, specify 

<span class="pre">redirect=False</span>

:

>>> r = http.request(
...     'GET', 'http://httpbin.org/redirect/1', redirect=False)
>>> r.status
302

For more granular control you can use a 

<span class="pre">Retry</span>

 instance. This class allows you far greater control of how requests are retried.

For example, to do a total of 3 retries, but limit to only 2 redirects:

>>> http.request(
...     'GET',
...     'http://httpbin.org/redirect/3',
...     retries=urllib3.Retry(3, redirect=2))
MaxRetryError

You can also disable exceptions for too many redirects and just return the 

<span class="pre">302</span>

response:

>>> r = http.request(
...     'GET',
...     'http://httpbin.org/redirect/3',
...     retries=urllib3.Retry(
...         redirect=2, raise_on_redirect=False))
>>> r.status
302

If you want all requests to be subject to the same retry policy, you can specify the retry at the 

<span class="pre">PoolManager</span>

 level:

>>> http = urllib3.PoolManager(retries=False)
>>> http = urllib3.PoolManager(
...     retries=urllib3.Retry(5, redirect=2))

You still override this pool-level retry policy by specifying 

<span class="pre">retries</span>

 to 

<span class="pre">request()</span>

.

Errors & Exceptions

urllib3 wraps lower-level exceptions, for example:

>>> try:
...     http.request('GET', 'nx.example.com', retries=False)
>>> except urllib3.exceptions.NewConnectionError:
...     print('Connection failed.')

See 

<span class="pre">exceptions</span>

 for the full list of all exceptions.

Logging

If you are using the standard library 

<span class="pre">logging</span>

 module urllib3 will emit several logs. In some cases this can be undesirable. You can use the standard logger interface to change the log level for urllib3’s logger:

>>> logging.getLogger("urllib3").setLevel(logging.WARNING)

 

 

python操作redis-过期时间

#!/usr/bin/python
#!coding:utf-8

import time
import redis

if __name__ == “__main__”:
try:
conn=redis.StrictRedis(host=’192.168.80.41′)
conn.set(‘name’,’大白兔’)
conn.expire(‘name’,10)
#设置键的过期时间为10s
for item in range(12):
value=conn.get(‘name’)
if value != None:
print(value.decode(‘utf8’))
else:
print(‘the key has been deleted…’)
break
time.sleep(1)
except Exception as err:
print(err)