# 产生<saml2:AuthnRequest>s

如前所述, Spring Security的SAML2.0支持生成一个<saml2:AuthnRequest>,以开始与主张方进行身份验证。

Spring 安全性在一定程度上通过在过滤器链中注册Saml2WebSsoAuthenticationRequestFilter来实现这一点。默认情况下,此筛选器响应端点/saml2/authenticate/{registrationId}

例如,如果你被部署到[https://rp.example.com](https://rp.example.com),并且你的注册ID为okta,那么你可以导航到:

[https://rp.example.org/saml2/authenticate/ping](https://rp.example.org/saml2/authenticate/ping)

结果将是一个重定向,该重定向包含一个SAMLRequest参数,该参数包含有符号的、已泄气的和编码的<saml2:AuthnRequest>

# 更改<saml2:AuthnRequest>的存储方式

Saml2WebSsoAuthenticationRequestFilter使用Saml2AuthenticationRequestRepository在[将<saml2:AuthnRequest>](# Servlet-saml2login-sp-initiated-factory)发送给断言方前持久化AbstractSaml2AuthenticationRequest实例。

此外,Saml2WebSsoAuthenticationFilterSaml2AuthenticationTokenConverter使用Saml2AuthenticationRequestRepository来加载任何AbstractSaml2AuthenticationRequest作为[验证<saml2:Response>](authentication.html# Servlet-saml2login-attenticate-responses)的一部分。

默认情况下, Spring Security使用HttpSessionSaml2AuthenticationRequestRepository,它将AbstractSaml2AuthenticationRequest存储在HttpSession中。

如果你有Saml2AuthenticationRequestRepository的自定义实现,则可以通过将其公开为@Bean来配置它,如以下示例所示:

Java

@Bean
Saml2AuthenticationRequestRepository<AbstractSaml2AuthenticationRequest> authenticationRequestRepository() {
	return new CustomSaml2AuthenticationRequestRepository();
}

Kotlin

@Bean
open fun authenticationRequestRepository(): Saml2AuthenticationRequestRepository<AbstractSaml2AuthenticationRequest> {
    return CustomSaml2AuthenticationRequestRepository()
}

# 更改<saml2:AuthnRequest>的发送方式

默认情况下, Spring Security对每个<saml2:AuthnRequest>进行签名,并将其作为get发送给主张方。

许多有主张的当事人不需要签署<saml2:AuthnRequest>。这可以通过RelyingPartyRegistrations自动配置,也可以手动提供,例如:

例1.不需要签名的authnrequests

引导

spring:
  security:
    saml2:
      relyingparty:
        okta:
          identityprovider:
            entity-id: ...
            singlesignon.sign-request: false

Java

RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration.withRegistrationId("okta")
        // ...
        .assertingPartyDetails(party -> party
            // ...
            .wantAuthnRequestsSigned(false)
        )
        .build();

Kotlin

var relyingPartyRegistration: RelyingPartyRegistration =
    RelyingPartyRegistration.withRegistrationId("okta")
        // ...
        .assertingPartyDetails { party: AssertingPartyDetails.Builder -> party
                // ...
                .wantAuthnRequestsSigned(false)
        }
        .build();

否则,你将需要为RelyingPartyRegistration#signingX509Credentials指定一个私钥,以便 Spring Security可以在发送之前对<saml2:AuthnRequest>进行签名。

默认情况下, Spring Security将使用rsa-sha256<saml2:AuthnRequest>进行签名,尽管一些主张方将需要不同的算法,如其元数据中所示。

你可以基于断言一方的[元数据使用RelyingPartyRegistrations]来配置算法(Overview.html# Servlet-SAML2Login-RelyingPartyRegistrationRepository)。

或者,你可以手动提供它:

Java

String metadataLocation = "classpath:asserting-party-metadata.xml";
RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistrations.fromMetadataLocation(metadataLocation)
        // ...
        .assertingPartyDetails((party) -> party
            // ...
            .signingAlgorithms((sign) -> sign.add(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA512))
        )
        .build();

Kotlin

var metadataLocation = "classpath:asserting-party-metadata.xml"
var relyingPartyRegistration: RelyingPartyRegistration =
    RelyingPartyRegistrations.fromMetadataLocation(metadataLocation)
        // ...
        .assertingPartyDetails { party: AssertingPartyDetails.Builder -> party
                // ...
                .signingAlgorithms { sign: MutableList<String?> ->
                    sign.add(
                        SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA512
                    )
                }
        }
        .build();
上面的代码片段使用OpenSAMLSignatureConstants类来提供算法名称。
但是,这只是为了方便。
因为数据类型是String,所以你可以直接提供算法的名称。

一些主张的缔约方要求张贴<saml2:AuthnRequest>。这可以通过RelyingPartyRegistrations自动配置,也可以手动提供,例如:

Java

RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration.withRegistrationId("okta")
        // ...
        .assertingPartyDetails(party -> party
            // ...
            .singleSignOnServiceBinding(Saml2MessageBinding.POST)
        )
        .build();

Kotlin

var relyingPartyRegistration: RelyingPartyRegistration? =
    RelyingPartyRegistration.withRegistrationId("okta")
        // ...
        .assertingPartyDetails { party: AssertingPartyDetails.Builder -> party
            // ...
            .singleSignOnServiceBinding(Saml2MessageBinding.POST)
        }
        .build()

# 定制OpenSAML的AuthnRequest实例

你可能需要调整AuthnRequest的原因有很多。例如,你可能希望将ForceAuthN设置为true,而 Spring Security将其默认设置为false

如果你不需要来自HttpServletRequest的信息来做出决定,那么最简单的方法是[用OpenSAML注册一个自定义AuthnRequestMarshaller](overview.html# Servlet-saml2login-opensaml-customization)。这将使你能够在序列化之前访问后处理AuthnRequest实例。

但是,如果你确实需要来自该请求的某些内容,那么你可以使用创建一个自定义Saml2AuthenticationRequestContext实现,然后创建一个Converter<Saml2AuthenticationRequestContext, AuthnRequest>来构建自己的AuthnRequest,就像这样:

Java

@Component
public class AuthnRequestConverter implements
        Converter<Saml2AuthenticationRequestContext, AuthnRequest> {

    private final AuthnRequestBuilder authnRequestBuilder;
    private final IssuerBuilder issuerBuilder;

    // ... constructor

    public AuthnRequest convert(Saml2AuthenticationRequestContext context) {
        MySaml2AuthenticationRequestContext myContext = (MySaml2AuthenticationRequestContext) context;
        Issuer issuer = issuerBuilder.buildObject();
        issuer.setValue(myContext.getIssuer());

        AuthnRequest authnRequest = authnRequestBuilder.buildObject();
        authnRequest.setIssuer(issuer);
        authnRequest.setDestination(myContext.getDestination());
        authnRequest.setAssertionConsumerServiceURL(myContext.getAssertionConsumerServiceUrl());

        // ... additional settings

        authRequest.setForceAuthn(myContext.getForceAuthn());
        return authnRequest;
    }
}

Kotlin

@Component
class AuthnRequestConverter : Converter<Saml2AuthenticationRequestContext, AuthnRequest> {
    private val authnRequestBuilder: AuthnRequestBuilder? = null
    private val issuerBuilder: IssuerBuilder? = null

    // ... constructor
    override fun convert(context: Saml2AuthenticationRequestContext): AuthnRequest {
        val myContext: MySaml2AuthenticationRequestContext = context
        val issuer: Issuer = issuerBuilder.buildObject()
        issuer.value = myContext.getIssuer()
        val authnRequest: AuthnRequest = authnRequestBuilder.buildObject()
        authnRequest.issuer = issuer
        authnRequest.destination = myContext.getDestination()
        authnRequest.assertionConsumerServiceURL = myContext.getAssertionConsumerServiceUrl()

        // ... additional settings
        authRequest.setForceAuthn(myContext.getForceAuthn())
        return authnRequest
    }
}

然后,可以构造自己的Saml2AuthenticationRequestContextResolverSaml2AuthenticationRequestFactory,并将它们发布为@Beans:

Java

@Bean
Saml2AuthenticationRequestContextResolver authenticationRequestContextResolver() {
    Saml2AuthenticationRequestContextResolver resolver =
            new DefaultSaml2AuthenticationRequestContextResolver();
    return request -> {
        Saml2AuthenticationRequestContext context = resolver.resolve(request);
        return new MySaml2AuthenticationRequestContext(context, request.getParameter("force") != null);
    };
}

@Bean
Saml2AuthenticationRequestFactory authenticationRequestFactory(
        AuthnRequestConverter authnRequestConverter) {

    OpenSaml4AuthenticationRequestFactory authenticationRequestFactory =
            new OpenSaml4AuthenticationRequestFactory();
    authenticationRequestFactory.setAuthenticationRequestContextConverter(authnRequestConverter);
    return authenticationRequestFactory;
}

Kotlin

@Bean
open fun authenticationRequestContextResolver(): Saml2AuthenticationRequestContextResolver {
    val resolver: Saml2AuthenticationRequestContextResolver = DefaultSaml2AuthenticationRequestContextResolver()
    return Saml2AuthenticationRequestContextResolver { request: HttpServletRequest ->
        val context = resolver.resolve(request)
        MySaml2AuthenticationRequestContext(
            context,
            request.getParameter("force") != null
        )
    }
}

@Bean
open fun authenticationRequestFactory(
    authnRequestConverter: AuthnRequestConverter?
): Saml2AuthenticationRequestFactory? {
    val authenticationRequestFactory = OpenSaml4AuthenticationRequestFactory()
    authenticationRequestFactory.setAuthenticationRequestContextConverter(authnRequestConverter)
    return authenticationRequestFactory
}

SAML2日志概览SAML2身份验证响应