目录[-]
现在大多数软件公司的业务不再是单条线,而是发展成多元化的产品线。包括多个网站应用、移动APP以及桌面软件,那么当然希望能实现统一用户和统一登录。统一用户基本都已实现,然而统一登录却还是有不少公司未予以实现。这倒不是说SSO有多复杂或多难实现,这其中可能有历史遗留问题也或是其它原因。这是题外话,本文不作深究。
什么是统一用户
统一用户指的是多个应用共用一套帐号体系。比如Z公司旗下有aw和bw两个网站,有帐号goal,那么使用帐号goal能登录aw和bw。这个在技术上也不难实现,通常来说有2个方案:
-
共享持久层
这是最常用的方式。aw和bw通过直接访问同一个后端数据库来达到数据共享。
-
通过代理访问
这种方式类似于通过网关来访问同一个后端数据库。本质上跟共享持久层是一样的,无外乎多了层网关,这样是有好处的,本文接下来会涉及到。
这看起来好像够了,但是请您考虑这样一个场景:aw和bw是两个不同的网页游戏,那么首先aw和bw都有自己的激活流程,然后有自己的游戏角色、装备等各种属性。所以aw和bw应该有各自的profile。对此我归纳了几下几点:
-
统一用户帐号表仅保存各个应用的公共属性,其它应用可以重写这些属性
-
应该能标识出是否已激活过
-
应该能标识出属于哪个应用
对于第一点,这没什么好说的。第二点和第三点可以分别用一个整型字段来表示,属性存储在不同的位上,而且一般都是这么做的。如下代码所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
#!/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应用。"
|
1
2
3
4
|
$ 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之间单点登录
web和web之间的单点登录
原理
对于使用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,请先安装。
1
|
$ 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
#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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
#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源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
#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源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
<! 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') }}{% if referer %}?referer={{ referer }}{% endif %}" >请登录</ a >
</ body >
</ html >
|
1
2
3
4
5
6
7
8
9
|
$ python aw /app .py
* Running on http: //0 .0.0.0:6666/
* Restarting with reloader
$ python bw /app .py
* Running on http: //0 .0.0.0:6667/
* Restarting with reloader
$ python sso /app .py
* Running on http: //0 .0.0.0:6668/
* Restarting with reloader
|
打开aw站点,发现未登录,则跳转到SSO,点击登录成功后SSO设置登录态并跳转回aw并携带上ticket,aw根据ticket设置登录态。流程对于bw也同样适用。如果关闭浏览器,则aw和bw所设置的登录态失效,但SSO上设置的并未过期,因此重启浏览器打开aw站点将导至跳转到SSO,并且在SSO上授权检测成功,之后再同样设置aw的登录态。
以上是基于跳转的方式实现的SSO,对于退出登录也可以通过同样的方式来实现。
ajax或jsonp方式
对于ajax和jsonp方式来说,这只是请求登录接口的不同方案。因为ajax不能跨域请求,所以需要服务器端代为请求并将结果返回,而jsonp方式是通过<script>标记调用远程脚本来实现的,如果SSO支持jsonp方式,则应优先选用。登录请求过程比较简单,ajax就没什么好说的,因为太常用了。对于jsonp来说,远程执行完毕会返回一段JS代码,通常是返回一个变量的定义,那么我们就可以利用这个变量来拿到ticket并为应用设置登录态。
但是试想下,这种非跳转方式需要跨域设置SSO的登录态,那么这其实是可以通过<script>和<iframe>来实现的。对于IE来说,还需要设置p3p头部,而其它浏览器则不需要设置。在这点上其实是IE遵循了隐私规范,我们不妨为IE点个赞。本文不打算实现ajax和jsonp方式的登录,如果各位有问题,可以一起讨论。
本文将通过<script>的方式对SSO进行跨域设置登录态。很显然,SSO需要提供一个URL调用给应用,并且SSO可以提供一个JS脚本供应用使用,这样就不须各个应用再去实现一遍了。OK,让我们先清除SSO上的会话信息,再重启浏览器。代码如下:
www/aw
----app.py
----templates
--------index.html
app.py源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
#coding: utf8 import os
from datetime import timedelta
from flask import Flask, session, request, render_template
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
return render_template( 'index.html' )
if __name__ = = '__main__' :
app.run(
host = "0.0.0.0" ,
port = int ( "6666" ),
debug = True
)
|
index.html源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
<! DOCTYPE html>
< html >
< head >
< meta charset = "utf-8" >
< title >aw</ title >
< meta name = "author" content = "" />
< meta http-equiv = "X-UA-Compatible" content = "IE=7" />
< meta name = "keywords" content = "aw" />
< meta name = "description" content = "aw" />
< script type = "text/javascript" src = "http://cdn.staticfile.org/jquery/2.1.0/jquery.min.js" ></ script >
< script type = "text/javascript" src = "http://www.sso.com:6668/static/sso.js" ></ script >
</ head >
< body >
</ body >
</ html >
|
www/sso
----app.py
----static
--------sso.js
app.py源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
#coding: utf8 import os
from datetime import timedelta
from flask import Flask, session, request, make_response
import urllib
app = Flask(__name__)
app.secret_key = os.urandom( 24 )
app.permanent_session_lifetime = timedelta(seconds = 30 * 24 * 60 * 60 )
@app .route( '/setLoginState' )
def setLoginState():
session.permanent = True
session[ 'name' ] = 'goal'
session[ 'nick' ] = '陈一回'
resp = make_response('')
resp.headers[ 'P3P' ] = 'CP="CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR"'
return resp
@app .route( '/test' )
def test():
session.permanent = True
_str = ''
if 'name' in session:
_str = session[ 'name' ]
if 'nick' in session:
_str + = '---' + session[ 'nick' ]
return _str
if __name__ = = '__main__' :
app.run(
host = "0.0.0.0" ,
port = int ( "6668" ),
debug = True
)
|
sso.js源码:
1
2
3
4
5
|
$( function () {
$.getScript( "http://www.sso.com:6668/setLoginState" , function () {
console.log( 'success.' );
});
}); |
1
2
3
4
5
6
|
$ python aw /app .py
* Running on http: //0 .0.0.0:6666/
* Restarting with reloader
$ python sso /app .py
* Running on http: //0 .0.0.0:6668/
* Restarting with reloader
|
通过访问 http://www.aw.com:6666 来设置SSO的登录态,之后可以通过 http://www.sso.com:6668/test 来查看输出,结果很明显是设置成功的。关于非跳转方式的授权检测以及退出登录也是大同小异的,明白了原理,实现起来就很简单了。
统一退出
统一退出的概念即是任何一方应用退出登录,在清除应用自身的登录态时也清除SSO的登录态。这看起来没有什么问题,举个例来说,aw和bw都登录过,也就说aw、bw和SSO都设置过登录态。aw的退出将会清除aw和SSO的登录态,但bw还在会话期内,除非浏览器被关闭,否则bw还是会处于登录状态的。解决方案也是有的,在aw退出时,顺便也清除bw的登录态(可通过远程URL调用和P3P结合的方式来实现)。但如果SSO关联的应用非常多,那么退出的过程也变得漫长。有些公司的网站甚至是通过跳转方式来进行逐一清除登录态,这个没有完美的解决方案,关键在于取舍。
统一授权检测
之前所述的aw和bw本身也会设置登录态。如果不想设置登录态,则可以通过SSO实现JS API供aw和bw来调用,在每个页面中通过远程URL调用SSO的授权检测。但这样很明显是弊大于利,不仅会令SSO的请求数呈指数级增长,并且增加了aw和bw的编码难度。
强制退出
考虑这么一种场景。基于同一个用户,在A电脑上登录了aw,之后没有关闭浏览器,然后在B电脑上也登录了aw。那么能否强制A电脑上的用户退出呢?这个退出分为SSO的退出和aw的退出。令SSO的退出是可以实现的,只要在登录态中保存登录时间戳,服务器端持有用户标识到登录态的映射,那么B电脑上的登录会令登录态和服务器端的映射同步,而A电脑上的登录态将会过期。这个时候如果在A电脑上开启bw(之前未登录),则会跳转到SSO,很明显,这个时候A电脑上的SSO将是未授权状态。对于A电脑上的aw,并没有办法去清除它的登录态。
UserAgent
之前一直忽略了一个事实,所谓共享SSO登录态,其实是基于同一个UserAgent的。对于web应用来说,UserAgent就是浏览器。这是因为浏览器之间无法共享cookie,而session是基于cookie的。
web和桌面软件、移动APP之间单点登录
这个其实不能算严格意义上的SSO,只能算是代签。可以登录2个QQ号来进行观察,登录后在2个QQ上点击邮箱图标进入邮箱,您可以发现链接上被附加了一串sid。sid是session id的缩写,可以用来标识一个会话。您可以清楚的看到邮箱上的每个链接都被附加上了一串sid参数,这是因为允许同时使用多个邮箱,如果设置登录态的话则会覆盖前一个。这种方式看起来就像早期PHP不支持session的做法,每次通过传递sid到服务器端来解密进行标识用户。
对于移动APP的授权,有使用OAuth方式,也有使用传递sid方式,对此不作深究。
SSO可以同时支持传递sid、OAuth方式和登录态方式的授权校验,并只能被授权后的应用使用SSO。
相关推荐
综上所述,本研究的意义在于提出了一种新的模拟城市用地变化的SSO-CA模型,并通过实证分析,验证了该模型在模拟精度和适用性方面相比于传统模型的优势。研究成果可以为城市规划决策提供有力的技术支持和参考依据。这...
spring boot整合spring security 实现SSO单点登陆 完整DEMO. ...2、先后启动SsoServer、sso-resource、sso-client1、sso-client2 3、访问http://sso-taobao:8083/client1/ 或 http://sso-tmall:8084/client2/
在这个“sso 同域之下简单模拟”中,我们将探讨如何在同一个域名下实现SSO功能。 一、SSO原理与流程 SSO的核心思想是用户只需要在一个地方(认证中心)验证身份,然后这个认证会被其他系统信任。通常,SSO流程包含...
单点登录(Single Sign-On,简称SSO)是一种网络身份验证机制,允许用户在一个系统上登录后,无需再次验证即可访问多个相互关联的系统。在IT行业中,SSO技术广泛应用于企业级应用,提高用户体验,简化管理并增强安全...
SSO(Single Sign-On)是一种身份验证机制,它允许用户在一个系统上登录后,无需再次认证即可访问其他相互信任的系统。这个"SSO Demo 例子"是一个教学资源,旨在帮助初学者理解并实践SSO的实施过程。下面将详细阐述...
SSO(Single Sign-On)单点登录是一种网络身份验证机制,允许用户在一次登录后访问多个相互关联的应用系统,而无需再次输入凭证。这个技术在现代企业信息化建设中扮演着重要角色,因为它提供了便捷的用户体验,同时...
在XXL-SSO中,可能有多个SP示例,模拟了不同的应用环境,它们会将用户的登录请求转发给CAS,并接收验证后的用户信息。 3. **票据(Ticket)**:在SSO流程中,CAS在验证用户身份后会生成一个票据,这个票据会被传递...
SSO(Single Sign-On)是单点登录的缩写,是一种网络身份验证机制,允许用户在一次登录后访问多个相互关联的应用系统,而无需再次进行身份验证。在本文中,我们将探讨SSO的原理,以及如何通过提供的压缩包文件实现...
SSOWebDemo1和SSOWebDemo2是两个独立的Web应用示例,它们模拟了实际环境中需要SSO功能的应用。每个应用都应该配置了相应的客户端设置,如服务端点、回调URL等,以便与SSOAuth服务器正确通信。同时,它们的前端代码...
SSO(Single Sign-On)单点登录是一种身份验证机制,允许用户在一次登录后,就能访问多个相互关联的应用系统,而无需再次进行身份验证。在Java中实现SSO,我们可以利用Spring Boot框架的强大功能,它提供了丰富的...
本文将深入探讨SSO的几种常见实现方案,包括虚拟目录的主应用与子应用之间的SSO、不同验证机制下的SSO、同一域名或不同域名下子域名间SSO的实现,以及不同.NET版本和混合身份验证模式下的SSO解决方案。 ### 虚拟...
### SSO配置:详解MOSS单点登录机制与配置步骤 #### 一、SSO服务概览 在现代企业IT架构中,单点登录(Single Sign-On,简称SSO)是一种用户认证机制,允许用户通过一次登录即可访问多个应用系统而无需再次输入密码...
SSO单点登录解决方案 SSO(Single Sign-On)单点登录解决方案旨在提供一个统一的身份验证入口,满足集团多个成员网站的身份验证需求。该方案的主要目标是实现单点登录,提高用户体验,降低成员网站的登录负载,并...
你可以设置不同的场景,比如模拟用户登录、登出,观察服务端和客户端之间的通信日志,这将有助于巩固理论知识并提升实际操作能力。此外,你还可以尝试自定义认证策略,或者对接新的客户端应用,进一步扩展你的SSO...
**Laravel 开发中的 SSO 实现** 在 Laravel 框架中,Single Sign-On (SSO) 是一种用户身份验证方法,允许用户在一个应用系统中登录后,无需再次输入凭证即可访问其他关联的应用系统。这提高了用户体验,尤其是在...
Java Web SSO(Single Sign-On)实现是一种身份验证机制,允许用户在一次登录后访问多个相互关联的应用系统,而无需多次输入凭证。本教程将基于提供的博文链接进行讲解,但请注意,由于没有直接提供具体内容,这里会...
SSO(Single Sign-On)是一种身份验证机制,它允许用户在一个系统上登录后,无需再次认证即可访问其他多个相互信任的系统。SSO7.0.2是该技术的一个特定版本,可能包含了优化的安全特性、增强的用户体验以及对新系统...
单点登录(Single Sign-On,简称SSO)是一种身份验证机制,允许用户在一次登录后访问多个相互关联的应用系统,而无需再次输入凭证。在本文中,我们将深入探讨基于Spring Boot、MyBatis和Redis实现的SSO解决方案——...
单点登录(Single Sign-On,简称SSO)是一种网络身份验证机制,允许用户在一次登录后,无需再次输入凭证即可访问多个相互关联的应用系统。它简化了用户在多个应用之间的身份验证过程,提高了用户体验,同时也降低了...