zoukankan      html  css  js  c++  java
  • flask-login原理详解

    最近发现项目中使用的flask-login中有些bug,直接使用官网的方式确实可以用,但仅仅是可以用,对于原理或解决问题没有什么帮助,最近通过查看网上资料、分析源码、通过demo、从零开始总结了flask-login的使用方式及内部实现原理。

    先说使用,安装组件就不说了,太简单。

    安装好了之后就是把组件注册到flask中,这个简单说下,具体flask如何注册这些扩展的原理后续再补上,引用官网的说法:登录管理(login manager)包含了让你的应用和 Flask-Login 协同工作的代码,比如怎样从一个 ID 加载用户,当用户需要登录的时候跳转到哪里等等。具体注册代码如下:

    # encoding:utf-8
    from flask.ext.login import LoginManager
    app = Flask(__name__)
    login_manager = LoginManager()
    login_manager.init_app(app)
    login_manager.login_view = "login" #配置如果需要登录调整的路由

    注册好了之后,就可以使用了,以下是路由函数的写法,具体login_required、login_user的实现原理后面会再说

    @app.route('/')
    @login_required                                         #进入首页需要判断用户是否登入,没有登录则跳转到注册时配置的路由
    def index():
        return render_template('index.html')
    
    @app.route('/login', methods = ['POST', 'GET'])
    def login():
        if request.method == 'POST':
            req = request.get_json()
            username = req['username']
            userpassword = req['userpassward']
            if auth_user(username, userpassword):            #判断用户名密码是否正确,这里随便写一个方法,写死用户名密码就可以
                login_user(User(14,'root', 'root'))          #用户名密码验证通过调用login_user把user注册到请求上下文session中,这个session其实就是一个LocalStack
                return url_for('index')
            flash(u'无效的用户名或密码')
        return render_template('login.html')

    还差一部分,创建models模块,用处两个,一个用来定义User类,二用来注册回调函数,这个回调函数通过user_id返回User实例。这里只是想弄清楚login的原理所有没用数据库,尽量聚焦

    from flask.ext.login import UserMixin
    from app import login_manager
    
    class User(UserMixin):
        __tablename__ = 'users'
    
        def __init__(self, id,password, username):
            '''
            :param id:
            :param username:
            '''
            self.id = id                    #这个属性一定要有,否则自己要重写get_id方法,不信自己去试下
            self.password = password
            self.username = username
    
        def __repr__(self):
            return '<User %r>' % self.username
    
    
    @login_manager.user_loader
    def load_user(user_id):
        print user_id
        return User(14, 'root', 'root')

    ok,基本使用完成了,前端再写两个简单页面就可以index.html和login.html。代码如下,这里主要理解后端流程,前端能用就可以

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>hello, {{ name }}</title>
    </head>
    <body>
    <form name="myform">
      <label>用户名:</label>
      <input type="text" name="fname"/>
      <br>
      <label>密码:</label>
      <input type="text" name="address"/>
      <button type="button" onclick="login()">提交</button>
    </form>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    
    <script>
      function login() {
        axios({
          method: 'post',
          url: '/login',
          data: {
            username: myform.fname.value,
            userpassward: myform.address.value
          }
        }).then(function (response) {
          location.href = response.data
        }).catch(function (error) {
          console.log(error);
        });
      }
      
    </script>
    
    </body>
    </html>
    login
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>Title</title>
    </head>
    <body>
    ceshi
    <button type="button" onclick="login()">提交</button>
    </body>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <script>
      {#  发送用户名、密码#}
      function login() {
        axios({
          method: 'post',
          url: '/test',
          data: {
            username: 'c',
            userpassward: 'y'
          }
        }).then(function (response) {
          console.log(response.data);
        }).catch(function (error) {
          console.log(error);
        });
      }
    </script>
    </html>
    index.html

    具体使用过程梳理完成。

    再看看其实现的原理,后端路由接收到前端请求后,会进入login_require装饰器中,而此装饰器用来判断用户鉴权情况,如下图:

     

     那用户登录鉴权的关键在装饰器@login_required中,先看下整体的鉴权流程,再深入各个部分,下图为鉴权的整体流程图

    此图对应的代码

    def login_required(func):
        @wraps(func)
        def decorated_view(*args, **kwargs):
            if current_app.login_manager._login_disabled:
                return func(*args, **kwargs)
            elif not current_user.is_authenticated:
                return current_app.login_manager.unauthorized()
            return func(*args, **kwargs)
        return decorated_view
    login_required

    流程解析

    1. 判断请求是否需要鉴权,current_app.login_manager._login_disabled,通常的命令get、post等请求都需要鉴权,此属性为False。

    2. 判断当前用户是否鉴权,current_user.is_authenticated。current_user--------从哪获取?继续分析代码

    current_user = LocalProxy(lambda: _get_user())

    current_user通过LoaclProxy创建了一个无名函数_get_user,为什么用lambda:_get_user()而不直接使用_get_user?我也没想明白,有牛人可以解释一下。

    LoaclProxy具体可以参考https://www.jianshu.com/p/3f38b777a621,说白了就是理解为把方法地址传给变量,以后可以动态调用代理方法

    获取current_user通过_get_user()函数,先看下该函数的代码

    def _get_user():
        if has_request_context() and not hasattr(_request_ctx_stack.top, 'user'):
            current_app.login_manager._load_user()
    
        return getattr(_request_ctx_stack.top, 'user', None)

    解释一下,首先需要知道请求上下文,代码中的_request_ctx_stack.top中是否有user这个变量,如果没有则_load_user,如果有直接取这个user属性返回给前面的说用来鉴权使用。

     _load_user会调用reload_user函数,

        def reload_user(self, user=None):
            ctx = _request_ctx_stack.top
    
            if user is None:
                user_id = session.get('user_id')
                if user_id is None:
                    ctx.user = self.anonymous_user()
                else:
                    if self.user_callback is None:
                        raise Exception(
                            "No user_loader has been installed for this "
                            "LoginManager. Add one with the "
                            "'LoginManager.user_loader' decorator.")
                    user = self.user_callback(user_id)
                    if user is None:
                        ctx.user = self.anonymous_user()
                    else:
                        ctx.user = user
            else:
                ctx.user = user

    该函数判断_request_ctx_stack.top赋值user对象,对象可以传入,也可以使用我们在使用过程中定义的def load_user(user_id),这个user_callback就是我们定义的load_user函数。

     获取user如图

    登入时和重新请求时都会将user放入到栈中,每次请求后都出清理top中的user。放入top时会设置user_id到session中。

  • 相关阅读:
    1393 0和1相等串 鸽笼原理 || 化简dp公式
    C. Coin Troubles 有依赖的背包 + 完全背包变形
    D. PolandBall and Polygon BIT + 欧拉公式
    51NOD 1639 绑鞋带 数学
    D. Fedor and coupons 二分暴力
    hdu 4104 Discount
    bnu 51640 Training Plan DP
    hdu 5745 La Vie en rose DP + bitset优化
    hdu 5036 Explosion bitset优化floyd
    1354 选数字 DP背包 + 数学剪枝
  • 原文地址:https://www.cnblogs.com/caoyi/p/9956296.html
Copyright © 2011-2022 走看看