|
|
|
@ -157,6 +157,7 @@ import org.springframework.util.StringUtils; |
|
|
|
* asserting party, IDP, verification certificates. |
|
|
|
* asserting party, IDP, verification certificates. |
|
|
|
* </p> |
|
|
|
* </p> |
|
|
|
* |
|
|
|
* |
|
|
|
|
|
|
|
* @author Ryan Cassar |
|
|
|
* @since 5.2 |
|
|
|
* @since 5.2 |
|
|
|
* @see <a href= |
|
|
|
* @see <a href= |
|
|
|
* "https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf#page=38">SAML 2 |
|
|
|
* "https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf#page=38">SAML 2 |
|
|
|
@ -211,6 +212,32 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi |
|
|
|
|
|
|
|
|
|
|
|
private Converter<Saml2AuthenticationToken, Decrypter> decrypterConverter = new DecrypterConverter(); |
|
|
|
private Converter<Saml2AuthenticationToken, Decrypter> decrypterConverter = new DecrypterConverter(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private Consumer<ResponseToken> assertionDecrypter = (responseToken) -> { |
|
|
|
|
|
|
|
List<Assertion> assertions = new ArrayList<>(); |
|
|
|
|
|
|
|
for (EncryptedAssertion encryptedAssertion : responseToken.getResponse().getEncryptedAssertions()) { |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
Decrypter decrypter = this.decrypterConverter.convert(responseToken.getToken()); |
|
|
|
|
|
|
|
Assertion assertion = decrypter.decrypt(encryptedAssertion); |
|
|
|
|
|
|
|
assertions.add(assertion); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
catch (DecryptionException ex) { |
|
|
|
|
|
|
|
throw createAuthenticationException(Saml2ErrorCodes.DECRYPTION_ERROR, ex.getMessage(), ex); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
responseToken.getResponse().getAssertions().addAll(assertions); |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private Consumer<ResponseToken> principalDecrypter = (responseToken) -> { |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
Decrypter decrypter = this.decrypterConverter.convert(responseToken.getToken()); |
|
|
|
|
|
|
|
Assertion assertion = CollectionUtils.firstElement(responseToken.getResponse().getAssertions()); |
|
|
|
|
|
|
|
assertion.getSubject().setNameID((NameID) decrypter.decrypt(assertion.getSubject().getEncryptedID())); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
catch (DecryptionException ex) { |
|
|
|
|
|
|
|
throw createAuthenticationException(Saml2ErrorCodes.DECRYPTION_ERROR, ex.getMessage(), ex); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Creates an {@link OpenSamlAuthenticationProvider} |
|
|
|
* Creates an {@link OpenSamlAuthenticationProvider} |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
@ -332,6 +359,52 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi |
|
|
|
this.responseTimeValidationSkew = responseTimeValidationSkew; |
|
|
|
this.responseTimeValidationSkew = responseTimeValidationSkew; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* Sets the assertion response custom decrypter. |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* You can use this method like so: |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* <pre> |
|
|
|
|
|
|
|
* YourDecrypter decrypter = // ... your custom decrypter
|
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider(); |
|
|
|
|
|
|
|
* provider.setAssertionDecrypter((responseToken) -> { |
|
|
|
|
|
|
|
* Response response = responseToken.getResponse(); |
|
|
|
|
|
|
|
* EncryptedAssertion encrypted = response.getEncryptedAssertions().get(0); |
|
|
|
|
|
|
|
* Assertion assertion = decrypter.decrypt(encrypted); |
|
|
|
|
|
|
|
* response.getAssertions().add(assertion); |
|
|
|
|
|
|
|
* }); |
|
|
|
|
|
|
|
* </pre> |
|
|
|
|
|
|
|
* @param assertionDecrypter response token consumer |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
public void setAssertionDecrypter(Consumer<ResponseToken> assertionDecrypter) { |
|
|
|
|
|
|
|
Assert.notNull(assertionDecrypter, "Consumer<ResponseToken> required"); |
|
|
|
|
|
|
|
this.assertionDecrypter = assertionDecrypter; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* Sets the principal custom decrypter. |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* You can use this method like so: |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* <pre> |
|
|
|
|
|
|
|
* YourDecrypter decrypter = // ... your custom decrypter
|
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider(); |
|
|
|
|
|
|
|
* provider.setAssertionDecrypter((responseToken) -> { |
|
|
|
|
|
|
|
* Assertion assertion = CollectionUtils.firstElement(responseToken.getResponse().getAssertions()); |
|
|
|
|
|
|
|
* EncryptedID encrypted = assertion.getSubject().getEncryptedID(); |
|
|
|
|
|
|
|
* NameID name = decrypter.decrypt(encrypted); |
|
|
|
|
|
|
|
* assertion.getSubject().setNameID(name) |
|
|
|
|
|
|
|
* }); |
|
|
|
|
|
|
|
* </pre> |
|
|
|
|
|
|
|
* @param principalDecrypter response token consumer |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
public void setPrincipalDecrypter(Consumer<ResponseToken> principalDecrypter) { |
|
|
|
|
|
|
|
Assert.notNull(principalDecrypter, "Consumer<ResponseToken> required"); |
|
|
|
|
|
|
|
this.principalDecrypter = principalDecrypter; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Construct a default strategy for validating each SAML 2.0 Assertion and associated |
|
|
|
* Construct a default strategy for validating each SAML 2.0 Assertion and associated |
|
|
|
* {@link Authentication} token |
|
|
|
* {@link Authentication} token |
|
|
|
@ -429,8 +502,8 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi |
|
|
|
boolean responseSigned = response.isSigned(); |
|
|
|
boolean responseSigned = response.isSigned(); |
|
|
|
Saml2ResponseValidatorResult result = validateResponse(token, response); |
|
|
|
Saml2ResponseValidatorResult result = validateResponse(token, response); |
|
|
|
|
|
|
|
|
|
|
|
Decrypter decrypter = this.decrypterConverter.convert(token); |
|
|
|
ResponseToken responseToken = new ResponseToken(response, token); |
|
|
|
List<Assertion> assertions = decryptAssertions(decrypter, response); |
|
|
|
List<Assertion> assertions = decryptAssertions(responseToken); |
|
|
|
if (!isSigned(responseSigned, assertions)) { |
|
|
|
if (!isSigned(responseSigned, assertions)) { |
|
|
|
String description = "Either the response or one of the assertions is unsigned. " |
|
|
|
String description = "Either the response or one of the assertions is unsigned. " |
|
|
|
+ "Please either sign the response or all of the assertions."; |
|
|
|
+ "Please either sign the response or all of the assertions."; |
|
|
|
@ -439,7 +512,7 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi |
|
|
|
result = result.concat(validateAssertions(token, response)); |
|
|
|
result = result.concat(validateAssertions(token, response)); |
|
|
|
|
|
|
|
|
|
|
|
Assertion firstAssertion = CollectionUtils.firstElement(response.getAssertions()); |
|
|
|
Assertion firstAssertion = CollectionUtils.firstElement(response.getAssertions()); |
|
|
|
NameID nameId = decryptPrincipal(decrypter, firstAssertion); |
|
|
|
NameID nameId = decryptPrincipal(responseToken); |
|
|
|
if (nameId == null || nameId.getValue() == null) { |
|
|
|
if (nameId == null || nameId.getValue() == null) { |
|
|
|
Saml2Error error = new Saml2Error(Saml2ErrorCodes.SUBJECT_NOT_FOUND, |
|
|
|
Saml2Error error = new Saml2Error(Saml2ErrorCodes.SUBJECT_NOT_FOUND, |
|
|
|
"Assertion [" + firstAssertion.getID() + "] is missing a subject"); |
|
|
|
"Assertion [" + firstAssertion.getID() + "] is missing a subject"); |
|
|
|
@ -511,19 +584,9 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi |
|
|
|
return Saml2ResponseValidatorResult.failure(errors); |
|
|
|
return Saml2ResponseValidatorResult.failure(errors); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private List<Assertion> decryptAssertions(Decrypter decrypter, Response response) { |
|
|
|
private List<Assertion> decryptAssertions(ResponseToken response) { |
|
|
|
List<Assertion> assertions = new ArrayList<>(); |
|
|
|
this.assertionDecrypter.accept(response); |
|
|
|
for (EncryptedAssertion encryptedAssertion : response.getEncryptedAssertions()) { |
|
|
|
return response.getResponse().getAssertions(); |
|
|
|
try { |
|
|
|
|
|
|
|
Assertion assertion = decrypter.decrypt(encryptedAssertion); |
|
|
|
|
|
|
|
assertions.add(assertion); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
catch (DecryptionException ex) { |
|
|
|
|
|
|
|
throw createAuthenticationException(Saml2ErrorCodes.DECRYPTION_ERROR, ex.getMessage(), ex); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
response.getAssertions().addAll(assertions); |
|
|
|
|
|
|
|
return response.getAssertions(); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private Saml2ResponseValidatorResult validateAssertions(Saml2AuthenticationToken token, Response response) { |
|
|
|
private Saml2ResponseValidatorResult validateAssertions(Saml2AuthenticationToken token, Response response) { |
|
|
|
@ -567,21 +630,16 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi |
|
|
|
return true; |
|
|
|
return true; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private NameID decryptPrincipal(Decrypter decrypter, Assertion assertion) { |
|
|
|
private NameID decryptPrincipal(ResponseToken responseToken) { |
|
|
|
|
|
|
|
Assertion assertion = CollectionUtils.firstElement(responseToken.getResponse().getAssertions()); |
|
|
|
if (assertion.getSubject() == null) { |
|
|
|
if (assertion.getSubject() == null) { |
|
|
|
return null; |
|
|
|
return null; |
|
|
|
} |
|
|
|
} |
|
|
|
if (assertion.getSubject().getEncryptedID() == null) { |
|
|
|
if (assertion.getSubject().getEncryptedID() == null) { |
|
|
|
return assertion.getSubject().getNameID(); |
|
|
|
return assertion.getSubject().getNameID(); |
|
|
|
} |
|
|
|
} |
|
|
|
try { |
|
|
|
this.principalDecrypter.accept(responseToken); |
|
|
|
NameID nameId = (NameID) decrypter.decrypt(assertion.getSubject().getEncryptedID()); |
|
|
|
return assertion.getSubject().getNameID(); |
|
|
|
assertion.getSubject().setNameID(nameId); |
|
|
|
|
|
|
|
return nameId; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
catch (DecryptionException ex) { |
|
|
|
|
|
|
|
throw createAuthenticationException(Saml2ErrorCodes.DECRYPTION_ERROR, ex.getMessage(), ex); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private static Map<String, List<Object>> getAssertionAttributes(Assertion assertion) { |
|
|
|
private static Map<String, List<Object>> getAssertionAttributes(Assertion assertion) { |
|
|
|
|