背景

某项目需要集成新系统和邮箱单点登录,邮箱是outlook,邮箱系统有自己的单点登录系统ADFS,门户单点登录使用OAM,OAM和ADFS通过SAML2.0进行单点登录。

碰到的问题是,其他系统都可以登录进去,唯独邮箱无法登录,而且老系统已经做了和邮箱的单点登录,并且可以成功登录,新系统一直无法登录。

老系统和邮箱做的是伪单点,就是在代码里通过登录oamconsole获取到cookie(也是骚操作:) ),将cookie返回给浏览器,通过这种间接的方式进行登录,新门户通过配置ohs+webgate进行单点登录。

以上是背景。

你需要了解的知识点

这里先介绍下OAM单点登录和SAML2.0基本知识。

单点登录

单点登录也称SSO(Single Sign On),做过企业应用的应该对这个都很了解,就是在多个系统中,用户只要登录一次就能登录所有都系统,避免重复登录,就像我们登淘宝就不需要再登录天猫一样,不仅方便用户,每个系统也可以不用单独为用户管理和登录行为管理做单独的开发,基本所有的企业都会实施单点登录系统,单点登录系统基本也是企业信息化的第一步。因为局限于浏览器的沙盒模型,单点登录虽然有多种实现协议,但总体上大同小异,没有太多新的花样。

令牌

令牌是单点登录系统里的凭证,也有其他名称,比如token,凭证等,都是一个意思,这里就称为令牌,单点登录里面核心就是实现令牌传递,不同系统间必须要有手段将各自的令牌传递到对方,这样对方才能知道你已经登录过,所以搞清楚令牌是怎么传递的非常重要。

cookie

http是无状态协议,什么意思呢,就是说我后台根本就不知道是谁在请求,也就没有登录的概念,但这样肯定不行的,应用层需要知道是谁在访问才能提供相应的业务服务,因此就有了cookie,cookie标识一个用户的登录状态,如果你的cookie被人拿到了就意味着你被盗号了,别人就算不知道你的用户名密码也能以你的身份进行访问。

根据浏览器安全策略,不同domain是不可以设置对方的cookie的,也就是cookie是有主人的,每个网站只能设置自己域名下的cookie,这里的网站是指一级域名级别,一级域名相同的可以互相设置对方的cookie,比如a.definesys.com可以将cookie设置在.definesys.com下,这样所有*.definesys.com的站点都能使用这个cookie。

以上是了解单点登录的一些基本知识,下面开始介绍oam单点登录过程

OAM单点登录

首先,至少要有三套系统

  • app1(app1.com)
  • app2 (app2.com)
  • oam (oam.com)

app1登录后用户访问app2自动登录不需要再次登录,同样app2登录后app1也不需要再登录,oam就是实现以上过程的核心。

假设你喜欢上一个姑娘,你们两情相悦,但她父母不允许你们早恋,你要怎么向她表达你的爱意,你可以收买她弟弟或者闺蜜,让第三个人帮你传达,单点登录也是同样的道理,两个系统间是隔离的,必须借助第三个系统才能进行通信,app1和app2单点登录过程如下:

  • 1. 用户访问app1.com,app1检测到用户未登录app1,因为app1.com下没有cookie,跳转到oam的登录页
  • 2. oam检测到当前用户没有在任何系统登录过(因为oam.com下没有任何cookie),显示登录页,要求用户输入账号
  • 3. 用户输入账号,表单提交到oam后台,oam认证通过后,设置oam.com下的cookie(这个非常重要,oam下有cookie了,而且这个cookie是登录过的),并且跳转到地址app1.com?token={token},将令牌以参数的形式返回给app1
  • 4. app1接收到令牌验证令牌的可靠性,这里可以有多种方式,你可以后台调用oam的接口进行认证,也可以双方用密钥加密,验证通过后设置用户在app1.com下的cookie为登录状态
  • 5. 用户访问app2.com,app2检测到用户为登录app2,系统永远只知道有没有登录自己的系统,无法知道有没有登录其他系统,跳转到oam的登录页
  • 6. 重点来了,这时候因为在第3步用户在oam登录过,所以oam.com有了cookie,这时候oam就知道你已经登录过我oam系统了,不显示登录页,直接跳转到app2.com?token={token},也给app2颁发了一个token
  • 7. app2对token进行验证,验证通过设置用户在app2.com下的cookie为登录状态。

用图表示如图:

SAML2.0

SAML是一种实现单点登录的协议,最新的版本是2.0,SAML协议十分复杂,但大方向和上面介绍的差不多,我们没必要去深究协议细节,只要清楚过程是怎么就行,在SAML里,作为认证服务器被称做IDP(Identity Provider),提供用户服务的叫做SP(Service Providers),上面的例子中,app1和app2就是两个sp,oam就是idp,单点登录过程如下:

这里对具体过程不再展开,基本和上面一样,有两个地方需要关注:

  • idp将用户信息通过私钥进行加密签名,sp通过idp的公钥进行解密,所以两边是要配好公钥要私钥的。
  • sp跳转idp的时候会带上一个SAMLRequest的xml文件,该文件经过base64进行加密后进行压缩以请求参数的形式传递给sp,该文件记录了sp的信息,最重要的是告诉idp认证成功后跳转到哪里,因为内容是被压缩的,所以并不能通过base64进行解密,可以通过该工具进行解密
<samlp:AuthnRequest ID="id-60bc225c-4848-4463-b8b3-bf1977d7af37" Version="2.0" IssueInstant="2020-01-07T10:55:13.000Z"
                    Destination="https://oamprod1.domain.com.cn:4443/oamfed/idp/samlv20"
                    Consent="urn:oasis:names:tc:SAML:2.0:consent:unspecified"
                    xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
  <Issuer xmlns="urn:oasis:names:tc:SAML:2.0:assertion">http://loginsrv02.domain.com.cn/adfs/services/trust</Issuer>
  <samlp:NameIDPolicy Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified" AllowCreate="true"/>
</samlp:AuthnRequest>
  • idp认证成功后,会将用户信息进行加密后,以表单形式提交给sp,对的,你没看错,是表单,所以这里就必须要求idp需要提供一个能够自动提交表单的页面。

排查过程

既然老门户是成功的,新门户不行,那么最直接的方法就是对比两边有什么不一样

这是老系统邮箱登录步骤

  • 1. 跳转邮箱系统,邮箱系统检测没有登录,跳转到oam登录页,也就是图中第2步
  • 2. oam检测已经登录,跳回邮箱系统,并且将用户信息加密后以表单方式返回给邮箱系统
  • 3. 邮箱系统得到数据后进行解密,记录登录状态,对用户放行
邮箱跳转oam请求SAMLRequest
邮箱跳转oam请求SAMLRequest
oam认证成功返回SAMLResponse表单
oam认证成功返回SAMLResponse表单

新老系统的认证步骤一模一样。

SAMLRequest里没有什么信息,就一个成功后回调的地址,问题应该出在SAMLResponse上,oam既然已经成功返回SAMLResponse说明在oam上已经认证成功了,那么问题就是adfs不认这个结果,由于没有adfs的权限,而且连adfs系统长什么样都不知道,所以只能靠猜:)。

在猜了一天没结果后,决定自己搭一套SAML单点登录系统,看下整个过程是怎么工作的,到github上找相关项目,找到两个项目

第一个是国人写的,以为国人写的应该更好懂些,把项目配好运行起来才知道,里面东西都是写死的,根本没有参考价值,浪费很多时间,坑。

第二项目认证过程基本和项目一样,在运行成功后通过阅读源码理清整个认证过程,才发现一个被自己忽略的细节,原来 SAMLResponse并没有加密,就是base64,我一直以为SAMLResponse是经过私钥加密的,不可解密,因为网上只有SAMLRequest的解密工具并没有SAMLResponse解密工具,我也曾尝试用base64进行解密,但是是乱码,但是,我拿的是表单数据,也就是数据经过base64加密后再进行url编码处理,所以要得到源文件,需要对数据进行url解码,再base64解码。哭:)

于是对两个SAMLResponse进行解密,可以在这里进行url解码,再到这里这里进行base64解密。

  • 老系统
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:dsig="http://www.w3.org/2000/09/xmldsig#"
                xmlns:enc="http://www.w3.org/2001/04/xmlenc#" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
                xmlns:x500="urn:oasis:names:tc:SAML:2.0:profiles:attribute:X500"
                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                Destination="https://loginsrv02.domain.com.cn/adfs/ls/"
                ID="id--v2ptGOlxd5fumygqbzTWCVVmZqok3SK5ispcH9S" InResponseTo="id-fee2f86a-c0a9-4721-bad2-609f43ffcfb9"
                IssueInstant="2020-01-07T08:16:08Z" Version="2.0">
  <saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">https://oamprod1.domain.com.cn:4443/oam/fed
  </saml:Issuer>
  
  //......忽略细节
    <saml:AuthnStatement AuthnInstant="2020-01-07T08:15:40Z" SessionIndex="id-B9gWKlimTwwZMQAQvl108109hJLsi2v5BGflo9Rl"
                         SessionNotOnOrAfter="2020-01-07T09:16:08Z">
      <saml:AuthnContext>
        <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
        </saml:AuthnContextClassRef>
      </saml:AuthnContext>
    </saml:AuthnStatement>
  </saml:Assertion>
</samlp:Response>
  • 新系统
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:dsig="http://www.w3.org/2000/09/xmldsig#"
                xmlns:enc="http://www.w3.org/2001/04/xmlenc#" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
                xmlns:x500="urn:oasis:names:tc:SAML:2.0:profiles:attribute:X500"
                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                Destination="https://loginsrv02.domain.com.cn/adfs/ls/"
                ID="id-S7OSl-MwBR-ZUworN1-ijmgajH6AfoVXd-FMCoHp" InResponseTo="id-22ca93a4-a0dc-456c-b134-43c5fcce8426"
                IssueInstant="2020-01-07T08:21:39Z" Version="2.0">
    <saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">
        https://oamprod1.tsingtao.com.cn:4443/oam/fed
    </saml:Issuer>
    //...忽略细节
        <saml:AuthnStatement AuthnInstant="2020-01-07T08:21:43Z"
                             SessionIndex="id-2uWkrsrFPZ9J-0qPmn-MQSNifzezvpkUnbPpnbeb"
                             SessionNotOnOrAfter="2020-01-07T09:21:39Z">
            <saml:AuthnContext>
                <saml:AuthnContextClassRef>newportalSchema</saml:AuthnContextClassRef>
            </saml:AuthnContext>
        </saml:AuthnStatement>
    </saml:Assertion>
</samlp:Response>

可以发现两次请求只有AuthnContextClassRef是不一样的,通过搜索得知,这个参数表示鉴别方式,比如一个网站同时支持口令认证和Kerberos两种方式,则口令认证和Kerberos就是两个authnContextClassRef选项,老系统中的urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport是SAML标准鉴别方式,新系统newportalSchema是自定义,而newportalSchema是OAM中认证schema名称

认证schema定义了用户认证的方式以及登录页和回调页面地址,老系统是通过登录oamsconsole方式进行单点,不需要定义schema,独立系统必须要定义schema,定义了就带上这个名称,那怎么办?

骚操作来了,那名字取成urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport不就行了,试了下,竟然可以了,笑:)

写到最后

这个问题虽然不复杂,却花了两天时间排查解决,总结了下主要是对SAML协议细节不了解就盲目开干,自己弄个saml配下很多细节就明白了,还是很感谢这个问题,对OAM有进一步的认识,也对saml初步了解。

,
Trackback

no comment untill now

Add your comment now