Flask验证码的完善,使用redis实现验证码的超时过期和验证失效
  • 分类:Python
  • 发表:2019-10-22
  • 围观(65)
  • 评论(0)

验证码的总是与网站安全挂钩,所以我这段时间一直在研究如何用Flask实现最基础的图形验证码。之前已经发了篇文章:Flask验证码的使用,运用PIL库生成简单的图形验证码。该文章实现了简单的图形验证码生成和验证,但是同样留下了很多致命的问题。主要是因为之前的实现了简单的cookie验证,完全没有做验证码的使用过期处理和超时过期处理,导致一个验证码可以一直使用不会失效,充其量算是个残次品。可以说只能防君子不能防小人,可是君子的话根本没有验证码存在的必要不是么?

所以我一直在思考如何给验证码加一个过期时间,同时让使用过后的验证码不能继续使用,可以说这个问题困扰了我很久。要想验证码有过期时间的话就一定要把验证码存在数据库中,同时只验证数据库中存在的验证码就可以防止单个验证码重复使用的问题。常用的关系型数据库明显是不符合当前的使用场景的,而redis这种基于key-value的非关系型数据库就派上用场了。而且redis支持给key设置过期时间,可以说与我们的要求十分契合了。所以我就试着用redis做了简单的实现。

验证码前端效果

我先用Bootstrap写了一个简单的登录页面,验证码是直接生成的随机字符,并没有用PIL生成具体的验证码。如果需要生成验证码的代码可以去看之前的文章。具体效果就是点击登录之后如果通过验证且验证码的时候将会返回登录成功。但是如果这个时候按下F5重新发送表单的话将会提示验证码失效。同时如果在登录界面超过一定的时间没有登陆的话验证码就会失效,此时登录的话会提示验证码已经失效。

其实我本来想把极验证集成到Flask里面,奈何我连账号都注册不到,填写了注册信息之后他们说24小时会联系我,但是却没有。算了算了,用不起用不起,还是自己研究研究吧。

具体实现

技术栈已经确定了,现在就差如何代码的实现了。需要把验证码存到redis数据库面,value值很容易想到就是我们验证码数据,但是这个key应该用那个值来定义呢?首先这个key值他不是随机生成的,它要与每一个客户端对应,同一个客户端的key不可以一直变化。其次这个key需要时唯一的,不同用户之间的key不可以冲突。

我首先想到的是通过客户端传来的请求头中的数据,比如说IP+user-agent通过一定的hash函数生成一个key,这样就可以保证key唯一且不会随机变化。但是在我最近使用Flask-WTF这个扩展的时候,Flask-WTF帮我们为每一个form生成的csrf_token似乎就满足上述的要求。这个csrf_token并不是渲染到form表单里面的那个token,而是Flask-WTF生成存在于session里的token。这个token可以保证浏览器唯一,而且不会随机改变。选用他作为我们数据库的key再好不过了。

具体验证流程

整个流程不涉及很多难点,只是需要我们通过Flask操作redis数据库,这个对我来说可能有点陌生。但是redis入门真的并不是很难,况且有pyredis和Flask-redis这样现成的轮子给我们用,上手起来的确是不费力气。


代码实现

因为代码的确不是很复杂,也只是用到几个核心的函数,比如说生成随机验证码和主验证视图函数。这里只做代码的简单演示,真的有兴趣的同学代码已经开源到Github了,可以clone下来跑一下。

@web.route("/", methods=("GET", "POST"))
def login():
    form = LoginForm()

    if form.validate_on_submit():
        if form.username.data == "weiney" and form.password.data == "123456":
            csrf_token = session.get("csrf_token")
            server_code = redis_store.get(csrf_token)
            if server_code is None:
                return "验证码失效,请刷新重试"
            if server_code == form.verification.data.lower():
                redis_store.delete(csrf_token)
                return "登录成功"
            else:
                return "验证码错误"

    code = randon_code()
    redis_store.set(session.get("csrf_token"), code.lower(), ex=current_app.config.get("VERIFICATION_TIMEOUT", 300))
    return render_template("login/login.html", form=form, code=code)

具体逻辑和我上面展示的流程图没有什么出入,代码只做了简单的验证,真正的账号密码验证肯定不能像这样随意。不过无所谓了我也只是做测试而已,更何况用文字验证码代替图形验证码这种事情我都干出来了,还有什么做不出来的?

具体的屁话我也不想多说了,我再也不愿意记录之前那些又臭又长的文字了。Talking is cheap,show me the code,这句话简直真理了。有不懂的可以留言问我,我肯定是会回的,毕竟就这几个人看我都是把你们当宝贝一样捧在手心里的哦!!!

写在最后

学习Flask的路上总会有些心得,我每天都有写一点代码,虽然效率很低。我想自己用Flask实现很多简单Web的模块,权当作自己的练手项目。我会把这个项目开源到Github上面,项目名称就叫Raspberry,你问我为什么叫这个名字,因为我把这个项目部署到了我的树莓派上所以就起了这么个名字,希望这个小项目可以帮助到大家,一起学习一起进步。

Github地址:https://github.com/Weiney/raspberry,都是些没有技术含量的代码,但是也希望有小星星⭐⭐!


共有 0 条评论

Top