继承类型中NameIDFormat的XML验证失败原因排查
SAML元数据验证异常:基类
SSODescriptorType中NameIDFormat元素未被识别 问题背景
在验证客户SAML元数据时,遇到NameIDFormat元素验证失败,但同属基类SSODescriptorType的ArtifactResolutionService可正常通过验证。将NameIDFormat的定义从SSODescriptorType移至IDPSSODescriptorType后,元数据验证通过,但需要明确背后的原因。
验证失败的元数据片段
<IDPSSODescriptor protocolSupportEnumeration="urn:mace:shibboleth:1.0 urn:oasis:names:tc:SAML:1.1:protocol urn:oasis:names:tc:SAML:2.0:protocol"> <Extensions> <shibmd:Scope regexp="false">...</shibmd:Scope> </Extensions> <KeyDescriptor> <ds:KeyInfo> <ds:X509Data> <ds:X509Certificate> ... </ds:X509Certificate> </ds:X509Data> </ds:KeyInfo> </KeyDescriptor> <ArtifactResolutionService Binding="urn:oasis:names:tc:SAML:1.0:bindings:SOAP-binding" Location="..." index="1"/> <ArtifactResolutionService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="..." index="2"/> <SingleSignOnService Binding="urn:mace:shibboleth:1.0:profiles:AuthnRequest" Location="..."/> <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="..."/> <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign" Location="..."/> <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="h..."/> <NameIDFormat>urn:mace:shibboleth:1.0:nameIdentifier</NameIDFormat> <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat> </IDPSSODescriptor>
报错信息
cvc-complex-type.2.4.a: Invalid content was found starting with element 'NameIDFormat'. One of '{"urn:oasis:names:tc:SAML:2.0:metadata":SingleSignOnService, "urn:oasis:names:tc:SAML:2.0:metadata":NameIDMappingService, "urn:oasis:names:tc:SAML:2.0:metadata":AssertionIDRequestService, "urn:oasis:names:tc:SAML:2.0:metadata":AttributeProfile, "urn:oasis:names:tc:SAML:2.0:assertion":Attribute}' is expected.
相关Schema片段(saml-schema-metadata-2.0.xsd)
<complexType name="SSODescriptorType" abstract="true"> <complexContent> <extension base="md:RoleDescriptorType"> <sequence> <element ref="md:ArtifactResolutionService" minOccurs="0" maxOccurs="unbounded"/> <element ref="md:SingleLogoutService" minOccurs="0" maxOccurs="unbounded"/> <element ref="md:ManageNameIDService" minOccurs="0" maxOccurs="unbounded"/> <element ref="md:NameIDFormat" minOccurs="0" maxOccurs="unbounded"/> </sequence> </extension> </complexContent> </complexType> <element name="ArtifactResolutionService" type="md:IndexedEndpointType"/> <element name="SingleLogoutService" type="md:EndpointType"/> <element name="ManageNameIDService" type="md:EndpointType"/> <element name="NameIDFormat" type="anyURI"/> <element name="IDPSSODescriptor" type="md:IDPSSODescriptorType"/> <complexType name="IDPSSODescriptorType"> <complexContent> <extension base="md:SSODescriptorType"> <sequence> <element ref="md:SingleSignOnService" maxOccurs="unbounded"/> <element ref="md:NameIDMappingService" minOccurs="0" maxOccurs="unbounded"/> <element ref="md:AssertionIDRequestService" minOccurs="0" maxOccurs="unbounded"/> <element ref="md:AttributeProfile" minOccurs="0" maxOccurs="unbounded"/> <element ref="saml:Attribute" minOccurs="0" maxOccurs="unbounded"/> </sequence> <attribute name="WantAuthnRequestsSigned" type="boolean" use="optional"/> </extension> </complexContent> </complexType> <element name="SingleSignOnService" type="md:EndpointType"/> <element name="NameIDMappingService" type="md:EndpointType"/> <element name="AssertionIDRequestService" type="md:EndpointType"/> <element name="AttributeProfile" type="anyURI"/>
验证代码(Java 8 javax.xml.validation默认实现)
private static String[] schemas = { "/schema/xml.xsd", "/schema/XMLSchema.xsd", "/schema/xmldsig-core-schema.xsd", "/schema/xenc-schema.xsd", "/schema/saml-schema-assertion-2.0.xsd", "/schema/saml-schema-metadata-2.0.xsd", }; public boolean validateXMLSchema(Document document) { try { SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); Schema schema = factory.newSchema(getSources().toArray(new Source[0])); Validator validator = schema.newValidator(); validator.validate(new DOMSource(document)); return true; } catch (SAXException e) { log.error("Exception: " + e.getMessage()); } catch (Exception ex) { log.debug("Exception: ", ex); } return false; }
问题原因分析
核心问题在于XML Schema扩展的内容模型顺序要求:
当使用<extension>扩展复杂类型时,基类的<sequence>元素必须先于子类的<sequence>元素出现在实例文档中。具体来说:
SSODescriptorType(基类)的内容顺序为:ArtifactResolutionService→SingleLogoutService→ManageNameIDService→NameIDFormatIDPSSODescriptorType(子类)的内容顺序为:SingleSignOnService→NameIDMappingService→ ...
而你的元数据中,NameIDFormat被放在了SingleSignOnService(子类元素)之后,违反了"基类元素在前,子类元素在后"的顺序规则。
为什么ArtifactResolutionService可以通过验证?因为它是基类<sequence>的第一个元素,且出现在所有子类元素之前,符合顺序要求。
将NameIDFormat移至IDPSSODescriptorType的<sequence>后,它成为子类内容模型的一部分,此时元数据中的位置符合子类的顺序规则,因此验证通过。
解决方案
无需修改Schema,只需调整元数据中元素的顺序,将所有基类元素(包括NameIDFormat)放在子类元素之前:
<IDPSSODescriptor ...> <!-- 基类元素:先出现 --> <ArtifactResolutionService .../> <ArtifactResolutionService .../> <NameIDFormat>...</NameIDFormat> <NameIDFormat>...</NameIDFormat> <!-- 子类元素:后出现 --> <SingleSignOnService .../> <SingleSignOnService .../> ... </IDPSSODescriptor>
内容的提问来源于stack exchange,提问作者Dave Rager
相关产品推荐
相关产品推荐

