# Flask Extension(04)\_Flask-Login ###### tags: `book` `flask` `flask 2.x` `flask extension` `flask-login` `python` [Flask-Login](https://flask-login.readthedocs.io/en/latest/) [Flask-Login_github](https://github.com/maxcountryman/flask-login) Flask-Login,非常老牌的一個擴展應用,對於我們實作登入系統來說非常的實用。在我寫flask 1.x的時候所用的似乎是版本0.3,目前(20230213)最新為0.7,更重要的是,這個套件一直都有持續在更新,用起來當然就會特別放心。 當然,官方文件開頭就說明著這個套件的功能就在於幫我們處理登入、登出、以及我們在網頁切換過程中的一些檢核,大概就是: 1. 把你的使用者id保存在flask session之中 2. 把你的`view`設置為需求登入(利用裝飾器`login_required`) 3. 幫你處理`remember me`這個實用的功能 4. 保護你的使用者session不會被偷走 剩下的就沒有,使用Flask最大的優點就是自由度高,所以剩下的你可以自己寫或是再找找有沒有其它的擴展可以搭配使用的。比方說,也許你會想要一個驗證申請的功能,這就要自己寫了,當然,這是我們實作上必然會寫到的功能。 ## 安裝 ```python= pip install flask-login ``` ## 應用 flask-login中最重要的就是`LoginManager`,實作的方式很簡單: ```python= from flask_login import LoginManager login_manager = LoginManager() ``` 如果有參數要宣染的話,就可以接著做: ```python= # app即為flask object login_manager.init_app(app) ``` 事實上這類的擴展設置在flask中是非常標準的流程,唯一要注意的就是flask-login是使用flask session來做為驗證,所以你必需一定肯定務必要先行設置`secret key`: ```python= app.config['SECRET_KEY'] = '自己設置你的密鑰' ``` :::info * 關於如何生成一個好的密鑰,你可以參考[官方文件(這是超連結)](https://flask.palletsprojects.com/en/2.2.x/quickstart/#sessions)中的How to generate good secret keys * 官方還很貼心的提到login之後的redirect要小心注意些什麼,這可以參考[Securely Redirect Back](https://web.archive.org/web/20120517003641/http://flask.pocoo.org/snippets/62/) ::: ## 範例 ### 前置設置一 初始化flask-login之後有一個函數必需要設置,也就是`user_loader`: ```python= @login_manager.user_loader def load_user(user_id): return User.get(user_id) ``` 這個函數主要是讓flask-login隨時想到就能找user,所以可以發現它是用我們剛剛初始化的`login_manager`做為裝飾器來包裝`load_user`,回傳的就是使用者。官方文件中有提到,當給定的`user_id`取不到資料的時候不會拋出異常,而是回傳`None`。 ### 前置設置二 另外就是,根據[官方文件](https://flask-login.readthedocs.io/en/latest/#your-user-class)所說,在我們所規劃的使用者模型中有幾個必需要設置的屬性: 1. is_authenticated 2. is_active 3. is_anonymous 4. get_id() 關於這個部份我們可以透過繼承官方所提供的`UserMixin`來處理即可,後續實作中就可以看到相對應的應用了,不用擔心看不懂。 ## 範例 範例來自來自[官方git](https://github.com/maxcountryman/flask-login),相關的說明都以註解的方式寫在上面,較為直觀瞭解。 首先我們先來初始化flask: ```python= import flask app = flask.Flask(__name__) app.secret_key = 'super secret string' # 這邊的密鑰記得設置成你自己的 ``` 接著就是初始化flask-login,然後讓flask認識它: ```python= import flask_login login_manager = flask_login.LoginManager() login_manager.init_app(app) # 讓flask認識flask-login ``` 弄一筆假資料: ```python= users = {'foo@bar.tld': {'password': 'secret'}} ``` 設置一個類別`User`,並且這個類別是繼承上面我們提過的`UserMixin`: ```python= class User(flask_login.UserMixin): pass ``` 接著設置在前置設置一中提過的callback function,`user_loader`: ```python= @login_manager.user_loader def user_loader(email): if email not in users: return user = User() user.id = email return user ``` 官方還提供一個另類的選擇,`request_loader`,這未來如果有談到api的話應該有機會可以用到: ```python= @login_manager.request_loader def request_loader(request): email = request.form.get('email') if email not in users: return user = User() user.id = email return user ``` 現在,我們可以來定義關於login的view: ```python= @app.route('/login', methods=['GET', 'POST']) def login(): if flask.request.method == 'GET': return ''' <form action='login' method='POST'> <input type='text' name='email' id='email' placeholder='email'/> <input type='password' name='password' id='password' placeholder='password'/> <input type='submit' name='submit'/> </form> ''' email = flask.request.form['email'] if email in users and flask.request.form['password'] == users[email]['password']: user = User() user.id = email flask_login.login_user(user) return flask.redirect(flask.url_for('protected')) return 'Bad login' @app.route('/protected') @flask_login.login_required def protected(): return 'Logged in as: ' + flask_login.current_user.id ``` 上面`login`這個view的程式碼範例基本上就是先判斷現在這個request是GET還是POST,如果是GET那就直接回傳一個頁面給使用者填入資料。如果是POST那就進入資料的判斷,確定資料無誤就把當前的登入用戶註冊給flask-login。 接著再將用戶引導到`protected`這個view,可以看的到這個view的上頭存在一個`@flask_login.login_required`的裝飾器,被這個裝飾器包裝起來的view就意謂著必需是登入狀態才能進入,否則你就是會被引導回`login`這個view。 當然,有登入就會有登出,實作登出的部份也是很容易的: ```python= @app.route('/logout') def logout(): flask_login.logout_user() return 'Logged out' ``` 這裡面還有一個未授權的管理: ```python= @login_manager.unauthorized_handler def unauthorized_handler(): return 'Unauthorized', 401 ``` 這意謂著用戶如果是在未授權情況下某一個view,就會被引導到這邊並且拋出401,我們可以執行官方提供的範例,就把全部的程式碼擠在一個python文件中,然後加一段: ```python= if __name__ == '__main__': app.run() ``` 執行之後,在未登入的情況下直接去`protected`這個view會得到下面的回應: ![](https://hackmd.io/_uploads/S1VEdMFaj.png) 還記得嗎?這是一個利用`login_required`包裝起來的view,因此必需是在登入狀態才能順利進入這個view。只要你先到`login`,然後輸入正確的帳號密碼,就會直接把你引導到`protected`: ![](https://hackmd.io/_uploads/Hk7h_zKpi.png) ## 結論 利用flask-login可以有效幫助我們處理掉不少登入狀態管理的事情,後面實作上當然還會有登入狀態以外的事情需要處理,不過有flask-login的幫助著實省了不少功。 值得注意的是,我們這邊所聊到的不過就是官方文件的一半,比方說我們並沒有提到`current_user`之類的,所以還是會非常建議簡單快速的看過一次的官方文件。