@ -53,6 +53,7 @@ import org.opensaml.xmlsec.signature.support.SignaturePrevalidator;
import org.opensaml.xmlsec.signature.support.SignatureTrustEngine ;
import org.opensaml.xmlsec.signature.support.SignatureTrustEngine ;
import org.springframework.core.convert.converter.Converter ;
import org.springframework.core.convert.converter.Converter ;
import org.springframework.lang.NonNull ;
import org.springframework.security.authentication.AbstractAuthenticationToken ;
import org.springframework.security.authentication.AbstractAuthenticationToken ;
import org.springframework.security.authentication.AuthenticationProvider ;
import org.springframework.security.authentication.AuthenticationProvider ;
import org.springframework.security.core.Authentication ;
import org.springframework.security.core.Authentication ;
@ -60,6 +61,7 @@ import org.springframework.security.core.AuthenticationException;
import org.springframework.security.saml2.core.Saml2Error ;
import org.springframework.security.saml2.core.Saml2Error ;
import org.springframework.security.saml2.core.Saml2ErrorCodes ;
import org.springframework.security.saml2.core.Saml2ErrorCodes ;
import org.springframework.security.saml2.core.Saml2ResponseValidatorResult ;
import org.springframework.security.saml2.core.Saml2ResponseValidatorResult ;
import org.springframework.security.saml2.provider.service.registration.AssertingPartyMetadata ;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration ;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration ;
import org.springframework.util.Assert ;
import org.springframework.util.Assert ;
import org.springframework.util.StringUtils ;
import org.springframework.util.StringUtils ;
@ -114,6 +116,7 @@ public final class OpenSaml5AuthenticationProvider implements AuthenticationProv
* /
* /
public OpenSaml5AuthenticationProvider ( ) {
public OpenSaml5AuthenticationProvider ( ) {
this . delegate = new BaseOpenSamlAuthenticationProvider ( new OpenSaml5Template ( ) ) ;
this . delegate = new BaseOpenSamlAuthenticationProvider ( new OpenSaml5Template ( ) ) ;
setResponseValidator ( ResponseValidator . withDefaults ( ) ) ;
setAssertionValidator ( AssertionValidator . withDefaults ( ) ) ;
setAssertionValidator ( AssertionValidator . withDefaults ( ) ) ;
}
}
@ -301,12 +304,11 @@ public final class OpenSaml5AuthenticationProvider implements AuthenticationProv
* Construct a default strategy for validating the SAML 2 . 0 Response
* Construct a default strategy for validating the SAML 2 . 0 Response
* @return the default response validator strategy
* @return the default response validator strategy
* @since 5 . 6
* @since 5 . 6
* @deprecated please use { @link ResponseValidator # withDefaults ( ) } instead
* /
* /
@Deprecated
public static Converter < ResponseToken , Saml2ResponseValidatorResult > createDefaultResponseValidator ( ) {
public static Converter < ResponseToken , Saml2ResponseValidatorResult > createDefaultResponseValidator ( ) {
Converter < BaseOpenSamlAuthenticationProvider . ResponseToken , Saml2ResponseValidatorResult > delegate = BaseOpenSamlAuthenticationProvider
return ResponseValidator . withDefaults ( ) ;
. createDefaultResponseValidator ( ) ;
return ( token ) - > delegate
. convert ( new BaseOpenSamlAuthenticationProvider . ResponseToken ( token . getResponse ( ) , token . getToken ( ) ) ) ;
}
}
/ * *
/ * *
@ -459,6 +461,135 @@ public final class OpenSaml5AuthenticationProvider implements AuthenticationProv
}
}
/ * *
* A response validator that checks the { @code InResponseTo } value against the
* correlating { @link AbstractSaml2AuthenticationRequest }
*
* @since 6 . 5
* /
public static final class InResponseToValidator implements Converter < ResponseToken , Saml2ResponseValidatorResult > {
@Override
@NonNull
public Saml2ResponseValidatorResult convert ( ResponseToken responseToken ) {
AbstractSaml2AuthenticationRequest request = responseToken . getToken ( ) . getAuthenticationRequest ( ) ;
Response response = responseToken . getResponse ( ) ;
String inResponseTo = response . getInResponseTo ( ) ;
return BaseOpenSamlAuthenticationProvider . validateInResponseTo ( request , inResponseTo ) ;
}
}
/ * *
* A response validator that compares the { @code Destination } value to the configured
* { @link RelyingPartyRegistration # getAssertionConsumerServiceLocation ( ) }
*
* @since 6 . 5
* /
public static final class DestinationValidator implements Converter < ResponseToken , Saml2ResponseValidatorResult > {
@Override
@NonNull
public Saml2ResponseValidatorResult convert ( ResponseToken responseToken ) {
Response response = responseToken . getResponse ( ) ;
Saml2AuthenticationToken token = responseToken . getToken ( ) ;
String destination = response . getDestination ( ) ;
String location = token . getRelyingPartyRegistration ( ) . getAssertionConsumerServiceLocation ( ) ;
if ( StringUtils . hasText ( destination ) & & ! destination . equals ( location ) ) {
String message = "Invalid destination [" + destination + "] for SAML response [" + response . getID ( )
+ "]" ;
return Saml2ResponseValidatorResult
. failure ( new Saml2Error ( Saml2ErrorCodes . INVALID_DESTINATION , message ) ) ;
}
return Saml2ResponseValidatorResult . success ( ) ;
}
}
/ * *
* A response validator that compares the { @code Issuer } value to the configured
* { @link AssertingPartyMetadata # getEntityId ( ) }
*
* @since 6 . 5
* /
public static final class IssuerValidator implements Converter < ResponseToken , Saml2ResponseValidatorResult > {
@Override
@NonNull
public Saml2ResponseValidatorResult convert ( ResponseToken responseToken ) {
Response response = responseToken . getResponse ( ) ;
Saml2AuthenticationToken token = responseToken . getToken ( ) ;
String issuer = response . getIssuer ( ) . getValue ( ) ;
String assertingPartyEntityId = token . getRelyingPartyRegistration ( )
. getAssertingPartyMetadata ( )
. getEntityId ( ) ;
if ( ! StringUtils . hasText ( issuer ) | | ! issuer . equals ( assertingPartyEntityId ) ) {
String message = String . format ( "Invalid issuer [%s] for SAML response [%s]" , issuer , response . getID ( ) ) ;
return Saml2ResponseValidatorResult . failure ( new Saml2Error ( Saml2ErrorCodes . INVALID_ISSUER , message ) ) ;
}
return Saml2ResponseValidatorResult . success ( ) ;
}
}
/ * *
* A composite response validator that confirms a { @code SUCCESS } status , that there
* is at least one assertion , and any other configured converters
*
* @since 6 . 5
* @see InResponseToValidator
* @see DestinationValidator
* @see IssuerValidator
* /
public static final class ResponseValidator implements Converter < ResponseToken , Saml2ResponseValidatorResult > {
private static final List < Converter < ResponseToken , Saml2ResponseValidatorResult > > DEFAULTS = List
. of ( new InResponseToValidator ( ) , new DestinationValidator ( ) , new IssuerValidator ( ) ) ;
private final List < Converter < ResponseToken , Saml2ResponseValidatorResult > > validators ;
@SafeVarargs
public ResponseValidator ( Converter < ResponseToken , Saml2ResponseValidatorResult > . . . validators ) {
this . validators = List . of ( validators ) ;
Assert . notEmpty ( this . validators , "validators cannot be empty" ) ;
}
public static ResponseValidator withDefaults ( ) {
return new ResponseValidator ( new InResponseToValidator ( ) , new DestinationValidator ( ) ,
new IssuerValidator ( ) ) ;
}
@SafeVarargs
public static ResponseValidator withDefaults (
Converter < ResponseToken , Saml2ResponseValidatorResult > . . . validators ) {
List < Converter < ResponseToken , Saml2ResponseValidatorResult > > defaults = new ArrayList < > ( DEFAULTS ) ;
defaults . addAll ( List . of ( validators ) ) ;
return new ResponseValidator ( defaults . toArray ( Converter [ ] : : new ) ) ;
}
@Override
public Saml2ResponseValidatorResult convert ( ResponseToken responseToken ) {
Response response = responseToken . getResponse ( ) ;
Collection < Saml2Error > errors = new ArrayList < > ( ) ;
List < String > statusCodes = BaseOpenSamlAuthenticationProvider . getStatusCodes ( response ) ;
if ( ! BaseOpenSamlAuthenticationProvider . isSuccess ( statusCodes ) ) {
for ( String statusCode : statusCodes ) {
String message = String . format ( "Invalid status [%s] for SAML response [%s]" , statusCode ,
response . getID ( ) ) ;
errors . add ( new Saml2Error ( Saml2ErrorCodes . INVALID_RESPONSE , message ) ) ;
}
}
for ( Converter < ResponseToken , Saml2ResponseValidatorResult > validator : this . validators ) {
errors . addAll ( validator . convert ( responseToken ) . getErrors ( ) ) ;
}
if ( response . getAssertions ( ) . isEmpty ( ) ) {
errors . add ( new Saml2Error ( Saml2ErrorCodes . MALFORMED_RESPONSE_DATA , "No assertions found in response." ) ) ;
}
return Saml2ResponseValidatorResult . failure ( errors ) ;
}
}
/ * *
/ * *
* A default implementation of { @link OpenSaml5AuthenticationProvider } ' s assertion
* A default implementation of { @link OpenSaml5AuthenticationProvider } ' s assertion
* validator . This does not check the signature as signature verification is performed
* validator . This does not check the signature as signature verification is performed