Python的Flask架構中實現簡單的登入功能的教程

來源:互聯網
上載者:User
回顧

在前面的系列章節中,我們建立了一個資料庫並且學著用使用者和郵件來填充,但是到現在我們還沒能夠植入到我們的程式中。 兩章之前,我們已經看到怎麼去建立網路表單並且留下了一個實現完全的登陸表單。

在這篇文章中,我們將基於我門所學的網路表單和資料庫來構建並實現我們自己的使用者登入系統。教程的最後我們小程式會實現新使用者註冊,登陸和退出的功能。

為了能跟上這章節,你需要前一章節最後部分,我們留下的微博程式。請確保你的程式已經正確安裝和運行。


在前面的章節,我們開始配置我們將要用到的Flask擴充。為了登入系統,我們將使用兩個擴充,Flask-Login 和 Flask-OpenID. 配置如下所示 (fileapp\__init__.py):

import osfrom flaskext.login import LoginManagerfrom flaskext.openid import OpenIDfrom config import basedir lm = LoginManager()lm.setup_app(app)oid = OpenID(app, os.path.join(basedir, 'tmp'))

Flask-OpenID 擴充為了可以儲存臨時檔案,需要一個臨時檔案夾路徑。為此,我們提供了它的位置。


重訪我們的使用者模型

Flask-Login擴充需要在我們的User類裡實現一些方法。除了這些方法以外,類沒有被要求實現其它方法。

下面是我們的User類 (fileapp/models.py):

class User(db.Model): id = db.Column(db.Integer, primary_key = True) nickname = db.Column(db.String(64), unique = True) email = db.Column(db.String(120), unique = True) role = db.Column(db.SmallInteger, default = ROLE_USER) posts = db.relationship('Post', backref = 'author', lazy = 'dynamic')  def is_authenticated(self):  return True  def is_active(self):  return True  def is_anonymous(self):  return False  def get_id(self):  return unicode(self.id)  def __repr__(self):  return '' % (self.name)

is_authenticated方法是一個誤導性的名字的方法,通常這個方法應該返回True,除非對象代表一個由於某種原因沒有被認證的使用者。

is_active方法應該為使用者返回True除非使用者不是啟用的,例如,他們已經被禁了。

is_anonymous方法應該為那些不被獲准登入的使用者返回True。

最後,get_id方法為使用者返回唯一的unicode標識符。我們用資料庫層產生唯一的id。

使用者載入回調

現在我們通過使用Flask-Login和Flask-OpenID擴充來實現登入系統

首先,我們需要寫一個方法從資料庫載入到一個使用者。這個方法會被Flask-Login使用(fileapp/views.py):

@lm.user_loaderdef load_user(id): return User.query.get(int(id))

記住Flask-Login裡的user id一直是unicode類型的,所以在我們把id傳遞給Flask-SQLAlchemy時,有必要把它轉化成integer類型。

登入視圖函數

接下來我們要更新登入視圖函數(fileapp/views.py):

from flask import render_template, flash, redirect, session, url_for, request, gfrom flaskext.login import login_user, logout_user, current_user, login_requiredfrom app import app, db, lm, oidfrom forms import LoginFormfrom models import User, ROLE_USER, ROLE_ADMIN @app.route('/login', methods = ['GET', 'POST'])@oid.loginhandlerdef login(): if g.user is not None and g.user.is_authenticated():  return redirect(url_for('index')) form = LoginForm() if form.validate_on_submit():  session['remember_me'] = form.remember_me.data  return oid.try_login(form.openid.data, ask_for = ['nickname', 'email']) return render_template('login.html',  title = 'Sign In',  form = form,  providers = app.config['OPENID_PROVIDERS'])

注意到我們匯入了一些新的模組,其中有些後面會用到。

跟上個版本的變化很小。我們給視圖函數添加了一個新的裝飾器:oid.loginhandler。它告訴Flask-OpenID這是我們的登入視圖函數。

在方法體的開頭,我們檢測是是否使用者是已經經過登入認證的,如果是就重新導向到index頁面。這兒的思路是如果一個使用者已經登入了,那麼我們不會讓它做二次登入。

全域變數g是Flask設定的,在一個request生命週期中,用來儲存和共用資料的變數。所以我猜你已經想到了,我們將把已經登入的使用者放到g變數裡。

我們在調用redirect()時使用的url_for()方法是Flask定義的從給定的view方法擷取url。如果你想重新導向到index頁面,你h很可能使用redirect('/index'),但是我們有很好的理由讓Flask為你構造url。

當我們從登入表單得到返回資料,接下來要啟動並執行代碼也是新寫的。這兒我們做兩件事。首先我們儲存remember_me的布爾值到Flask的session中,別和Flask-SQLAlchemy的db.session混淆了。我們已經知道在一個request的生命週期中用Flask的g對象來儲存和共用資料。沿著這條線路Flask的session提供了更多,更複雜的服務。一旦資料被儲存到session中,它將在同一用戶端發起的這次請求和這次以後的請求中永存而不會消亡。資料將保持在session中直到被明確的移除。為了做到這些,Flask為每個用戶端建立各自的session。

下面的oid.try_login是通過Flask-OpenID來執行使用者認證。這個方法有兩個參數,web表單提供的openid和OpenID provider提供的我們想要的list資料項目。由於我們定義了包含nickname和email的User類,所以我們要從找nickname和email這些項。

基於OpenID的認證是非同步。如果認證成功,Flask-OpenID將調用有由oid.after_login裝飾器註冊的方法。如果認證失敗那麼使用者會被重新導向到login頁面。

Flask-OpenID登入回調

這是我們實現的after_login方法(app/views.py)

@oid.after_logindef after_login(resp): if resp.email is None or resp.email == "":  flash('Invalid login. Please try again.')  redirect(url_for('login')) user = User.query.filter_by(email = resp.email).first() if user is None:  nickname = resp.nickname  if nickname is None or nickname == "":   nickname = resp.email.split('@')[0]  user = User(nickname = nickname, email = resp.email, role = ROLE_USER)  db.session.add(user)  db.session.commit() remember_me = False if 'remember_me' in session:  remember_me = session['remember_me']  session.pop('remember_me', None) login_user(user, remember = remember_me) return redirect(request.args.get('next') or url_for('index'))

傳給after_login方法的resp參數包含了OpenID provider返回的一些資訊。

第一個if聲明僅僅是為了驗證。我們要求一個有效email,所以一個沒有沒提供的email我們是沒法讓他登入的。


接下來,我們將根據email尋找資料庫。如果email沒有被找到我們就認為這是一個新的使用者,所以我們將在資料庫中增加一個新使用者,做法就像我們從之前章節學到的一樣。注意我們沒有處理nickname,因為一些OpenID provider並沒有包含這個資訊。

做完這些我們將從Flask session中擷取remember_me的值,如果它存在,那它是我們之前在login view方法中儲存到session中的boolean類型的值。

然後我們調用Flask-Login的login_user方法,來註冊這個有效登入。


最後,在最後一行我們重新導向到下一個頁面,或者如果在request請求中沒有提供下個頁面時,我們將重新導向到index頁面。

跳轉到下一頁的這個概念很簡單。比方說我們需要你登入才能導航到一個頁面,但你現在並未登入。在Flask-Login中你可以通過login_required裝飾器來限定未登入使用者。如果一個使用者想串連到一個限定的url,那麼他將被自動的重新導向到login頁面。Flask-Login將儲存最初的url作為下一個頁面,一旦登入完成我們便跳轉到這個頁面。

做這個工作Flask-Login需要知道使用者當前在那個頁面。我們可以在app的初始化組件裡配置它(app/__init__.py):

lm = LoginManager()lm.setup_app(app)lm.login_view = 'login'

全域變數g.user

如果你注意力很集中,那麼你應該記得在login view方法中我們通過檢查g.user來判斷一個使用者是否登入了。為了實現這個我們將使用Flask提供的before_request事件。任何一個被before_request裝飾器裝飾的方法將會在每次request請求被收到時提前與view方法執行。所以在這兒來設定我們的g.user變數(app/views.py):

@app.before_requestdef before_request(): g.user = current_user

這就是它要做的一切,current_user全域變數是被Flask-Login設定的,所以我們只需要把它拷貝到更容易被訪問的g變數就OK了。這樣,所有的請求都能訪問這個登入的使用者,甚至於內部的模板。

index視圖

在之前的章節中我們用假代碼遺留了我們的index視圖,因為那個時候我們系統裡並沒有使用者和部落格文章。現在我們有使用者了,所以,讓我們來完成它吧:

@app.route('/')@app.route('/index')@login_requireddef index(): user = g.user posts = [  {   'author': { 'nickname': 'John' },   'body': 'Beautiful day in Portland!'  },  {   'author': { 'nickname': 'Susan' },   'body': 'The Avengers movie was so cool!'  } ] return render_template('index.html',  title = 'Home',  user = user,  posts = posts)

在這個方法中只有兩處變動。首先,我們增加了login_required裝飾器。這樣表明了這個頁面只有登入使用者才能訪問。

另一個改動是把g.user傳給了模板,替換了之間的假對象。


現在可以運行我們的應用了。

當我們串連到http://localhost:5000你將會看到登陸頁面。記著如果你通過OpenID登入那麼你必須使用你的提供者提供的OpenID URL。你可以下面URL中的任何一個OpenID provider來為你產生一個正確的URL。

作為登入進程的一部分,你將會被重新導向到OpenID供應商的網站,你將在那兒認證和授權你共用給我們應用的一些資訊(我們只需要email和nickname,放心,不會有任何密碼或者其他個人資訊被曝光)。

一旦登入完成你將作為已登入使用者被帶到index頁面。

試試勾選remember_me複選框。有了這個選項當你在瀏覽器關閉應用後重新開啟時,你還是已登入狀態。

登出登入

我們已經實現了登入,現在是時候來實現登出登入了。

登出登入的方法灰常簡單(file app/views.py):

@app.route('/logout')def logout(): logout_user() return redirect(url_for('index'))

但我們在模板中還沒有登出登入的連結。我們將在base.html中的頂部導覽列添加這個連結(file app/templates/base.html):


  {% if title %} {{title}} - microblog {% else %} microblog {% endif %}   Microblog:  Home  {% if g.user.is_authenticated() %}  | Logout  {% endif %}   {% with messages = get_flashed_messages() %} {% if messages %} 
 
    {% for message in messages %}
  • {{ message }}
  • {% endfor %}
{% endif %} {% endwith %} {% block content %}{% endblock %}

這是多麼多麼簡單啊,我們只需要檢查一下g.user中是否有一個有效使用者,如果有我們就添加註銷連結。在我們的模板中我們再一次使用了url_for方法。

最後的話
我們現在有了一個全功能的使用者登入系統。在下一章中,我們將建立使用者的設定檔頁,並顯示使用者的頭像。

在此期間,這裡是更新的應用程式代碼,包括在這篇文章中的所有變化:

下載 microblog-0.5.zip 。

  • 相關文章

    聯繫我們

    該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

    如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.