Skip to content

Commit 6e41246

Browse files
GitHanterjzheaux
authored andcommitted
Throw Saml2AuthenticationException
Closes gh-9310
1 parent 3e8ad4b commit 6e41246

File tree

3 files changed

+93
-7
lines changed

3 files changed

+93
-7
lines changed

config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurerTests.java

+40-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -30,12 +30,14 @@
3030

3131
import javax.servlet.ServletException;
3232
import javax.servlet.http.HttpServletRequest;
33+
import javax.servlet.http.HttpServletResponse;
3334

3435
import org.junit.After;
3536
import org.junit.Assert;
3637
import org.junit.Before;
3738
import org.junit.Rule;
3839
import org.junit.Test;
40+
import org.mockito.ArgumentCaptor;
3941
import org.opensaml.saml.saml2.core.Assertion;
4042
import org.opensaml.saml.saml2.core.AuthnRequest;
4143

@@ -62,10 +64,13 @@
6264
import org.springframework.security.core.authority.SimpleGrantedAuthority;
6365
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
6466
import org.springframework.security.saml2.Saml2Exception;
67+
import org.springframework.security.saml2.core.Saml2ErrorCodes;
68+
import org.springframework.security.saml2.core.Saml2Utils;
6569
import org.springframework.security.saml2.core.TestSaml2X509Credentials;
6670
import org.springframework.security.saml2.provider.service.authentication.OpenSamlAuthenticationProvider;
6771
import org.springframework.security.saml2.provider.service.authentication.OpenSamlAuthenticationRequestFactory;
6872
import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
73+
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException;
6974
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationRequestContext;
7075
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationRequestFactory;
7176
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationToken;
@@ -78,6 +83,7 @@
7883
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationRequestContextResolver;
7984
import org.springframework.security.web.FilterChainProxy;
8085
import org.springframework.security.web.authentication.AuthenticationConverter;
86+
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
8187
import org.springframework.security.web.context.HttpRequestResponseHolder;
8288
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
8389
import org.springframework.security.web.context.SecurityContextRepository;
@@ -210,6 +216,24 @@ public void authenticateWhenCustomAuthenticationConverterThenUses() throws Excep
210216
verify(CustomAuthenticationConverter.authenticationConverter).convert(any(HttpServletRequest.class));
211217
}
212218

219+
@Test
220+
public void authenticateWithInvalidDeflatedSAMLResponseThenFailureHandlerUses() throws Exception {
221+
this.spring.register(CustomAuthenticationFailureHandler.class).autowire();
222+
byte[] invalidDeflated = "invalid".getBytes();
223+
String encoded = Saml2Utils.samlEncode(invalidDeflated);
224+
MockHttpServletRequestBuilder request = get("/login/saml2/sso/registration-id").queryParam("SAMLResponse",
225+
encoded);
226+
this.mvc.perform(request);
227+
ArgumentCaptor<Saml2AuthenticationException> captor = ArgumentCaptor
228+
.forClass(Saml2AuthenticationException.class);
229+
verify(CustomAuthenticationFailureHandler.authenticationFailureHandler).onAuthenticationFailure(
230+
any(HttpServletRequest.class), any(HttpServletResponse.class), captor.capture());
231+
Saml2AuthenticationException exception = captor.getValue();
232+
assertThat(exception.getSaml2Error().getErrorCode()).isEqualTo(Saml2ErrorCodes.INVALID_RESPONSE);
233+
assertThat(exception.getSaml2Error().getDescription()).isEqualTo("Unable to inflate string");
234+
assertThat(exception.getCause()).isInstanceOf(IOException.class);
235+
}
236+
213237
private void validateSaml2WebSsoAuthenticationFilterConfiguration() {
214238
// get the OpenSamlAuthenticationProvider
215239
Saml2WebSsoAuthenticationFilter filter = getSaml2SsoFilter(this.springSecurityFilterChain);
@@ -314,6 +338,21 @@ public <O extends OpenSamlAuthenticationProvider> O postProcess(O provider) {
314338

315339
}
316340

341+
@EnableWebSecurity
342+
@Import(Saml2LoginConfigBeans.class)
343+
static class CustomAuthenticationFailureHandler extends WebSecurityConfigurerAdapter {
344+
345+
static final AuthenticationFailureHandler authenticationFailureHandler = mock(
346+
AuthenticationFailureHandler.class);
347+
348+
@Override
349+
protected void configure(HttpSecurity http) throws Exception {
350+
http.authorizeRequests((authz) -> authz.anyRequest().authenticated())
351+
.saml2Login((saml2) -> saml2.failureHandler(authenticationFailureHandler));
352+
}
353+
354+
}
355+
317356
@EnableWebSecurity
318357
@Import(Saml2LoginConfigBeans.class)
319358
static class CustomAuthenticationRequestContextResolver extends WebSecurityConfigurerAdapter {

saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/Saml2AuthenticationTokenConverter.java

+14-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -28,7 +28,9 @@
2828

2929
import org.springframework.core.convert.converter.Converter;
3030
import org.springframework.http.HttpMethod;
31-
import org.springframework.security.saml2.Saml2Exception;
31+
import org.springframework.security.saml2.core.Saml2Error;
32+
import org.springframework.security.saml2.core.Saml2ErrorCodes;
33+
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException;
3234
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationToken;
3335
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
3436
import org.springframework.security.web.authentication.AuthenticationConverter;
@@ -83,7 +85,13 @@ private String inflateIfRequired(HttpServletRequest request, byte[] b) {
8385
}
8486

8587
private byte[] samlDecode(String s) {
86-
return BASE64.decode(s);
88+
try {
89+
return BASE64.decode(s);
90+
}
91+
catch (Exception ex) {
92+
throw new Saml2AuthenticationException(
93+
new Saml2Error(Saml2ErrorCodes.INVALID_RESPONSE, "Failed to decode SAMLResponse"), ex);
94+
}
8795
}
8896

8997
private String samlInflate(byte[] b) {
@@ -94,8 +102,9 @@ private String samlInflate(byte[] b) {
94102
inflaterOutputStream.finish();
95103
return new String(out.toByteArray(), StandardCharsets.UTF_8);
96104
}
97-
catch (IOException ex) {
98-
throw new Saml2Exception("Unable to inflate string", ex);
105+
catch (Exception ex) {
106+
throw new Saml2AuthenticationException(
107+
new Saml2Error(Saml2ErrorCodes.INVALID_RESPONSE, "Unable to inflate string"), ex);
99108
}
100109
}
101110

saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/web/Saml2AuthenticationTokenConverterTests.java

+39-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -29,14 +29,17 @@
2929
import org.springframework.core.convert.converter.Converter;
3030
import org.springframework.core.io.ClassPathResource;
3131
import org.springframework.mock.web.MockHttpServletRequest;
32+
import org.springframework.security.saml2.core.Saml2ErrorCodes;
3233
import org.springframework.security.saml2.core.Saml2Utils;
34+
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException;
3335
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationToken;
3436
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
3537
import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations;
3638
import org.springframework.util.StreamUtils;
3739
import org.springframework.web.util.UriUtils;
3840

3941
import static org.assertj.core.api.Assertions.assertThat;
42+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
4043
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
4144
import static org.mockito.ArgumentMatchers.any;
4245
import static org.mockito.BDDMockito.given;
@@ -64,6 +67,22 @@ public void convertWhenSamlResponseThenToken() {
6467
.isEqualTo(this.relyingPartyRegistration.getRegistrationId());
6568
}
6669

70+
@Test
71+
public void convertWhenSamlResponseInvalidBase64ThenSaml2AuthenticationException() {
72+
Saml2AuthenticationTokenConverter converter = new Saml2AuthenticationTokenConverter(
73+
this.relyingPartyRegistrationResolver);
74+
given(this.relyingPartyRegistrationResolver.convert(any(HttpServletRequest.class)))
75+
.willReturn(this.relyingPartyRegistration);
76+
MockHttpServletRequest request = new MockHttpServletRequest();
77+
request.setParameter("SAMLResponse", "invalid");
78+
assertThatExceptionOfType(Saml2AuthenticationException.class).isThrownBy(() -> converter.convert(request))
79+
.withCauseInstanceOf(IllegalArgumentException.class)
80+
.satisfies((ex) -> assertThat(ex.getSaml2Error().getErrorCode())
81+
.isEqualTo(Saml2ErrorCodes.INVALID_RESPONSE))
82+
.satisfies((ex) -> assertThat(ex.getSaml2Error().getDescription())
83+
.isEqualTo("Failed to decode SAMLResponse"));
84+
}
85+
6786
@Test
6887
public void convertWhenNoSamlResponseThenNull() {
6988
Saml2AuthenticationTokenConverter converter = new Saml2AuthenticationTokenConverter(
@@ -100,6 +119,25 @@ public void convertWhenGetRequestThenInflates() {
100119
.isEqualTo(this.relyingPartyRegistration.getRegistrationId());
101120
}
102121

122+
@Test
123+
public void convertWhenGetRequestInvalidDeflatedThenSaml2AuthenticationException() {
124+
Saml2AuthenticationTokenConverter converter = new Saml2AuthenticationTokenConverter(
125+
this.relyingPartyRegistrationResolver);
126+
given(this.relyingPartyRegistrationResolver.convert(any(HttpServletRequest.class)))
127+
.willReturn(this.relyingPartyRegistration);
128+
MockHttpServletRequest request = new MockHttpServletRequest();
129+
request.setMethod("GET");
130+
byte[] invalidDeflated = "invalid".getBytes();
131+
String encoded = Saml2Utils.samlEncode(invalidDeflated);
132+
request.setParameter("SAMLResponse", encoded);
133+
assertThatExceptionOfType(Saml2AuthenticationException.class).isThrownBy(() -> converter.convert(request))
134+
.withCauseInstanceOf(IOException.class)
135+
.satisfies((ex) -> assertThat(ex.getSaml2Error().getErrorCode())
136+
.isEqualTo(Saml2ErrorCodes.INVALID_RESPONSE))
137+
.satisfies(
138+
(ex) -> assertThat(ex.getSaml2Error().getDescription()).isEqualTo("Unable to inflate string"));
139+
}
140+
103141
@Test
104142
public void constructorWhenResolverIsNullThenIllegalArgument() {
105143
assertThatIllegalArgumentException().isThrownBy(() -> new Saml2AuthenticationTokenConverter(null));

0 commit comments

Comments
 (0)