SAML 即安全断言标记语言,英文全称是 Security Assertion Markup Language。它是一个基于 XML 的标准,用于在不同的安全域(security domain)之间交换认证和授权数据。在 SAML 标准定义了身份提供者 (identity provider) 和服务提供者 (service provider),这两者构成了前面所说的不同的安全域。 SAML 是 OASIS 组织安全服务技术委员会(Security Services Technical Committee) 的产品。
SAML(Security Assertion Markup Language)是一个 XML 框架,也就是一组协议,可以用来传输安全声明。比如,两台远程机器之间要通讯,为了保证安全,我们可以采用加密等措施,也可以采用 SAML 来传输,传输的数据以 XML 形式,符合 SAML 规范,这样我们就可以不要求两台机器采用什么样的系统,只要求能理解 SAML 规范即可,显然比传统的方式更好。SAML 规范是一组 Schema 定义。
可以这么说,在 Web Service 领域,schema 就是规范,在 Java 领域,API 就是规范。
认证声明:声明用户是否已经认证,通常用于单点登录。
属性声明:声明某个 Subject 所具有的属性。
授权决策声明:声明某个资源的权限,即一个用户在资源 R 上具有给定的 E 权限而能够执行 A 操作。
SP(Service Provider): 向用户提供正式商业服务的实体,通常需要认证一个用户的身份;
IDP(Identity Provider): 提供用户的身份鉴别,确保用户是其所声明的身份
断言 (Assertions) 即信息:断言是在 SAML 中用来描述认证的对象,其中包括一个用户在什么时间、以什么方式被认证,同时还可以包括一些扩展信息,比如用户的 Email 地址和电话等等。下面是一个断言的例子:
<saml:Assertion xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" ID="_d71a3a8e9fcc45c9e9d248ef7049393fc8f04e5f75" Version="2.0" IssueInstant="2014-07-17T01:01:48Z">
<saml:Issuer>http://idp.example.com/metadata.phpsaml:Issuer>
<saml:Subject>
<saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">[email protected]saml:NameID>
<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<saml:SubjectConfirmationData NotOnOrAfter="2024-01-18T06:21:48Z" Recipient="http://sp.example.com/demo1/index.php?acs" InResponseTo="ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685"/>
saml:SubjectConfirmation>
saml:Subject>
<saml:Conditions NotBefore="2014-07-17T01:01:18Z" NotOnOrAfter="2024-01-18T06:21:48Z">
<saml:AudienceRestriction>
<saml:Audience>http://sp.example.com/demo1/metadata.phpsaml:Audience>
saml:AudienceRestriction>
saml:Conditions>
<saml:AuthnStatement AuthnInstant="2014-07-17T01:01:48Z" SessionNotOnOrAfter="2024-07-17T09:01:48Z" SessionIndex="_be9967abd904ddcae3c0eb4189adbe3f71e327cf93">
<saml:AuthnContext>
<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Passwordsaml:AuthnContextClassRef>
saml:AuthnContext>
saml:AuthnStatement>
<saml:AttributeStatement>
<saml:Attribute Name="uid" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml:AttributeValue xsi:type="xs:string">testsaml:AttributeValue>
saml:Attribute>
<saml:Attribute Name="mail" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml:AttributeValue xsi:type="xs:string">[email protected]saml:AttributeValue>
saml:Attribute>
<saml:Attribute Name="eduPersonAffiliation" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml:AttributeValue xsi:type="xs:string">userssaml:AttributeValue>
<saml:AttributeValue xsi:type="xs:string">examplerole1saml:AttributeValue>
saml:Attribute>
saml:AttributeStatement>
saml:Assertion>
协议 (Protocol) 即通信:协议规定如何执行不同的行为。这些行为被细化成一些列的 Request 和 Response 对象,而在这些请求和相应的对象中包含了行为所特别需要的信息。比如,认证请求协议(AuthnRequest Protocol)就规定了一个 SP 如何请求去获得一个被认证的与用户。
这是一个 AuthnRequest 对象示例:
<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="ONELOGIN_809707f0030a5d00620c9d9df97f627afe9dcc24" Version="2.0" ProviderName="SP test" IssueInstant="2014-07-16T23:52:45Z"
Destination="http://idp.example.com/SSOService.php" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" AssertionConsumerServiceURL="http://sp.example.com/demo1/index.php?acs">
<saml:Issuer>http://sp.example.com/demo1/metadata.phpsaml:Issuer>
samlp:AuthnRequest>
绑定 (Binding) 即传输:绑定定义了 SAML 信息如何使用通信协议被传输的。比如,HTTP 重定向绑定,即声明 SAML 信息将通过 HTTP 重定向消息传输;再比如 SAML SOAP 绑定,声明了通过 SOAP 来传递 SAML 消息。比如上面 AuthnRequest 就声明了 Http-POst 绑定
元数据 (MetaData):SAML 的元数据是配置数据,其包含关于 SAML 通信各方的信息,比如通信另一方的 ID、Web Service 的 IP 地址、所支持的绑定类型以及通信中实用的密钥等等。
SAML 协议内容比较复杂,绑定方式不只一个,在这里我们以应该算是最简单的一种绑定方式 HTTP-POST 方式说明 SAML 协议的工作流程
1、用户通过浏览器访问 SP 的某个受保护的资源
2、SP 鉴别到该用户未鉴权,于是将该用户重定向到 IDP 端;前提是改 IDP 是受 SP 信任的身份认证中心;
3、IDP 端通过自己的认证方式对该用户合法性进行认证;
4、认证通过后,IDP 端生成响应后返回给 SP
5、SP 接收到 IDP 的响应后解析出用户认证信息,合法,则允许用户访问受保护的资源
通常情况下,SP 端是请求发起端,即当用户访问 SP 端的受保护资源时,由 SP 端向认证中心(IDP 端)发起认证请求。最终请求会回到 SP 端并由 SP 端将受保护资源授权给用户。
假设,SP 有一受保护静态资源 index.html,通常情况下,为了保护该静态资源,SP 可以选择用过滤器对访问该资源的请求进行过滤,如果该用户已被授权,则将 index.html 返回给用户;否则将生成认证请求到 IDP。
首先,我们先看一个有 SP 端生成的认证请求参数,它是普通的 xml 文档:
<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" AssertionConsumerServiceURL="http://localhost:8080/sp/consumer" Destination="http://localhost:8080/idp/sso"
ID="3983b844-5f42-4c66-b476-f69704f00b5b" IssueInstant="2017-12-21T08:35:56.975Z" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Version="2.0">
<saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">http://localhost:8080/spsaml:Issuer>
<samlp:NameIDPolicy AllowCreate="true" Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"/>
samlp:AuthnRequest>
该请求根节点是 samlp:AuthnRequest 来表明是一个认证请求,有如下几个参数:
1、AssertionConsumerServiceURL断言消费服务地址,即当 IDP 认证成功后响应返回的地址
2、Destination目标地址,即 IDP 接收请求的地址
3、ID该请求的唯一标识
4、IssueInstant请求的时间
5、ProtocolBindingSP 声明此次通信的绑定方式,不同的绑定方式意味着不同的通信流程,SAML2.0 主要包括:HTTP-Artifact,HTTP-POST,HTTP-Redirect,SOAP 等几种绑定方式。
6、Version版本号
7、saml:IssuerSP 的 Id 标识
8、samlp:NameIDPolicyIDP 对于用户身份的标识;NameID policy 是 SP 关于 NameID 是如何被创建的说明;Format 指明 SP 需要返回什么类型的标识(SAML Artifact);属性AllowCreate指明 IDP 是否被允许当发现用户不存在时创建用户账号。
创建 AuthnRequest 对象采用了开源的 opensaml,代码如下:
/**
* 创建AutheRequest对象 * @param idpSsoUrl
* @param acsUrl
* @param spEntityId
* @return
*/
public AuthnRequest createRequest(String idpSsoUrl,String acsUrl,String spEntityId){
AuthnRequest authnRequest = create(AuthnRequest.class,AuthnRequest.DEFAULT_ELEMENT_NAME);
authnRequest.setIssueInstant(new DateTime());
authnRequest.setDestination(idpSsoUrl);
authnRequest.setProtocolBinding(SAMLConstants.SAML2_POST_BINDING_URI);
authnRequest.setID(UUID.randomUUID().toString());
authnRequest.setAssertionConsumerServiceURL(acsUrl);
Issuer issuer = create(Issuer.class,Issuer.DEFAULT_ELEMENT_NAME);
issuer.setValue(spEntityId);
authnRequest.setIssuer(issuer);
NameIDPolicy nameIDPolicy = create(NameIDPolicy.class,NameIDPolicy.DEFAULT_ELEMENT_NAME);
nameIDPolicy.setAllowCreate(true);
nameIDPolicy.setFormat(NameID.UNSPECIFIED);
authnRequest.setNameIDPolicy(nameIDPolicy);
return authnRequest;
}
生成对象后转化成字符串:
Document document = asDOMDocument(authnRequest);
DOMSource source=new DOMSource(document);
TransformerFactory tf = TransformerFactory.newInstance();
Transformer former=tf.newTransformer();
former.setOutputProperty(OutputKeys.STANDALONE, "yes");
StringWriter sw = new StringWriter();
StreamResult sr = new StreamResult(sw);
former.transform(source, sr);
String result=sw.toString();
将 xml 字符串进行 base64 编码后拼接成认证请求地址发起认证请求,认证请求地址示例如下:
http://localhost:8080/idp/sso?SAMLRequest=fZJdT8IwFIb/SnPuy7qhwBqGQYmRxA8i0wvvSneQJl07ezo//r1zQKKJets+ed+e53R69l5b9oqBjHcFpAMBDJ32lXHPBTyUl3wCjKJylbLeYQHOw9lsSqq2jZy3cefu8aVFiqzLcST7iwLa4KRXZEg6VSPJqOV6fnMts4GQTfDRa2+BzYkwxK74wjtqawxrDK9G48P9dQG7GBuZJNZrZXeeopyIiUioSfQBBrboeo1TsX/677ypmoTIA1suCsiHW9SjVPBTvVX8JB/lPNfVho+z0ViMhgKHm9OOJGpx6b6GjgVkIh3zNONZWopcZpnMJgMhTp6ArQ5znBu3t/Xf0Js9RPKqLFd8dbcugT0erXcA7J3Kvjx8k/l/rDoahNlfvqbJt+Dj5m67pOVi5a3RH2xurX+7CKhit98YWgR26UOt4t/d6SDtT0zFtz0qW0cNarM1WEEy25f+/CGzTw==
后续就是 IDP 收到该请求后解析、认证、返回的过程了
IDP,即提供身份认证服务的一端,通常,当 IDP 接收到 SP 发送的 SAML 认证请求后,解析 SAMLRequest 参数,包括 acs 地址、SP EntityId、绑定方式、是否加密等信息,当身份认证成功后便根据 SP 请求参数进行后续的通信。
在绑定方式为 Http post 方式中,当 IDP 认证成功后,便生成 Response 信息返回给 SP,一个 Response 的 xml 如下:
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" Destination="https://bw30.worktile.com/api/sso/postback/5a2f38f7499e182113286d28" ID="_8e8dc5f69a98cc4c1ff3427e5ce34606fd672f91e6" IssueInstant="2017-12-13T09:05:48Z" Version="2.0">
<saml:Issuer>https://localhost:8080/idpsaml:Issuer>
<samlp:Status>
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
samlp:Status>
<saml:Assertion xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ID="_d71a3a8e9fcc45c9e9d248ef7049393fc8f04e5f75" IssueInstant="017-12-13T09:05:48Z" Version="2.0">
<saml:Issuer>https://localhost:8080/idpsaml:Issuer><Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/><Reference URI="#_d71a3a8e9fcc45c9e9d248ef7049393fc8f04e5f75"><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><DigestValue>k9i4QGA5BDFkW5I+Igr8hR1ibZs=DigestValue>Reference>SignedInfo><SignatureValue>PLIKGZOFbMt8qEM1yw6f/Uct7R9Xd8KWZXw5925gIJdA8+q9MfY34+sQwDcy1Tqnxzak6hx6A6ol
Qr+zJCQH8O/S+sDgCEUhXG+PFU4j2pxnnYqwI3jKc2yeT7A7f8ShStgwN7IgjZ0TFLx2TO3tlZ76
2GwFHNN0lH9ohtAv8Zs=SignatureValue><KeyInfo><X509Data><X509Certificate>MIICaDCCAdGgAaIBAgIEfnIVCzANBgkqhkiG9w0BAQsFADBnMQ4wDAYDVQQGEwVjaGluYTEQMA4G
A1UECBMHYmVpamluZzEQMA4GA1UEBxMHYmVpamluZzERMA8GA1UEChMIYmV3aW5uZXIxDTALBgNV
BAsTBGJ3MzAxDzANBgNVBAMTBnhpYW9zeTAeFw0xNzExMDgwOTU1NDVaFw0yNzExMDYwOTU1NDVa
MGcxDjAMBgNVBAYTBWNoaW5hMRAwDgYDVQQIEwdiZWlqaW5nMRAwDgYDVQQHEwdiZWlqaW5nMREw
DwYDVQQKEwhiZXdpbm5lcjENMAsGA1UECxMEYnczMDEPMA0GA1UEAxMGeGlhb3N5MIGfMA0GCSqG
SIb3DQEBAQUAA4GNADCBiQKBgQCgmrEMgAMY7zygYqBtYzMal0vTVsQNyjGkD3tbA+pEk18YfN13
UEBoqrp/XQiR4v334xqHjdtG8lxDzEUJ4fQippxMpw6Fab45pz6uOr33DI6X3IwLPxtb7q1MyIj3
TXBY6R01rwIaE+G8/5z76mN5qq4/lhoY3bs0D06pwUSSSQIDAQABoyEwHzAdBgNVHQ4EFgQURAyK
5AjoSEOk32ceEloftZ8TiWcwDQYJKoZIhvcNAQELBQADgYEAZuNWxMO8HOItqAoCI8f6+PfjbL/7
xTwDjs8PxnermmVjACx5JiW0O98M0D5Guo0OABf8mMxiDYQvRwpNoEfMOXr3TjPxqioLMq+s1Nt8
0Duilqel+O6Q/XDJ8rlVdm8vPhLxWZ14FIdI8n7CuuUwUExe4Uj05shCMwgNRo6bmaU=X509Certificate>X509Data>KeyInfo>Signature>
<saml:Subject>
<saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">[email protected]saml:NameID>
<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<saml:SubjectConfirmationData NotOnOrAfter="2024-01-18T06:21:48Z" Recipient="https://localhost:8080/idp/sso"/>
saml:SubjectConfirmation>
saml:Subject>
<saml:Conditions NotBefore="017-12-13T09:05:48Z" NotOnOrAfter="2024-01-18T06:21:48Z">
<saml:AudienceRestriction>
<saml:Audience>https://localhost:8080/sp/saml:Audience>
saml:AudienceRestriction>
saml:Conditions>
<saml:AuthnStatement AuthnInstant="017-12-13T09:05:48Z">
<saml:AuthnContext>
<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransportsaml:AuthnContextClassRef>
saml:AuthnContext>
saml:AuthnStatement>
saml:Assertion>
samlp:Response>
1、samlp:Response根节点,表明这是一个 response 对象
2、Destination目标地址,即 ACS 地址,Response 返回的地址
3、ID唯一标识
4、IssueInstant时间戳
5、saml:IssuerIDP 身份信息,IDP 的 EntityId
6、samlp:Status认证结果,samlp:StatusCode表明认证成功或失败
7、saml:Assertion断言,这是 Response 中最为重要的字段,里面包含着用户身份信息
8、Signature断言的签名,使用非对称私钥对 Assertion 内容(不包含 Signature)进行签名,防止信息被篡改
9、saml:Subject身份主体,主要包括身份信息
10、saml:NameID身份信息
11、saml:Conditions给出了断言被认为有效的验证条件。
12、saml:AuthnStatement描述了在身份提供者的认证行为。
首先生成断言信息 Assertion,然后对断言进行签名,将签名信息插入到 Assertion 的子节点中,即 的信息,最后生成 Response 结构,将 Response 字符串进行 base64 编码后 post 到 acs 地址上,SP 对其进行验证。
当然,如果 SP 请求需要加密断言、加密响应,IDP 端必须对 Assertion 和 Signature 进行加密处理。
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<Reference URI="#_d71a3a8e9fcc45c9e9d248ef7049393fc8f04e5f75">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><DigestValue>k9i4QGA5BDFkW5I+Igr8hR1ibZs=DigestValue>
Reference>
SignedInfo><SignatureValue>PLIKGZOFbMt8qEM1yw6f/Uct7R9Xd8KWZXw5925gIJdA8+q9MfY34+sQwDcy1Tqnxzak6hx6A6ol
Qr+zJCQH8O/S+sDgCEUhXG+PFU4j2pxnnYqwI3jKc2yeT7A7f8ShStgwN7IgjZ0TFLx2TO3tlZ762GwFHNN0lH9ohtAv8Zs=SignatureValue>
<KeyInfo>
<X509Data><X509Certificate>MIICaDCCAdGgAaIBAgIEfnIVCzANBgkqhkiG9w0BAQsFADBnMQ4wDAYDVQQGEwVjaGluYTEQMA4GA1UECBMHYmVpamluZzEQMA4GA1UEBxMHYmVpamluZzERMA8GA1UEChMIYmV3aW5uZXIxDTALBgNVBAsTBGJ3MzAxDzANBgNVBAMTBnhpYW9zeTAeFw0xNzExMDgwOTU1NDVaFw0yNzExMDYwOTU1NDVaMGcxDjAMBgNVBAYTBWNoaW5hMRAwDgYDVQQIEwdiZWlqaW5nMRAwDgYDVQQHEwdiZWlqaW5nMREwDwYDVQQKEwhiZXdpbm5lcjENMAsGA1UECxMEYnczMDEPMA0GA1UEAxMGeGlhb3N5MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCgmrEMgAMY7zygYqBtYzMal0vTVsQNyjGkD3tbA+pEk18YfN13UEBoqrp/XQiR4v334xqHjdtG8lxDzEUJ4fQippxMpw6Fab45pz6uOr33DI6X3IwLPxtb7q1MyIj3TXBY6R01rwIaE+G8/5z76mN5qq4/lhoY3bs0D06pwUSSSQIDAQABoyEwHzAdBgNVHQ4EFgQURAyK5AjoSEOk32ceEloftZ8TiWcwDQYJKoZIhvcNAQELBQADgYEAZuNWxMO8HOItqAoCI8f6+PfjbL/7xTwDjs8PxnermmVjACx5JiW0O98M0D5Guo0OABf8mMxiDYQvRwpNoEfMOXr3TjPxqioLMq+s1Nt80Duilqel+O6Q/XDJ8rlVdm8vPhLxWZ14FIdI8n7CuuUwUExe4Uj05shCMwgNRo6bmaU=
X509Certificate>
X509Data>
KeyInfo>
Signature>
SAML 中的签名算法就是对 xml 文档树进行签名,说明如下:
1、确认签名内容,通过 URL 将这些内容表示为引用资源,用Reference标识。对于断言信息来说,其 URI 是saml:Assertion的 ID
2、对待签名的数据进行转化处理,包括执行编码规则、规范化算法等,Transform指定了转化的算法
3、对整个断言进行消息摘要,DigestMethod指定了消息摘要算法,消息摘要的结果保存在DigestValue元素中
4、构造包含 Reference 的SignedInfo元素
5、CanonicalizationMethod元素指定了规范化的算法,如果不对其进行规范化处理,验证 xml 签名时可能因为 xml 结构表示不同而失败
6、计算 SignedInfo 的摘要,使用SignatureMethod声明的签名算法,并对其进行签名,结果保存到SignatureValue元素中
7、KeyInfo元素可选,表明签名的公钥信息
SP 接收到 IDP 的响应后,验证签名,获取用户的信息后跳转响应页面。
作者浅夏丶未央写了一个小 demo, 源码地址是:https://github.com/xiaosiyuan/saml.git
验证方法:
1、运行 APP.java
2、模拟用户访问http://localhost:8080/index.html ,index.html 是一个受保护的资源
3、被重定向到 login.html 页面,输入用户名 admin、密码 admin, 登陆成功后跳转到 index.html 页面
SAML2.0 协议初识(一)
SAML2.0 协议初识(二)
SAML2.0 协议初识(三)