资讯专栏INFORMATION COLUMN

python开发时几种安全验证的实现

int64 / 3537人阅读

摘要:在这两种情况下,如果你签名的都是用户,那么该用户可以在激活账户和升级账户时,复用的可变部分。变量是一个元组,包括一个透视变换的系数。

额,一个突然的交流让我想起来我耽搁许久各种验证的实现迟迟没做过
趁着这个机会就搞了一下
分为三部分:邮箱验证,短信验证,图片验证码

邮箱验证

这个部分是主要参考的经典书籍-狗书
思路就是根据用户某些信息通过JSON Web签名生成token,然后再发送邮件验证,经典思路
生成和验证函数都加载在模型中
完整代码
itsdangerous中文文档这里介绍了几种签名方式

token生成和验证
TimedJSONWebSignatureSerializer,看这个表面词的意思可以看出这里序列化加入了当前时间
这也是实现设置过期时间的依据吧
查看itsdangerous源码可以看到具体的加密方式
from itsdangerous import (TimedJSONWebSignatureSerializer as Serializer, BadSignature, SignatureExpired)
...
class User(db.Model):
    __tablename__ = "user"
    id = db.Column(db.Integer,primary_key=True)
    name=db.Column(db.String(64),unique=True,index=True)

    def genter_auth_token(self,expiration=300): #设置有效期
        s=Serializer(current_app.config["SECRET_KEY"],expires_in=expiration)
        return s.dumps({"code":self.name}) #将用户名当作签名对象

    @staticmethod
    def verify_auth_token(token):
        s=Serializer(current_app.config["SECRET_KEY"])
        try:
            data=s.loads(token) #加载数据
        except BadSignature:
            return None
        except SignatureExpired:
            return None
        return data

同时这里itdangerous类的签名方式都可以接收一个salt
文档中这样描述了salt的作用:

itsdangerous中的盐,是为了一个截然不同的目的而产生的。你可以将它视为成命名空间
假设你想签名两个链接。你的系统有个激活链接,用来激活一个用户账户,并且你有一个升级链接,可以让一个用户账户升级为付费用户,这两个链接使用email发送。在这两种情况下,如果你签名的都是用户ID,那么该用户可以在激活账户和升级账户时,复用URL的可变部分。现在你可以在你签名的地方加上更多信息(如升级或激活的意图),但是你也可以用不同的盐

即只有使用相同盐的序列化器才能成功把值加载出来

def genter_auth_token(self,expiration=300):
    s=Serializer(current_app.config["SECRET_KEY"],salt="activate-salt",expires_in=expiration)
    return s.dumps({"code":self.name})

@staticmethod
def verify_auth_token(token):
    s=Serializer(current_app.config["SECRET_KEY"],salt="activate-salt")
图片验证码

这个验证码可以直接调用一些平台的智能验证,也可以用另一种
另一个也许是比较传统的思路,就是自己生成的图片水印,保存验证码
python和php里都有相应的图片操作方法,这里就写下python的

流程就是生成任意的数字,保存,添加图片水印

这里肯定要用的python强大的图片处理库PIL,其中用到了
加线条,滤镜等增加干扰
下面是完整代码,该做注释的地方我已经加了注释
看代码之前,最好先好好看下PIL官方文档,和一些基本概念
部分我参考的博文也贴在了文末

#!/usr/bin/env python 
#coding=utf-8
import os
import random
from flask import Flask,send_from_directory
from PIL import Image,ImageFont,ImageDraw,ImageFilter

app=Flask(__name__)
app.debug=True

class picture:
    def __init__(self):
        self.size = (240,60)
        self.mode="RGB"
        self.color="white"
        self.font = ImageFont.truetype("C:WindowsFontsArial.ttf", 36) #设置字体大小

    def randChar(self):
        basic="23456789abcdefghijklmnpqrstwxyzABCDEFGHIJKLMNPQRSTWXYZ"
        return basic[random.randint(0,len(basic)-1)] #随机字符

    def randBdColor(self):
        return (random.randint(64,255),random.randint(64,255),random.randint(64,255)) #背景

    def randTextColor(self):
        return (random.randint(32, 127), random.randint(32, 127), random.randint(32, 127)) #随机颜色

    def proPicture(self):
        new_image=Image.new(self.mode,self.size,self.color) #创建新图像有三个默认参数:尺寸,颜色,模式
        drawObject=ImageDraw.Draw(new_image) #创建一个可以对image操作的对象
        line_num = random.randint(4,6) # 干扰线条数
        for i in range(line_num):
            #size=(240,60)
            begin = (random.randint(0, self.size[0]), random.randint(0, self.size[1]))
            end = (random.randint(0, self.size[0]), random.randint(0, self.size[1]))
            drawObject.line([begin, end], self.randTextColor())

        for x in range(240):
            for y in range(60):
                tmp = random.randint(0,50)
                if tmp>30: #调整干扰点数量
                    drawObject.point((x,y),self.randBdColor())

        randchar=""  
        for i in range(5):
            rand=self.randChar()
            randchar+=rand
            drawObject.text([50*i+10,10],rand,self.randTextColor(),font=self.font) #写入字符

        new_image = new_image.filter(ImageFilter.SHARPEN) # 滤镜    

        return new_image,randchar
@app.route("/")
def get_file(filename):
    return send_from_directory(os.getcwd(),filename)

@app.route("/")
def index():
    test=picture()
    image,code=test.proPicture()
    image.save("new.jpg")
    url="http://127.0.0.1:5000/new.jpg"
    return "
"+"图中的code为:"+code #这里有缓存,需要CTRL+F5才会有效果 if __name__=="__main__": app.run()

另外,有的前辈会再加入了扭曲图像增加分辨难度

# 图形扭曲参数 
params = [1 - float(random.randint(1, 2)) / 100, 
              0, 
              0, 
              0, 
              1 - float(random.randint(1, 10)) / 100, 
              float(random.randint(1, 2)) / 500, 
              0.001, 
              float(random.randint(1, 2)) / 500 
              ] 
img = img.transform(size, Image.PERSPECTIVE, params) # 创建扭曲

这里有篇文章详细的介绍了下:

对当前图像进行透视变换,产生给定尺寸的新图像。
变量data是一个8元组(a,b,c,d,e,f,g,h),包括一个透视变换的系数。对于输出图像中的每个像素点,新的值来自于输入图像的位置的(a x + b y + c)/(g x + h y + 1), (d x+ e y + f)/(g x + h y + 1)像素,使用最接近的像素进行近似

这个的源定义就牵涉到了一个仿射变换,涉及一些数学的计算
看得我有点懵逼,就没加到我的代码中,先留坑

这个地方现在很多网站会使用另一种回答问题的方式,这个方法的实现
我个人感觉也是应该也是相同的手段,只是将随机的字符串改为问题,将验证方式改为答案
不过这里或许要把问题和答案存进数据库,更方便点,也才能实现

短信验证

有时候想自己是不是出生太晚了。。。。。想写的东西,都能搜到很好的博文,如下:flask开发restful api系列(5)-短信验证码
这里云通讯是文中所用平台的开发文档,不过平台可以自由选择,结果都是一样
这里就简化一下前辈的代码,把关于验证码处理的重点代码撸了出来,用到了Redis,我也趁机学了一波,的确挺好用的

import redis
import random

phonenumber=188888888
#这里可以利用正则过滤一下电话号码,比如:
#/^(13[0-9]|14[5-9]|15[0-9]|16[6]|17[0-8]|18[0-9]|19[8-9])d{8}$/

conn=redis.StrictRedis(host="127.0.0.1",port=6379)

def producCode():
    verifyCode=str(random.randint(100000,999999))

    pipe=conn.pipeline() #添加管道,可以一次连接执行多次命令
    pipe.set("phone%s"%phonenumber,verifyCode)
    pipe.expire("phone%s"%phonenumber,60) #设置过期时间一分钟
    pipe.execute()

def checkCode():
    pipe=conn.pipeline() #添加管道,可以一次连接执行多次命令
    pipe.set("postNum%s"%phonenumber,"0")
    validate_number = request.get_json().get("validate_number")
    pipe.incr("postNum%s"%phonenumber) #记录提交次数防止爆破
    if conn.get("postNum%s"%phonenumber)>3:
        pass
    ...
    if validate_number != validate_number_in_redis:
        return jsonify({"code": 0, "message": "验证没有通过"})
    pipe.set("is_validate:%s" % phone_number, "1") #通过验证码设置value为1
    pipe.expire("is_validate:%s" % phone_number, 120)
    pipe.execute()

    return jsonify({"code": 1, "message": "验证通过"})
def postMessage():
    result=conn.get("phone%s"%phonenumber)
    #此时如果通过验证码,result为1,否则为0
    ...
    #剩下的其他操作

这里提到了泄露接口导致验证码爆破的情况,我也添加了一些代码
另外就是在某些功能模块,也易出现漏洞,比如修改资料处,验证码不仅仅要与phone一致
也要检查用户名的一致性,要不然如果只是通过验证码,用户修改为自己的号码,验证码手机号都通过验证
(感觉一般人不会出现这种错误)

而你的代码又是直接传入用户名进行修改操作,这将可能导致任意用户重置密码
或者你的代码直接将phone作为索引进行修改

Github完整代码地址

参考文章: 狗书authentication
杂项之图像处理pillow
PIL一些基本概念
PIL中的Image模块
Python PIL ImageDraw和ImageFont模块学习
Python图像处理库PIL的ImageFilter模块介绍
Redis中文文档
redis-py

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/41003.html

相关文章

  • Ant Design UI组件之Select踩坑

    摘要:了解到项目使用版本是版本的,怀疑是版本问题。在了解到问题的根源后,修改相应代码。再去查看相应官方文档由于英文不好,没有理解到官方文档的意思。还是需要加强对英文官方文档的理解。前言 1. 在使用Ant design UI组件时总会遇到一些奇奇怪怪的问题,在本篇中将总结在使用Select时几种常见的问题 遇到的问题 在初始化Select值,如何根据value显示对应文本 showImg(http...

    NotFound 评论0 收藏0
  • 发布你自己轮子 - PyPI打包上传实践

    摘要:推荐遵循语义化版本号规则,简单说就像这样作者姓名和邮箱地址不一定要和你的账号一致。上传并完成发布你可以任选以下两种方式之一发布你的轮子。文件已经存在了,你每一次上次都应该更新版本号。 本文仅讨论上传相关的步骤,关于如何给写一个setup.py 请参阅官方文档: https://docs.python.org/2/dis... 上传前的注意事项 假设你的包已经开发完成,并且根目录必须要...

    sunny5541 评论0 收藏0
  • 现代后端开发者必备技能-2018版

    摘要:现在开始创建一个包并分发给其他人使用,并确保遵循你迄今为止学到的标准和最佳实践。第步实践对于练习,继续编写单元测试,以完成目前为止所做的实际任务,特别是你在步骤中所做的练习。 今天的Web开发与几年前完全不同,有很多不同的东西可以很容易地阻止任何人进入Web开发。这是我们决定制作这些循序渐进的视觉指南的原因之一,这些指南展示了更大的图景,并让任何人清楚了解他们在网页开发中扮演的角色。 ...

    eternalshallow 评论0 收藏0
  • 理解JWT(JSON Web Token)认证及python实践

    摘要:认证服务器,即服务提供商专门用来处理认证的服务器。它与认证服务器,可以是同一台服务器,也可以是不同的服务器。客户端使用上一步获得的授权,向认证服务器申请令牌。认证服务器对客户端进行认证以后,确认无误,同意发放令牌。 最近想做个小程序,需要用到授权认证流程。以前项目都是用的 OAuth2 认证,但是Sanic 使用OAuth2 不太方便,就想试一下 JWT 的认证方式。这一篇主要内容是 ...

    BigTomato 评论0 收藏0
  • 如何使用密码保护以太坊JSON-RPCAPI?

    摘要:本文面向以太坊智能合约应用程序开发人员,并讨论如何在密码保护后,安全地运行你的以太坊节点,以便通过进行安全输出。以太坊,主要是针对工程师使用进行区块链以太坊开发的详解。 本文面向以太坊智能合约应用程序开发人员,并讨论如何在密码保护后,安全地运行你的以太坊节点,以便通过Internet进行安全输出。 Go Ethereum(geth)是以太坊节点最受欢迎的软件。其他流行的以太坊实现是Pa...

    Zachary 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<