SSO单点登录对比分析
引言:
单点登录的英文名叫做:Single Sign On(简称SSO)。
指在同一帐号平台下的多个应用系统中,用户只需登录一次,即可访问所有相互信任的系统,实现多个系统,统一登陆。
本文主要介绍了常见单点登录流程,并对三种登录授权方式原理做了分析比较:cookie,cas,oauth2。
可以将单点登录简单分为:同根域单点登录、不同根域下单点登录。
一、同根域Cookie登录
Cookie和Session是相对的概念,都是基于key-value结构存储数据,不同是一个用在客户端一个用在服务端。
Cookie可以应用在很多场景,如一些内网后台管理系统、Portal门户、SSO统一登录、追踪用户的身份、根据特定用户进行推送或推荐等…
默认情况下,Cookie只能在同一域名下使用,通过设置domain为根域名,则Cookie可以实现同根域下共享,同根域名下基于cookie就可以方便实现SSO登录;在某些约束下,也可以实现跨域的cookie共享。
1.1 同根域下单点登录:
一般一个企业只会有一个主域名,通过二级域名区分子系统。举个例子:一个企业中有三个子系统(app1.domain.com、app2.domain.com、sso.domain.com),根域名同为domain.com。
登录过程:登录sso.domain.com后,通过http response写入cookie信息,包含了登录的sessionId等信息,domain设置为根域 domain.com;用户访问app1和app2时服务器就可以获取到sessionId,验证用户为登录状态,用户在浏览器上就可以正常获取app1和app2的资源。
简单易用缺点也比较明显:
cookie中如果存放了用户的敏感信息,一旦被窃取(用户的Cookie可能会由于网站的XSS漏洞而被盗取),这个负面影响大。
如果大量的用户信息放入cookie,对浏览器端压力较大,同时cookie存储的信息量大小有限不够轻量级,跨域情况下将会带来一定的麻烦。
1.2 cookie共享代码实例
搭建了基于SpringBoot的HTTPS服务,使用keytools生成了证书,SSL配置:
server:
port: 9043
ssl:
key-store: classpath:ssLserver.jks
key-store-password: '@lmggTy6XNZmJwu7'
key-store-type: JKS
注:为了消除浏览器安全提示,需要将证书导入信任证书列表中。
并修改host文件,配置域名映射,均指向本机,类似:
127.0.0.1 app1.domain.com
127.0.0.1 app2.domain.com
关于Cookie共享的代码验证,分5个场景:
(1) 设置默认cookie
请求: https://app1.domain.com:9043/setDefaultCookie?name=defaultCk&value=ckValue
ResponseCookie cookie = ResponseCookie.from(name, value)
.httpOnly(true)//httpOnly 防止XSS漏洞js读取cookie
.secure(true)
.path("/")
.maxAge(3600)
.build();
response.setHeader(HttpHeaders.SET_COOKIE, cookie.toString());
(2) 设置根域名cookie: domain.com
请求:https://app1.domain.com:9043/setRootCookie?name=rootCk&value=rtCkValue
ResponseCookie cookie = ResponseCookie.from(name, value)
.httpOnly(true)
.secure(true)
.domain("domain.com")
.path("/")
.maxAge(3600)
.build();
response.setHeader(HttpHeaders.SET_COOKIE, cookie.toString());
访问https://app2.domain.com:9043/printCookie 可发送根域名的cookie
(3) 不同子域名cookie:app2.domain.com ✘
请求:https://app1.domain.com:9043/setOtherDomainSameRootCookie?name=sameRoot&value=Val
ResponseCookie cookie = ResponseCookie.from(name, value)
.httpOnly(true)
.secure(true)
.domain("app2.domain.com")
.path("/")
.maxAge(3600)
.build();
response.setHeader(HttpHeaders.SET_COOKIE, cookie.toString());
(4) 设置不同根域名cookie: app1.host.com ✘
请求:https://app1.domain.com:9043/setOtherDomainDiffRootCookie?name=diffRoot&value=val
domain("app1.host.com")
(5) 设置跨域共享cookie: app1.host.com
请求:https://app1.domain.com:9043/setNoneSameSite?name=noneSameSiteCk&value=val
ResponseCookie cookie = ResponseCookie.from(name, value)
.httpOnly(true)
.secure(true)
.path("/")
.maxAge(3600)
.sameSite("None")
.build();
response.setHeader(HttpHeaders.SET_COOKIE, cookie.toString());
跨域获取:https://app2.domain.com:9043/printCookie 是无法获取cookie的。实际上完整描述应该是“跨域请求获取”。
跨域请求获取又可以细分为:基于Form的表单请求、Ajax跨域请求
Form表单跨域请求:
某高仿APP1提供了一个页面https://app1.host.com:9043/indexPage 如下:
<form id="fm_crossDomainForm" method="post">
</form>
<div >
<a href="javascript:void(0);" onclick="formSubmit()">Form提交</a>
</div>
<script>
function formSubmit() {
$('#fm_crossDomainForm').form('submit', {
url:"https://app1.domain.com:9043/printCookies"
});
}
</script>
用户访问后就会将第(5)种cookie提交上去,这就是一种CSRF攻击。
可以采取以下措施防范防范措施:
✓ 搭建HTTPS协议安全网站,cookie不要设置为 sameSite:none
✓ 短信验证码
注:sameSite: https://blog.csdn.net/qq_36690992/article/details/117965822
Ajax跨域请求:CORS
跨域请求服务端需要在响应头添加
response.setHeader("Access-Control-Allow-Origin", request.getHeader("origin"));
浏览器Ajax跨域请求
$.ajax({
url: "https://app1.domain.com:9043/printCookie",
contentType: 'application/x-www-form-urlencoded',
type: 'POST',
async: false,
xhrFields: {
withCredentials: true
},
success: function (result) {
console.log(result);;
},
error: function (data) {
console.log(data);
}
});
Ajax请求默认是不会传任何cookie的。加上代码第6、7行参数配置,才开启了浏览器上传cookie,同时服务端配置响应头
response.setHeader("Access-Control-Allow-Credentials", "true");
参考:https://blog.csdn.net/qq_33479841/article/details/123364237
二、不同根域下CAS单点登录
基于浏览器的安全机制, 不同的域名之间无法实现cookie共享。针对这种情况要实现单点登录,需要借鉴CAS(Central Authentication Service)的设计思想。CAS的设计思路主要如下图所示:


图 CAS时序图 https://apereo.github.io/cas/6.6.x/protocol/CAS-Protocol.html
示例:阿里云sso路径
https://login.alibaba-inc.com/ssoLogin.htm?
BACK_URL=https%3A%2F%2Fmail.alibaba-inc.com%2Falimail%2F
&preLoginKey=gQksImpGUd1666162247475pCPJHRnhCV
&CONTEXT_PATH=%2Falimail%2F
&APP_NAME=webmail
三、基于oauth2的认证
Oauth2是一种授权框架/协议,用来授权给第三方应用,获取用户数据(在不知道用户名、密码的前提下)。用于APP或网页应用微信登录、 QQ 登录、微博登录。第三方网站上授权获得用户信息之后,进行绑定等操作。
OAuth2.0 协议的授权流程可以参考下面的流程图,共四个角色,其中 Client 指第三方应用,Resource Owner 指用户,Authorization Server 是开放平台的授权服务器,Resource Server 是开放平台的 API 服务器(用户数据)。

图 抽象协议流

图 获取token及认证过程
强调local state的概念,这个字段是在登录成功后获取Authorization Code时传给AS的,是什么作用呢?

以绑定两个账号为例:攻击者A获取自己的Authorization Code,制造跳转连接到APP服务,用户B在不知情的情况下,在自己浏览器上使用该连接发起了绑定请求(GET方式),最终绑定了A的账号。
要防止这样的攻击其实很容易,作为第三方应用的开发者,只需在OAuth认证过程中加入state参数,并验证它的参数值即可。具体细节如下:
在将用户重定向到OAuth2的Authorization Endpoint去的时候,为用户生成一个随机的字符串,并作为state参数加入到URL中。
在收到OAuth2服务提供者返回的Authorization Code请求的时候,验证接收到的state参数值。如果是正确合法的请求,那么此时接受到的参数值应该和上一步提到的为该用户生成的state参数值完全一致,否则就是异常请求。
state参数值需要具备下面几个特性:
1.不可预测性:足够的随机,使得攻击者难以猜到正确的参数值
2.关联性:state参数值和当前用户会话(user session)是相互关联的
3.唯一性:每个用户,甚至每次请求生成的state参数值都是唯一的
4.时效性:state参数一旦被使用则立即失效
链接:https://www.jianshu.com/p/c7c8f51713b6
OAuth考虑了各种Web攻击,比如CSRF (Cross-Site Request Forgery), Clickjacking等,OAuth协议文档中已经有了比较全面的阐述
参考:https://www.rfc-editor.org/rfc/rfc6749
https://www.jianshu.com/p/c7c8f51713b6
四、CAS与OAuth2区别:
● CAS 客户端要获取的最终信息是,这个用户到底有没有权限访问我(CAS 客户端)的资源;
● OAuth2 获取的最终信息是,我(oauth2 服务提供方)的用户的资源到底能不能让你(oauth2 的客户端)访问。 存在资源的转移使用。
● CAS感觉就是把oauth2的Client和RS合并到了一起(很微妙也很关键的区别)。
总结:CAS是"直接认证",OAuth2是"授权访问",前者更简单直接,后者更灵活安全。
思考:
如何登出
附:
实现SSO常见的几个协议:CAS, SAML、OpenID, OAuth2(开放授权)
一些开源框架:
Apereo CAS https://www.apereo.org/projects/cas
keycloak https://www.keycloak.org/
云平台:

浙公网安备 33010602011771号