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

  • 相关阅读:
    ZQUOJ 1964 Hamilton回路(状压dp)
    ZQUOJ 1398 Hamilton路径(DFS,稀疏图,n≤40)
    2019 CCPC
    百度之星初赛第三场 1002 最短路2 (Floyd算法优化)
    2019牛客暑期多校训练营(第四场)J free(分层图最短路/模板题)
    2019牛客暑期多校训练营(第一场)I Points Division(DP+线段树)
    2019牛客暑期多校训练营(第七场)A String(枚举)
    2019牛客暑期多校训练营(第九场)B Quadratic equation(欧拉准则+解二次剩余)
    二次剩余系解法
    二次剩余的判断(欧拉准则)
  • 原文地址:https://www.cnblogs.com/xsseng/p/10659812.html
Copyright © 2011-2022 走看看