首先看下exchange和adfs跳转的方式
在我访问exchange owa时,因为没有身份,所以被重定向到了AdfsIssuer
Request URL: https://mail.testyunwei.com/owa/ Request Method: GET Status Code: 302
然后ADFS将我们重定向到我们新增的声明提供方,目标地址是声明提供方的saml登录终结点
Request URL: https://adfs.testyunwei.com/adfs/ls/?wa=wsignin1.0&wtrealm=https%3a%2f%2fmail.testyunwei.com%2fowa%2f&wctx=rm%3d0%26id%3dpassive%26ru%3d%252fowa%252f&wct=2021-06-11T09%3a19%3a26Z Request Method: GET Status Code: 302 Found
最终到了IDP系统上来,等待IDP完成身份验证
Request URL: https://saml.testyunwei.com:8088/sso/redirect?SAMLRequest=fZFBS8QwEIX%2fSsk9TVp3axq6hWUXoaAiKh68xXTKBtqkZlLX%2ffem7UmFPeZl3rz5eBWqoR%2flfgon%2bwyfE2BImuOOmJbeiNtOb%2fOSQptxulECqNDbggrxUZQtlO2m5CR5A4%2fG2R3J0%2fhqECdoLAZlQ5R4nlFe0Cx75aXMSpkXKef8nSTHmGOsCovzFMKIkrH5lDTEn8tkz2BS7QYpuBAM0TEPrfGgA0kOziLM6ydvpVNoUFo1AMqg5cv%2b4V7GS6Reh%2bRkcQRtOgMtSb6H3qJciK%2b7R%2b%2bC064ndbUQ%2bdV63aQQwc9EpJ6JIpBqO%2fwDtGgsDn4ZDciCnzBUbA2pq7WMx7i6OT653uhLcuf8oK7AZmm2KLGwbhn9jbzve3c%2beFABdiSmAUlYXbH%2frdc%2f&RelayState=28163370-9f40-4cbd-a4f8-c23350ed99bc Request Method: GET Status Code: 200 OK
我们的IDP可以从url里获取两个参数:SAMLRequest和RelayState。
RelayState看起来像一个标识符,SAMLRequest是一个加密后的值,参考 源码里的解密代码,进行解密,可以得到如下信息
import xml.dom.minidom compressed = base64.b64decode(SAMLRequest) inflated = zlib.decompress(compressed, -15) dom = xml.dom.minidom.parseString(inflated.decode()) print(dom.toprettyxml())
<samlp:LogoutRequest Consent="urn:oasis:names:tc:SAML:2.0:consent:unspecified" Destination="https://saml.testyunwei.com:8088/slo/redirect" ID="_37ce40ee-d98e-4232-abb6-6a8e84098796" IssueInstant="2021-06-15T01:43:13.133Z" Version="2.0" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"> <Issuer xmlns="urn:oasis:names:tc:SAML:2.0:assertion">http://adfs.testyunwei.com/adfs/services/trust</Issuer> <NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified" NameQualifier="https://adfs.testyunwei.com:8088/idp.xml" SPNameQualifier="http://adfs.testyunwei.com/adfs/services/trust" xmlns="urn:oasis:names:tc:SAML:2.0:assertion">7af1c646f9558815bb1d7c004743a9264dbf53aef0ce2ffaee648094a139f033</NameID> <samlp:SessionIndex>id-RKMFicL4MPmzo1XFS</samlp:SessionIndex> </samlp:LogoutRequest>
这里面包含了很多信息比如 adfs 终结点的url,nameid,sessionindex等等
分析源码,这里有几个有用的参数被保存进了session:
-
RelayState
-
samlp:LogoutRequest[‘ID’] 保存在 destination 变量内
获取了必须的数据后,返回了我们登录验证的页面,然后是处理账号密码验证的逻辑
Request URL: https://saml.testyunwei.com:8088/verify Request Method: POST Status Code: 302 Found
登录成功后,会被重定向到soo/redirect登录url
Request URL: https://saml.testyunwei.com:8088/sso/redirect?id=HDca0LudApPvE6FUBHkYTI5u&key=8ba47480e949b298a866fed9d2ebd5ee0759dec7 Request Method: GET Status Code: 200 OK
经过一系列的判断和执行后,最后到了下面两段代码
_resp = IDP.create_authn_response( identity, userid=self.user, encrypt_cert_assertion=encrypt_cert, **resp_args )
http_args = IDP.apply_binding( self.binding_out, "%s" % _resp, self.destination, relay_state, response=True, **kwargs )
identity 变量是我们最终获取的用户信息,这里他的值是,也就是我们最初设置的用户的upn
{"upn": "test01@testyunwei.com"}
self.destination 是 SAMLRequest 解密出来的 samlp:LogoutRequest[‘ID’] 的值
relay_state 是 RelayState
最终,http_args 会得到一个html的格式的内容,把http_args直接通过httpresponse到前端后,就会自动跳转adfs完成登录了。
我们模拟登陆一次后,抓取到http_args 内容如下
<html> <head> <meta charset="utf-8" /> </head> <body onload="document.forms[0].submit()"> <noscript> <p> <strong>Note:</strong> Since your browser does not support JavaScript, you must press the Continue button once to proceed. </p> </noscript> <form action="https://adfs.testyunwei.com/adfs/ls/" method="post"> <input type="hidden" name="SAMLResponse" value="25zMDpSZXNwb25zZT4K"/> <input type="hidden" name="RelayState" value="add3499b-3fd2-4726-b0f6-4dd8201e014e"/> <noscript> <input type="submit" value="Continue"/> </noscript> </form> </body> </html>
这里 SAMLResponse input字段的value很长很长,我进行了截取,只留了一小部分。很明显可以看出来,最终会将 SAMLResponse 和 RelayState 的值由form表单提交至 https://adfs.testyunwei.com/adfs/ls/ 。那么我们后面自己实现的时候,最好将 http_args 自动跳转逻辑改写,由后端http返回json,前端统一跳转
我这里抓取了一下 saml response 后的 xml,红框内可以看到,里面包含了 用户的upn
到这里对 pysaml2 基本流程和逻辑也都大致清楚了,我们系统的架构图大致如下