SAML2.0 踩坑记录
近期帮甲方开发+部署一个 SAML2.0 到 OAuth2.0 的转换服务,记录一下踩坑过程。
不得不吐槽一下 SAML2.0 协议实在是臃肿和繁琐。
For AD FS
我对接的甲方用微软 AD FS 作为 SAML2.0 的 IdP。需要注意以下几点:
- 配置SP时会要求回调终结点/metadata的URL必须是HTTPS的
- 注意看看IdP的metadata中对摘要签名算法的要求,区分SHA1和SHA256
Responder error: NoAuthnContext
在使用AD FS作为IdP时,可能会遇到这个错误,由于我没法设置AD FS的配置,所以只能在SP这边做处理。
SP可以不请求AuthnContext
,这样就不会出现这个错误(显然有点野蛮)。
或者请求多种AuthnContext
,并使用更宽松的匹配方式,这样就不会因为IdP返回的AuthnContext
不匹配而报错。
由于node-saml
的默认配置是exact
,所以很容易出现这个问题,可以修改配置来解决,
比如在 node-saml
中可以这样配置:
1 | new SAML({ |
详情可以看看相关 GitHub Issue 。
Responder error: unspecified
这个错误信息太过于宽泛,基本无法定位问题。
最好的办法是查看IdP的日志,看看具体的错误信息。
我遇到的情况是IdP返回的StatusCode
为urn:oasis:names:tc:SAML:2.0:status:Responder
,并且没有StatusMessage
,因此为unspecified
。
这种情况下,可能是IdP的配置有问题,比如证书不匹配、签名算法不匹配等等。
最终排查发现是摘要算法不匹配导致的,IdP的 metadata 中要求使用SHA256
,而node-saml
这个库并不会根据 IdP metadata 自动配置,因此还在使用默认的SHA1
。
手动修改配置后问题解决。(所以前文提到要注意看看IdP的metadata)
1 | new SAML({ |
Invalid Signature
前面签名算法都已经改了为什么还会报错呢?看了一下传入assert
端点的SAMLResponse
,发现里面已经有了登陆用户的断言信息了,相当于是已经过了IdP这关,那可以定位问题就出在SP这边。
同样,查到 (GitHub Issue)[https://github.com/node-saml/passport-saml/issues/816] 得知,新版 node-saml
默认会校验断言签名以及AuthnResponse
签名。
但是在我的场景下,较古老的IdP返回的SAMLResponse
中并没有对AuthnResponse
签名,因此 SP 端签名校验会报错。
只需要关闭这个校验即可:
1 | new SAML({ |
注意: 还有一个选项是wantAssertionsSigned
,这个是控制是否校验断言签名的。不过 wantAuthnResponseSigned
和wantAssertionsSigned
二者最多只能关闭一个,否则会报错。因此不能想着偷懒把两个都关闭。
无法获取用户信息
签名校验通过后,node-saml
库会解析断言信息,但是在我的场景下,解析后的用户信息为空。
发现断言信息中用的不是标准的 nameID
,而是中文的名称ID
(也属实逆天了),因此需要手动配置:
1 | const username = (profile['名称ID'] as string) ?? profile.nameID; |
SAML assertion not yet valid
这个错误通常是由于 IdP 和 SP 的时间不同步导致的(IdP 时间比 SP 时间快,生成了未来的断言),可以通过同步时间解决。
不过由于我没法设置 IdP 的系统时间,因此只能通过让 SP 等待一段时间或者让 SP 允许时间偏差:
1 | new SAML({ |