2 changed files with 195 additions and 0 deletions
@ -0,0 +1,60 @@
@@ -0,0 +1,60 @@
|
||||
/* |
||||
* Copyright 2002-2024 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.security.web.authentication; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
import jakarta.servlet.http.HttpServletRequest; |
||||
|
||||
import org.springframework.security.core.Authentication; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* A {@link AuthenticationConverter}, that iterates over multiple |
||||
* {@link AuthenticationConverter}. The first non-null {@link Authentication} will be used |
||||
* as a result. |
||||
* |
||||
* @author Max Batischev |
||||
* @since 6.3 |
||||
*/ |
||||
public final class DelegatingAuthenticationConverter implements AuthenticationConverter { |
||||
|
||||
private final List<AuthenticationConverter> delegates; |
||||
|
||||
public DelegatingAuthenticationConverter(List<AuthenticationConverter> delegates) { |
||||
Assert.notEmpty(delegates, "delegates cannot be null"); |
||||
this.delegates = new ArrayList<>(delegates); |
||||
} |
||||
|
||||
public DelegatingAuthenticationConverter(AuthenticationConverter... delegates) { |
||||
Assert.notEmpty(delegates, "delegates cannot be null"); |
||||
this.delegates = List.of(delegates); |
||||
} |
||||
|
||||
@Override |
||||
public Authentication convert(HttpServletRequest request) { |
||||
for (AuthenticationConverter delegate : this.delegates) { |
||||
Authentication authentication = delegate.convert(request); |
||||
if (authentication != null) { |
||||
return authentication; |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,135 @@
@@ -0,0 +1,135 @@
|
||||
/* |
||||
* Copyright 2002-2024 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.security.web.authentication; |
||||
|
||||
import jakarta.servlet.http.HttpServletRequest; |
||||
import org.junit.jupiter.api.Test; |
||||
import org.junit.jupiter.api.extension.ExtendWith; |
||||
import org.mockito.Mock; |
||||
import org.mockito.junit.jupiter.MockitoExtension; |
||||
|
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.mock.web.MockHttpServletRequest; |
||||
import org.springframework.security.authentication.AuthenticationDetailsSource; |
||||
import org.springframework.security.authentication.BadCredentialsException; |
||||
import org.springframework.security.authentication.TestingAuthenticationToken; |
||||
import org.springframework.security.core.Authentication; |
||||
import org.springframework.security.test.web.CodecTestUtils; |
||||
import org.springframework.security.web.authentication.www.BasicAuthenticationConverter; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; |
||||
|
||||
/** |
||||
* Tests for {@link DelegatingAuthenticationConverter}. |
||||
* |
||||
* @author Max Batischev |
||||
*/ |
||||
@ExtendWith(MockitoExtension.class) |
||||
public class DelegatingAuthenticationConverterTests { |
||||
|
||||
private static final String X_AUTH_TOKEN_HEADER = "X-Auth-Token"; |
||||
|
||||
private static final String TEST_X_AUTH_TOKEN = "test-x-auth-token"; |
||||
|
||||
private static final String TEST_CUSTOM_PRINCIPAL = "test_custom_principal"; |
||||
|
||||
private static final String TEST_CUSTOM_CREDENTIALS = "test_custom_credentials"; |
||||
|
||||
private static final String TEST_BASIC_CREDENTIALS = "username:password"; |
||||
|
||||
private static final String INVALID_BASIC_CREDENTIALS = "invalid_credentials"; |
||||
|
||||
private DelegatingAuthenticationConverter converter; |
||||
|
||||
@Mock |
||||
private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource; |
||||
|
||||
@Test |
||||
public void requestWhenBasicAuthorizationHeaderIsPresentThenAuthenticates() { |
||||
MockHttpServletRequest request = new MockHttpServletRequest(); |
||||
request.addHeader(HttpHeaders.AUTHORIZATION, "Basic " + CodecTestUtils.encodeBase64(TEST_BASIC_CREDENTIALS)); |
||||
this.converter = new DelegatingAuthenticationConverter( |
||||
new BasicAuthenticationConverter(this.authenticationDetailsSource), |
||||
new TestNullableAuthenticationConverter()); |
||||
|
||||
Authentication authentication = this.converter.convert(request); |
||||
|
||||
assertThat(authentication).isNotNull(); |
||||
assertThat(authentication.getName()).isEqualTo("username"); |
||||
} |
||||
|
||||
@Test |
||||
public void requestWhenXAuthHeaderIsPresentThenAuthenticates() { |
||||
MockHttpServletRequest request = new MockHttpServletRequest(); |
||||
request.addHeader(X_AUTH_TOKEN_HEADER, TEST_X_AUTH_TOKEN); |
||||
this.converter = new DelegatingAuthenticationConverter(new TestAuthenticationConverter(), |
||||
new TestNullableAuthenticationConverter()); |
||||
|
||||
Authentication authentication = this.converter.convert(request); |
||||
|
||||
assertThat(authentication).isNotNull(); |
||||
assertThat(authentication.getName()).isEqualTo(TEST_CUSTOM_PRINCIPAL); |
||||
} |
||||
|
||||
@Test |
||||
public void requestWhenXAuthHeaderIsPresentThenDoesntAuthenticate() { |
||||
MockHttpServletRequest request = new MockHttpServletRequest(); |
||||
request.addHeader(X_AUTH_TOKEN_HEADER, TEST_X_AUTH_TOKEN); |
||||
this.converter = new DelegatingAuthenticationConverter(new TestNullableAuthenticationConverter()); |
||||
|
||||
Authentication authentication = this.converter.convert(request); |
||||
|
||||
assertThat(authentication).isNull(); |
||||
} |
||||
|
||||
@Test |
||||
public void requestWhenInvalidBasicAuthorizationTokenThenError() { |
||||
MockHttpServletRequest request = new MockHttpServletRequest(); |
||||
request.addHeader(HttpHeaders.AUTHORIZATION, "Basic " + CodecTestUtils.encodeBase64(INVALID_BASIC_CREDENTIALS)); |
||||
this.converter = new DelegatingAuthenticationConverter( |
||||
new BasicAuthenticationConverter(this.authenticationDetailsSource), |
||||
new TestNullableAuthenticationConverter()); |
||||
|
||||
assertThatExceptionOfType(BadCredentialsException.class).isThrownBy(() -> this.converter.convert(request)); |
||||
} |
||||
|
||||
private static class TestAuthenticationConverter implements AuthenticationConverter { |
||||
|
||||
@Override |
||||
public Authentication convert(HttpServletRequest request) { |
||||
String header = request.getHeader(X_AUTH_TOKEN_HEADER); |
||||
if (header != null) { |
||||
return new TestingAuthenticationToken(TEST_CUSTOM_PRINCIPAL, TEST_CUSTOM_CREDENTIALS); |
||||
} |
||||
else { |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
private static class TestNullableAuthenticationConverter implements AuthenticationConverter { |
||||
|
||||
@Override |
||||
public Authentication convert(HttpServletRequest request) { |
||||
return null; |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue