從頭搭建一個flask鑑權系統之登陸

2021-02-07 蘿蔔大雜燴

 從今天開始,準備從頭開始搭建一個基於flask的鑑權系統,一點一滴,積累於生活

本文涉及到如下知識點

1. flask-login的簡單使用

2. 本地鑑權實踐

3. GitHub鑑權登陸實踐,flask-github使用

4. 可擴展的表結構設計思路

我們首先設計一個User用戶表,裡面的欄位可以包括username,password,email等用戶信息,大致如下

usernamepasswordemailuser1p1user1@gmail.comuser2p2user2@gmail.comuser3p3user3@gmail.com

因為我們還會涉及到第三方登陸,那麼為了後面便於擴展,再設計一張表,就命名為ThirdAuth,裡面可以包括user_id,與user表關聯,oauth_name,oauth_access_token等欄位

user_idoauth_nameoauth_access_tokenuser-id1auth1token1user-id2auth2token2user-id3auth3token3

這樣,oauth_name欄位可以用來存儲第三方來源,例如github,以此來區別不同的第三方登陸用戶。

到此,一個簡單的表結構就設計好了。

03.OAuth鑑權

簡單來說,為一個網站添加第三方登錄指的是提供通過其他第三方平臺帳號登入當前網站的功能。比如,使用QQ、微信、新浪微博帳號登錄。對於某些網站,甚至可以僅提供社交帳號登錄的選項,這樣網站本身就不需要管理用戶帳戶等相關信息。對用戶來說,使用第三方登錄可以省去註冊的步驟,更加方便和快捷。這裡,我就是使用GitHub的OAuth認證來進行鑑權登陸。

這裡首先需要在自己的GitHub上創建一個OAuth程序,非常簡單,訪問這個地址:https://github.com/settings/applications/new,按照要求填寫即可。

其中的callback需要填寫一個回調函數,具體後面再說。

創建好這個OAuth程序後,我們就會獲得Client ID(客戶端ID)和Client Secret(客戶端密鑰),在後面調用Github的API時使用。

04. 本地鑑權

根據剛才的表結構設計,對於本地鑑權,可以在models.py文件中創建一個WebUser類,定義對應的資料庫欄位。

對於password,不建議直接在資料庫中存儲明文,所以這裡使用了werkzeug庫來做hash轉換。

同時WebUser類還繼承自flask-login的UserMixin類,該類實現了關鍵的用於檢測用戶狀態的方法:

    is_authenticated,如果用戶已經登陸返回True,否則返回False

    is_active,如果用戶允許登陸,返回True,否則返回Flase

    is_anonymous,對普通用戶必須返回False

    get_id,必須返回用戶的唯一標識

後面主要使用到了is_authenticated方法。

而init_user是用來初始化第一個用戶的,password等幾個方法分別是用來檢測密碼是否正確的。

class WebUser(UserMixin, db.Model):
    __tablename__ = 'webuser'
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.String(64), unique=True, index=True)
    email = db.Column(db.String(64), unique=True, index=True)
    username = db.Column(db.String(64), unique=True, index=True)
    password_hash = db.Column(db.String(128))

    @staticmethod
    def init_user():
        users = WebUser.query.filter_by(username='admin').first()
        if users is None:
            users = WebUser(email='admin@123.com', username='admin', user_id=time.time())
        users.password = '123456'
        db.session.add(users)
        db.session.commit()

    @property
    def password(self):
        raise AttributeError('password is not readable attribute')

    @password.setter
    def password(self, password):
        self.password_hash = generate_password_hash(password)

    def verify_password(self, password):
        return check_password_hash(self.password_hash, password)

2. 定義登陸表單

登陸表單比較簡單,兩個輸入框,分別為用戶名和密碼,一個check box,用來選擇是否保持登陸,外加一個提交按鈕

class LoginForm(FlaskForm):
    email = StringField('Email', validators=[DataRequired(), Length(1, 64), Email()])
    password = PasswordField('Password', validators=[DataRequired()])
    remember_me = BooleanField('Keep me logged in')
    submit = SubmitField('Log In')

3. 定義登陸登出函數

當表單正確提交時,如果用戶名和密碼匹配,則提示登陸成功,並跳轉頁面,否則提示登陸失敗。

因為是使用flask-login擴展,所以登陸直接調用login_user()即可。

@auth.route('/login', methods=['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        user = WebUser.query.filter_by(email=form.email.data).first()
        if user is not None and user.verify_password(form.password.data):
            login_user(user, form.remember_me.data)
            return redirect(request.args.get('next') or url_for('main.index'))
        flash('Invalid username or password!')
    return render_template('auth/login.html', form=form)

對於登出,同樣簡單,注意需要用login_required裝飾器保證只有已經登陸的用戶才能調用該函數。

@auth.route('/logout')
@login_required
def logout():
    flash('You have logged out!')
    return redirect(url_for('main.index'))

4. web模板

創建一個base.html基礎模板(繼承自flask-bootstrap模板),後面其他頁面都繼承自該模板,這樣可以保證所有的頁面風格統一,也可以減少代碼量。

{% extends "bootstrap/base.html" %} 

{% block title %}Flasky{% endblock %} 

{% block navbar %}
<div class="navbar navbar-inverse" role="navigation">
    <div class="container">
        <div class="navbar-header">
            <button type="button" class="navbar-toggle"  data-toggle="collapse" data-target=".navbar-collapse">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="/">WebAuth</a>
        </div>
        <div class="navbar-collapse collapse">
            <ul class="nav navbar-nav">
                <li><a href="/">Home</a></li>
            </ul>
            <ul class="nav navbar-nav navbar-right">
                {% if current_user.is_authenticated %}
                <li><a href="{{ url_for('auth.logout') }}">Sign Out</a></li>
                {% else %}
                <li><a href="{{ url_for('auth.login') }}">Sign In</a></li>
                {% endif %}
            </ul>
        </div>
    </div>
</div>
{% endblock %}

{% block content %}
<div class="container">
    {% block page_content %}{% endblock %}
</div>
{% endblock %}

5. 登陸頁面

登陸頁面繼承自base.html模板,並使用wtf快速渲染表單

{% extends "base.html" %}
{% import  "bootstrap/wtf.html" as wtf %}
{% block title %}Login{% endblock %}
{% block page_content %}
<div class="page-header">
    <h1>Login</h1>
</div>

<div class="col-md-4">
    {{ wtf.quick_form(form) }}
</div>
{% endblock %}

最後的登陸頁面為

6. 初始化資料庫

使用flask-script擴展,定義runserver和shell兩個命令行命令,shell用於資料庫等調測操作,runserver用於啟動服務。

from app import create_app, db
from flask_script import Manager, Shell, Server
from app.models import WebUser


app = create_app('testing')
manager = Manager(app)


def make_shell_context():
    return dict(app=app, db=db, WebUser=WebUser)


manager.add_command("runserver", Server(use_debugger=True, host='0.0.0.0', port='9982'))
manager.add_command("shell", Shell(make_context=make_shell_context))


if __name__ == '__main__':
    manager.run(default_command='runserver')

在命令行輸入python manage.py shell,進入調測shell,然後輸入db.create_all()和WebUser.init_user(),分別創建表並插入原始用戶。

7. 登陸測試

在輸入框分別鍵入admin@163.com和123456,並點擊登陸,發現可以正常登陸,效果如下

其中index頁面代碼為

{% extends "base.html" %}
{% import  "bootstrap/wtf.html" as wtf %}
{% block title %}Login{% endblock %}
{% block page_content %}
<div class="container">
    {% for message in get_flashed_messages() %}
    <div class="alert alert-warning">
        <button type="button" class="close" data-dismiss="alert">&times;</button>
        {{ message }}
    </div>
    {% endfor %}
</div>
<div class="page-header">
    <h1>Home</h1>
</div>
<div class="col-md-4">
    這是首頁
</div>
<div class="col-md-12">
{% if current_user.is_authenticated %}
    {{ current_user.username }}
    {{ name }}
    <div>
    <img style="-webkit-user-select: none;" src="{{ avatar }}" />
    </div>
{% else %}
    Your are not login yet
{% endif %}
</div>
{% endblock %}

05. GitHub鑑權

1. 創建表結構

類似的,定義需要的欄位即可

class ThirdOAuth(db.Model):
    __tablename__ = 'thirdoauth'
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.String(64), unique=True, index=True)
    oauth_name = db.Column(db.String(128))
    oauth_id = db.Column(db.String(128), unique=True, index=True)
    oauth_access_token = db.Column(db.String(128), unique=True, index=True)
    oauth_expires = db.Column(db.String(64), unique=True, index=True)

2. 發送授權請求

這一步,flask-github已經為我們封裝好了,直接調用即可

@auth.route('/githublogin', methods=['GET', 'POST'])
def githublogin():
    return github.authorize(scope='repo')

這裡需要說明,該調用需要用到我們前面獲得的客戶端ID和密鑰,我這裡把相關信息寫到了一個配置文件中,並在初始化flask app時加載

配置文件

class Config:
    SECRET_KEY = "hardtoguess"
    GITHUB_CLIENT_ID = 'cf1AA35ef11d20bcdXXX'
    GITHUB_CLIENT_SECRET = 'ba7c8c8SSe9cd574eb3da1b5e704d11d35aXXXb8'

初始化app

def create_app(config_name):
    app = Flask(__name__)
    app.config.from_object(config[config_name])
    config[config_name].init_app(app)
    db.init_app(app)
    cors.init_app(app, supports_credentials=True)
    login_manager.init_app(app)
    bootstrap.init_app(app)
    github.init_app(app)

    from .main import main as main_blueprint
    app.register_blueprint(main_blueprint)
    from .api_1_0 import api_1_0 as api_blueprint
    app.register_blueprint(api_blueprint)
    from .auth import auth as auth_blueprint
    app.register_blueprint(auth_blueprint, url_prefix='/auth')

    return app

3. 獲取access令牌

當用戶同意授權或拒絕授權後,GitHub會將用戶重定向到我們設置的callback URL,我們需要創建一個視圖函數來處理回調請求。如果用戶同意授權,GitHub會在重定向的請求中加入code參數,一個臨時生成的值,用於程序再次發起請求交換access token。程序這時需要向請求訪問令牌URL(即https://github.com/login/oauth/access_token)發起一個POST請求,附帶客戶端ID、客戶端密鑰、code。請求成功後的的響應會包含訪問令牌(Access Token)。

很幸運,上面的一系列工作flask-github會在背後替我們完成。我們只需要創建一個視圖函數,定義正確的URL規則(這裡的URL規則需要和GitHub上填寫的Callback URL匹配),並為其附加一個github.authorized_handler裝飾器。另外,這個函數要接受一個access_token參數,GitHub-Flask會在授權請求結束後通過這個參數傳入訪問令牌。

同時判斷,該用戶是否存在於資料庫中,並更新相關欄位。

@auth.route('/callback/github')
@github.authorized_handler
def authorized(access_token):
    if access_token is None:
        flash('Login Failed!')
        return redirect(url_for('main.index'))
    response = github.get('user', access_token=access_token)
    username = response['login']
    u_id = response['id']
    email = response['email']
    avatar = response['avatar_url']
    user = WebUser.query.filter_by(username=username).first()
    if user is None:
        user = WebUser(username=username, user_id=time.time())
        db.session.add(user)
        db.session.commit()
        thirduser = ThirdOAuth(user_id=WebUser.query.filter_by(username=username).first().user_id,
                               oauth_name='github', oauth_access_token=access_token,
                               oauth_id=u_id)
        db.session.add(thirduser)
        db.session.commit()
        login_user(user)
        user.email = email
        db.session.add(user)
        db.session.commit()
        session['userid'] = user.user_id
        return render_template('index.html', avatar=avatar)
    else:
        thirduser = ThirdOAuth.query.filter_by(user_id=user.user_id).first()
        thirduser.oauth_access_token = access_token
        db.session.add(thirduser)
        db.session.commit()
        user.email = email
        db.session.add(user)
        db.session.commit()
        login_user(user)
        session['userid'] = user.user_id
        return render_template('index.html', avatar=avatar)

更多的GitHub開發文檔資料可以查看:

https://developer.github.com/apps/building-oauth-apps/understanding-scopes-for-oauth-apps/

更多flask-github資料可以查看:

https://github-flask.readthedocs.io/en/latest/

4. 更新登陸頁面

更新登陸頁面,增加一個以GitHub登陸的按鈕

<div class="col-md-12">
    <a class="btn btn-primary" href="{{ url_for('auth.githublogin') }}">Login with GitHub</a>
</div>

現在的登陸頁面為

更新index路由函數,增加以GitHub登陸時的頭像

@main.route('/', methods=['GET', 'POST'])
def index():
    
    if current_user.is_authenticated:
        if 'userid' in session:
            user = ThirdOAuth.query.filter_by(user_id=session['userid']).first()
            if user:
                response = github.get('user', access_token=user.oauth_access_token)
                avatar = response['avatar_url']
                username = response['login']
                return render_template('index.html', username=username, avatar=avatar)
    return render_template('index.html')

又因為在callback函數中增加了session.userid欄位,所以在logout時,把該欄位手動刪除

@auth.route('/logout')
@login_required
def logout():
    logout_user()
    if 'userid' in session:
        session.pop('userid')
    flash('You have logged out!')
    return redirect(url_for('main.index'))

5. 測試GitHub登陸

登陸成功後,如下

至此,登陸功能完成

完整代碼:

https://github.com/zhouwei713/flask-webauth

往期文章:

       使用sklearn+jieba完成一個文檔分類器

      從頭完成一個restful API服務

      從頭搭建一個HTTPS網站

       運用百度API來測評女神的質量 

聖誕來臨,爬取女神美圖放鬆下

用Python探索紅樓夢裡的關係

Kubernetes系列入門文章

爬取女神王祖賢的海報評論,看看粉絲們是怎麼說

果喜歡我的文章,那就關注我吧!

萬分感謝!

歡迎留言討論

相關焦點

  • python+flask搭建CNN在線識別手寫中文網站
    向AI轉型的程式設計師都關注了這個號👇👇👇大數據挖掘DT機器學習  公眾號: datayx使用python+flask搭建的一個網站,然後從網頁的寫字板上獲取滑鼠手寫的漢字經過轉碼後傳回後臺,並經過圖片裁剪處理之後傳入CNN手寫中文識別的模型中進行識別,最後通過PIL將識別結果生成圖片,最後異步回傳給web端進行識別結果展示。
  • 身份證三要素鑑權是什麼意思?鑑權API數據接口介紹
    原標題:身份證三要素鑑權是什麼意思?鑑權API數據接口介紹   隨著網際網路的快速發展,帶動了行動支付的快速崛起,不過支付雙方都需要強大的驗證系統予以支持,而身份證三要素鑑權接口也因此得到了廣泛的關注和各行業的應用。
  • VS Code + Python + Flask(Windows)
    /)下載python3.7b.點擊next,根據自己的需求進行個性化設計3.在vscode中配置pythona.在設置中搜索pythonPath,並設置成python的安裝目錄如:"python.pythonPath": "C:\\Program Files\\Python37\\python.exe",4.Python-env搭建虛擬環境
  • python+flask搭建CNN在線識別手寫中文網站!簡直太屌了!
    使用python+flask搭建的一個網站,然後從網頁的寫字板上獲取滑鼠手寫的漢字經過轉碼後傳回後臺,並經過圖片裁剪處理之後傳入CNN手寫中文識別的模型中進行識別
  • 加強醫療管理 深圳美年11家體檢中心上線「鑑權合規管理系統」
    近日,深圳美年大健康旗下11家體檢中心正式上線「鑑權合規管理系統」,各科室落實「醫護醫技人員人臉識別上崗」。憑藉創新科技,美年在深圳體檢領域內率先推行智慧醫療管理,保障醫療質量,為消費者提供更加放心的體檢服務。
  • wtforms數據校驗—Flask網站製作(20)
    WTForms是一個支持多個web框架的form組件,主要用於對用戶請求數據進行驗證。只看是看不懂的,實戰後立刻通體通透!Python寫的且前後端未做分離,所以直接自動傳這個字典,如果是前後端分離的系統,那麼需先取到這個值並轉成json串後再向服務端發送在前後端不分離的系統中,我們可通過此函數取到輸入的值並傳遞給一個用來校驗數據的函數或類來進行數據校驗 validators 標準wtforms自帶的校驗功能說明專門用於做數據校驗的函數,它是標準wtforms
  • Flask 入門系列教程(一)
    環境準備首先你當然需要擁有一個 Python 開發環境,無論是 Windows,MacOS 還是 Linux 系統,安裝 Python 都是很簡單的事情,不過對於沒有 Unix 作業系統使用經驗的初學者來說,我還是比較建議使用 Windows 作為開發環境。
  • Flask 0.11 發布, Python 開發的 Web 框架
    Added **kwargs to flask.Test.test_client to support passingadditional keyword arguments to the constructor offlask.Flask.test_client_class.
  • 基於 Flask 部署 Keras 深度學習模型
    1、項目簡介該部分簡要介紹一下前一段時間所做的工作:這是第一次進行深度學習模型的 web 應用部署,在整個過程中,進一步折射出以前知識面之窄,在不斷的入坑、解坑中實現一版。2、項目流程這部分從項目實施的流程入手,記錄所做的工作及用到的工具。2.1 圖像分類模型1.
  • 騰訊發布身份鑑權設備Qkey 攜合作夥伴共築安全支付環境
    摘要:4月28日,2016年移動網際網路大會(GMIC)在北京舉行,騰訊公司發布了國內首款具備「支付鑑權」功能的智能手環Qkey,並推出了TUSI認證標準和「領御守護計劃」開放平臺。他還表示,Qkey有五大使用場景,包括免密碼登錄APP、網站,防止個人信息洩露;快捷支付,可為閃付提供離線支付;支付鑑權,為銀行網銀、微信支付等第三方支付提供更安全的支付鑑權;智慧卡包,可集成門禁、公交卡、飯卡、工卡等;物聯網身份認證,便捷、安全登錄智能家居設備。
  • 淺談flask ssti 繞過原理
    文章轉自先知社區:https://xz.aliyun.com/t/8029關於flask的中ssti的繞過文章還是很多的,
  • 基於《Flask Web開發:基於Python的Web應用開發實戰》最全總結
    例如:許多系統處理動態埠從1024左右開始。動態和/或私有埠(Dynamic and/or Private Ports):從49152到65535。理論上,不應為服務分配這些埠。實際上,機器通常從1024起分配動態埠。
  • Flask Web 小項目——SQL 建表語句生成器
    具體思路如下:團隊裡一般會有自己的建表語句範例,以此作為基礎框架,然後傳參進行填充;根據寫好的 SQL 語句,給建表語句填充 column 名稱;構建欄位與注釋的字典,保證同一個欄位,同一個業務含義和注釋;二、數據工作流輸入 SQL 及建表信息 → 解析出新建表要定義的欄位 → 欄位自動化注釋 → 構建建表語句 → 封裝建表語句構建 API →
  • Flask 入門系列教程(四)
    這些操作如果都從頭開始編寫,那麼就太複雜了,不過幸運的是,我們有強大的 WTForms 幫助我們解決。HTML 表單在 HTML 表單中,可以通過 <form> 標籤來創建,通過 <input> 來定義欄位。<form method="post">  <!
  • 進銷存管理系統搭建流程
    隨著電商的不斷發展,需求的不斷增加,商家的商品進庫、出庫、銷售和訂單物流跟蹤方面的工作量也越來越大,傳統的手工登記的方式已經沒有辦法滿足企業的發展,所以需要通過使用進銷存系統來對商品從進貨到銷售的流程進行跟蹤。你知道如何搭建一個進銷存系統嗎?下面和小編一起來了解一下相關的知識吧!
  • 法國自建節登陸武漢 竹子礦泉水桶搭建「水上城市」
    法國自建節登陸武漢 竹子礦泉水桶搭建「水上城市」 來源:人民網-湖北頻道    2014年09月20日23:18 據悉,自建節起初只是一群法國年輕建築師將建築學付諸實踐、搭建臨時性建築物的嘗試,如今已經成為千人參與、在實踐中探討藝術與可持續發展的國際性實驗研究項目。自建節已在世界不少地區舉辦,卻是第一次來到中國,也是第一次以打造水上居所為研究課題。 早在2012年12月,法國建築師團隊博拉思革已經來訪過武漢,之後也多次到訪並就武漢地區的水域進行了一番考察。
  • 聊聊OkHttp實現WebSocket細節,包括鑑權和長連接保活及其原理!
    那本文就來聊聊,利用 OkHttp 實現 WebSocket 的一些細節,包括對 WebSocket 的介紹,以及在傳輸前如何做到鑑權、長連接保活及其原理。二、WebSocket 簡介2.1 為什麼使用 WebSocket?
  • Python的Flask框架中的Jinja2模板引擎學習教程
    它本身是一個字典。在模板中,你一樣可以獲取這些內容,只要用表達式符號」{{ }}」括起來即可。1<p>{{ request.url }}</p>在沒有請求上下文的環境中,這個對象不可用。會話對象sessionsession對象可以用來獲取當前會話中保存的狀態,它本身是一個字典。
  • 基於EAP的動態地址分配擴展協議(DHCP )IP網接入認證鑑權技術要求
    基於EAP的動態地址分配擴展協議(DHCP )IP網接入認證鑑權技術要求 基於EAP的動態地址分配擴展協議(DHCP )IP網接入認證鑑權技術要求