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中。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM