有奖捉虫:办公协同&微信生态&物联网文档专题 HOT

创建应用、授权访问

创建应用

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>
请求地址也可参考应用管理页面 > 选定指定应用 > 单击应用配置 >对应的 “SP发起地址”。
请求参数说明:
参数
参数位置
类型
是否必选
描述
示例值
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>
获取验签所用的公钥证书。 可参考应用管理页面 > 选定指定应用 > 单击应用配置 > 单击导出IDaaS SAML元配置文件,从下载的 XML 文件中获取。


验签,并从返回报文中获取身份信息、断言信息,参考代码如下: 其中,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 certificate
CertificateFactory 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 response
byte[] 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:Issuer
xmlns: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:Signature
xmlns: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+zhG6XRN6t6OJAjJ8Ph6VsVOWRVgWbjOM4AT
Ma/dcLvzGq261Rmp83pHVxZ1exDqt5NFWiJP6HkAR6WHq8URlCQ6A3MP29FEcNwmdWUun180qmtw
Qqu0MiWIngd/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:AttributeValue
xmlns: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:AttributeValue
xmlns: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”,具体请参看上文“自定义断言属性”。