zoukankan      html  css  js  c++  java
  • Flask使用修饰器做后台登录认证

    说说需求

    在Flask中,我们会有登录认证的需求,比如说一个后台的Api,我们需要用户登录过才能访问,那么我们就要在Api前去判断用户是否拥有此权限。

    就拿我的项目Onebel来说,在没有用修饰器前我是这么写的

    1 from flask import Flask, url_for, request, make_response, session
    2 
    3 class doHeader:
    4     def islogin(self):
    5         if session.get('isLogin'):
    6             return None
    7         else:
    8             return True

    这里是一个验证是否登录的类,每次路由根据该类返回一个bool,确定是否登录过,在处理登录逻辑

    # -*- coding: UTF-8 -*-
    from . import main
    from flask import request, render_template, session, redirect, url_for, make_response
    from module.Mysql import *
    from module.header import *
    
    @main.route('/index')
    def index():
        return 'hello world!'
    
    @main.route('/login', methods = ['GET', 'POST'])
    def login(name=None):
        if request.method == 'GET':
            return render_template('login.html',name=name)
        else:
            username = request.form['username']
            password = request.form['password']
            t = Mysqlclass()
            q = t.getOnedata("SELECT * from crm_user where username = %s and password = %s",(username,password))
            if q:
                session.permanent = True
                session['isLogin'] = 1
                session['username'] = username
                return redirect(url_for('main.member'))
            else:
                return 'password error'
            #处理登录逻辑
    
    @main.route('/member')
    def member(name=None):
        t = doHeader()
        if t.islogin():
            return '0'
    return 'hello' + str(session.get('username'))

    当然@main.route('/member')我还没有写好,不过大概就是实例化doHeader()类,来做是否登录的判断,登录则继续执行代码,不登录则跳转到/login,那么就会出现一个问题,每个后台路由如果都要实例化一个类来的话,代码就会很复炸,并且也容易忘记自己要做权限控制,于是就有了修饰器。

    修饰器

    于是就发现,有一种修饰器,可以直接在路由下面引入,做权限认证,首先我们看一下修饰器的原理

    @abcd
    def f():
        pass

    这一段语句等价于

    def f():
        pass

    f=abcd(f)

    我们现在有一个函数abcd,这个函数的本质是:

    接受另一个函数当做参数,且返回一个函数。(至于返回的函数用来干嘛那就是你的事了)。这时候,abcd仅仅就是个函数而已,还不是修饰器。

    而由于以下这个需求十分的常见:有一个旧函数,我们又想定义一个新函数,这个新函数大体上功能与旧函数接近,只是多了一点点的新功能,比如打印个日期,判断个权限什么的。那么定义新函数的过程中肯定会调用这个旧函数,然而新函数其实改变不大,旧函数往往也没用了(因为一般我们后面都是用的新函数了),那么为了不让命名空间变得混乱和方便开发,我们可以简单的就用旧函数的名字来表示新函数,也就是在定义完了一个新函数之后,我们把它的名字又变回以前的f,而以前的f就不要了。所以我们可以这样做:定义一个函数abcd,它接受一个函数f,且返回一个新的函数,再把它的返回值(新函数),再赋值给f(python里函数名也可以赋值,成为另一个函数)。这实际上就是我上面的第二段代码做的事情。由于这个需求太过常见,所以python专门为它定义了语法。你不是每次都要f=abcd(f)吗,那你就直接在f的def语句前面加个@abcd得了,也别每次再写后面那句了,不仅麻烦,有时还容易误解。这时候,abcd就成为了装饰器。

    @permission_required(permission)
    def old():
        pass

    等价于

    def old():
        pass
    old = permission_required(permission)(old)

    优先级相同,运算从左到右,首先计算permission_required(permission),它返回decorator是一个函数,这时候变成old = decorator(old)为了满足成为修饰器的要求,这个decorator应当返回一个新函数才行,在这里就是decorated_function,所以原赋值语句变成old = decorated_function。到这里比较清晰了,把一个函数的名字赋值给一个变量(old),所以old就变成了decorated_function这里所定义的函数。
    过程也就是:

    old = permission_required(permission)(old)
    -> old = decorator(old)
    -> old = decorated_function

    在举个例子就是

    from functools import wraps
    
    def permission_required(permission):
        """返回装饰器,装饰器中使用入参 permission
        """
        def decorator(f):
            @wraps(f)
            def decorated_function(*args, **kwargs):
                if not permission:
                    print '403'
                    return
                return f(*args, **kwargs)
            return decorated_function
        return decorator
    
    
    def admin_required_true(f):
        """装饰器函数,返回装饰器
        """
        return permission_required(True)(f)
    
    def admin_required_false(f):
        """装饰器函数,返回装饰器
        """
        return permission_required(False)(f)
    
    @admin_required_true
    def foo():
        """使用装饰器
        """
        print 'foo'
        
    @admin_required_false
    def bar():
        """使用装饰器
        """
        print 'bar'
    
    foo()
    bar()

    输出结果:

    foo
    403

    完成登录认证

    因此我们可以写一个查看session的修饰器(这之前我找了一些认证,发现大家偏好于用数据库来处理这个问题。实际上这个适用于后台还有单独的权限管理,一般的用以下代码就可以)

    # -*- coding: UTF-8 -*-
    #权限控制
    from flask import session, redirect
    
    def loginAuth(func):
        def inner(*args,**kwargs):
            if not session.get('isLogin'):
                return redirect('/admin/login')
            return func(*args,**kwargs)
        return inner

    看看路由怎么处理

    # -*- coding: UTF-8 -*-
    from . import main
    from flask import request, render_template, session, redirect, url_for, make_response
    from module.Mysql import *
    from module.header import *
    
    @main.route('/index')
    def index():
        return 'hello world!'
    
    @main.route('/login', methods = ['GET', 'POST'])
    def login(name=None):
        if request.method == 'GET':
            return render_template('login.html',name=name)
        else:
            username = request.form['username']
            password = request.form['password']
            t = Mysqlclass()
            q = t.getOnedata("SELECT * from crm_user where username = %s and password = %s",(username,password))
            if q:
                session.permanent = True
                session['isLogin'] = 1
                session['username'] = username
                return redirect('/admin/member')
            else:
                return 'password error'
            #处理登录逻辑
    
    @main.route('/member')
    @loginAuth
    def member(name=None):
        return 'hello' + str(session.get('username'))

    于是就完成了登录,不过需要注意的是,登录后的跳转不可以直接用url_for了,因为这里用了修饰器的缘故

    redirect('/admin/member')

    所以我们只能直接跳转,那么以后要改接口path,我们就要改两个地方,比较麻烦,不知道有没有还可以用url_for实现的办法,知道的朋友可以给个留言

    参考https://segmentfault.com/q/1010000009349276

  • 相关阅读:
    高级特性(4)- 数据库编程
    UVA Jin Ge Jin Qu hao 12563
    UVA 116 Unidirectional TSP
    HDU 2224 The shortest path
    poj 2677 Tour
    【算法学习】双调欧几里得旅行商问题(动态规划)
    南洋理工大学 ACM 在线评测系统 矩形嵌套
    UVA The Tower of Babylon
    uva A Spy in the Metro(洛谷 P2583 地铁间谍)
    洛谷 P1095 守望者的逃离
  • 原文地址:https://www.cnblogs.com/xsseng/p/10659812.html
Copyright © 2011-2022 走看看