You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Spring Security SAML2 SP元数据中添加RequestedAttribute?

如何在Spring Security SAML2 SP元数据中添加RequestedAttribute?

部分身份提供商(IDP)默认不会返回所有请求的属性,我希望在服务提供商(SP)的SAML元数据中声明这些属性,以此鼓励IDP释放这些属性。目前已经能配置nameid-format这类元数据,但找不到添加RequestedAttribute条目的方法,尝试过.saml2Metadata()和.saml2Login()的配置器都没找到对应入口。

使用的POM依赖

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-saml2-service-provider</artifactId>
    <version>6.5.5</version>
    <exclusions>
        <exclusion>
            <groupId>org.opensaml</groupId>
            <artifactId>*</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.opensaml</groupId>
    <artifactId>opensaml-saml-api</artifactId>
    <version>5.1.6</version>
</dependency>
<dependency>
    <groupId>org.opensaml</groupId>
    <artifactId>opensaml-saml-impl</artifactId>
    <version>5.1.6</version>
</dependency>

RelyingPartyRegistration构建代码

public RelyingPartyRegistration apply(RelyingPartyRegistration.Builder builder) {
        
        Saml2X509Credential signing = Saml2X509Credential.signing(this.key, this.certificate);
        Saml2X509Credential decryption = Saml2X509Credential.decryption(key, certificate); 

        return builder.entityId(this.entityId)
            .assertionConsumerServiceLocation(this.sso)
            .singleLogoutServiceBinding(this.slo.getBinding())
            .singleLogoutServiceLocation(this.slo.getLocation())
            .singleLogoutServiceResponseLocation(this.slo.getResponseLocation())
            .signingX509Credentials((c) -> c.add(signing))
            .decryptionX509Credentials((d) -> d.add(decryption))
            .nameIdFormat("urn:oasis:names:tc:SAML:2.0:nameid-format:persistent")
            .build();
    }

SecurityFilterChain配置

.saml2Login(saml2 -> {
    saml2.loginPage("/Shibboleth/");
    saml2.authenticationManager(new ProviderManager(authenticationProvider));
    saml2.successHandler(loginSuccessHandlerService);
    saml2.failureHandler(customAuthenticationFailureHandler);
})
.saml2Metadata(metadataConfigurer -> {
    metadataConfigurer.metadataUrl("/saml/metadata");
})

当前生成的元数据

<?xml version="1.0" encoding="UTF-8"?>
<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" entityID="--redacted--">
    <md:SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
        <md:KeyDescriptor use="signing">
            <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
                <ds:X509Data>
                    <ds:X509Certificate>--redacted--</ds:X509Certificate>
                </ds:X509Data>
            </ds:KeyInfo>
        </md:KeyDescriptor>
        <md:KeyDescriptor use="encryption">
            <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
                <ds:X509Data>
                    <ds:X509Certificate>--redacted--</ds:X509Certificate>
                </ds:X509Data>
            </ds:KeyInfo>
        </md:KeyDescriptor>
        <md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://--redacted--/logout/saml2/slo" ResponseLocation="https://--redacted--/logout/saml2/slo"/>
        <md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</md:NameIDFormat>
        <md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://--redacted--/login/saml2/sso" index="1"/>
    </md:SPSSODescriptor>
</md:EntityDescriptor>

期望生成的元数据

<?xml version="1.0" encoding="UTF-8"?>
<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" entityID="--redacted--">
    <md:SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
        <md:KeyDescriptor use="signing">
            <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
                <ds:X509Data>
                    <ds:X509Certificate>--redacted--</ds:X509Certificate>
                </ds:X509Data>
            </ds:KeyInfo>
        </md:KeyDescriptor>
        <md:KeyDescriptor use="encryption">
            <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
                <ds:X509Data>
                    <ds:X509Certificate>--redacted--</ds:X509Certificate>
                </ds:X509Data>
            </ds:KeyInfo>
        </md:KeyDescriptor>
        <md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://--redacted--/logout/saml2/slo" ResponseLocation="https://--redacted--/logout/saml2/slo"/>
        <md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</md:NameIDFormat>
        <md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://--redacted--/login/saml2/sso" index="1"/>
        <md:AttributeConsumingService isDefault="true" index="1">
            <md:ServiceName xml:lang="en">Service Provider Portal</md:ServiceName>
            <md:RequestedAttribute NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.1" FriendlyName="eduPersonAffiliation">
            </md:RequestedAttribute>
        </md:AttributeConsumingService>
    </md:SPSSODescriptor>
</md:EntityDescriptor>

解决方案

Spring Security SAML2 6.x版本未提供直接添加AttributeConsumingService和RequestedAttribute的配置API,需要通过自定义元数据生成器实现:

1. 自定义元数据生成器

继承默认的OpenSamlMetadataGenerator,重写元数据填充逻辑:

import org.opensaml.saml.saml2.metadata.AttributeConsumingService;
import org.opensaml.saml.saml2.metadata.RequestedAttribute;
import org.opensaml.saml.saml2.metadata.ServiceName;
import org.springframework.security.saml2.provider.service.metadata.OpenSamlMetadataGenerator;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;

public class CustomSaml2MetadataGenerator extends OpenSamlMetadataGenerator {

    public CustomSaml2MetadataGenerator(RelyingPartyRegistration registration) {
        super(registration);
    }

    @Override
    protected void populateSPSSODescriptor(SPSSODescriptor descriptor) {
        super.populateSPSSODescriptor(descriptor);

        // 构建AttributeConsumingService节点
        AttributeConsumingService attributeService = buildSAMLObject(AttributeConsumingService.class);
        attributeService.setIndex(1);
        attributeService.setIsDefault(true);

        // 添加服务名称
        ServiceName serviceName = buildSAMLObject(ServiceName.class);
        serviceName.setLang("en");
        serviceName.setValue("Service Provider Portal");
        attributeService.getNames().add(serviceName);

        // 添加RequestedAttribute
        RequestedAttribute requestedAttr = buildSAMLObject(RequestedAttribute.class);
        requestedAttr.setName("urn:oid:1.3.6.1.4.1.5923.1.1.1.1");
        requestedAttr.setFriendlyName("eduPersonAffiliation");
        requestedAttr.setNameFormat("urn:oasis:names:tc:SAML:2.0:attrname-format:uri");
        attributeService.getRequestedAttributes().add(requestedAttr);

        descriptor.getAttributeConsumingServices().add(attributeService);
    }
}

2. 注册自定义生成器

在SecurityFilterChain配置中替换默认的元数据生成器:

.saml2Metadata(metadataConfigurer -> {
    metadataConfigurer.metadataUrl("/saml/metadata")
        .metadataGenerator((registration) -> new CustomSaml2MetadataGenerator(registration));
})

3. 补充说明

  • 代码依赖OpenSAML API构建元数据节点,确保依赖中包含opensaml-saml-api和opensaml-saml-impl
  • 若需添加多个RequestedAttribute,重复创建对象并加入列表即可
  • 旧版MetadataGenerator已被移除,6.x版本默认使用OpenSamlMetadataGenerator实现

内容的提问来源于stack exchange,提问作者Andrew

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 03:09:53