mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-07-08 19:42:48 +00:00
Detect UserDetailsService bean in X509 configuration
Closes gh-11174
This commit is contained in:
parent
9dd393cb9c
commit
736f439bb5
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2018 the original author or authors.
|
* Copyright 2002-2022 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -18,15 +18,19 @@ package org.springframework.security.config.annotation.web.configurers;
|
|||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.security.authentication.AuthenticationDetailsSource;
|
import org.springframework.security.authentication.AuthenticationDetailsSource;
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
|
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
|
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
|
||||||
import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper;
|
import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper;
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||||
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint;
|
import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint;
|
||||||
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider;
|
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider;
|
||||||
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
|
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
|
||||||
@ -141,7 +145,9 @@ public final class X509Configurer<H extends HttpSecurityBuilder<H>>
|
|||||||
/**
|
/**
|
||||||
* Specifies the {@link AuthenticationUserDetailsService} to use. If not specified,
|
* Specifies the {@link AuthenticationUserDetailsService} to use. If not specified,
|
||||||
* the shared {@link UserDetailsService} will be used to create a
|
* the shared {@link UserDetailsService} will be used to create a
|
||||||
* {@link UserDetailsByNameServiceWrapper}.
|
* {@link UserDetailsByNameServiceWrapper}. If a {@link SecurityFilterChain} bean is
|
||||||
|
* used instead of the {@link WebSecurityConfigurerAdapter}, then the
|
||||||
|
* {@link UserDetailsService} bean will be used by default.
|
||||||
* @param authenticationUserDetailsService the
|
* @param authenticationUserDetailsService the
|
||||||
* {@link AuthenticationUserDetailsService} to use
|
* {@link AuthenticationUserDetailsService} to use
|
||||||
* @return the {@link X509Configurer} for further customizations
|
* @return the {@link X509Configurer} for further customizations
|
||||||
@ -200,9 +206,30 @@ public final class X509Configurer<H extends HttpSecurityBuilder<H>>
|
|||||||
private AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> getAuthenticationUserDetailsService(
|
private AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> getAuthenticationUserDetailsService(
|
||||||
H http) {
|
H http) {
|
||||||
if (this.authenticationUserDetailsService == null) {
|
if (this.authenticationUserDetailsService == null) {
|
||||||
userDetailsService(http.getSharedObject(UserDetailsService.class));
|
userDetailsService(getSharedOrBean(http, UserDetailsService.class));
|
||||||
}
|
}
|
||||||
return this.authenticationUserDetailsService;
|
return this.authenticationUserDetailsService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private <C> C getSharedOrBean(H http, Class<C> type) {
|
||||||
|
C shared = http.getSharedObject(type);
|
||||||
|
if (shared != null) {
|
||||||
|
return shared;
|
||||||
|
}
|
||||||
|
return getBeanOrNull(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> T getBeanOrNull(Class<T> type) {
|
||||||
|
ApplicationContext context = getBuilder().getSharedObject(ApplicationContext.class);
|
||||||
|
if (context == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return context.getBean(type);
|
||||||
|
}
|
||||||
|
catch (NoSuchBeanDefinitionException ex) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2019 the original author or authors.
|
* Copyright 2002-2022 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -34,10 +34,15 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe
|
|||||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||||
import org.springframework.security.config.test.SpringTestContext;
|
import org.springframework.security.config.test.SpringTestContext;
|
||||||
import org.springframework.security.config.test.SpringTestContextExtension;
|
import org.springframework.security.config.test.SpringTestContextExtension;
|
||||||
|
import org.springframework.security.core.userdetails.User;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||||
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter;
|
import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter;
|
||||||
import org.springframework.test.web.servlet.MockMvc;
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
|
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.spy;
|
import static org.mockito.Mockito.spy;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.springframework.security.config.Customizer.withDefaults;
|
import static org.springframework.security.config.Customizer.withDefaults;
|
||||||
@ -95,6 +100,26 @@ public class X509ConfigurerTests {
|
|||||||
// @formatter:on
|
// @formatter:on
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void x509WhenUserDetailsServiceNotConfiguredThenUsesBean() throws Exception {
|
||||||
|
this.spring.register(UserDetailsServiceBeanConfig.class).autowire();
|
||||||
|
X509Certificate certificate = loadCert("rod.cer");
|
||||||
|
// @formatter:off
|
||||||
|
this.mvc.perform(get("/").with(x509(certificate)))
|
||||||
|
.andExpect(authenticated().withUsername("rod"));
|
||||||
|
// @formatter:on
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void x509WhenUserDetailsServiceAndBeanConfiguredThenDoesNotUseBean() throws Exception {
|
||||||
|
this.spring.register(UserDetailsServiceAndBeanConfig.class).autowire();
|
||||||
|
X509Certificate certificate = loadCert("rod.cer");
|
||||||
|
// @formatter:off
|
||||||
|
this.mvc.perform(get("/").with(x509(certificate)))
|
||||||
|
.andExpect(authenticated().withUsername("rod"));
|
||||||
|
// @formatter:on
|
||||||
|
}
|
||||||
|
|
||||||
private <T extends Certificate> T loadCert(String location) {
|
private <T extends Certificate> T loadCert(String location) {
|
||||||
try (InputStream is = new ClassPathResource(location).getInputStream()) {
|
try (InputStream is = new ClassPathResource(location).getInputStream()) {
|
||||||
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
|
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
|
||||||
@ -206,4 +231,59 @@ public class X509ConfigurerTests {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@EnableWebSecurity
|
||||||
|
static class UserDetailsServiceBeanConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||||
|
// @formatter:off
|
||||||
|
http
|
||||||
|
.x509(withDefaults());
|
||||||
|
// @formatter:on
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
UserDetailsService userDetailsService() {
|
||||||
|
// @formatter:off
|
||||||
|
return new InMemoryUserDetailsManager(
|
||||||
|
User.withDefaultPasswordEncoder()
|
||||||
|
.username("rod")
|
||||||
|
.password("password")
|
||||||
|
.roles("USER", "ADMIN")
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
// @formatter:on
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@EnableWebSecurity
|
||||||
|
static class UserDetailsServiceAndBeanConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||||
|
// @formatter:off
|
||||||
|
UserDetailsService customUserDetailsService = new InMemoryUserDetailsManager(
|
||||||
|
User.withDefaultPasswordEncoder()
|
||||||
|
.username("rod")
|
||||||
|
.password("password")
|
||||||
|
.roles("USER", "ADMIN")
|
||||||
|
.build());
|
||||||
|
http
|
||||||
|
.x509((x509) -> x509
|
||||||
|
.userDetailsService(customUserDetailsService)
|
||||||
|
);
|
||||||
|
// @formatter:on
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
UserDetailsService userDetailsService() {
|
||||||
|
// @formatter:off
|
||||||
|
return mock(UserDetailsService.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user