http://www.cnblogs.com/super-d2/p/4719660.htmljavascript
PHP使用P3P完成COOKIE跨域操做
實際實用中,相似的需求有,好比說咱們有兩個域名,咱們想實如今一個域名登陸後,能自動完成另外一個域名的登陸,也就是單點登陸(SSO)功能。
爲了測試的方便,先編輯hosts文件,加入測試域名php
sudo vim /etc/hostshtml
192.168.1.112 www.a.comjava
192.168.1.112 www.b.compython
代碼實現jquery
www.a.com域名下的代碼文件:
[a_setcookie.php]
<?php
//header('P3P: CP="CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR"');
setcookie("test", $_GET['id'], time()+3600, "/", ".a.com");
?>
[a_getcookie.php]
<?php
var_dump($_COOKIE); web
?>ajax
www.b.com域名下的代碼文件:
[b_setcookie.php]
<?php
//header('P3P: CP="CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR"');
setcookie("test", $_GET['id'], time()+3600, "/", ".b.com");算法
?>shell
[b_getcookie.php]
<?php
var_dump($_COOKIE);
?>
依次訪問
http://www.b.com/b_setcookie.php
http://www.a.com/a_getcookie.php
會發現a.com域上已經有cookie值了
代碼分析
在www.b.com的域名下給www.a.com建立cookie。
若用戶登陸到www.b.com中,由此域名的b_setcookie.html中js實現方式給www.a.com域名設置cookie。
假設www.a.com域名下的a_getcookie.php有cookie則設定www.a.com登陸成功。
b_setcookie.html:
<script src="http://www.a.com/a_setcookie.php?id=www.b.com"></script>
總結P3P的在上述代碼中最主要的職責是:
跨域產生 cookie
注:上述代碼在非IE下測試,即便不發送P3P頭信息,也能成功。IE瀏覽器必需發送P3P才能成功!因此要跨域產生cookie仍是有必要發送P3P的,畢竟IE的用戶羣體仍是很大的。
參考:
http://my.oschina.net/goal/blog/199978
####--------------------------
摘要: 如今大多數軟件公司的業務再也不是單條線,而是發展成多元化的產品線。包括多個網站應用、移動APP以及桌面軟件,那麼固然但願能實現統一用戶和統一登陸。統一用戶基本都已實現,然而統一登陸卻仍是有很多公司未予以實現。這倒不是說SSO有多複雜或多難實現,這其中可能有歷史遺留問題也或是其它緣由。這是題外話,本文不做深究。
如今大多數軟件公司的業務再也不是單條線,而是發展成多元化的產品線。包括多個網站應用、移動APP以及桌面軟件,那麼固然但願能實現統一用戶和統一登陸。統一用戶基本都已實現,然而統一登陸卻仍是有很多公司未予以實現。這倒不是說SSO有多複雜或多難實現,這其中可能有歷史遺留問題也或是其它緣由。這是題外話,本文不做深究。
統一用戶指的是多個應用共用一套賬號體系。好比Z公司旗下有aw和bw兩個網站,有賬號goal,那麼使用賬號goal能登陸aw和bw。這個在技術上也不難實現,一般來講有2個方案:
共享持久層
這是最經常使用的方式。aw和bw經過直接訪問同一個後端數據庫來達到數據共享。
經過代理訪問
這種方式相似於經過網關來訪問同一個後端數據庫。本質上跟共享持久層是同樣的,無外乎多了層網關,這樣是有好處的,本文接下來會涉及到。
這看起來好像夠了,可是請您考慮這樣一個場景:aw和bw是兩個不一樣的網頁遊戲,那麼首先aw和bw都有本身的激活流程,而後有本身的遊戲角色、裝備等各類屬性。因此aw和bw應該有各自的profile。對此我概括了幾下幾點:
統一用戶賬號表僅保存各個應用的公共屬性,其它應用能夠重寫這些屬性
應該能標識出是否已激活過
應該能標識出屬於哪一個應用
對於第一點,這沒什麼好說的。第二點和第三點能夠分別用一個整型字段來表示,屬性存儲在不一樣的位上,並且通常都是這麼作的。以下代碼所示:
#!/usr/bin/env python #coding: utf8 #已保存flag,從最低位算起,第一位表示aw,第二位表示bw app_flag = 0x3 ac_flag = 0x2 #測試某個應用是否已激活 if ac_flag & 0x1: print "aw已激活。" elif ac_flag & 0x2: print "bw已激活。" #進行激活aw應用 ac_flag |= 0x1 #測試是哪一個app if app_flag & 0x1: print "這是aw應用。" if app_flag & 0x2: print "這是bw應用。"
$ python test.py bw已激活。 這是aw應用。 這是bw應用。
統一登陸又稱SSO(Single Sign On),即單點登陸。實現統一登陸的前提是已經實現了統一用戶。在實現SSO以前的登陸流程是這樣的:
aw和bw各自維護本身的登陸會話
aw的登陸不會致使bw登陸,相反也是如此
aw的退出不會致使bw的退出,相反也是如此
這種體驗對用戶來講是極不友好的,明明是一樣的賬戶,卻不得不逐個去輸入用戶名和密碼來登陸。SSO正好能夠解決這些問題。SSO通常被用於web和web之間,但有時也被用於和桌面軟件、移動APP之間的統一登陸。不過只有web和web之間才能算是標準的SSO,其它的卻不是。接下來分別談談這幾種方式的原理:
web和web之間單點登陸
web和桌面軟件、移動APP之間單點登陸
對於使用session來保存登陸態想必各位都沒有什麼疑問,不明白的能夠去自行 Google 。好比有站點aw和bw須要統一登陸,那麼會出現2種狀況:
aw和bw是二級子域名
例如aw和bw站點域名分別是aw.test.com和bw.test.com,那麼其實能夠設置session的cookie domain爲.test.com來使aw和bw共享會話信息。這種方式不具有通用性而且簡單,所以不做深究。
aw和bw都是獨立的域名
由於是2個獨立的域名,因此就不能經過設置session的cookie domain來實現了。SSO的作法就是將登陸態保存在SSO域(通常也稱passort或通行證)上,aw和bw的登陸、退出以及受權檢查都經過SSO來進行。本文將通篇使用aw, bw和SSO這三個站點來描述,而且使用Python的Flask框架來進行演示,若是沒有安裝Flask,請先安裝。
$ pip install flask
aw和bw是2個不一樣的web應用,都須要登陸才能訪問,而SSO就是爲aw和bw來提供服務的。爲此我配置了3個host,以下圖:
調用SSO的方式又能夠分爲如下2種:
跳轉方式
ajax或jsonp方式
嚴格意義上來講,ajax和jsonp是屬於不一樣方式。由於ajax無法跨域去調用SSO,它須要經過服務器端代理的方式去調用SSO。而jsonp是能夠直接去調用SSO的,固然前提是SSO提供了jsonp方式的訪問。這樣劃分的依據只是爲了區分跳轉與非跳轉。
當用戶在aw和bw未登陸時,則攜帶相應參數(如來源網址等)跳轉到SSO進行登陸,如登陸失敗則停留在SSO的登陸頁,登陸成功則SSO會生成ticket並附加給來源網址跳轉回去。固然SSO在跳轉回來源網址時會在SSO域上設置好登陸態。既然在SSO上設置登陸態,那麼在aw和bw上是否須要設置登陸態呢?答案是應該設置。舉例來講,若是aw跳轉到SSO進行登陸成功並在SSO上設置好登陸態後攜帶ticket跳轉回來,aw須要受權的頁面其實都是須要檢查用戶在aw上是否受權成功,若是不在aw上設置登陸態,則始終會跳轉到SSO去檢測受權,這樣的結果就是致使無限循環的跳轉,最終致使不可訪問。固然還有其它解決方案,那就是經過<script>或<iframe />來實現調用SSO檢測,但這是後話,將會在使用ajax或jsonp方式時進行講解。
如上所述,仍是應該在aw和bw上設置各自的登陸態,這樣在訪問aw時首先會在aw域上檢測受權,若是沒有受權,則跳轉到SSO進行登陸受權,登陸成功以後攜帶ticket跳轉回來。ticket是SSO爲這次登陸所生成的用戶基本信息加密串,來源域可經過解密ticket來獲取用戶基本信息,從而在來源域中設置登陸態。
可是aw和bw應該爲登陸態設置多長存活期呢?通常設爲瀏覽器進程存活期,也就是說aw和bw的登陸態的存活期直到瀏覽器關閉。SSO域上登陸態的存活期取決於具體的業務,本文中設爲30天。代碼以下:
aw代碼:
www/aw
----app.py
#coding: utf8 import os from datetime import timedelta from flask import Flask, session, redirect, url_for, request import urllib app = Flask(__name__) app.secret_key = os.urandom(24) app.permanent_session_lifetime = timedelta(seconds=24 * 60 * 60) @app.route('/') def index(): #表示存活期爲瀏覽器進程的存活期 session.permanent = False ticket = request.args.get('ticket', None) if ticket is not None: session['name'] = ticket.strip() #檢測登陸態 if 'name' in session: return '登陸成功' else: referer = urllib.quote('http://www.aw.com:6666/') return redirect('http://www.sso.com:6668/login?referer=' + referer) if __name__ == '__main__': app.run( host="0.0.0.0", port=int("6666"), debug=True )
bw代碼:
www/bw
----app.py
#coding: utf8 import os from datetime import timedelta from flask import Flask, session, redirect, url_for, request import urllib app = Flask(__name__) app.secret_key = os.urandom(24) app.permanent_session_lifetime = timedelta(seconds=24 * 60 * 60) @app.route('/') def index(): #表示存活期爲瀏覽器進程的存活期 session.permanent = False ticket = request.args.get('ticket', None) if ticket is not None: session['name'] = ticket.strip() #檢測登陸態 if 'name' in session: return '登陸成功' else: referer = urllib.quote('http://www.bw.com:6667/') return redirect('http://www.sso.com:6668/login?referer=' + referer) if __name__ == '__main__': app.run( host="0.0.0.0", port=int("6667"), debug=True )
sso代碼:
www/sso
----app.py
----templates
--------login.html
app.py源碼:
#coding: utf8 import os from datetime import timedelta from flask import Flask, session, render_template, request, redirect import urllib app = Flask(__name__) app.secret_key = os.urandom(24) app.permanent_session_lifetime = timedelta(seconds=30 * 24 * 60 * 60) @app.route('/login') def login(): session.permanent = True referer = request.args.get('referer', None) if referer is not None: referer = referer.strip() if 'name' in session: if referer is not None: return redirect(referer + '?ticket=' + _makeTicket()) return render_template('login.html', **dict(referer=referer)) @app.route('/dologin') def doLogin(): '''這裏其實忽略了判斷是否登陸的流程''' session.permanent = True referer = request.args.get('referer', None) if referer is not None: referer = urllib.unquote(referer.strip()) #不實現登陸功能,直接設置登陸態 _setLoginState() if referer: return redirect(referer + '?ticket=' + _makeTicket()) else: return 'error' def _setLoginState(): session['name'] = 'goal' def _makeTicket(): '''生成ticket,這裏只是簡單返回用戶名,真實場景中可使用des之類的加密算法''' return 'goal' if __name__ == '__main__': app.run( host="0.0.0.0", port=int("6668"), debug=True )
login.html源碼:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>SSO</title> <meta name="author" content="" /> <meta http-equiv="X-UA-Compatible" content="IE=7" /> <meta name="keywords" content="SSO" /> <meta name="description" content="SSO" /> </head> <body> <a href="{{ url_for('doLogin') }