提要:
本文主要讲的是一个小白(我)使用python构建一个伪IdP,实现SAML部分功能,进而接入阿里云访问控制SSO的过程。
在正文开始前,先说一下有关SAML中的几个概念:
统一认证中心(Indentity Provider,IdP) 此处指我方的统一登录系统
服务提供者(Service Provider,SP) 此处指阿里云访问控制SSO
SAML request: SP 发给 IdP要求断言的请求
SAML response:IdP发给SP包含断言的响应
SAML协议的核心是: IdP和SP通过用户的浏览器的重定向访问来实现交换数据(断言)。
白话了介么多,以下就是是正文了:)
先说一下我方情况:已经有了一套统一登录系统。
这个丑丑的界面就是自建的统一登录SSO - SingleSignOn
其实更应该叫做单点登录...
这个系统的作用就如同ta的名字一样,作为各个系统的统一登录⻔户。只要在这一处登录,就能在接入此系统的各个地方直接获取用户信息完成登录动作。
当然,自己公司内部的登录,统一登录啥的可以协调一下就接入了,但是对于家大业大的阿里,咱就没法任性了,只能跟着对方的节奏走。
接下来就是读文档了解一下阿里的接入方案,以下是阿里的文档:
https://help.aliyun.com/document_detail/93684.html?spm=a2c4g.11186623.6.632.68f14f732BUKiO
我方现有的使用阿里云的访问控制功能是基于用户的。每个用户配置相应的权限,且用户名与统一登录的用户名(花名拼音)是相对应的,比较适合文档中提到的用户SSO。
讲真,在接触阿里云的这个访问控制(RAM)的SSO之前,我真的晓不得SAML是个啥。在看了阿里云犹抱琵琶半遮面的文档之后,有了自己的理解:用XML传递断言的一个协议。
根据下图(来源于阿里云)
我方统一登录就是相当于IdP的⻆色。
其具体流程梳理如下:
1、用户Alice使用浏览器登录阿里云,阿里云(SP)将SAML认证请求(SAML request)返回给浏览器。
2、浏览器向IdP(我方统一登录)转发SAML认证请求(SAML request)。
3、IdP(我方统一登录)接收用户Alice发送的SAML认证请求,判断用户身份状态,是否登录:
如果用户Alice未登录,生成login⻚面给用户浏览器,进行登录认证操作。
如果已有登录状态,直接根据SAML request请求与Alice的身份信息生成SAML响应(SAML Response)返回给浏览器。
4、浏览器将SAML响应转发给阿里云SSO服务。
5、SSO服务通过SAML互信配置,验证SAML响应(SAML Response)的数字签名来判断SAML断言的真伪,并通过SAML断言的NameID元素值,匹配到对应阿里云账号中的RAM用户身份。
6、SSO服务向浏览器返回控制台的URL。(阿里云原文是这样,但是貌似并非如此,后文再讲)
7、浏览器重定向到阿里云控制台。
总结下来,我方统一登录接入阿里云SSO要做的四件事就是:
验证用户身份(本职工作,已经完成)。
本地配置SAML IdP,阿里云配置SAML SP。
解析SAML请求(SAML request)。
生成SAML响应(SAML response)。
下面我们从 2 开始(就不说从头开始,哼~)
一般来说,要完成IdP与SP之间的互信,需要从互传SAML的metadata开始。
这个metadata是xml格式的。阿里云(SP)的metadata直接给提供了,在SSO的配置⻚面就能看到。
(你看我这马赛克,是又大又匀)
打开红框里的链接,就可以导到相应的SP的配置信息。
一般来说,如果是通过例如Shibboleth 微软的 ADFS等现成的IdP的话,直接把这个xml添加到相应的配置中,就可以直接分析出需要的信息。
但是本文就是要自己搞,所以我们还是康康这个xml里都有些啥吧。
从上到下,第一个红框里的entityID ,下面的红框里的Location
这两个是下文构建SAML response所需要的。
中间厚码是阿里云提供的公钥(在下文你会发现,这个公钥并没什么卵用
)。
entityID:
https://signin.aliyun.com/88888888/saml/SSO
Location:
https://signin.aliyun.com/saml/SSO
接下来构建我方作为IdP而提供给阿里云SP的metadata.xml。
在metadata.xml中需要指定的内容包括:
IdP的公钥:用于签名xml
IdP的entityID:用于SP识别身份
断言格式
IdP的login url 、logout url
在这个xml里有个很重要的内容就是密钥。
SAML 需要密钥对交互的信息的xml文档进行签名。
为方便大家, 这里给出一个生成X509密钥的bash脚本
(这个脚本非原创,是从python的某个与saml相关模块里搜刮到的,具体是那个......忘记⻦)
这里设定的过期时间是3650天就是10年,怎么也够用了。
如果运行正常会生成两个密钥文件:
xxxxprivate-key.pem
xxxxcertificate.pem
一个私钥一个公钥。
构建metadata.xml需要的是公钥,注意,千万不要泄露私钥!
下面是个具体示例
从上到下
第一个红框里是本地IdP的entityID ,这个相当于给自己的IdP起个名字
http://my.sso.com/saml
第二个红框指定断言格式,这个基本是固定
<md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:email</md:NameIDFormat>
第三个红框指定login 和logout的url 。
这两个url用于接收SP发送过来SAML request
loginin http://my.sso.com/saml/login
lougout http://my.sso.com/samllogout
中部两个红色椭圆的内容是IdP公钥(太⻓,懒得打码,直接折叠起来了)
编辑好这个metadata.xml就可以上传给阿里云,然后打开阿里云访问控制的SSO开关
登出阿里云子账号,然后重新访问康康会发生什么
原本输入账号密码的登录⻚变成了只有一个 使用企业账户登录 按钮的⻚面。
根据上文SAML的接入流程,用户点击登录后,SP(阿里云访问控制SSO)会向用户发送一个SAML request重定向,重定向的目的地是IdP的login url。
由于是SAML request请求是经过浏览器进行重定向,所以我们完全可以通过修改本地hosts配合搭建一个本机服务来获取这个request。
用flask可以很容易的获取这个请求。那我们来看看这个request到底是什么亚子的
示例代码:
编辑hosts文件,添加一行,然后运行上面这段代码
浏览器访问阿里云的访问控制登录⻚面,点击 使用企业账号登录 看一下终端,此时SAML request就会被重定向到刚刚创建的Flask应用上了。
如图所示:
SAMLRequest 是SP发送请求的具体内容。
RelayState 是在登录前访问的⻚面
接下来的问题就是:
根据SAML标准,这个SamlRequest是经过了deflated压缩和urlencode的xml数据。所以我们要来个逆向操作,把它还原成xml(没错全都是xml)
示例代码如下
将获取的SAMLrequest作为参数传递到这个方法中,得到的返回值就是原始的xml了。
在解析过后的xml里最需要的就是本次请求的requestID。这个requestID对接下来生成SAML response很重要,可以通过xmltodict这个模块来获取requestID的具体值。
示例代码如下
搞定了request,下一步就是拼装response了。
由于俺真的是不了解SAML,拼装这个respone真是要了命了,最直接的问题就是,我根本不知道他⻓什么样。
经过与阿里云售后的沟通,拉钉钉群,售后@技术,技术无回应,等待两小时,上微博吐槽,再等待两小时后,阿里云的技术小哥哥给了我一个SAML response 示例。
一下午时间就这么过去了,我只是想要个示例,不是核弹的设计图......怪不得别人,自己学艺不精。
剩下的就是根据这个示例,再结合上文的配置信息,拼接成一个xml。
xml模板如下:
InResponseTo:上文获取的RequestID
username:登录阿里云访问控制的用户名。
这个用户名要符合递交给阿里云的IdP metadata中定义的NameID格式。
以下是上文模板所用到的一些方法:生成随机ID,生成时间戳
xml生成完毕,下一步就是对其进行签名。
这里可以使用signxml模块来操作。
根据其文档,要在生成签名的位置插入placeholder作为占位符
就是上文中xml模板第八行的位置,不要直接抄signxml模块的demo,一定要指定ds的namespace。
具体签名方法如下:
最后一步就是将相关信息发送给SP了。
这里要注意的是,SAML的操作都是通过浏览器重定向实现的,所以很多IdP在发送SAML response的操作实际上是生成一个自动提交的表单,表单中包含两项内容:
SAMLResponse 其内容是base64编码的XML。
RelayState 其内容是IdP接受到的request中的RelayState,就是登录前⻚面。
这里我们用一个模板进行此操作。示例代码如下:
模板文件saml_post.html
下面给出从接受SAMLrequest到生成SAMLresponse到发送给SP的代码。
至此,就基本实现了一个伪IdP功能。
有没有发现什么问题?
好像没有用到阿里云SP提供的公钥......
但是我的功能确实实现了......
这也是个安全隐患,如果IdP的私钥被盗,只要通过修改hosts文件就可以完成整套动作(我本地调试就是这么干的)。
另外还记不记得上文划掉的那句话?
SSO服务向浏览器返回控制台的URL。
事实是,向阿里云SP发送POST请求后,他返回的真不是控制台URL,而是整个⻚面。
扫描上方二维码
更多技术干货分享
回复【求职】海量岗位在等你~
网友评论