创建应用、授权访问
创建应用
1. 登录 数字身份管控平台(员工版)控制台,在左侧导航栏,单击应用管理。
2. 在应用管理页面,单击新建应用,选择应用协议模板SAML2,单击下一步。
3. 在编辑应用信息页面,登记接入应用的相关信息,如下图所示:
IdP Entity ID:用于标识 SAML IdP 的信息,默认为 tencent_idp,会在 SAML 协议返回的 XML 报文中体现。
SP Entity ID:用于标识 SAML SP 的信息,会在 SAML 协议返回的XML报文中体现。
SP ACS URL:为应用系统的重定向地址,用于接收数字身份管控平台(员工版)以重定向的方式返回的 SAML 协议 XML 报文。
SP 登出地址:应用系统的“退出”请求地址。
NameIdFormat:默认为
urn:oasis:names:tc:SAML:2.0:nameid-format:persistent
。Assertion Attribute:自定义的断言属性,可参考 自定义断言属性。
授权访问
开发对接步骤
步骤1:获取 SAML 返回报文(SAMLResponse)
应用系统发起请求数字身份管控平台(员工版),数字身份管控平台(员工版)校验用户身份生成后生成 XML 格式的 SAML 返回报文,并通过 POST方 式重定向返回应用系统。
请求方式:GET
请求 Url:
https://<auth.domain>/auth/sso/ssoLogin?service=<service>
请求参数说明:
参数 | 参数位置 | 类型 | 是否必选 | 描述 | 示例值 |
service | Query | String | 是 | 应用 ID,应用系统的唯一标志 | fa4a90ff-d2df-47da-abb7-9e680d62a469 |
重定向回应用系统 Url(POST 方式):
http(s)://<redirect_uri>?SAMLResponse=<SAMLResponse>
返回参数说明:
参数 | 参数位置 | 类型 | 是否必选 | 描述 | 示例值 |
SAMLResponse | Body | String | 是 | Base64 编码后的 XML 格式返回报文 | - |
步骤2:验证签名并获取返回报文中的断言信息
应用系统侧,可使用 opensaml api 对返回报文(SAMLResponse)中的签名信息进行验签,验签成功后获取返回报文中的断言信息,包括用户身份、自定义属性等。
应用系统 pom.xml 引入 maven 依赖:
<dependency><groupId>org.apache.velocity</groupId><artifactId>velocity</artifactId><version>1.6.2</version></dependency><dependency><groupId>org.opensaml</groupId><artifactId>opensaml-security-api</artifactId><version>3.2.0</version></dependency><dependency><groupId>org.opensaml</groupId><artifactId>opensaml-saml-api</artifactId><version>3.2.0</version></dependency><dependency><groupId>org.opensaml</groupId><artifactId>opensaml-core</artifactId><version>3.2.0</version></dependency><dependency><groupId>org.opensaml</groupId><artifactId>opensaml-saml-impl</artifactId><version>3.2.0</version></dependency><dependency><groupId>io.github.openfeign</groupId><artifactId>feign-core</artifactId><version>11.0</version></dependency><dependency><groupId>net.shibboleth.utilities</groupId><artifactId>java-support</artifactId><version>7.2.0</version></dependency>
验签,并从返回报文中获取身份信息、断言信息,参考代码如下:
其中,certificateS 参数为验签所用的公钥证书。
import org.apache.commons.codec.binary.Base64;import org.opensaml.Configuration;import org.opensaml.DefaultBootstrap;import org.opensaml.saml2.core.*;import org.opensaml.saml2.core.impl.*;import org.opensaml.xml.io.*;import org.opensaml.xml.security.x509.BasicX509Credential;import org.w3c.dom.*;import org.opensaml.xml.*;import org.apache.commons.codec.binary.Base64;import java.io.*;import java.security.*;import java.security.cert.*;import java.security.spec.*;import javax.xml.parsers.*;public class SAMLResponseHandler {private static final String certificateS = "MIIENTCCAx2gA***************iEAblF1hYy5dDb/Fjc3W0Q==";public void handle(String responseMessage) {// Read certificateCertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");InputStream inputStream = new ByteArrayInputStream(Base64.decodeBase64(certificateS.getBytes("UTF-8")));X509Certificate certificate = (X509Certificate) certificateFactory.generateCertificate(inputStream);inputStream.close();BasicX509Credential credential = new BasicX509Credential();KeyFactory keyFactory = KeyFactory.getInstance("RSA");X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(certificate.getPublicKey().getEncoded());PublicKey key = keyFactory.generatePublic(publicKeySpec);credential.setPublicKey(key);// Parse responsebyte[] base64DecodedResponse = Base64.decodeBase64(responseMessage);ByteArrayInputStream is = new ByteArrayInputStream(base64DecodedResponse);DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();documentBuilderFactory.setNamespaceAware(true);DocumentBuilder docBuilder = documentBuilderFactory.newDocumentBuilder();Document document = docBuilder.parse(is);Element element = document.getDocumentElement();UnmarshallerFactory unmarshallerFactory = Configuration.getUnmarshallerFactory();Unmarshaller unmarshaller = unmarshallerFactory.getUnmarshaller(element);XMLObject responseXmlObj = unmarshaller.unmarshall(element);Response responseObj = (Response) responseXmlObj;Assertion assertion = responseObj.getAssertions().get(0);String subject = assertion.getSubject().getNameID().getValue();String issuer = assertion.getIssuer().getValue();String audience = assertion.getConditions().getAudienceRestrictions().get(0).getAudiences().get(0).getAudienceURI();String statusCode = responseObj.getStatus().getStatusCode().getValue();org.opensaml.xml.signature.Signature sig = assertion.getSignature();org.opensaml.xml.signature.SignatureValidator validator = new org.opensaml.xml.signature.SignatureValidator(credential);validator.validate(sig);}}
SAMLReponse 示例:
PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHNhbWwycDpSZXNwb25zZSBEZXN0aW5hdGlvbj0iaHR0cDovL2xvY2FsaG9zdDo5MDgwL2F1dGgvcmVkaXJlY3QvcG9zdCIgSUQ9Il83MjIzMjdhMGYwYWRmOGY2OGM0ODJhNzU2OTkxYTAwZCIgSXNzdWVJbnN0YW50PSIyMDIxLTA1LTEzVDA5OjQzOjEzLjEzMloiIFZlcnNpb249IjIuMCIgeG1sbnM6c2FtbDJwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiPjxzYW1sMjpJc3N1ZXIgeG1sbnM6c2FtbDI9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iPnRlbmNlbnRfaWRwPC9zYW1sMjpJc3N1ZXI+PHNhbWwycDpTdGF0dXM+PHNhbWwycDpTdGF0dXNDb2RlIFZhbHVlPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6c3RhdHVzOlN1Y2Nlc3MiLz48L3NhbWwycDpTdGF0dXM+PHNhbWwyOkFzc2VydGlvbiBJRD0iX2ZjNzc0N2Q4ZTk2OTUzZDNhNTliMzljMjhmOWFjODExIiBJc3N1ZUluc3RhbnQ9IjIwMjEtMDUtMTNUMDk6NDM6MTMuMDY3WiIgVmVyc2lvbj0iMi4wIiB4bWxuczpzYW1sMj0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgeG1sbnM6eHNkPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSI+PHNhbWwyOklzc3Vlcj50ZW5jZW50X2lkcDwvc2FtbDI6SXNzdWVyPjxkczpTaWduYXR1cmUgeG1sbnM6ZHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPgo8ZHM6U2lnbmVkSW5mbz4KPGRzOkNhbm9uaWNhbGl6YXRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz4KPGRzOlNpZ25hdHVyZU1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNyc2Etc2hhMSIvPgo8ZHM6UmVmZXJlbmNlIFVSST0iI19mYzc3NDdkOGU5Njk1M2QzYTU5YjM5YzI4ZjlhYzgxMSI+CjxkczpUcmFuc2Zvcm1zPgo8ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI2VudmVsb3BlZC1zaWduYXR1cmUiLz4KPGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyI+PGVjOkluY2x1c2l2ZU5hbWVzcGFjZXMgUHJlZml4TGlzdD0ieHNkIiB4bWxuczplYz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+PC9kczpUcmFuc2Zvcm0+CjwvZHM6VHJhbnNmb3Jtcz4KPGRzOkRpZ2VzdE1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI3NoYTI1NiIvPgo8ZHM6RGlnZXN0VmFsdWU+MzA2a3M4OXBDYUFNZzVVdnlCelBCeDlNMWl1TEVwWkxPZExkaWpIa3RVaz08L2RzOkRpZ2VzdFZhbHVlPgo8L2RzOlJlZmVyZW5jZT4KPC9kczpTaWduZWRJbmZvPgo8ZHM6U2lnbmF0dXJlVmFsdWU+ClJqcVhhL1JtSlZCcGZYNEJmNlZWSFQ2dTRVNW1rdVpZV1ZkeWZoS0oremhHNlhSTjZ0Nk9KQWpKOFBoNlZzVk9XUlZnV2JqT000QVQKTWEvZGNMdnpHcTI2MVJtcDgzcEhWeFoxZXhEcXQ1TkZXaUpQNkhrQVI2V0hxOFVSbENRNkEzTVAyOUZFY053bWRXVXVuMTgwcW10dwpRcXUwTWlXSW5nZC9BTkVBUUtrPQo8L2RzOlNpZ25hdHVyZVZhbHVlPgo8L2RzOlNpZ25hdHVyZT48c2FtbDI6U3ViamVjdD48c2FtbDI6TmFtZUlEIEZvcm1hdD0iMi4wX3BlcnNpc3RlbnQiPmlhbWFkbWluPC9zYW1sMjpOYW1lSUQ+PHNhbWwyOlN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj48c2FtbDI6U3ViamVjdENvbmZpcm1hdGlvbkRhdGEgTm90T25PckFmdGVyPSIyMDIxLTA1LTEzVDA5OjQ4OjEzLjEyM1oiIFJlY2lwaWVudD0iaHR0cDovL2xvY2FsaG9zdDo5MDgwL2F1dGgvcmVkaXJlY3QvcG9zdCIvPjwvc2FtbDI6U3ViamVjdENvbmZpcm1hdGlvbj48L3NhbWwyOlN1YmplY3Q+PHNhbWwyOkNvbmRpdGlvbnM+PHNhbWwyOkF1ZGllbmNlUmVzdHJpY3Rpb24+PHNhbWwyOkF1ZGllbmNlPnRlc3R0ZXN0dGVzdDI8L3NhbWwyOkF1ZGllbmNlPjwvc2FtbDI6QXVkaWVuY2VSZXN0cmljdGlvbj48L3NhbWwyOkNvbmRpdGlvbnM+PHNhbWwyOkF1dGhuU3RhdGVtZW50IEF1dGhuSW5zdGFudD0iMjAyMS0wNS0xM1QwOTo0MzoxMy4xMjVaIj48c2FtbDI6QXV0aG5Db250ZXh0Lz48L3NhbWwyOkF1dGhuU3RhdGVtZW50PjxzYW1sMjpBdHRyaWJ1dGVTdGF0ZW1lbnQ+PHNhbWwyOkF0dHJpYnV0ZSBOYW1lPSJzZWNvbmRhcnlBY2NvdW50Ij48c2FtbDI6QXR0cmlidXRlVmFsdWUgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnR5cGU9InhzZDpzdHJpbmciPnNhbWwtdXNlci0xfHNhbWwtdXNlci0yPC9zYW1sMjpBdHRyaWJ1dGVWYWx1ZT48L3NhbWwyOkF0dHJpYnV0ZT48c2FtbDI6QXR0cmlidXRlIE5hbWU9InN1YmplY3RJZCI+PHNhbWwyOkF0dHJpYnV0ZVZhbHVlIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhzaTp0eXBlPSJ4c2Q6c3RyaW5nIj5lMmEwNWIzMC0zNDQ4LTQ2MzEtOGZkZC0wNTBhM2E1MmU1OWM8L3NhbWwyOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDI6QXR0cmlidXRlPjwvc2FtbDI6QXR0cmlidXRlU3RhdGVtZW50Pjwvc2FtbDI6QXNzZXJ0aW9uPjwvc2FtbDJwOlJlc3BvbnNlPg==
对 SAMLReponse 进行 Base64 解码后的 XML 返回报文:
<?xml version="1.0" encoding="UTF-8"?><saml2p:Response Destination="http://localhost:9080/auth/redirect/post" ID="_722327a0f0adf8f68c482a756991a00d" IssueInstant="2021-05-13T09:43:13.132Z" Version="2.0"xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol"><saml2:Issuerxmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">tencent_idp</saml2:Issuer><saml2p:Status><saml2p:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/></saml2p:Status><saml2:Assertion ID="_fc7747d8e96953d3a59b39c28f9ac811" IssueInstant="2021-05-13T09:43:13.067Z" Version="2.0"xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion"xmlns:xsd="http://www.w3.org/2001/XMLSchema"><saml2:Issuer>tencent_idp</saml2:Issuer><ds:Signaturexmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:SignedInfo><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/><ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/><ds:Reference URI="#_fc7747d8e96953d3a59b39c28f9ac811"><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"><ec:InclusiveNamespaces PrefixList="xsd"xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#"/></ds:Transform></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><ds:DigestValue>306ks89pCaAMg5UvyBzPBx9M1iuLEpZLOdLdijHktUk=</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>RjqXa/RmJVBpfX4Bf6VVHT6u4U5mkuZYWVdyfhKJ+zhG6XRN6t6OJAjJ8Ph6VsVOWRVgWbjOM4ATMa/dcLvzGq261Rmp83pHVxZ1exDqt5NFWiJP6HkAR6WHq8URlCQ6A3MP29FEcNwmdWUun180qmtwQqu0MiWIngd/ANEAQKk=</ds:SignatureValue></ds:Signature><saml2:Subject><saml2:NameID Format="2.0_persistent">iamadmin</saml2:NameID><saml2:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"><saml2:SubjectConfirmationData NotOnOrAfter="2021-05-13T09:48:13.123Z" Recipient="http://localhost:9080/auth/redirect/post"/></saml2:SubjectConfirmation></saml2:Subject><saml2:Conditions><saml2:AudienceRestriction><saml2:Audience>testtesttest2</saml2:Audience></saml2:AudienceRestriction></saml2:Conditions><saml2:AuthnStatement AuthnInstant="2021-05-13T09:43:13.125Z"><saml2:AuthnContext/></saml2:AuthnStatement><saml2:AttributeStatement><saml2:Attribute Name="secondaryAccount"><saml2:AttributeValuexmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xsd:string">saml-user-1|saml-user-2</saml2:AttributeValue></saml2:Attribute><saml2:Attribute Name="subjectId"><saml2:AttributeValuexmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xsd:string">e2a05b30-3448-4631-8fdd-050a3a52e59c</saml2:AttributeValue></saml2:Attribute></saml2:AttributeStatement></saml2:Assertion></saml2p:Response>
其中,
saml2p:Status 节点,表示 IdP 的相应码,“Success”表示成功;
saml2:Assertion节点,断言信息,包括 saml2:Issuer、ds:Signature、saml2:Subject、saml2:Conditions、saml2:AttributeStatement 等子节点;
saml2:Issuer 节点,对应创建应用或配置应用时,设置的“IDaaS IdentityId”;
ds:Signature 节点,子节点 ds:DigestValue 为签名信息;
saml2:Subject 节点,为登录用户名,或用户对应的应用账号;
saml2:Conditions 节点,对应创建应用或配置应用时,设置的“SP Entity ID”;
saml2:AttributeStatement 节点,为系统定义属性以及用户自定义属性。系统定义属性,包括:
subjectId,用户 ID;
secondaryAccount,表示用户对应的应用账号,多个应用账号以竖线分隔。
用户自定义属性,对应创建应用或配置应用时,设置的“Assertion Attribute”,具体请参看上文“自定义断言属性”。