乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      Flask: SSO原理及實現(xiàn)

       quasiceo 2014-05-15

      現(xiàn)在大多數(shù)軟件公司的業(yè)務不再是單條線,而是發(fā)展成多元化的產(chǎn)品線。包括多個網(wǎng)站應用、移動APP以及桌面軟件,那么當然希望能實現(xiàn)統(tǒng)一用戶和統(tǒng)一登錄。統(tǒng)一用戶基本都已實現(xiàn),然而統(tǒng)一登錄卻還是有不少公司未予以實現(xiàn)。這倒不是說SSO有多復雜或多難實現(xiàn),這其中可能有歷史遺留問題也或是其它原因。這是題外話,本文不作深究。

      什么是統(tǒng)一用戶

      統(tǒng)一用戶指的是多個應用共用一套賬號體系。比如Z公司旗下有aw和bw兩個網(wǎng)站,有賬號goal,那么使用賬號goal能登錄aw和bw。這個在技術上也不難實現(xiàn),通常來說有2個方案:

      1. 共享持久層

        這是最常用的方式。aw和bw通過直接訪問同一個后端數(shù)據(jù)庫來達到數(shù)據(jù)共享。

      2. 通過代理訪問

        這種方式類似于通過網(wǎng)關來訪問同一個后端數(shù)據(jù)庫。本質上跟共享持久層是一樣的,無外乎多了層網(wǎng)關,這樣是有好處的,本文接下來會涉及到。

      這看起來好像夠了,但是請您考慮這樣一個場景:aw和bw是兩個不同的網(wǎng)頁游戲,那么首先aw和bw都有自己的激活流程,然后有自己的游戲角色、裝備等各種屬性。所以aw和bw應該有各自的profile。對此我歸納了幾下幾點:

      1. 統(tǒng)一用戶賬號表僅保存各個應用的公共屬性,其它應用可以重寫這些屬性

      2. 應該能標識出是否已激活過

      3. 應該能標識出屬于哪個應用

      對于第一點,這沒什么好說的。第二點和第三點可以分別用一個整型字段來表示,屬性存儲在不同的位上,而且一般都是這么做的。如下代碼所示:

      01#!/usr/bin/env python
      02#coding: utf8
      03 
      04#已保存flag,從最低位算起,第一位表示aw,第二位表示bw
      05app_flag = 0x3
      06ac_flag  = 0x2
      07 
      08#測試某個應用是否已激活
      09if ac_flag & 0x1:
      10    print "aw已激活。"
      11elif ac_flag & 0x2:
      12    print "bw已激活。"
      13 
      14#進行激活aw應用
      15ac_flag |= 0x1
      16 
      17#測試是哪個app
      18if app_flag & 0x1:
      19    print "這是aw應用。"
      20if app_flag & 0x2:
      21    print "這是bw應用。"

      1$ python test.py
      2bw已激活。
      3這是aw應用。
      4這是bw應用。

      什么是統(tǒng)一登錄

      統(tǒng)一登錄又稱SSO(Single Sign On),即單點登錄。實現(xiàn)統(tǒng)一登錄的前提是已經(jīng)實現(xiàn)了統(tǒng)一用戶。在實現(xiàn)SSO之前的登錄流程是這樣的:

      1. aw和bw各自維護自己的登錄會話

      2. aw的登錄不會導致bw登錄,相反也是如此

      3. aw的退出不會導致bw的退出,相反也是如此

      這種體驗對用戶來說是極不友好的,明明是同樣的帳戶,卻不得不逐個去輸入用戶名和密碼來登錄。SSO正好可以解決這些問題。SSO一般被用于web和web之間,但有時也被用于和桌面軟件、移動APP之間的統(tǒng)一登錄。不過只有web和web之間才能算是標準的SSO,其它的卻不是。接下來分別談談這幾種方式的原理:

      1. web和web之間單點登錄

      2. web和桌面軟件、移動APP之間單點登錄

      web和web之間的單點登錄

      原理

      對于使用session來保存登錄態(tài)想必各位都沒有什么疑問,不明白的可以去自行 Google 。比如有站點aw和bw需要統(tǒng)一登錄,那么會出現(xiàn)2種情況:

      1. aw和bw是二級子域名

        例如aw和bw站點域名分別是aw.test.com和bw.test.com,那么其實可以設置session的cookie domain為.test.com來使aw和bw共享會話信息。這種方式不具備通用性并且簡單,因此不作深究。

      2. aw和bw都是獨立的域名

        因為是2個獨立的域名,所以就不能通過設置session的cookie domain來實現(xiàn)了。SSO的做法就是將登錄態(tài)保存在SSO域(一般也稱passort或通行證)上,aw和bw的登錄、退出以及授權檢查都通過SSO來進行。本文將通篇使用aw, bw和SSO這三個站點來描述,并且使用Python的Flask框架來進行演示,如果沒有安裝Flask,請先安裝。

      1$ pip install flask

      aw和bw是2個不同的web應用,都需要登錄才能訪問,而SSO就是為aw和bw來提供服務的。為此我配置了3個host,如下圖:

      調(diào)用SSO的方式又可以分為以下2種:

      1. 跳轉方式

      2. ajax或jsonp方式

      嚴格意義上來說,ajax和jsonp是屬于不同方式。因為ajax沒法跨域去調(diào)用SSO,它需要通過服務器端代理的方式去調(diào)用SSO。而jsonp是可以直接去調(diào)用SSO的,當然前提是SSO提供了jsonp方式的訪問。這樣劃分的依據(jù)只是為了區(qū)分跳轉與非跳轉。

      跳轉方式

      當用戶在aw和bw未登錄時,則攜帶相應參數(shù)(如來源網(wǎng)址等)跳轉到SSO進行登錄,如登錄失敗則停留在SSO的登錄頁,登錄成功則SSO會生成ticket并附加給來源網(wǎng)址跳轉回去。當然SSO在跳轉回來源網(wǎng)址時會在SSO域上設置好登錄態(tài)。既然在SSO上設置登錄態(tài),那么在aw和bw上是否需要設置登錄態(tài)呢?答案是應該設置。舉例來說,如果aw跳轉到SSO進行登錄成功并在SSO上設置好登錄態(tài)后攜帶ticket跳轉回來,aw需要授權的頁面其實都是需要檢查用戶在aw上是否授權成功,如果不在aw上設置登錄態(tài),則始終會跳轉到SSO去檢測授權,這樣的結果就是導致無限循環(huán)的跳轉,最終導致不可訪問。當然還有其它解決方案,那就是通過<script>或<iframe />來實現(xiàn)調(diào)用SSO檢測,但這是后話,將會在使用ajax或jsonp方式時進行講解。

      如上所述,還是應該在aw和bw上設置各自的登錄態(tài),這樣在訪問aw時首先會在aw域上檢測授權,如果沒有授權,則跳轉到SSO進行登錄授權,登錄成功之后攜帶ticket跳轉回來。ticket是SSO為此次登錄所生成的用戶基本信息加密串,來源域可通過解密ticket來獲取用戶基本信息,從而在來源域中設置登錄態(tài)。

      但是aw和bw應該為登錄態(tài)設置多長存活期呢?一般設為瀏覽器進程存活期,也就是說aw和bw的登錄態(tài)的存活期直到瀏覽器關閉。SSO域上登錄態(tài)的存活期取決于具體的業(yè)務,本文中設為30天。代碼如下:

      aw代碼:

      www/aw

      ----app.py

      01#coding: utf8
      02import os
      03from datetime import timedelta
      04from flask import Flask, session, redirect, url_for, request
      05import urllib
      06 
      07app = Flask(__name__)
      08 
      09app.secret_key = os.urandom(24)
      10app.permanent_session_lifetime = timedelta(seconds=24 * 60 * 60)
      11 
      12@app.route('/')
      13def index():
      14    #表示存活期為瀏覽器進程的存活期
      15    session.permanent = False
      16    ticket = request.args.get('ticket'None)
      17    if ticket is not None:
      18        session['name'= ticket.strip()
      19    #檢測登錄態(tài)
      20    if 'name' in session:
      21        return '登錄成功'
      22    else:
      23        referer = urllib.quote('http://www.:6666/')
      24        return redirect('http://www.:6668/login?referer=' + referer)
      25 
      26if __name__ == '__main__':
      27    app.run(
      28        host="0.0.0.0",
      29        port=int("6666"),
      30        debug=True
      31    )

      bw代碼:

      www/bw

      ----app.py

      01#coding: utf8
      02import os
      03from datetime import timedelta
      04from flask import Flask, session, redirect, url_for, request
      05import urllib
      06 
      07app = Flask(__name__)
      08 
      09app.secret_key = os.urandom(24)
      10app.permanent_session_lifetime = timedelta(seconds=24 * 60 * 60)
      11 
      12@app.route('/')
      13def index():
      14    #表示存活期為瀏覽器進程的存活期
      15    session.permanent = False
      16    ticket = request.args.get('ticket'None)
      17    if ticket is not None:
      18        session['name'= ticket.strip()
      19    #檢測登錄態(tài)
      20    if 'name' in session:
      21        return '登錄成功'
      22    else:
      23        referer = urllib.quote('http://www.:6667/')
      24        return redirect('http://www.:6668/login?referer=' + referer)
      25 
      26if __name__ == '__main__':
      27    app.run(
      28        host="0.0.0.0",
      29        port=int("6667"),
      30        debug=True
      31    )

      sso代碼:

      www/sso

      ----app.py

      ----templates

      --------login.html

      app.py源碼:

      01#coding: utf8
      02import os
      03from datetime import timedelta
      04from flask import Flask, session, render_template, request, redirect
      05import urllib
      06 
      07app = Flask(__name__)
      08 
      09app.secret_key = os.urandom(24)
      10app.permanent_session_lifetime = timedelta(seconds=30 * 24 * 60 * 60)
      11 
      12@app.route('/login')
      13def login():
      14    session.permanent = True
      15    referer = request.args.get('referer'None)
      16    if referer is not None:
      17        referer = referer.strip()
      18    if 'name' in session:
      19        if referer is not None:
      20            return redirect(referer + '?ticket=' + _makeTicket())
      21    return render_template('login.html'**dict(referer=referer))
      22 
      23@app.route('/dologin')
      24def doLogin():
      25    '''這里其實忽略了判斷是否登錄的流程'''
      26    session.permanent = True
      27    referer = request.args.get('referer'None)
      28    if referer is not None:
      29        referer = urllib.unquote(referer.strip())
      30    #不實現(xiàn)登錄功能,直接設置登錄態(tài)
      31    _setLoginState()
      32    if referer:
      33        return redirect(referer + '?ticket=' + _makeTicket())
      34    else:
      35        return 'error'
      36 
      37def _setLoginState():
      38    session['name'= 'goal'
      39 
      40def _makeTicket():
      41    '''生成ticket,這里只是簡單返回用戶名,真實場景中可以使用des之類的加密算法'''
      42    return 'goal'
      43 
      44if __name__ == '__main__':
      45    app.run(
      46        host="0.0.0.0",
      47        port=int("6668"),
      48        debug=True
      49    )

      login.html源碼:

      01<!DOCTYPE html>
      02<html>
      03<head>
      04    <meta charset="utf-8">
      05    <title>SSO</title>
      06    <meta name="author" content="" />
      07    <meta http-equiv="X-UA-Compatible" content="IE=7" />
      08    <meta name="keywords" content="SSO" />
      09    <meta name="description" content="SSO" />
      10</head>
      11<body>
      12<a href="{{ url_for('doLogin') }}{% if referer %}?referer={{ referer }}{% endif %}">請登錄</a>
      13</body>
      14</html>

      1$ python aw/app.py
      2 * Running on http://0.0.0.0:6666/
      3 * Restarting with reloader
      4$ python bw/app.py
      5 * Running on http://0.0.0.0:6667/
      6 * Restarting with reloader
      7$ python sso/app.py
      8 * Running on http://0.0.0.0:6668/
      9 * Restarting with reloader

      打開aw站點,發(fā)現(xiàn)未登錄,則跳轉到SSO,點擊登錄成功后SSO設置登錄態(tài)并跳轉回aw并攜帶上ticket,aw根據(jù)ticket設置登錄態(tài)。流程對于bw也同樣適用。如果關閉瀏覽器,則aw和bw所設置的登錄態(tài)失效,但SSO上設置的并未過期,因此重啟瀏覽器打開aw站點將導至跳轉到SSO,并且在SSO上授權檢測成功,之后再同樣設置aw的登錄態(tài)。

      以上是基于跳轉的方式實現(xiàn)的SSO,對于退出登錄也可以通過同樣的方式來實現(xiàn)。

      ajax或jsonp方式

      對于ajax和jsonp方式來說,這只是請求登錄接口的不同方案。因為ajax不能跨域請求,所以需要服務器端代為請求并將結果返回,而jsonp方式是通過<script>標記調(diào)用遠程腳本來實現(xiàn)的,如果SSO支持jsonp方式,則應優(yōu)先選用。登錄請求過程比較簡單,ajax就沒什么好說的,因為太常用了。對于jsonp來說,遠程執(zhí)行完畢會返回一段JS代碼,通常是返回一個變量的定義,那么我們就可以利用這個變量來拿到ticket并為應用設置登錄態(tài)。

      但是試想下,這種非跳轉方式需要跨域設置SSO的登錄態(tài),那么這其實是可以通過<script>和<iframe>來實現(xiàn)的。對于IE來說,還需要設置p3p頭部,而其它瀏覽器則不需要設置。在這點上其實是IE遵循了隱私規(guī)范,我們不防為IE點個贊。本文不打算實現(xiàn)ajax和jsonp方式的登錄,如果各位有問題,可以一起討論。

      本文將通過<script>的方式對SSO進行跨域設置登錄態(tài)。很顯然,SSO需要提供一個URL調(diào)用給應用,并且SSO可以提供一個JS腳本供應用使用,這樣就不須各個應用再去實現(xiàn)一遍了。OK,讓我們先清除SSO上的會話信息,再重啟瀏覽器。代碼如下:

      www/aw

      ----app.py

      ----templates

      --------index.html

      app.py源碼:

      01#coding: utf8
      02import os
      03from datetime import timedelta
      04from flask import Flask, session, request, render_template
      05import urllib
      06 
      07app = Flask(__name__)
      08 
      09app.secret_key = os.urandom(24)
      10app.permanent_session_lifetime = timedelta(seconds=24 * 60 * 60)
      11 
      12@app.route('/')
      13def index():
      14    session.permanent = False
      15    return render_template('index.html')
      16 
      17if __name__ == '__main__':
      18    app.run(
      19        host="0.0.0.0",
      20        port=int("6666"),
      21        debug=True
      22    )

      index.html源碼:

      01<!DOCTYPE html>
      02<html>
      03<head>
      04    <meta charset="utf-8">
      05    <title>aw</title>
      06    <meta name="author" content="" />
      07    <meta http-equiv="X-UA-Compatible" content="IE=7" />
      08    <meta name="keywords" content="aw" />
      09    <meta name="description" content="aw" />
      10    <script type="text/javascript" src="http://cdn./jquery/2.1.0/jquery.min.js"></script>
      11    <script type="text/javascript" src="http://www.:6668/static/sso.js"></script>
      12</head>
      13<body>
      14</body>
      15</html>

      www/sso

      ----app.py

      ----static

      --------sso.js

      app.py源碼:

      01#coding: utf8
      02import os
      03from datetime import timedelta
      04from flask import Flask, session, request, make_response
      05import urllib
      06 
      07app = Flask(__name__)
      08 
      09app.secret_key = os.urandom(24)
      10app.permanent_session_lifetime = timedelta(seconds=30 * 24 * 60 * 60)
      11 
      12@app.route('/setLoginState')
      13def setLoginState():
      14    session.permanent = True
      15    session['name'= 'goal'
      16    session['nick'= '陳一回'
      17    resp = make_response('')
      18    resp.headers['P3P'= 'CP="CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR"'
      19    return resp
      20 
      21@app.route('/test')
      22def test():
      23    session.permanent = True
      24    _str = ''
      25    if 'name' in session:
      26        _str = session['name']
      27    if 'nick' in session:
      28        _str += '---' + session['nick']
      29    return _str
      30 
      31if __name__ == '__main__':
      32    app.run(
      33        host="0.0.0.0",
      34        port=int("6668"),
      35        debug=True
      36    )

      sso.js源碼:

      1$(function() {
      2    $.getScript("http://www.:6668/setLoginState"function() {
      3        console.log('success.');
      4    });
      5});

      1$ python aw/app.py
      2 * Running on http://0.0.0.0:6666/
      3 * Restarting with reloader
      4$ python sso/app.py
      5 * Running on http://0.0.0.0:6668/
      6 * Restarting with reloader

      通過訪問 http://www.:6666  來設置SSO的登錄態(tài),之后可以通過 http://www.:6668/test  來查看輸出,結果很明顯是設置成功的。關于非跳轉方式的授權檢測以及退出登錄也是大同小異的,明白了原理,實現(xiàn)起來就很簡單了。

      統(tǒng)一退出

      統(tǒng)一退出的概念即是任何一方應用退出登錄,在清除應用自身的登錄態(tài)時也清除SSO的登錄態(tài)。這看起來沒有什么問題,舉個例來說,aw和bw都登錄過,也就說aw、bw和SSO都設置過登錄態(tài)。aw的退出將會清除aw和SSO的登錄態(tài),但bw還在會話期內(nèi),除非瀏覽器被關閉,否則bw還是會處于登錄狀態(tài)的。解決方案也是有的,在aw退出時,順便也清除bw的登錄態(tài)(可通過遠程URL調(diào)用和P3P結合的方式來實現(xiàn))。但如果SSO關聯(lián)的應用非常多,那么退出的過程也變得漫長。有些公司的網(wǎng)站甚至是通過跳轉方式來進行逐一清除登錄態(tài),這個沒有完美的解決方案,關鍵在于取舍。

      統(tǒng)一授權檢測

      之前所述的aw和bw本身也會設置登錄態(tài)。如果不想設置登錄態(tài),則可以通過SSO實現(xiàn)JS API供aw和bw來調(diào)用,在每個頁面中通過遠程URL調(diào)用SSO的授權檢測。但這樣很明顯是弊大于利,不僅會令SSO的請求數(shù)呈指數(shù)級增長,并且增加了aw和bw的編碼難度。

      強迫退出

      考慮這么一種場景?;谕粋€用戶,在A電腦上登錄了aw,之后沒有關閉瀏覽器,然后在B電腦上也登錄了aw。那么能否強制A電腦上的用戶退出呢?這個退出分為SSO的退出和aw的退出。令SSO的退出是可以實現(xiàn)的,只要在登錄態(tài)中保存登錄時間戳,服務器端持有用戶標識到登錄態(tài)的映射,那么B電腦上的登錄會令登錄態(tài)和服務器端的映射同步,而A電腦上的登錄態(tài)將會過期。這個時候如果在A電腦上開啟bw(之前未登錄),則會跳轉到SSO,很明顯,這個時候A電腦上的SSO將是未授權狀態(tài)。對于A電腦上的aw,并沒有辦法去清除它的登錄態(tài)。

      UserAgent

      之前一直忽略了一個事實,所謂共享SSO登錄態(tài),其實是基于同一個UserAgent的。對于web應用來說,UserAgent就是瀏覽器。這是因為瀏覽器之間無法共享cookie,而session是基于cookie的。

      web和桌面軟件、移動APP之間單點登錄

      這個其實不能算嚴格意義上的SSO,只能算是代簽??梢缘卿?個QQ號來進行觀察,登錄后在2個QQ上點擊郵箱圖標進入郵箱,您可以發(fā)現(xiàn)鏈接上被附加了一串sid。sid是session id的縮寫,可以用來標識一個會話。您可以清楚的看到郵箱上的每個鏈接都被附加上了一串sid參數(shù),這是因為允許同時使用多個郵箱,如果設置登錄態(tài)的話則會覆蓋前一個。這種方式看起就像早期PHP不支持session的做法,每次通過傳遞sid到服務器端來解密進行標識用戶。

      對于移動APP的授權,有使用OAuth方式,也有使用傳遞sid方式,對此不作深究。

      SSO可以同時支持傳遞sid、OAuth方式和登錄態(tài)方式的授權校驗,并只能被授權后的應用使用SSO。

      來自:http://my.oschina.net/goal/blog/199978


        本站是提供個人知識管理的網(wǎng)絡存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權內(nèi)容,請點擊一鍵舉報。
        轉藏 分享 獻花(0

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多