什么是ADFS?
在今天的OAuth协议风靡全球之前,微软2005年12月在Windows 2003 R2开始引入了活动目录联合服务 (ADFS),可以为多个Web应用程序提供单点登录。而OAtuh的历史才是2006 年11月 由Blaine Cook开发Twitter OpenID 时开始的。
传统的Windows认证
在早期的Windows域环境中,我们可能见过针对于企业内网的Web应用程序实现的Windows 认证。目标网站部署在域环境中某台服务器上,用户在域内的机器上访问目标网站时,目标网站可以拿到该访客的域用户名,形如domain\user,然后通过访问活动目录AD,辨别用户的身份权限是否属于某个特定的用户组。这样的部署有两个限制:
- 目标网站如果要能在internet上提供访问,它必须先能访问内网的活动目录,同时还得接入互联网。如果有多个网站,每个网站的服务器都得做出这样的苛刻配置。
- 如果是多个网站,用户在访问第一个网站时,需要输入用户凭据。在访问第下一个网站时,仍旧需要输入用户凭据。
ADFS的基本原理
于是,存在一种更优化的部署可能。专门部署一个Web代理服务器位于网关位置,既可以访问内网的活动目录,同时负责帮助部署在内网或者外网上面的其它Web应用程序提供认证功能。这样让目标网站的认证和部署更加灵活。ADFS就是这样实现的。
ADFS验证的基本流程
- 用户第一次访问目标网站,目标网站发现是匿名用户,将请求重定向至ADFS认证网站。
- 用户在ADFS认证网站上输入自己的域账号和密码,ADFS服务器会请求活动目录来验证用户身份。
- 如果验证通过ADFS会得到一个包含用户信息的用户访问令牌,直接发送给目标网站(这里是本文使用PowerShell调用的重点,下文会提到)
- 目标网站得到合法的用户令牌,会在返回给用户的请求中写入Federation Cookie。
- 接下来用户和目标网站之间的所有请求都会包含Cookie作为身份验证,直到Cookie过期。
什么是访问令牌?
访问令牌是一段包含用户信息的配置文件,可以把它看成一张登机牌,既包含了乘客(用户)是谁,也包含了乘客在什么时间段(有效期)可以乘坐某一次航班(目标网站)。
PowerShell获取ADFS的访问令牌环
PowerShell获取ADFS访问令牌环的意义在于可以通过编程的方式,而不是浏览器跳转完成ADFS验证。再实际一点的意义比如我们的某个service是基于ADFS验证的,在写API的自动化测试时,通过测试账号以静默方式拿到访问令牌环,功德无量啊!
ADFS访问令牌环可以直接调用.NET中的System.ServiceModel.dll和System.IdentityModel.dll来获取。
这里我引用了 Request ADFS Security Token with PowerShell
大家可以去微软脚本中心下载这段Invoke-ADFSSecurityTokenRequest脚本实现,下面是如何调用,非常方便吧!
$mySite= "我的目标网站" $token=Invoke-ADFSSecurityTokenRequest ` -ClientCredentialType UserName ` -ADFSBaseUri https://corp.sts.pstips.net ` -AppliesTo $mySite ` -UserName 'mosser' ` -Password '' ` -Domain 'pstips.net' ` -OutputType Token ` -SAMLVersion 1 ` -IgnoreCertificateErrors $createdTime = $token.ValidFrom.ToString("o") $expiresTime = $token.ValidTo.ToString("o") $binarySecurityToken = $token.TokenXml.OuterXml
PowerShell携带ADFS令牌环来发送请求
$securityToken=@' <t:RequestSecurityTokenResponse xmlns:t="http://schemas.xmlsoap.org/ws/2005/02/trust"> <t:Lifetime> <wsu:Created xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">{0}</wsu:Created> <wsu:Expires xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">{1}</wsu:Expires> </t:Lifetime> <wsp:AppliesTo xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"> <wsa:EndpointReference xmlns:wsa="http://www.w3.org/2005/08/addressing"> <wsa:Address>{2}</wsa:Address> </wsa:EndpointReference> </wsp:AppliesTo> <t:RequestedSecurityToken>{3}</t:RequestedSecurityToken> <t:TokenType>urn:oasis:names:tc:SAML:1.0:assertion</t:TokenType> <t:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</t:RequestType> <t:KeyType>http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey</t:KeyType> </t:RequestSecurityTokenResponse> '@ -f $createdTime,$expiresTime,$mySite,$binarySecurityToken $encodedSecurityToken = [System.Web.HttpUtility]::UrlEncode($securityToken) $requestTokenBody = "wa=wsignin1.0&wresult=$encodedSecurityToken&wctx=rm=0&id=passive&ru=%2f" $response = Invoke-WebRequest -Uri $mySite -Body $requestTokenBody -Method Post -ContentType 'application/x-www-form-urlencoded' -SessionVariable session $fedAuthCookie = ($session.Cookies.GetCookies($mySite) | Where-Object { $_.name -eq 'FedAuth'}).Value Invoke-WebRequest $mySite -WebSession $session
我们先把security token转换成RequestSecurityTokenResponse这样的XML格式,然后把它发送给目标网站。目标网站会在响应中包含一段cookie。在接下来的一连串请求中,我们只需携带cookie即可。
PowerShell发送请求时如何传递Cookie?
Cookie在HTTP请求中,位于请求的header中,但是在使用Invoke-WebRequest时却不能直接在header参数中指定Cookie。按照Invoke-WebRequest的设计来讲,Cookie应当是被保护的,所有的Cookie应当是来自目标网站的主动设置,而非客户端可以指定,随意摊派,随意篡改。
于是在发第一次请求时,我们通过-SessionVariable参数来记录当前会话,在接下来的请求中我们使用-WebSession参数来复用这一会话。稍微了解HTTP协议的您应当清楚,HTTP是无状态的,每一条请求都是孤立的,服务器端要认识客户端的某几条请求来自同一个会话,只能依赖于Cookie,而幸运的是上面的脚本中的$session刚好履行了保存Cookie来保持会话这一职责。
关于上面发送请求的代码,参考自:Making requests using WebClient and security tokens
注意
- 在拿令牌环时,我们有传递一个-SAMLVersion 1参数,其实也可以传递2,具体根据目标网站期望的版本。
- 不论用C#还是Powershell在测试时,遇到任何问题,都不要着急。您可以打开fiddler工具,在浏览器中打开目标网站,监测请求的参数,和链接地址。我们的目标是通过code发送的请求要和网站自己重定向的请求保持一致。
请尊重原作者和编辑的辛勤劳动,欢迎转载,并注明出处!
学习了!
很受用