mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-03-03 11:59:08 +00:00
parent
0957ecb1e9
commit
9a67441507
@ -56,6 +56,7 @@ import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
|
||||
import org.springframework.security.oauth2.client.InMemoryReactiveOAuth2AuthorizedClientService;
|
||||
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService;
|
||||
import org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeReactiveAuthenticationManager;
|
||||
@ -91,6 +92,8 @@ import org.springframework.security.oauth2.server.resource.web.access.server.Bea
|
||||
import org.springframework.security.oauth2.server.resource.web.server.BearerTokenServerAuthenticationEntryPoint;
|
||||
import org.springframework.security.oauth2.server.resource.web.server.ServerBearerTokenAuthenticationConverter;
|
||||
import org.springframework.security.web.PortMapper;
|
||||
import org.springframework.security.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor;
|
||||
import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor;
|
||||
import org.springframework.security.web.server.DelegatingServerAuthenticationEntryPoint;
|
||||
import org.springframework.security.web.server.MatcherSecurityWebFilterChain;
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||
@ -99,6 +102,7 @@ import org.springframework.security.web.server.WebFilterExchange;
|
||||
import org.springframework.security.web.server.authentication.AnonymousAuthenticationWebFilter;
|
||||
import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
|
||||
import org.springframework.security.web.server.authentication.HttpBasicServerAuthenticationEntryPoint;
|
||||
import org.springframework.security.web.server.authentication.ReactivePreAuthenticatedAuthenticationManager;
|
||||
import org.springframework.security.web.server.authentication.RedirectServerAuthenticationEntryPoint;
|
||||
import org.springframework.security.web.server.authentication.RedirectServerAuthenticationFailureHandler;
|
||||
import org.springframework.security.web.server.authentication.RedirectServerAuthenticationSuccessHandler;
|
||||
@ -108,6 +112,7 @@ import org.springframework.security.web.server.authentication.ServerAuthenticati
|
||||
import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler;
|
||||
import org.springframework.security.web.server.authentication.ServerFormLoginAuthenticationConverter;
|
||||
import org.springframework.security.web.server.authentication.ServerHttpBasicAuthenticationConverter;
|
||||
import org.springframework.security.web.server.authentication.ServerX509AuthenticationConverter;
|
||||
import org.springframework.security.web.server.authentication.logout.DelegatingServerLogoutHandler;
|
||||
import org.springframework.security.web.server.authentication.logout.LogoutWebFilter;
|
||||
import org.springframework.security.web.server.authentication.logout.SecurityContextServerLogoutHandler;
|
||||
@ -241,6 +246,8 @@ public class ServerHttpSecurity {
|
||||
|
||||
private HttpBasicSpec httpBasic;
|
||||
|
||||
private X509Spec x509;
|
||||
|
||||
private final RequestCacheSpec requestCache = new RequestCacheSpec();
|
||||
|
||||
private FormLoginSpec formLogin;
|
||||
@ -578,6 +585,93 @@ public class ServerHttpSecurity {
|
||||
return this.formLogin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures x509 authentication using a certificate provided by a client.
|
||||
*
|
||||
* <pre class="code">
|
||||
* @Bean
|
||||
* public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
||||
* http
|
||||
* .x509()
|
||||
* .authenticationManager(authenticationManager)
|
||||
* .principalExtractor(principalExtractor);
|
||||
* return http.build();
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* Note that if extractor is not specified, {@link SubjectDnX509PrincipalExtractor} will be used.
|
||||
* If authenticationManager is not specified, {@link ReactivePreAuthenticatedAuthenticationManager} will be used.
|
||||
*
|
||||
* @return the {@link X509Spec} to customize
|
||||
* @author Alexey Nesterov
|
||||
* @since 5.2
|
||||
*/
|
||||
public X509Spec x509() {
|
||||
if (this.x509 == null) {
|
||||
this.x509 = new X509Spec();
|
||||
}
|
||||
|
||||
return this.x509;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures X509 authentication
|
||||
*
|
||||
* @author Alexey Nesterov
|
||||
* @since 5.2
|
||||
* @see #x509()
|
||||
*/
|
||||
public class X509Spec {
|
||||
|
||||
private X509PrincipalExtractor principalExtractor;
|
||||
private ReactiveAuthenticationManager authenticationManager;
|
||||
|
||||
public X509Spec principalExtractor(X509PrincipalExtractor principalExtractor) {
|
||||
this.principalExtractor = principalExtractor;
|
||||
return this;
|
||||
}
|
||||
|
||||
public X509Spec authenticationManager(ReactiveAuthenticationManager authenticationManager) {
|
||||
this.authenticationManager = authenticationManager;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ServerHttpSecurity and() {
|
||||
return ServerHttpSecurity.this;
|
||||
}
|
||||
|
||||
protected void configure(ServerHttpSecurity http) {
|
||||
ReactiveAuthenticationManager authenticationManager = getAuthenticationManager();
|
||||
X509PrincipalExtractor principalExtractor = getPrincipalExtractor();
|
||||
|
||||
AuthenticationWebFilter filter = new AuthenticationWebFilter(authenticationManager);
|
||||
filter.setServerAuthenticationConverter(new ServerX509AuthenticationConverter(principalExtractor));
|
||||
http.addFilterAt(filter, SecurityWebFiltersOrder.AUTHENTICATION);
|
||||
}
|
||||
|
||||
private X509PrincipalExtractor getPrincipalExtractor() {
|
||||
if (this.principalExtractor != null) {
|
||||
return this.principalExtractor;
|
||||
}
|
||||
|
||||
return new SubjectDnX509PrincipalExtractor();
|
||||
}
|
||||
|
||||
private ReactiveAuthenticationManager getAuthenticationManager() {
|
||||
if (this.authenticationManager != null) {
|
||||
return this.authenticationManager;
|
||||
}
|
||||
|
||||
ReactiveUserDetailsService userDetailsService = getBean(ReactiveUserDetailsService.class);
|
||||
ReactivePreAuthenticatedAuthenticationManager authenticationManager = new ReactivePreAuthenticatedAuthenticationManager(userDetailsService);
|
||||
|
||||
return authenticationManager;
|
||||
}
|
||||
|
||||
private X509Spec() {
|
||||
}
|
||||
}
|
||||
|
||||
public OAuth2LoginSpec oauth2Login() {
|
||||
if (this.oauth2Login == null) {
|
||||
this.oauth2Login = new OAuth2LoginSpec();
|
||||
@ -1508,6 +1602,9 @@ public class ServerHttpSecurity {
|
||||
if (this.httpsRedirectSpec != null) {
|
||||
this.httpsRedirectSpec.configure(this);
|
||||
}
|
||||
if (this.x509 != null) {
|
||||
this.x509.configure(this);
|
||||
}
|
||||
if (this.csrf != null) {
|
||||
this.csrf.configure(this);
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ package org.springframework.security.config.web.server;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.Arrays;
|
||||
@ -34,6 +35,8 @@ import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor;
|
||||
import org.springframework.security.web.server.authentication.ServerX509AuthenticationConverter;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.publisher.TestPublisher;
|
||||
|
||||
@ -279,6 +282,43 @@ public class ServerHttpSecurityTests {
|
||||
assertThat(result.getResponseCookies().getFirst("SESSION")).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void addsX509FilterWhenX509AuthenticationIsConfigured() {
|
||||
X509PrincipalExtractor mockExtractor = mock(X509PrincipalExtractor.class);
|
||||
ReactiveAuthenticationManager mockAuthenticationManager = mock(ReactiveAuthenticationManager.class);
|
||||
|
||||
this.http.x509()
|
||||
.principalExtractor(mockExtractor)
|
||||
.authenticationManager(mockAuthenticationManager)
|
||||
.and();
|
||||
|
||||
SecurityWebFilterChain securityWebFilterChain = this.http.build();
|
||||
WebFilter x509WebFilter = securityWebFilterChain.getWebFilters().filter(this::isX509Filter).blockFirst();
|
||||
|
||||
assertThat(x509WebFilter).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addsX509FilterWhenX509AuthenticationIsConfiguredWithDefaults() {
|
||||
this.http.x509();
|
||||
|
||||
SecurityWebFilterChain securityWebFilterChain = this.http.build();
|
||||
WebFilter x509WebFilter = securityWebFilterChain.getWebFilters().filter(this::isX509Filter).blockFirst();
|
||||
|
||||
assertThat(x509WebFilter).isNotNull();
|
||||
}
|
||||
|
||||
private boolean isX509Filter(WebFilter filter) {
|
||||
try {
|
||||
Object converter = ReflectionTestUtils.getField(filter, "authenticationConverter");
|
||||
return converter.getClass().isAssignableFrom(ServerX509AuthenticationConverter.class);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// field doesn't exist
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private <T extends WebFilter> Optional<T> getWebFilter(SecurityWebFilterChain filterChain, Class<T> filterClass) {
|
||||
return (Optional<T>) filterChain.getWebFilters()
|
||||
.filter(Objects::nonNull)
|
||||
|
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2002-2019 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.server.authentication;
|
||||
|
||||
import org.springframework.security.authentication.AccountStatusUserDetailsChecker;
|
||||
import org.springframework.security.authentication.ReactiveAuthenticationManager;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UserDetailsChecker;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* Reactive version of {@link org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider}
|
||||
*
|
||||
* This manager receives a {@link PreAuthenticatedAuthenticationToken}, checks that associated account is not disabled,
|
||||
* expired, or blocked, and returns new authenticated {@link PreAuthenticatedAuthenticationToken}.
|
||||
*
|
||||
* If no {@link UserDetailsChecker} is provided, a default {@link AccountStatusUserDetailsChecker} will be
|
||||
* created.
|
||||
*
|
||||
* @author Alexey Nesterov
|
||||
* @since 5.2
|
||||
*/
|
||||
public class ReactivePreAuthenticatedAuthenticationManager
|
||||
implements ReactiveAuthenticationManager {
|
||||
|
||||
private final ReactiveUserDetailsService userDetailsService;
|
||||
private final UserDetailsChecker userDetailsChecker;
|
||||
|
||||
public ReactivePreAuthenticatedAuthenticationManager(ReactiveUserDetailsService userDetailsService) {
|
||||
this(userDetailsService, new AccountStatusUserDetailsChecker());
|
||||
}
|
||||
|
||||
public ReactivePreAuthenticatedAuthenticationManager(
|
||||
ReactiveUserDetailsService userDetailsService,
|
||||
UserDetailsChecker userDetailsChecker) {
|
||||
this.userDetailsService = userDetailsService;
|
||||
this.userDetailsChecker = userDetailsChecker;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Authentication> authenticate(Authentication authentication) {
|
||||
return Mono.just(authentication)
|
||||
.filter(this::supports)
|
||||
.map(Authentication::getName)
|
||||
.flatMap(userDetailsService::findByUsername)
|
||||
.switchIfEmpty(Mono.error(() -> new UsernameNotFoundException("User not found")))
|
||||
.doOnNext(userDetailsChecker::check)
|
||||
.map(ud -> {
|
||||
PreAuthenticatedAuthenticationToken result = new PreAuthenticatedAuthenticationToken(
|
||||
ud, authentication.getCredentials(), ud.getAuthorities());
|
||||
result.setDetails(authentication.getDetails());
|
||||
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
private boolean supports(Authentication authentication) {
|
||||
return PreAuthenticatedAuthenticationToken.class.isAssignableFrom(authentication.getClass());
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2002-2018 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.server.authentication;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.http.server.reactive.SslInfo;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
|
||||
import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
/**
|
||||
* Converts from a {@link SslInfo} provided by a request to an {@link PreAuthenticatedAuthenticationToken} that can be authenticated.
|
||||
*
|
||||
* @author Alexey Nesterov
|
||||
* @since 5.2
|
||||
*/
|
||||
public class ServerX509AuthenticationConverter implements ServerAuthenticationConverter {
|
||||
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
private final X509PrincipalExtractor principalExtractor;
|
||||
|
||||
public ServerX509AuthenticationConverter(@NonNull X509PrincipalExtractor principalExtractor) {
|
||||
this.principalExtractor = principalExtractor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Authentication> convert(ServerWebExchange exchange) {
|
||||
SslInfo sslInfo = exchange.getRequest().getSslInfo();
|
||||
if (sslInfo == null) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("No SslInfo provided with a request, skipping x509 authentication");
|
||||
}
|
||||
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
if (sslInfo.getPeerCertificates() == null || sslInfo.getPeerCertificates().length == 0) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("No peer certificates found in SslInfo, skipping x509 authentication");
|
||||
}
|
||||
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
X509Certificate clientCertificate = sslInfo.getPeerCertificates()[0];
|
||||
Object principal = this.principalExtractor.extractPrincipal(clientCertificate);
|
||||
|
||||
PreAuthenticatedAuthenticationToken authRequest = new PreAuthenticatedAuthenticationToken(
|
||||
principal, clientCertificate);
|
||||
|
||||
return Mono.just(authRequest);
|
||||
}
|
||||
}
|
@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 2002-2019 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.server.authentication;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.security.authentication.AccountExpiredException;
|
||||
import org.springframework.security.authentication.CredentialsExpiredException;
|
||||
import org.springframework.security.authentication.DisabledException;
|
||||
import org.springframework.security.authentication.LockedException;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* @author Alexey Nesterov
|
||||
* @since 5.2
|
||||
*/
|
||||
public class ReactivePreAuthenticatedAuthenticationManagerTest {
|
||||
|
||||
private ReactiveUserDetailsService mockUserDetailsService
|
||||
= mock(ReactiveUserDetailsService.class);
|
||||
|
||||
private ReactivePreAuthenticatedAuthenticationManager manager
|
||||
= new ReactivePreAuthenticatedAuthenticationManager(mockUserDetailsService);
|
||||
|
||||
private final User validAccount = new User("valid", "", Collections.emptySet());
|
||||
private final User nonExistingAccount = new User("non existing", "", Collections.emptySet());
|
||||
private final User disabledAccount = new User("disabled", "", false, true, true, true, Collections.emptySet());
|
||||
private final User expiredAccount = new User("expired", "", true, false, true, true, Collections.emptySet());
|
||||
private final User accountWithExpiredCredentials = new User("credentials expired", "", true, true, false, true, Collections.emptySet());
|
||||
private final User lockedAccount = new User("locked", "", true, true, true, false, Collections.emptySet());
|
||||
|
||||
@Test
|
||||
public void returnsAuthenticatedTokenForValidAccount() {
|
||||
when(mockUserDetailsService.findByUsername(anyString())).thenReturn(Mono.just(validAccount));
|
||||
|
||||
Authentication authentication = manager.authenticate(tokenForUser(validAccount.getUsername())).block();
|
||||
assertThat(authentication.isAuthenticated()).isEqualTo(true);
|
||||
}
|
||||
|
||||
@Test(expected = UsernameNotFoundException.class)
|
||||
public void returnsNullForNonExistingAccount() {
|
||||
when(mockUserDetailsService.findByUsername(anyString())).thenReturn(Mono.empty());
|
||||
|
||||
manager.authenticate(tokenForUser(nonExistingAccount.getUsername())).block();
|
||||
}
|
||||
|
||||
@Test(expected = LockedException.class)
|
||||
public void throwsExceptionForLockedAccount() {
|
||||
when(mockUserDetailsService.findByUsername(anyString())).thenReturn(Mono.just(lockedAccount));
|
||||
|
||||
manager.authenticate(tokenForUser(lockedAccount.getUsername())).block();
|
||||
}
|
||||
|
||||
@Test(expected = DisabledException.class)
|
||||
public void throwsExceptionForDisabledAccount() {
|
||||
when(mockUserDetailsService.findByUsername(anyString())).thenReturn(Mono.just(disabledAccount));
|
||||
|
||||
manager.authenticate(tokenForUser(disabledAccount.getUsername())).block();
|
||||
}
|
||||
|
||||
@Test(expected = AccountExpiredException.class)
|
||||
public void throwsExceptionForExpiredAccount() {
|
||||
when(mockUserDetailsService.findByUsername(anyString())).thenReturn(Mono.just(expiredAccount));
|
||||
|
||||
manager.authenticate(tokenForUser(expiredAccount.getUsername())).block();
|
||||
}
|
||||
|
||||
|
||||
@Test(expected = CredentialsExpiredException.class)
|
||||
public void throwsExceptionForAccountWithExpiredCredentials() {
|
||||
when(mockUserDetailsService.findByUsername(anyString())).thenReturn(Mono.just(accountWithExpiredCredentials));
|
||||
|
||||
manager.authenticate(tokenForUser(accountWithExpiredCredentials.getUsername())).block();
|
||||
}
|
||||
|
||||
private Authentication tokenForUser(String username) {
|
||||
return new PreAuthenticatedAuthenticationToken(username, null);
|
||||
}
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2002-2018 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.server.authentication;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.springframework.http.server.reactive.SslInfo;
|
||||
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
|
||||
import org.springframework.mock.web.server.MockServerWebExchange;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor;
|
||||
import org.springframework.security.web.authentication.preauth.x509.X509TestUtils;
|
||||
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class ServerX509AuthenticationConverterTests {
|
||||
|
||||
@Mock
|
||||
private X509PrincipalExtractor principalExtractor;
|
||||
|
||||
@InjectMocks
|
||||
private ServerX509AuthenticationConverter converter;
|
||||
|
||||
private X509Certificate certificate;
|
||||
|
||||
private MockServerHttpRequest.BaseBuilder<?> request;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
request = MockServerHttpRequest.get("/");
|
||||
|
||||
certificate = X509TestUtils.buildTestCertificate();
|
||||
when(principalExtractor.extractPrincipal(any())).thenReturn("Luke Taylor");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnNullForInvalidCertificate() {
|
||||
Authentication authentication = converter.convert(MockServerWebExchange.from(request.build())).block();
|
||||
|
||||
assertThat(authentication).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnAuthenticationForValidCertificate() {
|
||||
request.sslInfo(new MockSslInfo(certificate));
|
||||
|
||||
Authentication authentication = converter.convert(MockServerWebExchange.from(request.build())).block();
|
||||
|
||||
assertThat(authentication.getName()).isEqualTo("Luke Taylor");
|
||||
assertThat(authentication.getCredentials()).isEqualTo(certificate);
|
||||
}
|
||||
|
||||
class MockSslInfo implements SslInfo {
|
||||
|
||||
private final X509Certificate[] peerCertificates;
|
||||
|
||||
MockSslInfo(X509Certificate... peerCertificates) {
|
||||
this.peerCertificates = peerCertificates;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSessionId() {
|
||||
return "mock-session-id";
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Certificate[] getPeerCertificates() {
|
||||
return this.peerCertificates;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user