Create spring-boot-security module

This commit is contained in:
Andy Wilkinson
2025-04-23 11:20:00 +01:00
committed by Phillip Webb
parent 63255f3c62
commit f53b9786e5
123 changed files with 313 additions and 461 deletions

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2012-2025 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.boot.security.autoconfigure;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Conditional;
/**
* {@link Conditional @Conditional} that only matches when web security is available and
* the user has not defined their own configuration.
*
* @author Phillip Webb
* @since 4.0.0
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(DefaultWebSecurityCondition.class)
public @interface ConditionalOnDefaultWebSecurity {
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2012-2025 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.boot.security.autoconfigure;
import org.springframework.boot.autoconfigure.condition.AllNestedConditions;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Condition;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
/**
* {@link Condition} for
* {@link ConditionalOnDefaultWebSecurity @ConditionalOnDefaultWebSecurity}.
*
* @author Phillip Webb
*/
class DefaultWebSecurityCondition extends AllNestedConditions {
DefaultWebSecurityCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@ConditionalOnClass({ SecurityFilterChain.class, HttpSecurity.class })
static class Classes {
}
@ConditionalOnMissingBean({ SecurityFilterChain.class })
static class Beans {
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2012-2025 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.boot.security.autoconfigure;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.data.repository.query.SecurityEvaluationContextExtension;
/**
* Automatically adds Spring Security's integration with Spring Data.
*
* @author Rob Winch
* @since 4.0.0
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(SecurityEvaluationContextExtension.class)
public class SecurityDataConfiguration {
@Bean
@ConditionalOnMissingBean
public SecurityEvaluationContextExtension securityEvaluationContextExtension() {
return new SecurityEvaluationContextExtension();
}
}

View File

@@ -0,0 +1,161 @@
/*
* Copyright 2012-2025 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.boot.security.autoconfigure;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.DispatcherType;
import org.springframework.boot.web.servlet.filter.OrderedFilter;
import org.springframework.core.Ordered;
import org.springframework.util.StringUtils;
/**
* Configuration properties for Spring Security.
*
* @author Dave Syer
* @author Andy Wilkinson
* @author Madhura Bhave
* @since 4.0.0
*/
@ConfigurationProperties("spring.security")
public class SecurityProperties {
/**
* Order applied to the {@code SecurityFilterChain} that is used to configure basic
* authentication for application endpoints. Create your own
* {@code SecurityFilterChain} if you want to add your own authentication for all or
* some of those endpoints.
*/
public static final int BASIC_AUTH_ORDER = Ordered.LOWEST_PRECEDENCE - 5;
/**
* Order applied to the {@code WebSecurityCustomizer} that ignores standard static
* resource paths.
* @deprecated since 3.5.0 for removal in 4.0.0 since Spring Security no longer
* recommends using the {@code .ignoring()} method
*/
@Deprecated(since = "3.5.0", forRemoval = true)
public static final int IGNORED_ORDER = Ordered.HIGHEST_PRECEDENCE;
/**
* Default order of Spring Security's Filter in the servlet container (i.e. amongst
* other filters registered with the container). There is no connection between this
* and the {@code @Order} on a {@code SecurityFilterChain}.
*/
public static final int DEFAULT_FILTER_ORDER = OrderedFilter.REQUEST_WRAPPER_FILTER_MAX_ORDER - 100;
private final Filter filter = new Filter();
private final User user = new User();
public User getUser() {
return this.user;
}
public Filter getFilter() {
return this.filter;
}
public static class Filter {
/**
* Security filter chain order for Servlet-based web applications.
*/
private int order = DEFAULT_FILTER_ORDER;
/**
* Security filter chain dispatcher types for Servlet-based web applications.
*/
private Set<DispatcherType> dispatcherTypes = EnumSet.allOf(DispatcherType.class);
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
public Set<DispatcherType> getDispatcherTypes() {
return this.dispatcherTypes;
}
public void setDispatcherTypes(Set<DispatcherType> dispatcherTypes) {
this.dispatcherTypes = dispatcherTypes;
}
}
public static class User {
/**
* Default user name.
*/
private String name = "user";
/**
* Password for the default user name.
*/
private String password = UUID.randomUUID().toString();
/**
* Granted roles for the default user name.
*/
private List<String> roles = new ArrayList<>();
private boolean passwordGenerated = true;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
if (!StringUtils.hasLength(password)) {
return;
}
this.passwordGenerated = false;
this.password = password;
}
public List<String> getRoles() {
return this.roles;
}
public void setRoles(List<String> roles) {
this.roles = new ArrayList<>(roles);
}
public boolean isPasswordGenerated() {
return this.passwordGenerated;
}
}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright 2012-2025 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.boot.security.autoconfigure;
import java.util.Arrays;
import java.util.stream.Stream;
/**
* Common locations for static resources.
*
* @author Phillip Webb
* @since 2.0.0
*/
public enum StaticResourceLocation {
/**
* Resources under {@code "/css"}.
*/
CSS("/css/**"),
/**
* Resources under {@code "/js"}.
*/
JAVA_SCRIPT("/js/**"),
/**
* Resources under {@code "/images"}.
*/
IMAGES("/images/**"),
/**
* Resources under {@code "/webjars"}.
*/
WEB_JARS("/webjars/**"),
/**
* The {@code "favicon.ico"} resource.
*/
FAVICON("/favicon.*", "/*/icon-*");
private final String[] patterns;
StaticResourceLocation(String... patterns) {
this.patterns = patterns;
}
public Stream<String> getPatterns() {
return Arrays.stream(this.patterns);
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2012-2025 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.
*/
/**
* Auto-configuration for Spring Security.
*/
package org.springframework.boot.security.autoconfigure;

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2012-2025 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.boot.security.autoconfigure.reactive;
import org.springframework.boot.security.autoconfigure.StaticResourceLocation;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
/**
* Factory that can be used to create a {@link ServerWebExchangeMatcher} for commonly used
* paths.
*
* @author Madhura Bhave
* @since 4.0.0
*/
public final class PathRequest {
private PathRequest() {
}
/**
* Returns a {@link StaticResourceRequest} that can be used to create a matcher for
* {@link StaticResourceLocation locations}.
* @return a {@link StaticResourceRequest}
*/
public static StaticResourceRequest toStaticResources() {
return StaticResourceRequest.INSTANCE;
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright 2012-2025 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.boot.security.autoconfigure.reactive;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.security.autoconfigure.SecurityProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.WebFilterChainProxy;
import org.springframework.web.reactive.config.WebFluxConfigurer;
/**
* {@link EnableAutoConfiguration Auto-configuration} for Spring Security in a reactive
* application. Switches on {@link EnableWebFluxSecurity @EnableWebFluxSecurity} for a
* reactive web application if this annotation has not been added by the user. It
* delegates to Spring Security's content-negotiation mechanism for authentication. This
* configuration also backs off if a bean of type {@link WebFilterChainProxy} has been
* configured in any other way.
*
* @author Madhura Bhave
* @since 4.0.0
*/
@AutoConfiguration
@EnableConfigurationProperties(SecurityProperties.class)
@ConditionalOnClass({ Flux.class, EnableWebFluxSecurity.class, WebFilterChainProxy.class, WebFluxConfigurer.class })
public class ReactiveSecurityAutoConfiguration {
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@Configuration(proxyBeanMethods = false)
static class SpringBootWebFluxSecurityConfiguration {
@Bean
@ConditionalOnMissingBean({ ReactiveAuthenticationManager.class, ReactiveUserDetailsService.class,
SecurityWebFilterChain.class })
ReactiveAuthenticationManager denyAllAuthenticationManager() {
return (authentication) -> Mono.error(new UsernameNotFoundException(authentication.getName()));
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(WebFilterChainProxy.class)
@EnableWebFluxSecurity
static class EnableWebFluxSecurityConfiguration {
}
}
}

View File

@@ -0,0 +1,144 @@
/*
* Copyright 2012-2025 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.boot.security.autoconfigure.reactive;
import java.util.List;
import java.util.regex.Pattern;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.security.autoconfigure.SecurityProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authentication.ReactiveAuthenticationManagerResolver;
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.util.StringUtils;
/**
* Default user {@link Configuration @Configuration} for a reactive web application.
* Configures a {@link ReactiveUserDetailsService} with a default user and generated
* password. This backs-off completely if there is a bean of type
* {@link ReactiveUserDetailsService}, {@link ReactiveAuthenticationManager}, or
* {@link ReactiveAuthenticationManagerResolver}.
*
* @author Madhura Bhave
* @since 4.0.0
*/
@AutoConfiguration(before = ReactiveSecurityAutoConfiguration.class,
afterName = "org.springframework.boot.rsocket.autoconfigure.RSocketMessagingAutoConfiguration")
@ConditionalOnClass({ ReactiveAuthenticationManager.class })
@ConditionalOnMissingBean(
value = { ReactiveAuthenticationManager.class, ReactiveUserDetailsService.class,
ReactiveAuthenticationManagerResolver.class },
type = { "org.springframework.security.oauth2.jwt.ReactiveJwtDecoder" })
@Conditional({ ReactiveUserDetailsServiceAutoConfiguration.RSocketEnabledOrReactiveWebApplication.class,
ReactiveUserDetailsServiceAutoConfiguration.MissingAlternativeOrUserPropertiesConfigured.class })
@EnableConfigurationProperties(SecurityProperties.class)
public class ReactiveUserDetailsServiceAutoConfiguration {
private static final String NOOP_PASSWORD_PREFIX = "{noop}";
private static final Pattern PASSWORD_ALGORITHM_PATTERN = Pattern.compile("^\\{.+}.*$");
private static final Log logger = LogFactory.getLog(ReactiveUserDetailsServiceAutoConfiguration.class);
@Bean
public MapReactiveUserDetailsService reactiveUserDetailsService(SecurityProperties properties,
ObjectProvider<PasswordEncoder> passwordEncoder) {
SecurityProperties.User user = properties.getUser();
UserDetails userDetails = getUserDetails(user, getOrDeducePassword(user, passwordEncoder.getIfAvailable()));
return new MapReactiveUserDetailsService(userDetails);
}
private UserDetails getUserDetails(SecurityProperties.User user, String password) {
List<String> roles = user.getRoles();
return User.withUsername(user.getName()).password(password).roles(StringUtils.toStringArray(roles)).build();
}
private String getOrDeducePassword(SecurityProperties.User user, PasswordEncoder encoder) {
String password = user.getPassword();
if (user.isPasswordGenerated()) {
logger.info(String.format("%n%nUsing generated security password: %s%n", user.getPassword()));
}
if (encoder != null || PASSWORD_ALGORITHM_PATTERN.matcher(password).matches()) {
return password;
}
return NOOP_PASSWORD_PREFIX + password;
}
static class RSocketEnabledOrReactiveWebApplication extends AnyNestedCondition {
RSocketEnabledOrReactiveWebApplication() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@ConditionalOnBean(RSocketMessageHandler.class)
static class RSocketSecurityEnabledCondition {
}
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
static class ReactiveWebApplicationCondition {
}
}
static final class MissingAlternativeOrUserPropertiesConfigured extends AnyNestedCondition {
MissingAlternativeOrUserPropertiesConfigured() {
super(ConfigurationPhase.PARSE_CONFIGURATION);
}
@ConditionalOnMissingClass({
"org.springframework.security.oauth2.client.registration.ClientRegistrationRepository",
"org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector" })
static final class MissingAlternative {
}
@ConditionalOnProperty("spring.security.user.name")
static final class NameConfigured {
}
@ConditionalOnProperty("spring.security.user.password")
static final class PasswordConfigured {
}
}
}

View File

@@ -0,0 +1,139 @@
/*
* Copyright 2012-2025 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.boot.security.autoconfigure.reactive;
import java.util.EnumSet;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.stream.Stream;
import reactor.core.publisher.Mono;
import org.springframework.boot.security.autoconfigure.StaticResourceLocation;
import org.springframework.security.web.server.util.matcher.OrServerWebExchangeMatcher;
import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
import org.springframework.util.Assert;
import org.springframework.web.server.ServerWebExchange;
/**
* Used to create a {@link ServerWebExchangeMatcher} for static resources in commonly used
* locations. Returned by {@link PathRequest#toStaticResources()}.
*
* @author Madhura Bhave
* @since 4.0.0
* @see PathRequest
*/
public final class StaticResourceRequest {
static final StaticResourceRequest INSTANCE = new StaticResourceRequest();
private StaticResourceRequest() {
}
/**
* Returns a matcher that includes all commonly used {@link StaticResourceLocation
* Locations}. The
* {@link StaticResourceServerWebExchange#excluding(StaticResourceLocation, StaticResourceLocation...)
* excluding} method can be used to remove specific locations if required. For
* example: <pre class="code">
* PathRequest.toStaticResources().atCommonLocations().excluding(StaticResourceLocation.CSS)
* </pre>
* @return the configured {@link ServerWebExchangeMatcher}
*/
public StaticResourceServerWebExchange atCommonLocations() {
return at(EnumSet.allOf(StaticResourceLocation.class));
}
/**
* Returns a matcher that includes the specified {@link StaticResourceLocation
* Locations}. For example: <pre class="code">
* PathRequest.toStaticResources().at(StaticResourceLocation.CSS, StaticResourceLocation.JAVA_SCRIPT)
* </pre>
* @param first the first location to include
* @param rest additional locations to include
* @return the configured {@link ServerWebExchangeMatcher}
*/
public StaticResourceServerWebExchange at(StaticResourceLocation first, StaticResourceLocation... rest) {
return at(EnumSet.of(first, rest));
}
/**
* Returns a matcher that includes the specified {@link StaticResourceLocation
* Locations}. For example: <pre class="code">
* PathRequest.toStaticResources().at(locations)
* </pre>
* @param locations the locations to include
* @return the configured {@link ServerWebExchangeMatcher}
*/
public StaticResourceServerWebExchange at(Set<StaticResourceLocation> locations) {
Assert.notNull(locations, "'locations' must not be null");
return new StaticResourceServerWebExchange(new LinkedHashSet<>(locations));
}
/**
* The server web exchange matcher used to match against resource
* {@link StaticResourceLocation locations}.
*/
public static final class StaticResourceServerWebExchange implements ServerWebExchangeMatcher {
private final Set<StaticResourceLocation> locations;
private StaticResourceServerWebExchange(Set<StaticResourceLocation> locations) {
this.locations = locations;
}
/**
* Return a new {@link StaticResourceServerWebExchange} based on this one but
* excluding the specified locations.
* @param first the first location to exclude
* @param rest additional locations to exclude
* @return a new {@link StaticResourceServerWebExchange}
*/
public StaticResourceServerWebExchange excluding(StaticResourceLocation first, StaticResourceLocation... rest) {
return excluding(EnumSet.of(first, rest));
}
/**
* Return a new {@link StaticResourceServerWebExchange} based on this one but
* excluding the specified locations.
* @param locations the locations to exclude
* @return a new {@link StaticResourceServerWebExchange}
*/
public StaticResourceServerWebExchange excluding(Set<StaticResourceLocation> locations) {
Assert.notNull(locations, "'locations' must not be null");
Set<StaticResourceLocation> subset = new LinkedHashSet<>(this.locations);
subset.removeAll(locations);
return new StaticResourceServerWebExchange(subset);
}
private Stream<String> getPatterns() {
return this.locations.stream().flatMap(StaticResourceLocation::getPatterns);
}
@Override
public Mono<MatchResult> matches(ServerWebExchange exchange) {
return new OrServerWebExchangeMatcher(getDelegateMatchers().toList()).matches(exchange);
}
private Stream<ServerWebExchangeMatcher> getDelegateMatchers() {
return getPatterns().map(PathPatternParserServerWebExchangeMatcher::new);
}
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2012-2025 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.
*/
/**
* Auto-configuration for reactive Spring Security.
*/
package org.springframework.boot.security.autoconfigure.reactive;

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2012-2025 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.boot.security.autoconfigure.rsocket;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.rsocket.autoconfigure.RSocketMessageHandlerCustomizer;
import org.springframework.boot.rsocket.server.RSocketServerCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.rsocket.EnableRSocketSecurity;
import org.springframework.security.messaging.handler.invocation.reactive.AuthenticationPrincipalArgumentResolver;
import org.springframework.security.rsocket.core.SecuritySocketAcceptorInterceptor;
/**
* {@link EnableAutoConfiguration Auto-configuration} for Spring Security for an RSocket
* server.
*
* @author Madhura Bhave
* @author Brian Clozel
* @author Guirong Hu
* @since 4.0.0
*/
@AutoConfiguration
@EnableRSocketSecurity
@ConditionalOnClass({ RSocketServerCustomizer.class, SecuritySocketAcceptorInterceptor.class })
public class RSocketSecurityAutoConfiguration {
@Bean
RSocketServerCustomizer springSecurityRSocketSecurity(SecuritySocketAcceptorInterceptor interceptor) {
return (server) -> server.interceptors((registry) -> registry.forSocketAcceptor(interceptor));
}
@ConditionalOnClass(AuthenticationPrincipalArgumentResolver.class)
@Configuration(proxyBeanMethods = false)
static class RSocketSecurityMessageHandlerConfiguration {
@Bean
RSocketMessageHandlerCustomizer rSocketAuthenticationPrincipalMessageHandlerCustomizer() {
return (messageHandler) -> messageHandler.getArgumentResolverConfigurer()
.addCustomResolver(new AuthenticationPrincipalArgumentResolver());
}
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2012-2025 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.
*/
/**
* Auto-configuration for RSocket support in Spring Security.
*/
package org.springframework.boot.security.autoconfigure.rsocket;

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2012-2025 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.boot.security.autoconfigure.saml2;
import java.util.Collections;
import java.util.Map;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.security.autoconfigure.saml2.Saml2RelyingPartyProperties.Registration;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
/**
* Condition that matches if any {@code spring.security.saml2.relyingparty.registration}
* properties are defined.
*
* @author Madhura Bhave
* @author Phillip Webb
*/
class RegistrationConfiguredCondition extends SpringBootCondition {
private static final String PROPERTY = "spring.security.saml2.relyingparty.registration";
private static final Bindable<Map<String, Registration>> STRING_REGISTRATION_MAP = Bindable.mapOf(String.class,
Registration.class);
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConditionMessage.Builder message = ConditionMessage.forCondition("Relying Party Registration Condition");
Map<String, Registration> registrations = getRegistrations(context.getEnvironment());
if (registrations.isEmpty()) {
return ConditionOutcome.noMatch(message.didNotFind("any registrations").atAll());
}
return ConditionOutcome.match(message.found("registration", "registrations").items(registrations.keySet()));
}
private Map<String, Registration> getRegistrations(Environment environment) {
return Binder.get(environment).bind(PROPERTY, STRING_REGISTRATION_MAP).orElse(Collections.emptyMap());
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2012-2025 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.boot.security.autoconfigure.saml2;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.security.autoconfigure.ConditionalOnDefaultWebSecurity;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
import org.springframework.security.web.SecurityFilterChain;
import static org.springframework.security.config.Customizer.withDefaults;
/**
* {@link SecurityFilterChain} configuration for Spring Security's relying party SAML
* support.
*
* @author Madhura Bhave
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnDefaultWebSecurity
@ConditionalOnBean(RelyingPartyRegistrationRepository.class)
class Saml2LoginConfiguration {
@Bean
SecurityFilterChain samlSecurityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests((requests) -> requests.anyRequest().authenticated());
http.saml2Login(withDefaults());
http.saml2Logout(withDefaults());
return http.build();
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2012-2025 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.boot.security.autoconfigure.saml2;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.security.autoconfigure.servlet.SecurityAutoConfiguration;
import org.springframework.context.annotation.Import;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
/**
* {@link EnableAutoConfiguration Auto-configuration} for Spring Security's SAML 2.0
* authentication support.
*
* @author Madhura Bhave
* @since 4.0.0
*/
@AutoConfiguration(before = SecurityAutoConfiguration.class)
@ConditionalOnClass(RelyingPartyRegistrationRepository.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@Import({ Saml2RelyingPartyRegistrationConfiguration.class, Saml2LoginConfiguration.class })
@EnableConfigurationProperties(Saml2RelyingPartyProperties.class)
public class Saml2RelyingPartyAutoConfiguration {
}

View File

@@ -0,0 +1,430 @@
/*
* Copyright 2012-2025 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.boot.security.autoconfigure.saml2;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.io.Resource;
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
/**
* SAML2 relying party properties.
*
* @author Madhura Bhave
* @author Phillip Webb
* @author Moritz Halbritter
* @author Lasse Wulff
* @since 4.0.0
*/
@ConfigurationProperties("spring.security.saml2.relyingparty")
public class Saml2RelyingPartyProperties {
/**
* SAML2 relying party registrations.
*/
private final Map<String, Registration> registration = new LinkedHashMap<>();
public Map<String, Registration> getRegistration() {
return this.registration;
}
/**
* Represents a SAML Relying Party.
*/
public static class Registration {
/**
* Relying party's entity ID. The value may contain a number of placeholders. They
* are "baseUrl", "registrationId", "baseScheme", "baseHost", and "basePort".
*/
private String entityId = "{baseUrl}/saml2/service-provider-metadata/{registrationId}";
/**
* Assertion Consumer Service.
*/
private final Acs acs = new Acs();
private final Signing signing = new Signing();
private final Decryption decryption = new Decryption();
private final Singlelogout singlelogout = new Singlelogout();
/**
* Remote SAML Identity Provider.
*/
private final AssertingParty assertingparty = new AssertingParty();
/**
* Name ID format for a relying party registration.
*/
private String nameIdFormat;
public String getEntityId() {
return this.entityId;
}
public void setEntityId(String entityId) {
this.entityId = entityId;
}
public Acs getAcs() {
return this.acs;
}
public Signing getSigning() {
return this.signing;
}
public Decryption getDecryption() {
return this.decryption;
}
public Singlelogout getSinglelogout() {
return this.singlelogout;
}
public AssertingParty getAssertingparty() {
return this.assertingparty;
}
public String getNameIdFormat() {
return this.nameIdFormat;
}
public void setNameIdFormat(String nameIdFormat) {
this.nameIdFormat = nameIdFormat;
}
public static class Acs {
/**
* Assertion Consumer Service location template. Can generate its location
* based on possible variables of "baseUrl", "registrationId", "baseScheme",
* "baseHost", and "basePort".
*/
private String location = "{baseUrl}/login/saml2/sso/{registrationId}";
/**
* Assertion Consumer Service binding.
*/
private Saml2MessageBinding binding = Saml2MessageBinding.POST;
public String getLocation() {
return this.location;
}
public void setLocation(String location) {
this.location = location;
}
public Saml2MessageBinding getBinding() {
return this.binding;
}
public void setBinding(Saml2MessageBinding binding) {
this.binding = binding;
}
}
public static class Signing {
/**
* Credentials used for signing the SAML authentication request.
*/
private List<Credential> credentials = new ArrayList<>();
public List<Credential> getCredentials() {
return this.credentials;
}
public void setCredentials(List<Credential> credentials) {
this.credentials = credentials;
}
public static class Credential {
/**
* Private key used for signing.
*/
private Resource privateKeyLocation;
/**
* Relying Party X509Certificate shared with the identity provider.
*/
private Resource certificateLocation;
public Resource getPrivateKeyLocation() {
return this.privateKeyLocation;
}
public void setPrivateKeyLocation(Resource privateKey) {
this.privateKeyLocation = privateKey;
}
public Resource getCertificateLocation() {
return this.certificateLocation;
}
public void setCertificateLocation(Resource certificate) {
this.certificateLocation = certificate;
}
}
}
}
public static class Decryption {
/**
* Credentials used for decrypting the SAML authentication request.
*/
private List<Credential> credentials = new ArrayList<>();
public List<Credential> getCredentials() {
return this.credentials;
}
public void setCredentials(List<Credential> credentials) {
this.credentials = credentials;
}
public static class Credential {
/**
* Private key used for decrypting.
*/
private Resource privateKeyLocation;
/**
* Relying Party X509Certificate shared with the identity provider.
*/
private Resource certificateLocation;
public Resource getPrivateKeyLocation() {
return this.privateKeyLocation;
}
public void setPrivateKeyLocation(Resource privateKey) {
this.privateKeyLocation = privateKey;
}
public Resource getCertificateLocation() {
return this.certificateLocation;
}
public void setCertificateLocation(Resource certificate) {
this.certificateLocation = certificate;
}
}
}
/**
* Represents a remote Identity Provider.
*/
public static class AssertingParty {
/**
* Unique identifier for the identity provider.
*/
private String entityId;
/**
* URI to the metadata endpoint for discovery-based configuration.
*/
private String metadataUri;
private final Singlesignon singlesignon = new Singlesignon();
private final Verification verification = new Verification();
private final Singlelogout singlelogout = new Singlelogout();
public String getEntityId() {
return this.entityId;
}
public void setEntityId(String entityId) {
this.entityId = entityId;
}
public String getMetadataUri() {
return this.metadataUri;
}
public void setMetadataUri(String metadataUri) {
this.metadataUri = metadataUri;
}
public Singlesignon getSinglesignon() {
return this.singlesignon;
}
public Verification getVerification() {
return this.verification;
}
public Singlelogout getSinglelogout() {
return this.singlelogout;
}
/**
* Single sign on details for an Identity Provider.
*/
public static class Singlesignon {
/**
* Remote endpoint to send authentication requests to.
*/
private String url;
/**
* Whether to redirect or post authentication requests.
*/
private Saml2MessageBinding binding;
/**
* Whether to sign authentication requests.
*/
private Boolean signRequest;
public String getUrl() {
return this.url;
}
public void setUrl(String url) {
this.url = url;
}
public Saml2MessageBinding getBinding() {
return this.binding;
}
public void setBinding(Saml2MessageBinding binding) {
this.binding = binding;
}
public boolean isSignRequest() {
return this.signRequest;
}
public Boolean getSignRequest() {
return this.signRequest;
}
public void setSignRequest(Boolean signRequest) {
this.signRequest = signRequest;
}
}
/**
* Verification details for an Identity Provider.
*/
public static class Verification {
/**
* Credentials used for verification of incoming SAML messages.
*/
private List<Credential> credentials = new ArrayList<>();
public List<Credential> getCredentials() {
return this.credentials;
}
public void setCredentials(List<Credential> credentials) {
this.credentials = credentials;
}
public static class Credential {
/**
* Locations of the X.509 certificate used for verification of incoming
* SAML messages.
*/
private Resource certificate;
public Resource getCertificateLocation() {
return this.certificate;
}
public void setCertificateLocation(Resource certificate) {
this.certificate = certificate;
}
}
}
}
/**
* Single logout details.
*/
public static class Singlelogout {
/**
* Location where SAML2 LogoutRequest gets sent to.
*/
private String url;
/**
* Location where SAML2 LogoutResponse gets sent to.
*/
private String responseUrl;
/**
* Whether to redirect or post logout requests.
*/
private Saml2MessageBinding binding;
public String getUrl() {
return this.url;
}
public void setUrl(String url) {
this.url = url;
}
public String getResponseUrl() {
return this.responseUrl;
}
public void setResponseUrl(String responseUrl) {
this.responseUrl = responseUrl;
}
public Saml2MessageBinding getBinding() {
return this.binding;
}
public void setBinding(Saml2MessageBinding binding) {
this.binding = binding;
}
}
}

View File

@@ -0,0 +1,200 @@
/*
* Copyright 2012-2025 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.boot.security.autoconfigure.saml2;
import java.io.InputStream;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.security.autoconfigure.saml2.Saml2RelyingPartyProperties.AssertingParty;
import org.springframework.boot.security.autoconfigure.saml2.Saml2RelyingPartyProperties.AssertingParty.Verification;
import org.springframework.boot.security.autoconfigure.saml2.Saml2RelyingPartyProperties.Decryption;
import org.springframework.boot.security.autoconfigure.saml2.Saml2RelyingPartyProperties.Registration;
import org.springframework.boot.security.autoconfigure.saml2.Saml2RelyingPartyProperties.Registration.Signing;
import org.springframework.boot.ssl.pem.PemContent;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.security.saml2.core.Saml2X509Credential;
import org.springframework.security.saml2.core.Saml2X509Credential.Saml2X509CredentialType;
import org.springframework.security.saml2.provider.service.registration.AssertingPartyMetadata;
import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration.Builder;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrations;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* {@link Configuration @Configuration} used to map {@link Saml2RelyingPartyProperties} to
* relying party registrations in a {@link RelyingPartyRegistrationRepository}.
*
* @author Madhura Bhave
* @author Phillip Webb
* @author Moritz Halbritter
* @author Lasse Lindqvist
* @author Lasse Wulff
* @author Scott Frederick
*/
@Configuration(proxyBeanMethods = false)
@Conditional(RegistrationConfiguredCondition.class)
@ConditionalOnMissingBean(RelyingPartyRegistrationRepository.class)
class Saml2RelyingPartyRegistrationConfiguration {
@Bean
RelyingPartyRegistrationRepository relyingPartyRegistrationRepository(Saml2RelyingPartyProperties properties) {
List<RelyingPartyRegistration> registrations = properties.getRegistration()
.entrySet()
.stream()
.map(this::asRegistration)
.toList();
return new InMemoryRelyingPartyRegistrationRepository(registrations);
}
private RelyingPartyRegistration asRegistration(Map.Entry<String, Registration> entry) {
return asRegistration(entry.getKey(), entry.getValue());
}
private RelyingPartyRegistration asRegistration(String id, Registration properties) {
boolean usingMetadata = StringUtils.hasText(properties.getAssertingparty().getMetadataUri());
Builder builder = (!usingMetadata) ? RelyingPartyRegistration.withRegistrationId(id)
: createBuilderUsingMetadata(properties.getAssertingparty()).registrationId(id);
builder.assertionConsumerServiceLocation(properties.getAcs().getLocation());
builder.assertionConsumerServiceBinding(properties.getAcs().getBinding());
builder.assertingPartyMetadata(mapAssertingParty(properties.getAssertingparty()));
builder.signingX509Credentials((credentials) -> properties.getSigning()
.getCredentials()
.stream()
.map(this::asSigningCredential)
.forEach(credentials::add));
builder.decryptionX509Credentials((credentials) -> properties.getDecryption()
.getCredentials()
.stream()
.map(this::asDecryptionCredential)
.forEach(credentials::add));
builder.assertingPartyMetadata(
(details) -> details.verificationX509Credentials((credentials) -> properties.getAssertingparty()
.getVerification()
.getCredentials()
.stream()
.map(this::asVerificationCredential)
.forEach(credentials::add)));
builder.singleLogoutServiceLocation(properties.getSinglelogout().getUrl());
builder.singleLogoutServiceResponseLocation(properties.getSinglelogout().getResponseUrl());
builder.singleLogoutServiceBinding(properties.getSinglelogout().getBinding());
builder.entityId(properties.getEntityId());
builder.nameIdFormat(properties.getNameIdFormat());
RelyingPartyRegistration registration = builder.build();
boolean signRequest = registration.getAssertingPartyMetadata().getWantAuthnRequestsSigned();
validateSigningCredentials(properties, signRequest);
return registration;
}
private RelyingPartyRegistration.Builder createBuilderUsingMetadata(AssertingParty properties) {
String requiredEntityId = properties.getEntityId();
Collection<Builder> candidates = RelyingPartyRegistrations
.collectionFromMetadataLocation(properties.getMetadataUri());
for (RelyingPartyRegistration.Builder candidate : candidates) {
if (requiredEntityId == null || requiredEntityId.equals(getEntityId(candidate))) {
return candidate;
}
}
throw new IllegalStateException("No relying party with Entity ID '" + requiredEntityId + "' found");
}
private Object getEntityId(RelyingPartyRegistration.Builder candidate) {
String[] result = new String[1];
candidate.assertingPartyMetadata((builder) -> result[0] = builder.build().getEntityId());
return result[0];
}
private Consumer<AssertingPartyMetadata.Builder<?>> mapAssertingParty(AssertingParty assertingParty) {
return (details) -> {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(assertingParty::getEntityId).to(details::entityId);
map.from(assertingParty.getSinglesignon()::getBinding).to(details::singleSignOnServiceBinding);
map.from(assertingParty.getSinglesignon()::getUrl).to(details::singleSignOnServiceLocation);
map.from(assertingParty.getSinglesignon()::getSignRequest).to(details::wantAuthnRequestsSigned);
map.from(assertingParty.getSinglelogout()::getUrl).to(details::singleLogoutServiceLocation);
map.from(assertingParty.getSinglelogout()::getResponseUrl).to(details::singleLogoutServiceResponseLocation);
map.from(assertingParty.getSinglelogout()::getBinding).to(details::singleLogoutServiceBinding);
};
}
private void validateSigningCredentials(Registration properties, boolean signRequest) {
if (signRequest) {
Assert.state(!properties.getSigning().getCredentials().isEmpty(),
"Signing credentials must not be empty when authentication requests require signing.");
}
}
private Saml2X509Credential asSigningCredential(Signing.Credential properties) {
RSAPrivateKey privateKey = readPrivateKey(properties.getPrivateKeyLocation());
X509Certificate certificate = readCertificate(properties.getCertificateLocation());
return new Saml2X509Credential(privateKey, certificate, Saml2X509CredentialType.SIGNING);
}
private Saml2X509Credential asDecryptionCredential(Decryption.Credential properties) {
RSAPrivateKey privateKey = readPrivateKey(properties.getPrivateKeyLocation());
X509Certificate certificate = readCertificate(properties.getCertificateLocation());
return new Saml2X509Credential(privateKey, certificate, Saml2X509CredentialType.DECRYPTION);
}
private Saml2X509Credential asVerificationCredential(Verification.Credential properties) {
X509Certificate certificate = readCertificate(properties.getCertificateLocation());
return new Saml2X509Credential(certificate, Saml2X509Credential.Saml2X509CredentialType.ENCRYPTION,
Saml2X509Credential.Saml2X509CredentialType.VERIFICATION);
}
private RSAPrivateKey readPrivateKey(Resource location) {
Assert.state(location != null, "No private key location specified");
Assert.state(location.exists(), () -> "Private key location '" + location + "' does not exist");
try (InputStream inputStream = location.getInputStream()) {
PemContent pemContent = PemContent.load(inputStream);
PrivateKey privateKey = pemContent.getPrivateKey();
Assert.state(privateKey instanceof RSAPrivateKey,
() -> "PrivateKey in resource '" + location + "' must be an RSAPrivateKey");
return (RSAPrivateKey) privateKey;
}
catch (Exception ex) {
throw new IllegalArgumentException(ex);
}
}
private X509Certificate readCertificate(Resource location) {
Assert.state(location != null, "No certificate location specified");
Assert.state(location.exists(), () -> "Certificate location '" + location + "' does not exist");
try (InputStream inputStream = location.getInputStream()) {
PemContent pemContent = PemContent.load(inputStream);
List<X509Certificate> certificates = pemContent.getCertificates();
return certificates.get(0);
}
catch (Exception ex) {
throw new IllegalArgumentException(ex);
}
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2012-2025 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.
*/
/**
* Auto-configuration for Spring Security's SAML 2.0.
*/
package org.springframework.boot.security.autoconfigure.saml2;

View File

@@ -0,0 +1,95 @@
/*
* Copyright 2012-2025 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.boot.security.autoconfigure.servlet;
import java.util.function.Supplier;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.boot.h2console.autoconfigure.H2ConsoleProperties;
import org.springframework.boot.security.autoconfigure.StaticResourceLocation;
import org.springframework.boot.security.servlet.ApplicationContextRequestMatcher;
import org.springframework.boot.web.context.WebServerApplicationContext;
import org.springframework.context.ApplicationContext;
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.web.context.WebApplicationContext;
/**
* Factory that can be used to create a {@link RequestMatcher} for commonly used paths.
*
* @author Madhura Bhave
* @author Phillip Webb
* @since 4.0.0
*/
public final class PathRequest {
private PathRequest() {
}
/**
* Returns a {@link StaticResourceRequest} that can be used to create a matcher for
* {@link StaticResourceLocation locations}.
* @return a {@link StaticResourceRequest}
*/
public static StaticResourceRequest toStaticResources() {
return StaticResourceRequest.INSTANCE;
}
/**
* Returns a matcher that includes the H2 console location. For example:
* <pre class="code">
* PathRequest.toH2Console()
* </pre>
* @return the configured {@link RequestMatcher}
*/
public static H2ConsoleRequestMatcher toH2Console() {
return new H2ConsoleRequestMatcher();
}
/**
* The request matcher used to match against h2 console path.
*/
public static final class H2ConsoleRequestMatcher extends ApplicationContextRequestMatcher<ApplicationContext> {
private volatile RequestMatcher delegate;
private H2ConsoleRequestMatcher() {
super(ApplicationContext.class);
}
@Override
protected boolean ignoreApplicationContext(WebApplicationContext applicationContext) {
return WebServerApplicationContext.hasServerNamespace(applicationContext, "management");
}
@Override
protected void initialized(Supplier<ApplicationContext> context) {
String path = context.get().getBean(H2ConsoleProperties.class).getPath();
Assert.hasText(path, "'path' in H2ConsoleProperties must not be empty");
this.delegate = PathPatternRequestMatcher.withDefaults().matcher(path + "/**");
}
@Override
protected boolean matches(HttpServletRequest request, Supplier<ApplicationContext> context) {
return this.delegate.matches(request);
}
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2012-2025 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.boot.security.autoconfigure.servlet;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.security.autoconfigure.SecurityDataConfiguration;
import org.springframework.boot.security.autoconfigure.SecurityProperties;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
/**
* {@link EnableAutoConfiguration Auto-configuration} for Spring Security.
*
* @author Dave Syer
* @author Andy Wilkinson
* @author Madhura Bhave
* @since 4.0.0
*/
@AutoConfiguration(before = UserDetailsServiceAutoConfiguration.class)
@ConditionalOnClass(DefaultAuthenticationEventPublisher.class)
@EnableConfigurationProperties(SecurityProperties.class)
@Import({ SpringBootWebSecurityConfiguration.class, SecurityDataConfiguration.class })
public class SecurityAutoConfiguration {
@Bean
@ConditionalOnMissingBean(AuthenticationEventPublisher.class)
public DefaultAuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher publisher) {
return new DefaultAuthenticationEventPublisher(publisher);
}
}

View File

@@ -0,0 +1,79 @@
/*
* Copyright 2012-2025 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.boot.security.autoconfigure.servlet;
import java.util.EnumSet;
import java.util.stream.Collectors;
import jakarta.servlet.DispatcherType;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.security.autoconfigure.SecurityProperties;
import org.springframework.boot.web.servlet.DelegatingFilterProxyRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
/**
* {@link EnableAutoConfiguration Auto-configuration} for Spring Security's Filter.
* Configured separately from {@link SpringBootWebSecurityConfiguration} to ensure that
* the filter's order is still configured when a user-provided
* {@link WebSecurityConfiguration} exists.
*
* @author Rob Winch
* @author Phillip Webb
* @author Andy Wilkinson
* @since 4.0.0
*/
@AutoConfiguration(after = SecurityAutoConfiguration.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(SecurityProperties.class)
@ConditionalOnClass({ AbstractSecurityWebApplicationInitializer.class, SessionCreationPolicy.class })
public class SecurityFilterAutoConfiguration {
private static final String DEFAULT_FILTER_NAME = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME;
@Bean
@ConditionalOnBean(name = DEFAULT_FILTER_NAME)
public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(
SecurityProperties securityProperties) {
DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean(
DEFAULT_FILTER_NAME);
registration.setOrder(securityProperties.getFilter().getOrder());
registration.setDispatcherTypes(getDispatcherTypes(securityProperties));
return registration;
}
private EnumSet<DispatcherType> getDispatcherTypes(SecurityProperties securityProperties) {
if (securityProperties.getFilter().getDispatcherTypes() == null) {
return null;
}
return securityProperties.getFilter()
.getDispatcherTypes()
.stream()
.map((type) -> DispatcherType.valueOf(type.name()))
.collect(Collectors.toCollection(() -> EnumSet.noneOf(DispatcherType.class)));
}
}

View File

@@ -0,0 +1,83 @@
/*
* Copyright 2012-2025 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.boot.security.autoconfigure.servlet;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.security.autoconfigure.ConditionalOnDefaultWebSecurity;
import org.springframework.boot.security.autoconfigure.SecurityProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.BeanIds;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import static org.springframework.security.config.Customizer.withDefaults;
/**
* {@link Configuration @Configuration} class securing servlet applications.
*
* @author Madhura Bhave
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
class SpringBootWebSecurityConfiguration {
/**
* The default configuration for web security. It relies on Spring Security's
* content-negotiation strategy to determine what sort of authentication to use. If
* the user specifies their own {@link SecurityFilterChain} bean, this will back-off
* completely and the users should specify all the bits that they want to configure as
* part of the custom security configuration.
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnDefaultWebSecurity
static class SecurityFilterChainConfiguration {
@Bean
@Order(SecurityProperties.BASIC_AUTH_ORDER)
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests((requests) -> requests.anyRequest().authenticated());
http.formLogin(withDefaults());
http.httpBasic(withDefaults());
return http.build();
}
}
/**
* Adds the {@link EnableWebSecurity @EnableWebSecurity} annotation if Spring Security
* is on the classpath. This will make sure that the annotation is present with
* default security auto-configuration and also if the user adds custom security and
* forgets to add the annotation. If {@link EnableWebSecurity @EnableWebSecurity} has
* already been added or if a bean with name
* {@value BeanIds#SPRING_SECURITY_FILTER_CHAIN} has been configured by the user, this
* will back-off.
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(name = BeanIds.SPRING_SECURITY_FILTER_CHAIN)
@ConditionalOnClass(EnableWebSecurity.class)
@EnableWebSecurity
static class WebSecurityEnablerConfiguration {
}
}

View File

@@ -0,0 +1,160 @@
/*
* Copyright 2012-2025 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.boot.security.autoconfigure.servlet;
import java.util.EnumSet;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Stream;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.boot.security.autoconfigure.StaticResourceLocation;
import org.springframework.boot.security.servlet.ApplicationContextRequestMatcher;
import org.springframework.boot.web.context.WebServerApplicationContext;
import org.springframework.boot.webmvc.autoconfigure.DispatcherServletPath;
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.web.context.WebApplicationContext;
/**
* Used to create a {@link RequestMatcher} for static resources in commonly used
* locations. Returned by {@link PathRequest#toStaticResources()}.
*
* @author Madhura Bhave
* @author Phillip Webb
* @since 4.0.0
* @see PathRequest
*/
public final class StaticResourceRequest {
static final StaticResourceRequest INSTANCE = new StaticResourceRequest();
private StaticResourceRequest() {
}
/**
* Returns a matcher that includes all commonly used {@link StaticResourceLocation
* Locations}. The
* {@link StaticResourceRequestMatcher#excluding(StaticResourceLocation, StaticResourceLocation...)
* excluding} method can be used to remove specific locations if required. For
* example: <pre class="code">
* PathRequest.toStaticResources().atCommonLocations().excluding(StaticResourceLocation.CSS)
* </pre>
* @return the configured {@link RequestMatcher}
*/
public StaticResourceRequestMatcher atCommonLocations() {
return at(EnumSet.allOf(StaticResourceLocation.class));
}
/**
* Returns a matcher that includes the specified {@link StaticResourceLocation
* Locations}. For example: <pre class="code">
* PathRequest.toStaticResources().at(StaticResourceLocation.CSS, StaticResourceLocation.JAVA_SCRIPT)
* </pre>
* @param first the first location to include
* @param rest additional locations to include
* @return the configured {@link RequestMatcher}
*/
public StaticResourceRequestMatcher at(StaticResourceLocation first, StaticResourceLocation... rest) {
return at(EnumSet.of(first, rest));
}
/**
* Returns a matcher that includes the specified {@link StaticResourceLocation
* Locations}. For example: <pre class="code">
* PathRequest.toStaticResources().at(locations)
* </pre>
* @param locations the locations to include
* @return the configured {@link RequestMatcher}
*/
public StaticResourceRequestMatcher at(Set<StaticResourceLocation> locations) {
Assert.notNull(locations, "'locations' must not be null");
return new StaticResourceRequestMatcher(new LinkedHashSet<>(locations));
}
/**
* The request matcher used to match against resource {@link StaticResourceLocation
* Locations}.
*/
public static final class StaticResourceRequestMatcher
extends ApplicationContextRequestMatcher<DispatcherServletPath> {
private final Set<StaticResourceLocation> locations;
private volatile RequestMatcher delegate;
private StaticResourceRequestMatcher(Set<StaticResourceLocation> locations) {
super(DispatcherServletPath.class);
this.locations = locations;
}
/**
* Return a new {@link StaticResourceRequestMatcher} based on this one but
* excluding the specified locations.
* @param first the first location to exclude
* @param rest additional locations to exclude
* @return a new {@link StaticResourceRequestMatcher}
*/
public StaticResourceRequestMatcher excluding(StaticResourceLocation first, StaticResourceLocation... rest) {
return excluding(EnumSet.of(first, rest));
}
/**
* Return a new {@link StaticResourceRequestMatcher} based on this one but
* excluding the specified locations.
* @param locations the locations to exclude
* @return a new {@link StaticResourceRequestMatcher}
*/
public StaticResourceRequestMatcher excluding(Set<StaticResourceLocation> locations) {
Assert.notNull(locations, "'locations' must not be null");
Set<StaticResourceLocation> subset = new LinkedHashSet<>(this.locations);
subset.removeAll(locations);
return new StaticResourceRequestMatcher(subset);
}
@Override
protected void initialized(Supplier<DispatcherServletPath> dispatcherServletPath) {
this.delegate = new OrRequestMatcher(getDelegateMatchers(dispatcherServletPath.get()).toList());
}
private Stream<RequestMatcher> getDelegateMatchers(DispatcherServletPath dispatcherServletPath) {
return getPatterns(dispatcherServletPath).map(PathPatternRequestMatcher.withDefaults()::matcher);
}
private Stream<String> getPatterns(DispatcherServletPath dispatcherServletPath) {
return this.locations.stream()
.flatMap(StaticResourceLocation::getPatterns)
.map(dispatcherServletPath::getRelativePath);
}
@Override
protected boolean ignoreApplicationContext(WebApplicationContext applicationContext) {
return WebServerApplicationContext.hasServerNamespace(applicationContext, "management");
}
@Override
protected boolean matches(HttpServletRequest request, Supplier<DispatcherServletPath> context) {
return this.delegate.matches(request);
}
}
}

View File

@@ -0,0 +1,128 @@
/*
* Copyright 2012-2025 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.boot.security.autoconfigure.servlet;
import java.util.List;
import java.util.regex.Pattern;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.security.autoconfigure.SecurityProperties;
import org.springframework.boot.security.autoconfigure.servlet.UserDetailsServiceAutoConfiguration.MissingAlternativeOrUserPropertiesConfigured;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationManagerResolver;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.ObjectPostProcessor;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.util.StringUtils;
/**
* {@link EnableAutoConfiguration Auto-configuration} for a Spring Security in-memory
* {@link AuthenticationManager}. Adds an {@link InMemoryUserDetailsManager} with a
* default user and generated password.
*
* @author Dave Syer
* @author Rob Winch
* @author Madhura Bhave
* @author Lasse Wulff
* @since 4.0.0
*/
@AutoConfiguration
@ConditionalOnClass(AuthenticationManager.class)
@Conditional(MissingAlternativeOrUserPropertiesConfigured.class)
@ConditionalOnBean(ObjectPostProcessor.class)
@ConditionalOnMissingBean(value = { AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class,
AuthenticationManagerResolver.class }, type = "org.springframework.security.oauth2.jwt.JwtDecoder")
@ConditionalOnWebApplication(type = Type.SERVLET)
public class UserDetailsServiceAutoConfiguration {
private static final String NOOP_PASSWORD_PREFIX = "{noop}";
private static final Pattern PASSWORD_ALGORITHM_PATTERN = Pattern.compile("^\\{.+}.*$");
private static final Log logger = LogFactory.getLog(UserDetailsServiceAutoConfiguration.class);
@Bean
public InMemoryUserDetailsManager inMemoryUserDetailsManager(SecurityProperties properties,
ObjectProvider<PasswordEncoder> passwordEncoder) {
SecurityProperties.User user = properties.getUser();
List<String> roles = user.getRoles();
return new InMemoryUserDetailsManager(User.withUsername(user.getName())
.password(getOrDeducePassword(user, passwordEncoder.getIfAvailable()))
.roles(StringUtils.toStringArray(roles))
.build());
}
private String getOrDeducePassword(SecurityProperties.User user, PasswordEncoder encoder) {
String password = user.getPassword();
if (user.isPasswordGenerated()) {
logger.warn(String.format(
"%n%nUsing generated security password: %s%n%nThis generated password is for development use only. "
+ "Your security configuration must be updated before running your application in "
+ "production.%n",
user.getPassword()));
}
if (encoder != null || PASSWORD_ALGORITHM_PATTERN.matcher(password).matches()) {
return password;
}
return NOOP_PASSWORD_PREFIX + password;
}
static final class MissingAlternativeOrUserPropertiesConfigured extends AnyNestedCondition {
MissingAlternativeOrUserPropertiesConfigured() {
super(ConfigurationPhase.PARSE_CONFIGURATION);
}
@ConditionalOnMissingClass({
"org.springframework.security.oauth2.client.registration.ClientRegistrationRepository",
"org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector",
"org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository" })
static final class MissingAlternative {
}
@ConditionalOnProperty("spring.security.user.name")
static final class NameConfigured {
}
@ConditionalOnProperty("spring.security.user.password")
static final class PasswordConfigured {
}
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2012-2025 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.
*/
/**
* Auto-configuration for Servlet-based Spring Security.
*/
package org.springframework.boot.security.autoconfigure.servlet;

View File

@@ -0,0 +1,111 @@
/*
* Copyright 2012-2025 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.boot.security.reactive;
import java.util.function.Supplier;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
import org.springframework.util.Assert;
import org.springframework.web.server.ServerWebExchange;
/**
* {@link ApplicationContext} backed {@link ServerWebExchangeMatcher}. Can work directly
* with the {@link ApplicationContext}, obtain an existing bean or
* {@link AutowireCapableBeanFactory#createBean(Class) create a new bean} that is
* autowired in the usual way.
*
* @param <C> the type of the context that the match method actually needs to use. Can be
* an {@link ApplicationContext} or a class of an {@link ApplicationContext#getBean(Class)
* existing bean}.
* @author Madhura Bhave
* @since 4.0.0
*/
public abstract class ApplicationContextServerWebExchangeMatcher<C> implements ServerWebExchangeMatcher {
private final Class<? extends C> contextClass;
private volatile Supplier<C> context;
private final Object contextLock = new Object();
public ApplicationContextServerWebExchangeMatcher(Class<? extends C> contextClass) {
Assert.notNull(contextClass, "'contextClass' must not be null");
this.contextClass = contextClass;
}
@Override
public final Mono<MatchResult> matches(ServerWebExchange exchange) {
if (ignoreApplicationContext(exchange.getApplicationContext())) {
return MatchResult.notMatch();
}
return matches(exchange, getContext(exchange));
}
/**
* Decides whether the rule implemented by the strategy matches the supplied exchange.
* @param exchange the source exchange
* @param context a supplier for the initialized context (may throw an exception)
* @return if the exchange matches
*/
protected abstract Mono<MatchResult> matches(ServerWebExchange exchange, Supplier<C> context);
/**
* Returns if the {@link ApplicationContext} should be ignored and not used for
* matching. If this method returns {@code true} then the context will not be used and
* the {@link #matches(ServerWebExchange) matches} method will return {@code false}.
* @param applicationContext the candidate application context
* @return if the application context should be ignored
*/
protected boolean ignoreApplicationContext(ApplicationContext applicationContext) {
return false;
}
protected Supplier<C> getContext(ServerWebExchange exchange) {
if (this.context == null) {
synchronized (this.contextLock) {
if (this.context == null) {
Supplier<C> createdContext = createContext(exchange);
initialized(createdContext);
this.context = createdContext;
}
}
}
return this.context;
}
/**
* Called once the context has been initialized.
* @param context a supplier for the initialized context (may throw an exception)
*/
protected void initialized(Supplier<C> context) {
}
@SuppressWarnings("unchecked")
private Supplier<C> createContext(ServerWebExchange exchange) {
ApplicationContext context = exchange.getApplicationContext();
Assert.state(context != null, "No ApplicationContext found on ServerWebExchange.");
if (this.contextClass.isInstance(context)) {
return () -> (C) context;
}
return () -> context.getBean(this.contextClass);
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2012-2025 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.
*/
/**
* Classes and utilities for reactive Spring Security.
*/
package org.springframework.boot.security.reactive;

View File

@@ -0,0 +1,113 @@
/*
* Copyright 2012-2025 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.boot.security.servlet;
import java.util.function.Supplier;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
/**
* {@link ApplicationContext} backed {@link RequestMatcher}. Can work directly with the
* {@link ApplicationContext}, obtain an existing bean or
* {@link AutowireCapableBeanFactory#createBean(Class) create a new bean} that is
* autowired in the usual way.
*
* @param <C> the type of the context that the match method actually needs to use. Can be
* an {@link ApplicationContext} or a class of an {@link ApplicationContext#getBean(Class)
* existing bean}.
* @author Phillip Webb
* @since 4.0.0
*/
public abstract class ApplicationContextRequestMatcher<C> implements RequestMatcher {
private final Class<? extends C> contextClass;
private volatile boolean initialized;
private final Object initializeLock = new Object();
public ApplicationContextRequestMatcher(Class<? extends C> contextClass) {
Assert.notNull(contextClass, "'contextClass' must not be null");
this.contextClass = contextClass;
}
@Override
public final boolean matches(HttpServletRequest request) {
WebApplicationContext webApplicationContext = WebApplicationContextUtils
.getRequiredWebApplicationContext(request.getServletContext());
if (ignoreApplicationContext(webApplicationContext)) {
return false;
}
Supplier<C> context = () -> getContext(webApplicationContext);
if (!this.initialized) {
synchronized (this.initializeLock) {
if (!this.initialized) {
initialized(context);
this.initialized = true;
}
}
}
return matches(request, context);
}
@SuppressWarnings("unchecked")
private C getContext(WebApplicationContext webApplicationContext) {
if (this.contextClass.isInstance(webApplicationContext)) {
return (C) webApplicationContext;
}
return webApplicationContext.getBean(this.contextClass);
}
/**
* Returns if the {@link WebApplicationContext} should be ignored and not used for
* matching. If this method returns {@code true} then the context will not be used and
* the {@link #matches(HttpServletRequest) matches} method will return {@code false}.
* @param webApplicationContext the candidate web application context
* @return if the application context should be ignored
*/
protected boolean ignoreApplicationContext(WebApplicationContext webApplicationContext) {
return false;
}
/**
* Method that can be implemented by subclasses that wish to initialize items the
* first time that the matcher is called. This method will be called only once and
* only if {@link #ignoreApplicationContext(WebApplicationContext)} returns
* {@code false}. Note that the supplied context will be based on the
* <strong>first</strong> request sent to the matcher.
* @param context a supplier for the initialized context (may throw an exception)
* @see #ignoreApplicationContext(WebApplicationContext)
*/
protected void initialized(Supplier<C> context) {
}
/**
* Decides whether the rule implemented by the strategy matches the supplied request.
* @param request the source request
* @param context a supplier for the initialized context (may throw an exception)
* @return if the request matches
*/
protected abstract boolean matches(HttpServletRequest request, Supplier<C> context);
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2012-2025 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.
*/
/**
* Classes and utilities for Servlet-based Spring Security.
*/
package org.springframework.boot.security.servlet;

View File

@@ -0,0 +1,19 @@
{
"groups": [],
"properties": [
{
"name": "spring.security.filter.dispatcher-types",
"defaultValue": [
"async",
"error",
"forward",
"include",
"request"
]
},
{
"name": "spring.security.filter.order",
"defaultValue": -100
}
]
}

View File

@@ -0,0 +1,7 @@
org.springframework.boot.security.autoconfigure.reactive.ReactiveSecurityAutoConfiguration
org.springframework.boot.security.autoconfigure.reactive.ReactiveUserDetailsServiceAutoConfiguration
org.springframework.boot.security.autoconfigure.rsocket.RSocketSecurityAutoConfiguration
org.springframework.boot.security.autoconfigure.saml2.Saml2RelyingPartyAutoConfiguration
org.springframework.boot.security.autoconfigure.servlet.SecurityAutoConfiguration
org.springframework.boot.security.autoconfigure.servlet.SecurityFilterAutoConfiguration
org.springframework.boot.security.autoconfigure.servlet.UserDetailsServiceAutoConfiguration

View File

@@ -0,0 +1,88 @@
/*
* Copyright 2012-2025 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.boot.security.autoconfigure;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link SecurityProperties}.
*
* @author Dave Syer
* @author Madhura Bhave
*/
class SecurityPropertiesTests {
private final SecurityProperties security = new SecurityProperties();
private Binder binder;
private final MapConfigurationPropertySource source = new MapConfigurationPropertySource();
@BeforeEach
void setUp() {
this.binder = new Binder(this.source);
}
@Test
void validateDefaultFilterOrderMatchesMetadata() {
assertThat(this.security.getFilter().getOrder()).isEqualTo(-100);
}
@Test
void filterOrderShouldBind() {
this.source.put("spring.security.filter.order", "55");
this.binder.bind("spring.security", Bindable.ofInstance(this.security));
assertThat(this.security.getFilter().getOrder()).isEqualTo(55);
}
@Test
void userWhenNotConfiguredShouldUseDefaultNameAndGeneratedPassword() {
SecurityProperties.User user = this.security.getUser();
assertThat(user.getName()).isEqualTo("user");
assertThat(user.getPassword()).isNotNull();
assertThat(user.isPasswordGenerated()).isTrue();
assertThat(user.getRoles()).isEmpty();
}
@Test
void userShouldBindProperly() {
this.source.put("spring.security.user.name", "foo");
this.source.put("spring.security.user.password", "password");
this.source.put("spring.security.user.roles", "ADMIN,USER");
this.binder.bind("spring.security", Bindable.ofInstance(this.security));
SecurityProperties.User user = this.security.getUser();
assertThat(user.getName()).isEqualTo("foo");
assertThat(user.getPassword()).isEqualTo("password");
assertThat(user.isPasswordGenerated()).isFalse();
assertThat(user.getRoles()).containsExactly("ADMIN", "USER");
}
@Test
void passwordAutogeneratedIfEmpty() {
this.source.put("spring.security.user.password", "");
this.binder.bind("spring.security", Bindable.ofInstance(this.security));
assertThat(this.security.getUser().isPasswordGenerated()).isTrue();
}
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright 2012-2025 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.boot.security.autoconfigure.jpa;
import java.io.Serializable;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
@Entity
public class City implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue
private Long id;
@Column(nullable = false)
private String name;
@Column(nullable = false)
private String state;
@Column(nullable = false)
private String country;
@Column(nullable = false)
private String map;
protected City() {
}
public City(String name, String state, String country, String map) {
this.name = name;
this.state = state;
this.country = country;
this.map = map;
}
public String getName() {
return this.name;
}
public String getState() {
return this.state;
}
public String getCountry() {
return this.country;
}
public String getMap() {
return this.map;
}
@Override
public String toString() {
return getName() + "," + getState() + "," + getCountry();
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2012-2025 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.boot.security.autoconfigure.jpa;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration;
import org.springframework.boot.jdbc.autoconfigure.EmbeddedDataSourceConfiguration;
import org.springframework.boot.jpa.autoconfigure.hibernate.HibernateJpaAutoConfiguration;
import org.springframework.boot.security.autoconfigure.servlet.SecurityAutoConfiguration;
import org.springframework.boot.test.context.SpringBootContextLoader;
import org.springframework.context.annotation.Import;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
/**
* The EntityScanRegistrar can cause problems with Spring security and its eager
* instantiation needs. This test is designed to fail if the Entities can't be scanned
* because the registrar doesn't get a callback with the right beans (essentially because
* their instantiation order was accelerated by Security).
*
* @author Dave Syer
*/
@ContextConfiguration(classes = JpaUserDetailsTests.Main.class, loader = SpringBootContextLoader.class)
@DirtiesContext
class JpaUserDetailsTests {
@Test
void contextLoads() {
}
@Import({ EmbeddedDataSourceConfiguration.class, DataSourceAutoConfiguration.class,
HibernateJpaAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class,
SecurityAutoConfiguration.class })
static class Main {
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2012-2025 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.boot.security.autoconfigure.reactive;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link PathRequest}.
*
* @author Madhura Bhave
*/
class PathRequestTests {
@Test
void toStaticResourcesShouldReturnStaticResourceRequest() {
assertThat(PathRequest.toStaticResources()).isInstanceOf(StaticResourceRequest.class);
}
}

View File

@@ -0,0 +1,117 @@
/*
* Copyright 2012-2025 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.boot.security.autoconfigure.reactive;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Flux;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.WebFilterChainProxy;
import org.springframework.web.reactive.config.WebFluxConfigurer;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link ReactiveSecurityAutoConfiguration}.
*
* @author Madhura Bhave
*/
class ReactiveSecurityAutoConfigurationTests {
private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(ReactiveSecurityAutoConfiguration.class));
@Test
void backsOffWhenWebFilterChainProxyBeanPresent() {
this.contextRunner.withUserConfiguration(WebFilterChainProxyConfiguration.class)
.run((context) -> assertThat(context).hasSingleBean(WebFilterChainProxy.class));
}
@Test
void autoConfiguresDenyAllReactiveAuthenticationManagerWhenNoAlternativeIsAvailable() {
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(ReactiveSecurityAutoConfiguration.class)
.hasBean("denyAllAuthenticationManager"));
}
@Test
void enablesWebFluxSecurityWhenUserDetailsServiceIsPresent() {
this.contextRunner.withUserConfiguration(UserDetailsServiceConfiguration.class).run((context) -> {
assertThat(context).hasSingleBean(WebFilterChainProxy.class);
assertThat(context).doesNotHaveBean("denyAllAuthenticationManager");
});
}
@Test
void enablesWebFluxSecurityWhenReactiveAuthenticationManagerIsPresent() {
this.contextRunner
.withBean(ReactiveAuthenticationManager.class, () -> mock(ReactiveAuthenticationManager.class))
.run((context) -> {
assertThat(context).hasSingleBean(WebFilterChainProxy.class);
assertThat(context).doesNotHaveBean("denyAllAuthenticationManager");
});
}
@Test
void enablesWebFluxSecurityWhenSecurityWebFilterChainIsPresent() {
this.contextRunner.withBean(SecurityWebFilterChain.class, () -> mock(SecurityWebFilterChain.class))
.run((context) -> {
assertThat(context).hasSingleBean(WebFilterChainProxy.class);
assertThat(context).doesNotHaveBean("denyAllAuthenticationManager");
});
}
@Test
void autoConfigurationIsConditionalOnClass() {
this.contextRunner
.withClassLoader(new FilteredClassLoader(Flux.class, EnableWebFluxSecurity.class, WebFilterChainProxy.class,
WebFluxConfigurer.class))
.withUserConfiguration(UserDetailsServiceConfiguration.class)
.run((context) -> assertThat(context).doesNotHaveBean(WebFilterChainProxy.class));
}
@Configuration(proxyBeanMethods = false)
static class WebFilterChainProxyConfiguration {
@Bean
WebFilterChainProxy webFilterChainProxy() {
return mock(WebFilterChainProxy.class);
}
}
@Configuration(proxyBeanMethods = false)
static class UserDetailsServiceConfiguration {
@Bean
MapReactiveUserDetailsService userDetailsService() {
return new MapReactiveUserDetailsService(
User.withUsername("alice").password("secret").roles("admin").build());
}
}
}

View File

@@ -0,0 +1,226 @@
/*
* Copyright 2012-2025 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.boot.security.autoconfigure.reactive;
import java.time.Duration;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.rsocket.autoconfigure.RSocketMessagingAutoConfiguration;
import org.springframework.boot.rsocket.autoconfigure.RSocketStrategiesAutoConfiguration;
import org.springframework.boot.security.autoconfigure.SecurityProperties;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authentication.ReactiveAuthenticationManagerResolver;
import org.springframework.security.config.annotation.rsocket.EnableRSocketSecurity;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link ReactiveUserDetailsServiceAutoConfiguration}.
*
* @author Madhura Bhave
* @author HaiTao Zhang
*/
class ReactiveUserDetailsServiceAutoConfigurationTests {
private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(ReactiveUserDetailsServiceAutoConfiguration.class));
@Test
void configuresADefaultUser() {
this.contextRunner
.withClassLoader(
new FilteredClassLoader(ClientRegistrationRepository.class, ReactiveOpaqueTokenIntrospector.class))
.withUserConfiguration(TestSecurityConfiguration.class)
.run((context) -> {
ReactiveUserDetailsService userDetailsService = context.getBean(ReactiveUserDetailsService.class);
assertThat(userDetailsService.findByUsername("user").block(Duration.ofSeconds(30))).isNotNull();
});
}
@Test
void userDetailsServiceWhenRSocketConfigured() {
new ApplicationContextRunner()
.withClassLoader(
new FilteredClassLoader(ClientRegistrationRepository.class, ReactiveOpaqueTokenIntrospector.class))
.withConfiguration(AutoConfigurations.of(ReactiveUserDetailsServiceAutoConfiguration.class,
RSocketMessagingAutoConfiguration.class, RSocketStrategiesAutoConfiguration.class))
.withUserConfiguration(TestRSocketSecurityConfiguration.class)
.run((context) -> {
ReactiveUserDetailsService userDetailsService = context.getBean(ReactiveUserDetailsService.class);
assertThat(userDetailsService.findByUsername("user").block(Duration.ofSeconds(30))).isNotNull();
});
}
@Test
void doesNotConfigureDefaultUserIfUserDetailsServiceAvailable() {
this.contextRunner.withUserConfiguration(UserConfig.class, TestSecurityConfiguration.class).run((context) -> {
ReactiveUserDetailsService userDetailsService = context.getBean(ReactiveUserDetailsService.class);
assertThat(userDetailsService.findByUsername("user").block(Duration.ofSeconds(30))).isNull();
assertThat(userDetailsService.findByUsername("foo").block(Duration.ofSeconds(30))).isNotNull();
assertThat(userDetailsService.findByUsername("admin").block(Duration.ofSeconds(30))).isNotNull();
});
}
@Test
void doesNotConfigureDefaultUserIfAuthenticationManagerAvailable() {
this.contextRunner.withUserConfiguration(AuthenticationManagerConfig.class, TestSecurityConfiguration.class)
.withConfiguration(AutoConfigurations.of(ReactiveSecurityAutoConfiguration.class))
.run((context) -> assertThat(context).getBean(ReactiveUserDetailsService.class).isNull());
}
@Test
void doesNotConfigureDefaultUserIfAuthenticationManagerResolverAvailable() {
this.contextRunner.withUserConfiguration(AuthenticationManagerResolverConfig.class)
.run((context) -> assertThat(context).hasSingleBean(ReactiveAuthenticationManagerResolver.class)
.doesNotHaveBean(ReactiveUserDetailsService.class));
}
@Test
void doesNotConfigureDefaultUserIfResourceServerIsPresent() {
this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(ReactiveUserDetailsService.class));
}
@Test
void configuresDefaultUserWhenResourceServerIsPresentAndUsernameIsConfigured() {
this.contextRunner.withPropertyValues("spring.security.user.name=carol")
.run((context) -> assertThat(context).hasSingleBean(ReactiveUserDetailsService.class));
}
@Test
void configuresDefaultUserWhenResourceServerIsPresentAndPasswordIsConfigured() {
this.contextRunner.withPropertyValues("spring.security.user.password=p4ssw0rd")
.run((context) -> assertThat(context).hasSingleBean(ReactiveUserDetailsService.class));
}
@Test
void userDetailsServiceWhenPasswordEncoderAbsentAndDefaultPassword() {
this.contextRunner
.withClassLoader(
new FilteredClassLoader(ClientRegistrationRepository.class, ReactiveOpaqueTokenIntrospector.class))
.withUserConfiguration(TestSecurityConfiguration.class)
.run(((context) -> {
MapReactiveUserDetailsService userDetailsService = context.getBean(MapReactiveUserDetailsService.class);
String password = userDetailsService.findByUsername("user").block(Duration.ofSeconds(30)).getPassword();
assertThat(password).startsWith("{noop}");
}));
}
@Test
void userDetailsServiceWhenPasswordEncoderAbsentAndRawPassword() {
testPasswordEncoding(TestSecurityConfiguration.class, "secret", "{noop}secret");
}
@Test
void userDetailsServiceWhenPasswordEncoderAbsentAndEncodedPassword() {
String password = "{bcrypt}$2a$10$sCBi9fy9814vUPf2ZRbtp.fR5/VgRk2iBFZ.ypu5IyZ28bZgxrVDa";
testPasswordEncoding(TestSecurityConfiguration.class, password, password);
}
@Test
void userDetailsServiceWhenPasswordEncoderBeanPresent() {
testPasswordEncoding(TestConfigWithPasswordEncoder.class, "secret", "secret");
}
private void testPasswordEncoding(Class<?> configClass, String providedPassword, String expectedPassword) {
this.contextRunner
.withClassLoader(
new FilteredClassLoader(ClientRegistrationRepository.class, ReactiveOpaqueTokenIntrospector.class))
.withUserConfiguration(configClass)
.withPropertyValues("spring.security.user.password=" + providedPassword)
.run(((context) -> {
MapReactiveUserDetailsService userDetailsService = context.getBean(MapReactiveUserDetailsService.class);
String password = userDetailsService.findByUsername("user").block(Duration.ofSeconds(30)).getPassword();
assertThat(password).isEqualTo(expectedPassword);
}));
}
@Configuration(proxyBeanMethods = false)
@EnableWebFluxSecurity
@EnableConfigurationProperties(SecurityProperties.class)
static class TestSecurityConfiguration {
}
@Configuration(proxyBeanMethods = false)
@EnableRSocketSecurity
@EnableConfigurationProperties(SecurityProperties.class)
static class TestRSocketSecurityConfiguration {
}
@Configuration(proxyBeanMethods = false)
static class UserConfig {
@Bean
MapReactiveUserDetailsService userDetailsService() {
UserDetails foo = User.withUsername("foo").password("foo").roles("USER").build();
UserDetails admin = User.withUsername("admin").password("admin").roles("USER", "ADMIN").build();
return new MapReactiveUserDetailsService(foo, admin);
}
}
@Configuration(proxyBeanMethods = false)
static class AuthenticationManagerConfig {
@Bean
ReactiveAuthenticationManager reactiveAuthenticationManager() {
return (authentication) -> null;
}
}
@Configuration(proxyBeanMethods = false)
static class AuthenticationManagerResolverConfig {
@Bean
ReactiveAuthenticationManagerResolver<?> reactiveAuthenticationManagerResolver() {
return mock(ReactiveAuthenticationManagerResolver.class);
}
}
@Configuration(proxyBeanMethods = false)
@Import(TestSecurityConfiguration.class)
static class TestConfigWithPasswordEncoder {
@Bean
PasswordEncoder passwordEncoder() {
return mock(PasswordEncoder.class);
}
}
}

View File

@@ -0,0 +1,156 @@
/*
* Copyright 2012-2025 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.boot.security.autoconfigure.reactive;
import java.time.Duration;
import org.assertj.core.api.AssertDelegateTarget;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.security.autoconfigure.StaticResourceLocation;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.MockServerHttpResponse;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
import org.springframework.web.context.support.StaticWebApplicationContext;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebHandler;
import org.springframework.web.server.adapter.HttpWebHandlerAdapter;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link StaticResourceRequest}.
*
* @author Madhura Bhave
*/
class StaticResourceRequestTests {
private final StaticResourceRequest resourceRequest = StaticResourceRequest.INSTANCE;
@Test
void atCommonLocationsShouldMatchCommonLocations() {
ServerWebExchangeMatcher matcher = this.resourceRequest.atCommonLocations();
assertMatcher(matcher).matches("/css/file.css");
assertMatcher(matcher).matches("/js/file.js");
assertMatcher(matcher).matches("/images/file.css");
assertMatcher(matcher).matches("/webjars/file.css");
assertMatcher(matcher).matches("/favicon.ico");
assertMatcher(matcher).matches("/favicon.png");
assertMatcher(matcher).matches("/icons/icon-48x48.png");
assertMatcher(matcher).doesNotMatch("/bar");
}
@Test
void atCommonLocationsWithExcludeShouldNotMatchExcluded() {
ServerWebExchangeMatcher matcher = this.resourceRequest.atCommonLocations()
.excluding(StaticResourceLocation.CSS);
assertMatcher(matcher).doesNotMatch("/css/file.css");
assertMatcher(matcher).matches("/js/file.js");
}
@Test
void atLocationShouldMatchLocation() {
ServerWebExchangeMatcher matcher = this.resourceRequest.at(StaticResourceLocation.CSS);
assertMatcher(matcher).matches("/css/file.css");
assertMatcher(matcher).doesNotMatch("/js/file.js");
}
@Test
void atLocationsFromSetWhenSetIsNullShouldThrowException() {
assertThatIllegalArgumentException().isThrownBy(() -> this.resourceRequest.at(null))
.withMessageContaining("'locations' must not be null");
}
@Test
void excludeFromSetWhenSetIsNullShouldThrowException() {
assertThatIllegalArgumentException().isThrownBy(() -> this.resourceRequest.atCommonLocations().excluding(null))
.withMessageContaining("'locations' must not be null");
}
private RequestMatcherAssert assertMatcher(ServerWebExchangeMatcher matcher) {
StaticWebApplicationContext context = new StaticWebApplicationContext();
context.registerBean(ServerProperties.class);
return assertThat(new RequestMatcherAssert(context, matcher));
}
static class RequestMatcherAssert implements AssertDelegateTarget {
private final StaticApplicationContext context;
private final ServerWebExchangeMatcher matcher;
RequestMatcherAssert(StaticApplicationContext context, ServerWebExchangeMatcher matcher) {
this.context = context;
this.matcher = matcher;
}
void matches(String path) {
ServerWebExchange exchange = webHandler().createExchange(MockServerHttpRequest.get(path).build(),
new MockServerHttpResponse());
matches(exchange);
}
private void matches(ServerWebExchange exchange) {
assertThat(this.matcher.matches(exchange).block(Duration.ofSeconds(30)).isMatch())
.as("Matches " + getRequestPath(exchange))
.isTrue();
}
void doesNotMatch(String path) {
ServerWebExchange exchange = webHandler().createExchange(MockServerHttpRequest.get(path).build(),
new MockServerHttpResponse());
doesNotMatch(exchange);
}
private void doesNotMatch(ServerWebExchange exchange) {
assertThat(this.matcher.matches(exchange).block(Duration.ofSeconds(30)).isMatch())
.as("Does not match " + getRequestPath(exchange))
.isFalse();
}
private TestHttpWebHandlerAdapter webHandler() {
TestHttpWebHandlerAdapter adapter = new TestHttpWebHandlerAdapter(mock(WebHandler.class));
adapter.setApplicationContext(this.context);
return adapter;
}
private String getRequestPath(ServerWebExchange exchange) {
return exchange.getRequest().getPath().toString();
}
}
static class TestHttpWebHandlerAdapter extends HttpWebHandlerAdapter {
TestHttpWebHandlerAdapter(WebHandler delegate) {
super(delegate);
}
@Override
protected ServerWebExchange createExchange(ServerHttpRequest request, ServerHttpResponse response) {
return super.createExchange(request, response);
}
}
}

View File

@@ -0,0 +1,98 @@
/*
* Copyright 2012-2025 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.boot.security.autoconfigure.rsocket;
import io.rsocket.core.RSocketServer;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.rsocket.autoconfigure.RSocketMessagingAutoConfiguration;
import org.springframework.boot.rsocket.autoconfigure.RSocketStrategiesAutoConfiguration;
import org.springframework.boot.rsocket.server.RSocketServerCustomizer;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler;
import org.springframework.security.config.annotation.rsocket.RSocketSecurity;
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.messaging.handler.invocation.reactive.AuthenticationPrincipalArgumentResolver;
import org.springframework.security.rsocket.core.SecuritySocketAcceptorInterceptor;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link RSocketSecurityAutoConfiguration}.
*
* @author Madhura Bhave
* @author Brian Clozel
*/
class RSocketSecurityAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(RSocketSecurityAutoConfiguration.class,
RSocketMessagingAutoConfiguration.class, RSocketStrategiesAutoConfiguration.class))
.withUserConfiguration(UserDetailsServiceConfiguration.class);
@Test
void autoConfigurationEnablesRSocketSecurity() {
this.contextRunner.run((context) -> assertThat(context.getBean(RSocketSecurity.class)).isNotNull());
}
@Test
void autoConfigurationIsConditionalOnSecuritySocketAcceptorInterceptorClass() {
this.contextRunner.withClassLoader(new FilteredClassLoader(SecuritySocketAcceptorInterceptor.class))
.run((context) -> assertThat(context).doesNotHaveBean(RSocketSecurity.class));
}
@Test
void autoConfigurationAddsCustomizerForServerRSocketFactory() {
RSocketServer server = RSocketServer.create();
this.contextRunner.run((context) -> {
RSocketServerCustomizer customizer = context.getBean(RSocketServerCustomizer.class);
customizer.customize(server);
server.interceptors((registry) -> registry.forSocketAcceptor((interceptors) -> {
assertThat(interceptors).isNotEmpty();
assertThat(interceptors)
.anyMatch((interceptor) -> interceptor instanceof SecuritySocketAcceptorInterceptor);
}));
});
}
@Test
void autoConfigurationAddsCustomizerForAuthenticationPrincipalArgumentResolver() {
this.contextRunner.run((context) -> {
assertThat(context).hasSingleBean(RSocketMessageHandler.class);
RSocketMessageHandler handler = context.getBean(RSocketMessageHandler.class);
assertThat(handler.getArgumentResolverConfigurer().getCustomResolvers())
.anyMatch((customResolver) -> customResolver instanceof AuthenticationPrincipalArgumentResolver);
});
}
@Configuration(proxyBeanMethods = false)
static class UserDetailsServiceConfiguration {
@Bean
MapReactiveUserDetailsService userDetailsService() {
return new MapReactiveUserDetailsService(
User.withUsername("alice").password("secret").roles("admin").build());
}
}
}

View File

@@ -0,0 +1,442 @@
/*
* Copyright 2012-2025 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.boot.security.autoconfigure.saml2;
import java.io.InputStream;
import java.util.List;
import jakarta.servlet.Filter;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okio.Buffer;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.security.autoconfigure.servlet.SecurityAutoConfiguration;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.boot.testsupport.classpath.resources.WithPackageResources;
import org.springframework.boot.webmvc.autoconfigure.WebMvcAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.security.config.BeanIds;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
import org.springframework.security.saml2.provider.service.web.authentication.Saml2WebSsoAuthenticationFilter;
import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestFilter;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.filter.CompositeFilter;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link Saml2RelyingPartyAutoConfiguration}.
*
* @author Madhura Bhave
* @author Moritz Halbritter
* @author Lasse Lindqvist
* @author Scott Frederick
*/
class Saml2RelyingPartyAutoConfigurationTests {
private static final String PREFIX = "spring.security.saml2.relyingparty.registration";
private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner().withConfiguration(
AutoConfigurations.of(Saml2RelyingPartyAutoConfiguration.class, SecurityAutoConfiguration.class));
@Test
void autoConfigurationShouldBeConditionalOnRelyingPartyRegistrationRepositoryClass() {
this.contextRunner.withPropertyValues(getPropertyValues())
.withClassLoader(new FilteredClassLoader(
"org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository"))
.run((context) -> assertThat(context).doesNotHaveBean(RelyingPartyRegistrationRepository.class));
}
@Test
void autoConfigurationShouldBeConditionalOnServletWebApplication() {
new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(Saml2RelyingPartyAutoConfiguration.class))
.withPropertyValues(getPropertyValues())
.run((context) -> assertThat(context).doesNotHaveBean(RelyingPartyRegistrationRepository.class));
}
@Test
void relyingPartyRegistrationRepositoryBeanShouldNotBeCreatedWhenPropertiesAbsent() {
this.contextRunner
.run((context) -> assertThat(context).doesNotHaveBean(RelyingPartyRegistrationRepository.class));
}
@Test
@WithPackageResources({ "certificate-location", "private-key-location" })
void relyingPartyRegistrationRepositoryBeanShouldBeCreatedWhenPropertiesPresent() {
this.contextRunner.withPropertyValues(getPropertyValues()).run((context) -> {
RelyingPartyRegistrationRepository repository = context.getBean(RelyingPartyRegistrationRepository.class);
RelyingPartyRegistration registration = repository.findByRegistrationId("foo");
assertThat(registration.getAssertingPartyMetadata().getSingleSignOnServiceLocation())
.isEqualTo("https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php");
assertThat(registration.getAssertingPartyMetadata().getEntityId())
.isEqualTo("https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php");
assertThat(registration.getAssertionConsumerServiceLocation())
.isEqualTo("{baseUrl}/login/saml2/foo-entity-id");
assertThat(registration.getAssertionConsumerServiceBinding()).isEqualTo(Saml2MessageBinding.REDIRECT);
assertThat(registration.getAssertingPartyMetadata().getSingleSignOnServiceBinding())
.isEqualTo(Saml2MessageBinding.POST);
assertThat(registration.getAssertingPartyMetadata().getWantAuthnRequestsSigned()).isFalse();
assertThat(registration.getSigningX509Credentials()).hasSize(1);
assertThat(registration.getDecryptionX509Credentials()).hasSize(1);
assertThat(registration.getAssertingPartyMetadata().getVerificationX509Credentials()).isNotNull();
assertThat(registration.getEntityId()).isEqualTo("{baseUrl}/saml2/foo-entity-id");
assertThat(registration.getSingleLogoutServiceLocation())
.isEqualTo("https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SLOService.php");
assertThat(registration.getSingleLogoutServiceResponseLocation())
.isEqualTo("https://simplesaml-for-spring-saml.cfapps.io/");
assertThat(registration.getSingleLogoutServiceBinding()).isEqualTo(Saml2MessageBinding.POST);
assertThat(registration.getAssertingPartyMetadata().getSingleLogoutServiceLocation())
.isEqualTo("https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SLOService.php");
assertThat(registration.getAssertingPartyMetadata().getSingleLogoutServiceResponseLocation())
.isEqualTo("https://simplesaml-for-spring-saml.cfapps.io/");
assertThat(registration.getAssertingPartyMetadata().getSingleLogoutServiceBinding())
.isEqualTo(Saml2MessageBinding.POST);
});
}
@Test
@WithPackageResources({ "certificate-location", "private-key-location" })
void autoConfigurationWhenSignRequestsTrueAndNoSigningCredentialsShouldThrowException() {
this.contextRunner.withPropertyValues(getPropertyValuesWithoutSigningCredentials(true)).run((context) -> {
assertThat(context).hasFailed();
assertThat(context.getStartupFailure()).hasMessageContaining(
"Signing credentials must not be empty when authentication requests require signing.");
});
}
@Test
@WithPackageResources({ "certificate-location", "private-key-location" })
void autoConfigurationWhenSignRequestsFalseAndNoSigningCredentialsShouldNotThrowException() {
this.contextRunner.withPropertyValues(getPropertyValuesWithoutSigningCredentials(false))
.run((context) -> assertThat(context).hasSingleBean(RelyingPartyRegistrationRepository.class));
}
@Test
@WithPackageResources("idp-metadata")
void autoconfigurationShouldQueryAssertingPartyMetadataWhenMetadataUrlIsPresent() throws Exception {
try (MockWebServer server = new MockWebServer()) {
server.start();
String metadataUrl = server.url("").toString();
setupMockResponse(server, new ClassPathResource("idp-metadata"));
this.contextRunner.withPropertyValues(PREFIX + ".foo.assertingparty.metadata-uri=" + metadataUrl)
.run((context) -> {
assertThat(context).hasSingleBean(RelyingPartyRegistrationRepository.class);
assertThat(server.getRequestCount()).isOne();
});
}
}
@Test
@WithPackageResources("idp-metadata")
void autoconfigurationShouldUseBindingFromMetadataUrlIfPresent() throws Exception {
try (MockWebServer server = new MockWebServer()) {
server.start();
String metadataUrl = server.url("").toString();
setupMockResponse(server, new ClassPathResource("idp-metadata"));
this.contextRunner.withPropertyValues(PREFIX + ".foo.assertingparty.metadata-uri=" + metadataUrl)
.run((context) -> {
RelyingPartyRegistrationRepository repository = context
.getBean(RelyingPartyRegistrationRepository.class);
RelyingPartyRegistration registration = repository.findByRegistrationId("foo");
assertThat(registration.getAssertingPartyMetadata().getSingleSignOnServiceBinding())
.isEqualTo(Saml2MessageBinding.POST);
});
}
}
@Test
@WithPackageResources("idp-metadata")
void autoconfigurationWhenMetadataUrlAndPropertyPresentShouldUseBindingFromProperty() throws Exception {
try (MockWebServer server = new MockWebServer()) {
server.start();
String metadataUrl = server.url("").toString();
setupMockResponse(server, new ClassPathResource("idp-metadata"));
this.contextRunner
.withPropertyValues(PREFIX + ".foo.assertingparty.metadata-uri=" + metadataUrl,
PREFIX + ".foo.assertingparty.singlesignon.binding=redirect")
.run((context) -> {
RelyingPartyRegistrationRepository repository = context
.getBean(RelyingPartyRegistrationRepository.class);
RelyingPartyRegistration registration = repository.findByRegistrationId("foo");
assertThat(registration.getAssertingPartyMetadata().getSingleSignOnServiceBinding())
.isEqualTo(Saml2MessageBinding.REDIRECT);
});
}
}
@Test
@WithPackageResources({ "certificate-location", "private-key-location" })
void autoconfigurationWhenNoMetadataUrlOrPropertyPresentShouldUseRedirectBinding() {
this.contextRunner.withPropertyValues(getPropertyValuesWithoutSsoBinding()).run((context) -> {
RelyingPartyRegistrationRepository repository = context.getBean(RelyingPartyRegistrationRepository.class);
RelyingPartyRegistration registration = repository.findByRegistrationId("foo");
assertThat(registration.getAssertingPartyMetadata().getSingleSignOnServiceBinding())
.isEqualTo(Saml2MessageBinding.REDIRECT);
});
}
@Test
void relyingPartyRegistrationRepositoryShouldBeConditionalOnMissingBean() {
this.contextRunner.withPropertyValues(getPropertyValues())
.withUserConfiguration(RegistrationRepositoryConfiguration.class)
.run((context) -> {
assertThat(context).hasSingleBean(RelyingPartyRegistrationRepository.class);
assertThat(context).hasBean("testRegistrationRepository");
});
}
@Test
@WithPackageResources({ "certificate-location", "private-key-location" })
void samlLoginShouldBeConfigured() {
this.contextRunner.withPropertyValues(getPropertyValues())
.run((context) -> assertThat(hasSecurityFilter(context, Saml2WebSsoAuthenticationFilter.class)).isTrue());
}
@Test
@WithPackageResources({ "private-key-location", "certificate-location" })
void samlLoginShouldBackOffWhenASecurityFilterChainBeanIsPresent() {
this.contextRunner.withConfiguration(AutoConfigurations.of(WebMvcAutoConfiguration.class))
.withUserConfiguration(TestSecurityFilterChainConfig.class)
.withPropertyValues(getPropertyValues())
.run((context) -> assertThat(hasSecurityFilter(context, Saml2WebSsoAuthenticationFilter.class)).isFalse());
}
@Test
@WithPackageResources({ "certificate-location", "private-key-location" })
void samlLoginShouldShouldBeConditionalOnSecurityWebFilterClass() {
this.contextRunner
.withClassLoader(
new FilteredClassLoader(Thread.currentThread().getContextClassLoader(), SecurityFilterChain.class))
.withPropertyValues(getPropertyValues())
.run((context) -> assertThat(context).doesNotHaveBean(SecurityFilterChain.class));
}
@Test
@WithPackageResources({ "certificate-location", "private-key-location" })
void samlLogoutShouldBeConfigured() {
this.contextRunner.withPropertyValues(getPropertyValues())
.run((context) -> assertThat(hasSecurityFilter(context, Saml2LogoutRequestFilter.class)).isTrue());
}
private String[] getPropertyValuesWithoutSigningCredentials(boolean signRequests) {
return new String[] { PREFIX
+ ".foo.assertingparty.singlesignon.url=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php",
PREFIX + ".foo.assertingparty.singlesignon.binding=post",
PREFIX + ".foo.assertingparty.singlesignon.sign-request=" + signRequests,
PREFIX + ".foo.assertingparty.entity-id=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php",
PREFIX + ".foo.assertingparty.verification.credentials[0].certificate-location=classpath:certificate-location" };
}
@Test
@WithPackageResources("idp-metadata-with-multiple-providers")
void autoconfigurationWhenMultipleProvidersAndNoSpecifiedEntityId() throws Exception {
testMultipleProviders(null, "https://idp.example.com/idp/shibboleth");
}
@Test
@WithPackageResources("idp-metadata-with-multiple-providers")
void autoconfigurationWhenMultipleProvidersAndSpecifiedEntityId() throws Exception {
testMultipleProviders("https://idp.example.com/idp/shibboleth", "https://idp.example.com/idp/shibboleth");
testMultipleProviders("https://idp2.example.com/idp/shibboleth", "https://idp2.example.com/idp/shibboleth");
}
@Test
@WithPackageResources("idp-metadata")
void signRequestShouldApplyIfMetadataUriIsSet() throws Exception {
try (MockWebServer server = new MockWebServer()) {
server.start();
String metadataUrl = server.url("").toString();
setupMockResponse(server, new ClassPathResource("idp-metadata"));
this.contextRunner.withPropertyValues(PREFIX + ".foo.assertingparty.metadata-uri=" + metadataUrl,
PREFIX + ".foo.assertingparty.singlesignon.sign-request=true",
PREFIX + ".foo.signing.credentials[0].private-key-location=classpath:org/springframework/boot/security/autoconfigure/saml2/rsa.key",
PREFIX + ".foo.signing.credentials[0].certificate-location=classpath:org/springframework/boot/security/autoconfigure/saml2/rsa.crt")
.run((context) -> {
RelyingPartyRegistrationRepository repository = context
.getBean(RelyingPartyRegistrationRepository.class);
RelyingPartyRegistration registration = repository.findByRegistrationId("foo");
assertThat(registration.getAssertingPartyMetadata().getWantAuthnRequestsSigned()).isTrue();
});
}
}
@Test
@WithPackageResources("certificate-location")
void autoconfigurationWithInvalidPrivateKeyShouldFail() {
this.contextRunner.withPropertyValues(
PREFIX + ".foo.signing.credentials[0].private-key-location=classpath:certificate-location",
PREFIX + ".foo.signing.credentials[0].certificate-location=classpath:certificate-location",
PREFIX + ".foo.assertingparty.singlesignon.url=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php",
PREFIX + ".foo.assertingparty.singlesignon.binding=post",
PREFIX + ".foo.assertingparty.singlesignon.sign-request=false",
PREFIX + ".foo.assertingparty.entity-id=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php",
PREFIX + ".foo.assertingparty.verification.credentials[0].certificate-location=classpath:certificate-location")
.run((context) -> assertThat(context).hasFailed()
.getFailure()
.rootCause()
.hasMessageContaining("Missing private key or unrecognized format"));
}
@Test
@WithPackageResources("private-key-location")
void autoconfigurationWithInvalidCertificateShouldFail() {
this.contextRunner.withPropertyValues(
PREFIX + ".foo.signing.credentials[0].private-key-location=classpath:private-key-location",
PREFIX + ".foo.signing.credentials[0].certificate-location=classpath:private-key-location",
PREFIX + ".foo.assertingparty.singlesignon.url=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php",
PREFIX + ".foo.assertingparty.singlesignon.binding=post",
PREFIX + ".foo.assertingparty.singlesignon.sign-request=false",
PREFIX + ".foo.assertingparty.entity-id=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php",
PREFIX + ".foo.assertingparty.verification.credentials[0].certificate-location=classpath:private-key-location")
.run((context) -> assertThat(context).hasFailed()
.getFailure()
.rootCause()
.hasMessageContaining("Missing certificates or unrecognized format"));
}
private void testMultipleProviders(String specifiedEntityId, String expected) throws Exception {
try (MockWebServer server = new MockWebServer()) {
server.start();
String metadataUrl = server.url("").toString();
setupMockResponse(server, new ClassPathResource("idp-metadata-with-multiple-providers"));
WebApplicationContextRunner contextRunner = this.contextRunner
.withPropertyValues(PREFIX + ".foo.assertingparty.metadata-uri=" + metadataUrl);
if (specifiedEntityId != null) {
contextRunner = contextRunner
.withPropertyValues(PREFIX + ".foo.assertingparty.entity-id=" + specifiedEntityId);
}
contextRunner.run((context) -> {
assertThat(context).hasSingleBean(RelyingPartyRegistrationRepository.class);
assertThat(server.getRequestCount()).isOne();
RelyingPartyRegistrationRepository repository = context
.getBean(RelyingPartyRegistrationRepository.class);
RelyingPartyRegistration registration = repository.findByRegistrationId("foo");
assertThat(registration.getAssertingPartyMetadata().getEntityId()).isEqualTo(expected);
});
}
}
private String[] getPropertyValuesWithoutSsoBinding() {
return new String[] { PREFIX
+ ".foo.assertingparty.singlesignon.url=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php",
PREFIX + ".foo.assertingparty.singlesignon.sign-request=false",
PREFIX + ".foo.assertingparty.entity-id=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php",
PREFIX + ".foo.assertingparty.verification.credentials[0].certificate-location=classpath:certificate-location" };
}
private String[] getPropertyValues() {
return new String[] {
PREFIX + ".foo.signing.credentials[0].private-key-location=classpath:private-key-location",
PREFIX + ".foo.signing.credentials[0].certificate-location=classpath:certificate-location",
PREFIX + ".foo.decryption.credentials[0].private-key-location=classpath:private-key-location",
PREFIX + ".foo.decryption.credentials[0].certificate-location=classpath:certificate-location",
PREFIX + ".foo.singlelogout.url=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SLOService.php",
PREFIX + ".foo.singlelogout.response-url=https://simplesaml-for-spring-saml.cfapps.io/",
PREFIX + ".foo.singlelogout.binding=post",
PREFIX + ".foo.assertingparty.singlesignon.url=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php",
PREFIX + ".foo.assertingparty.singlesignon.binding=post",
PREFIX + ".foo.assertingparty.singlesignon.sign-request=false",
PREFIX + ".foo.assertingparty.entity-id=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php",
PREFIX + ".foo.assertingparty.verification.credentials[0].certificate-location=classpath:certificate-location",
PREFIX + ".foo.asserting-party.singlelogout.url=https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SLOService.php",
PREFIX + ".foo.asserting-party.singlelogout.response-url=https://simplesaml-for-spring-saml.cfapps.io/",
PREFIX + ".foo.asserting-party.singlelogout.binding=post",
PREFIX + ".foo.entity-id={baseUrl}/saml2/foo-entity-id",
PREFIX + ".foo.acs.location={baseUrl}/login/saml2/foo-entity-id",
PREFIX + ".foo.acs.binding=redirect" };
}
private boolean hasSecurityFilter(AssertableWebApplicationContext context, Class<? extends Filter> filter) {
return getSecurityFilterChain(context).getFilters().stream().anyMatch(filter::isInstance);
}
private SecurityFilterChain getSecurityFilterChain(AssertableWebApplicationContext context) {
Filter springSecurityFilterChain = context.getBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN, Filter.class);
FilterChainProxy filterChainProxy = getFilterChainProxy(springSecurityFilterChain);
SecurityFilterChain securityFilterChain = filterChainProxy.getFilterChains().get(0);
return securityFilterChain;
}
private FilterChainProxy getFilterChainProxy(Filter filter) {
if (filter instanceof FilterChainProxy filterChainProxy) {
return filterChainProxy;
}
if (filter instanceof CompositeFilter) {
List<?> filters = (List<?>) ReflectionTestUtils.getField(filter, "filters");
return (FilterChainProxy) filters.stream()
.filter(FilterChainProxy.class::isInstance)
.findFirst()
.orElseThrow();
}
throw new IllegalStateException("No FilterChainProxy found");
}
private void setupMockResponse(MockWebServer server, Resource resourceBody) throws Exception {
try (InputStream metadataSource = resourceBody.getInputStream()) {
try (Buffer metadataBuffer = new Buffer()) {
metadataBuffer.readFrom(metadataSource);
MockResponse metadataResponse = new MockResponse().setBody(metadataBuffer);
server.enqueue(metadataResponse);
}
}
}
@Configuration(proxyBeanMethods = false)
static class RegistrationRepositoryConfiguration {
@Bean
RelyingPartyRegistrationRepository testRegistrationRepository() {
return mock(RelyingPartyRegistrationRepository.class);
}
}
@EnableWebSecurity
static class WebSecurityEnablerConfiguration {
}
@Configuration(proxyBeanMethods = false)
static class TestSecurityFilterChainConfig {
@Bean
SecurityFilterChain testSecurityFilterChain(HttpSecurity http) throws Exception {
return http.securityMatcher("/**")
.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated())
.build();
}
}
}

View File

@@ -0,0 +1,122 @@
/*
* Copyright 2012-2025 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.boot.security.autoconfigure.saml2;
import java.util.Collections;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link Saml2RelyingPartyProperties}.
*
* @author Madhura Bhave
* @author Lasse Wulff
*/
class Saml2RelyingPartyPropertiesTests {
private final Saml2RelyingPartyProperties properties = new Saml2RelyingPartyProperties();
@Test
void customizeSsoUrl() {
bind("spring.security.saml2.relyingparty.registration.simplesamlphp.assertingparty.single-sign-on.url",
"https://simplesaml-for-spring-saml/SSOService.php");
assertThat(
this.properties.getRegistration().get("simplesamlphp").getAssertingparty().getSinglesignon().getUrl())
.isEqualTo("https://simplesaml-for-spring-saml/SSOService.php");
}
@Test
void customizeSsoBinding() {
bind("spring.security.saml2.relyingparty.registration.simplesamlphp.assertingparty.single-sign-on.binding",
"post");
assertThat(this.properties.getRegistration()
.get("simplesamlphp")
.getAssertingparty()
.getSinglesignon()
.getBinding()).isEqualTo(Saml2MessageBinding.POST);
}
@Test
void customizeSsoSignRequests() {
bind("spring.security.saml2.relyingparty.registration.simplesamlphp.assertingparty.single-sign-on.sign-request",
"false");
assertThat(this.properties.getRegistration()
.get("simplesamlphp")
.getAssertingparty()
.getSinglesignon()
.getSignRequest()).isFalse();
}
@Test
void customizeRelyingPartyEntityId() {
bind("spring.security.saml2.relyingparty.registration.simplesamlphp.entity-id",
"{baseUrl}/saml2/custom-entity-id");
assertThat(this.properties.getRegistration().get("simplesamlphp").getEntityId())
.isEqualTo("{baseUrl}/saml2/custom-entity-id");
}
@Test
void customizeRelyingPartyEntityIdDefaultsToServiceProviderMetadata() {
assertThat(RelyingPartyRegistration.withRegistrationId("id")).extracting("entityId")
.isEqualTo(new Saml2RelyingPartyProperties.Registration().getEntityId());
}
@Test
void customizeAssertingPartyMetadataUri() {
bind("spring.security.saml2.relyingparty.registration.simplesamlphp.assertingparty.metadata-uri",
"https://idp.example.org/metadata");
assertThat(this.properties.getRegistration().get("simplesamlphp").getAssertingparty().getMetadataUri())
.isEqualTo("https://idp.example.org/metadata");
}
@Test
void customizeSsoSignRequestsIsNullByDefault() {
this.properties.getRegistration().put("simplesamlphp", new Saml2RelyingPartyProperties.Registration());
assertThat(this.properties.getRegistration()
.get("simplesamlphp")
.getAssertingparty()
.getSinglesignon()
.getSignRequest()).isNull();
}
@Test
void customizeNameIdFormat() {
bind("spring.security.saml2.relyingparty.registration.simplesamlphp.name-id-format", "sampleNameIdFormat");
assertThat(this.properties.getRegistration().get("simplesamlphp").getNameIdFormat())
.isEqualTo("sampleNameIdFormat");
}
private void bind(String name, String value) {
bind(Collections.singletonMap(name, value));
}
private void bind(Map<String, String> map) {
ConfigurationPropertySource source = new MapConfigurationPropertySource(map);
new Binder(source).bind("spring.security.saml2.relyingparty", Bindable.ofInstance(this.properties));
}
}

View File

@@ -0,0 +1,117 @@
/*
* Copyright 2012-2025 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.boot.security.autoconfigure.servlet;
import jakarta.servlet.http.HttpServletRequest;
import org.assertj.core.api.AssertDelegateTarget;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.h2console.autoconfigure.H2ConsoleProperties;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockServletContext;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.context.WebApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link PathRequest}.
*
* @author Madhura Bhave
*/
class PathRequestTests {
@Test
void toStaticResourcesShouldReturnStaticResourceRequest() {
assertThat(PathRequest.toStaticResources()).isInstanceOf(StaticResourceRequest.class);
}
@Test
void toH2ConsoleShouldMatchH2ConsolePath() {
RequestMatcher matcher = PathRequest.toH2Console();
assertMatcher(matcher).matches("/h2-console");
assertMatcher(matcher).matches("/h2-console/subpath");
assertMatcher(matcher).doesNotMatch("/js/file.js");
}
@Test
void toH2ConsoleWhenManagementContextShouldNeverMatch() {
RequestMatcher matcher = PathRequest.toH2Console();
assertMatcher(matcher, "management").doesNotMatch("/h2-console");
assertMatcher(matcher, "management").doesNotMatch("/h2-console/subpath");
assertMatcher(matcher, "management").doesNotMatch("/js/file.js");
}
private RequestMatcherAssert assertMatcher(RequestMatcher matcher) {
return assertMatcher(matcher, null);
}
private RequestMatcherAssert assertMatcher(RequestMatcher matcher, String serverNamespace) {
TestWebApplicationContext context = new TestWebApplicationContext(serverNamespace);
context.registerBean(ServerProperties.class);
context.registerBean(H2ConsoleProperties.class);
return assertThat(new RequestMatcherAssert(context, matcher));
}
static class RequestMatcherAssert implements AssertDelegateTarget {
private final WebApplicationContext context;
private final RequestMatcher matcher;
RequestMatcherAssert(WebApplicationContext context, RequestMatcher matcher) {
this.context = context;
this.matcher = matcher;
}
void matches(String path) {
matches(mockRequest(path));
}
private void matches(HttpServletRequest request) {
assertThat(this.matcher.matches(request)).as("Matches " + getRequestPath(request)).isTrue();
}
void doesNotMatch(String path) {
doesNotMatch(mockRequest(path));
}
private void doesNotMatch(HttpServletRequest request) {
assertThat(this.matcher.matches(request)).as("Does not match " + getRequestPath(request)).isFalse();
}
private MockHttpServletRequest mockRequest(String path) {
MockServletContext servletContext = new MockServletContext();
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
MockHttpServletRequest request = new MockHttpServletRequest(servletContext);
request.setRequestURI(path);
return request;
}
private String getRequestPath(HttpServletRequest request) {
String url = request.getServletPath();
if (StringUtils.hasText(request.getRequestURI())) {
url += request.getRequestURI();
}
return url;
}
}
}

View File

@@ -0,0 +1,319 @@
/*
* Copyright 2012-2025 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.boot.security.autoconfigure.servlet;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.security.interfaces.RSAPublicKey;
import java.util.EnumSet;
import jakarta.servlet.DispatcherType;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.ConfigurationPropertiesBinding;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.convert.ApplicationConversionService;
import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration;
import org.springframework.boot.jpa.autoconfigure.hibernate.HibernateJpaAutoConfiguration;
import org.springframework.boot.security.autoconfigure.jpa.City;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.boot.testsupport.classpath.resources.WithResource;
import org.springframework.boot.web.servlet.DelegatingFilterProxyRegistrationBean;
import org.springframework.boot.web.servlet.filter.OrderedFilter;
import org.springframework.boot.webmvc.autoconfigure.WebMvcAutoConfiguration;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.data.repository.query.SecurityEvaluationContextExtension;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link SecurityAutoConfiguration}.
*
* @author Dave Syer
* @author Rob Winch
* @author Andy Wilkinson
* @author Madhura Bhave
*/
class SecurityAutoConfigurationTests {
private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner().withConfiguration(
AutoConfigurations.of(SecurityAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class));
@Test
void testWebConfiguration() {
this.contextRunner.run((context) -> {
assertThat(context.getBean(AuthenticationManagerBuilder.class)).isNotNull();
assertThat(context.getBean(FilterChainProxy.class).getFilterChains()).hasSize(1);
});
}
@Test
void enableWebSecurityIsConditionalOnClass() {
this.contextRunner.withClassLoader(new FilteredClassLoader("org.springframework.security.config"))
.run((context) -> assertThat(context).doesNotHaveBean("springSecurityFilterChain"));
}
@Test
void filterChainBeanIsConditionalOnClassSecurityFilterChain() {
this.contextRunner.withClassLoader(new FilteredClassLoader(SecurityFilterChain.class))
.run((context) -> assertThat(context).doesNotHaveBean(SecurityFilterChain.class));
}
@Test
void securityConfigurerBacksOffWhenOtherSecurityFilterChainBeanPresent() {
this.contextRunner.withConfiguration(AutoConfigurations.of(WebMvcAutoConfiguration.class))
.withUserConfiguration(TestSecurityFilterChainConfig.class)
.run((context) -> {
assertThat(context.getBeansOfType(SecurityFilterChain.class)).hasSize(1);
assertThat(context.containsBean("testSecurityFilterChain")).isTrue();
});
}
@Test
void testFilterIsNotRegisteredInNonWeb() {
try (AnnotationConfigApplicationContext customContext = new AnnotationConfigApplicationContext()) {
customContext.register(SecurityAutoConfiguration.class, SecurityFilterAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class);
customContext.refresh();
assertThat(customContext.containsBean("securityFilterChainRegistration")).isFalse();
}
}
@Test
void defaultAuthenticationEventPublisherRegistered() {
this.contextRunner.run((context) -> assertThat(context.getBean(AuthenticationEventPublisher.class))
.isInstanceOf(DefaultAuthenticationEventPublisher.class));
}
@Test
void defaultAuthenticationEventPublisherIsConditionalOnMissingBean() {
this.contextRunner.withUserConfiguration(AuthenticationEventPublisherConfiguration.class)
.run((context) -> assertThat(context.getBean(AuthenticationEventPublisher.class))
.isInstanceOf(AuthenticationEventPublisherConfiguration.TestAuthenticationEventPublisher.class));
}
@Test
void testDefaultFilterOrder() {
this.contextRunner.withConfiguration(AutoConfigurations.of(SecurityFilterAutoConfiguration.class))
.run((context) -> assertThat(
context.getBean("securityFilterChainRegistration", DelegatingFilterProxyRegistrationBean.class)
.getOrder())
.isEqualTo(OrderedFilter.REQUEST_WRAPPER_FILTER_MAX_ORDER - 100));
}
@Test
void testCustomFilterOrder() {
this.contextRunner.withConfiguration(AutoConfigurations.of(SecurityFilterAutoConfiguration.class))
.withPropertyValues("spring.security.filter.order:12345")
.run((context) -> assertThat(
context.getBean("securityFilterChainRegistration", DelegatingFilterProxyRegistrationBean.class)
.getOrder())
.isEqualTo(12345));
}
@Test
void testJpaCoexistsHappily() {
this.contextRunner.withPropertyValues("spring.datasource.url:jdbc:hsqldb:mem:testsecdb")
.withUserConfiguration(EntityConfiguration.class)
.withConfiguration(
AutoConfigurations.of(HibernateJpaAutoConfiguration.class, DataSourceAutoConfiguration.class))
.run((context) -> assertThat(context.getBean(JpaTransactionManager.class)).isNotNull());
// This can fail if security @Conditionals force early instantiation of the
// HibernateJpaAutoConfiguration (e.g. the EntityManagerFactory is not found)
}
@Test
void testSecurityEvaluationContextExtensionSupport() {
this.contextRunner
.run((context) -> assertThat(context).getBean(SecurityEvaluationContextExtension.class).isNotNull());
}
@Test
void defaultFilterDispatcherTypes() {
this.contextRunner.withConfiguration(AutoConfigurations.of(SecurityFilterAutoConfiguration.class))
.run((context) -> {
DelegatingFilterProxyRegistrationBean bean = context.getBean("securityFilterChainRegistration",
DelegatingFilterProxyRegistrationBean.class);
assertThat(bean).extracting("dispatcherTypes", InstanceOfAssertFactories.iterable(DispatcherType.class))
.containsExactlyInAnyOrderElementsOf(EnumSet.allOf(DispatcherType.class));
});
}
@Test
void customFilterDispatcherTypes() {
this.contextRunner.withPropertyValues("spring.security.filter.dispatcher-types:INCLUDE,ERROR")
.withConfiguration(AutoConfigurations.of(SecurityFilterAutoConfiguration.class))
.run((context) -> {
DelegatingFilterProxyRegistrationBean bean = context.getBean("securityFilterChainRegistration",
DelegatingFilterProxyRegistrationBean.class);
assertThat(bean).extracting("dispatcherTypes", InstanceOfAssertFactories.iterable(DispatcherType.class))
.containsOnly(DispatcherType.INCLUDE, DispatcherType.ERROR);
});
}
@Test
void emptyFilterDispatcherTypesDoNotThrowException() {
this.contextRunner.withPropertyValues("spring.security.filter.dispatcher-types:")
.withConfiguration(AutoConfigurations.of(SecurityFilterAutoConfiguration.class))
.run((context) -> {
DelegatingFilterProxyRegistrationBean bean = context.getBean("securityFilterChainRegistration",
DelegatingFilterProxyRegistrationBean.class);
assertThat(bean).extracting("dispatcherTypes", InstanceOfAssertFactories.iterable(DispatcherType.class))
.isEmpty();
});
}
@Test
@WithPublicKeyResource
void whenAConfigurationPropertyBindingConverterIsDefinedThenBindingToAnRsaKeySucceeds() {
this.contextRunner.withUserConfiguration(ConverterConfiguration.class, PropertiesConfiguration.class)
.withPropertyValues("jwt.public-key=classpath:public-key-location")
.run((context) -> assertThat(context.getBean(JwtProperties.class).getPublicKey()).isNotNull());
}
@Test
@WithPublicKeyResource
void whenTheBeanFactoryHasAConversionServiceAndAConfigurationPropertyBindingConverterIsDefinedThenBindingToAnRsaKeySucceeds() {
this.contextRunner
.withInitializer(
(context) -> context.getBeanFactory().setConversionService(new ApplicationConversionService()))
.withUserConfiguration(ConverterConfiguration.class, PropertiesConfiguration.class)
.withPropertyValues("jwt.public-key=classpath:public-key-location")
.run((context) -> assertThat(context.getBean(JwtProperties.class).getPublicKey()).isNotNull());
}
@Configuration(proxyBeanMethods = false)
@TestAutoConfigurationPackage(City.class)
static class EntityConfiguration {
}
@Configuration(proxyBeanMethods = false)
static class AuthenticationEventPublisherConfiguration {
@Bean
AuthenticationEventPublisher authenticationEventPublisher() {
return new TestAuthenticationEventPublisher();
}
class TestAuthenticationEventPublisher implements AuthenticationEventPublisher {
@Override
public void publishAuthenticationSuccess(Authentication authentication) {
}
@Override
public void publishAuthenticationFailure(AuthenticationException exception, Authentication authentication) {
}
}
}
@Configuration(proxyBeanMethods = false)
static class TestSecurityFilterChainConfig {
@Bean
SecurityFilterChain testSecurityFilterChain(HttpSecurity http) throws Exception {
return http.securityMatcher("/**")
.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated())
.build();
}
}
@Configuration(proxyBeanMethods = false)
static class ConverterConfiguration {
@Bean
@ConfigurationPropertiesBinding
static Converter<String, TargetType> targetTypeConverter() {
return new Converter<>() {
@Override
public TargetType convert(String input) {
return new TargetType();
}
};
}
}
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(JwtProperties.class)
static class PropertiesConfiguration {
}
@ConfigurationProperties("jwt")
static class JwtProperties {
private RSAPublicKey publicKey;
RSAPublicKey getPublicKey() {
return this.publicKey;
}
void setPublicKey(RSAPublicKey publicKey) {
this.publicKey = publicKey;
}
}
static class TargetType {
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@WithResource(name = "public-key-location", content = """
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdlatRjRjogo3WojgGHFHYLugd
UWAY9iR3fy4arWNA1KoS8kVw33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQs
HUfQrSDv+MuSUMAe8jzKE4qW+jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5D
o2kQ+X5xK9cipRgEKwIDAQAB
-----END PUBLIC KEY-----
""")
@interface WithPublicKeyResource {
}
}

View File

@@ -0,0 +1,162 @@
/*
* Copyright 2012-2025 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.boot.security.autoconfigure.servlet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.http.autoconfigure.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration;
import org.springframework.boot.test.system.CapturedOutput;
import org.springframework.boot.test.system.OutputCaptureExtension;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.testsupport.classpath.ClassPathExclusions;
import org.springframework.boot.tomcat.servlet.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.boot.webmvc.autoconfigure.DispatcherServletAutoConfiguration;
import org.springframework.boot.webmvc.autoconfigure.WebMvcAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration test to ensure {@link SecurityFilterAutoConfiguration} doesn't cause early
* initialization.
*
* @author Phillip Webb
*/
@ExtendWith(OutputCaptureExtension.class)
class SecurityFilterAutoConfigurationEarlyInitializationTests {
private static final Pattern PASSWORD_PATTERN = Pattern.compile("^Using generated security password: (.*)$",
Pattern.MULTILINE);
@Test
@ClassPathExclusions({ "spring-security-oauth2-client-*.jar", "spring-security-oauth2-resource-server-*.jar",
"spring-security-saml2-service-provider-*.jar" })
void testSecurityFilterDoesNotCauseEarlyInitialization(CapturedOutput output) {
try (AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext()) {
TestPropertyValues.of("server.port:0").applyTo(context);
context.register(Config.class);
context.refresh();
int port = context.getWebServer().getPort();
Matcher password = PASSWORD_PATTERN.matcher(output);
assertThat(password.find()).isTrue();
new TestRestTemplate("user", password.group(1)).getForEntity("http://localhost:" + port, Object.class);
// If early initialization occurred a ConverterNotFoundException is thrown
}
}
@Configuration(proxyBeanMethods = false)
@Import({ DeserializerBean.class, JacksonModuleBean.class, ExampleController.class, ConverterBean.class })
@ImportAutoConfiguration({ WebMvcAutoConfiguration.class, JacksonAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class, DispatcherServletAutoConfiguration.class,
SecurityAutoConfiguration.class, UserDetailsServiceAutoConfiguration.class,
SecurityFilterAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class })
static class Config {
@Bean
TomcatServletWebServerFactory webServerFactory() {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.setPort(0);
return factory;
}
}
static class SourceType {
public String foo;
}
static class DestinationType {
public String bar;
}
@Component
static class JacksonModuleBean extends SimpleModule {
private static final long serialVersionUID = 1L;
JacksonModuleBean(DeserializerBean myDeser) {
addDeserializer(SourceType.class, myDeser);
}
}
@Component
static class DeserializerBean extends StdDeserializer<SourceType> {
@Autowired
ConversionService conversionService;
DeserializerBean() {
super(SourceType.class);
}
@Override
public SourceType deserialize(JsonParser p, DeserializationContext ctxt) {
return new SourceType();
}
}
@RestController
static class ExampleController {
@Autowired
private ConversionService conversionService;
@RequestMapping("/")
void convert() {
this.conversionService.convert(new SourceType(), DestinationType.class);
}
}
@Component
static class ConverterBean implements Converter<SourceType, DestinationType> {
@Override
public DestinationType convert(SourceType source) {
return new DestinationType();
}
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2012-2025 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.boot.security.autoconfigure.servlet;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.http.autoconfigure.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration;
import org.springframework.boot.security.autoconfigure.servlet.SecurityFilterAutoConfigurationEarlyInitializationTests.ConverterBean;
import org.springframework.boot.security.autoconfigure.servlet.SecurityFilterAutoConfigurationEarlyInitializationTests.DeserializerBean;
import org.springframework.boot.security.autoconfigure.servlet.SecurityFilterAutoConfigurationEarlyInitializationTests.ExampleController;
import org.springframework.boot.security.autoconfigure.servlet.SecurityFilterAutoConfigurationEarlyInitializationTests.JacksonModuleBean;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebApplicationContext;
import org.springframework.boot.webmvc.autoconfigure.DispatcherServletAutoConfiguration;
import org.springframework.boot.webmvc.autoconfigure.WebMvcAutoConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.mock.web.MockServletContext;
/**
* Tests for {@link SecurityFilterAutoConfiguration}.
*
* @author Andy Wilkinson
*/
class SecurityFilterAutoConfigurationTests {
@Test
void filterAutoConfigurationWorksWithoutSecurityAutoConfiguration() {
try (AnnotationConfigServletWebApplicationContext context = new AnnotationConfigServletWebApplicationContext()) {
context.setServletContext(new MockServletContext());
context.register(Config.class);
context.refresh();
}
}
@Configuration(proxyBeanMethods = false)
@Import({ DeserializerBean.class, JacksonModuleBean.class, ExampleController.class, ConverterBean.class })
@ImportAutoConfiguration({ WebMvcAutoConfiguration.class, JacksonAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class, DispatcherServletAutoConfiguration.class,
SecurityFilterAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class })
static class Config {
}
}

View File

@@ -0,0 +1,178 @@
/*
* Copyright 2012-2025 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.boot.security.autoconfigure.servlet;
import jakarta.servlet.http.HttpServletRequest;
import org.assertj.core.api.AssertDelegateTarget;
import org.junit.jupiter.api.Test;
import org.springframework.boot.security.autoconfigure.StaticResourceLocation;
import org.springframework.boot.webmvc.autoconfigure.DispatcherServletPath;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockServletContext;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.context.WebApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link StaticResourceRequest}.
*
* @author Madhura Bhave
* @author Phillip Webb
*/
class StaticResourceRequestTests {
private final StaticResourceRequest resourceRequest = StaticResourceRequest.INSTANCE;
@Test
void atCommonLocationsShouldMatchCommonLocations() {
RequestMatcher matcher = this.resourceRequest.atCommonLocations();
assertMatcher(matcher).matches("/css/file.css");
assertMatcher(matcher).matches("/js/file.js");
assertMatcher(matcher).matches("/images/file.css");
assertMatcher(matcher).matches("/webjars/file.css");
assertMatcher(matcher).matches("/favicon.ico");
assertMatcher(matcher).matches("/favicon.png");
assertMatcher(matcher).matches("/icons/icon-48x48.png");
assertMatcher(matcher).doesNotMatch("/bar");
}
@Test
void atCommonLocationsWhenManagementContextShouldNeverMatch() {
RequestMatcher matcher = this.resourceRequest.atCommonLocations();
assertMatcher(matcher, "management").doesNotMatch("/css/file.css");
assertMatcher(matcher, "management").doesNotMatch("/js/file.js");
assertMatcher(matcher, "management").doesNotMatch("/images/file.css");
assertMatcher(matcher, "management").doesNotMatch("/webjars/file.css");
assertMatcher(matcher, "management").doesNotMatch("/foo/favicon.ico");
}
@Test
void atCommonLocationsWithExcludeShouldNotMatchExcluded() {
RequestMatcher matcher = this.resourceRequest.atCommonLocations().excluding(StaticResourceLocation.CSS);
assertMatcher(matcher).doesNotMatch("/css/file.css");
assertMatcher(matcher).matches("/js/file.js");
}
@Test
void atLocationShouldMatchLocation() {
RequestMatcher matcher = this.resourceRequest.at(StaticResourceLocation.CSS);
assertMatcher(matcher).matches("/css/file.css");
assertMatcher(matcher).doesNotMatch("/js/file.js");
}
@Test
void atLocationWhenHasServletPathShouldMatchLocation() {
RequestMatcher matcher = this.resourceRequest.at(StaticResourceLocation.CSS);
assertMatcher(matcher, null, "/foo").matches("/foo", "/css/file.css");
assertMatcher(matcher, null, "/foo").doesNotMatch("/foo", "/js/file.js");
}
@Test
void atLocationsFromSetWhenSetIsNullShouldThrowException() {
assertThatIllegalArgumentException().isThrownBy(() -> this.resourceRequest.at(null))
.withMessageContaining("'locations' must not be null");
}
@Test
void excludeFromSetWhenSetIsNullShouldThrowException() {
assertThatIllegalArgumentException().isThrownBy(() -> this.resourceRequest.atCommonLocations().excluding(null))
.withMessageContaining("'locations' must not be null");
}
private RequestMatcherAssert assertMatcher(RequestMatcher matcher) {
return assertMatcher(matcher, null, "");
}
private RequestMatcherAssert assertMatcher(RequestMatcher matcher, String serverNamespace) {
return assertMatcher(matcher, serverNamespace, "");
}
private RequestMatcherAssert assertMatcher(RequestMatcher matcher, String serverNamespace, String path) {
DispatcherServletPath dispatcherServletPath = () -> path;
TestWebApplicationContext context = new TestWebApplicationContext(serverNamespace);
context.registerBean(DispatcherServletPath.class, () -> dispatcherServletPath);
return assertThat(new RequestMatcherAssert(context, matcher));
}
static class RequestMatcherAssert implements AssertDelegateTarget {
private final WebApplicationContext context;
private final RequestMatcher matcher;
RequestMatcherAssert(WebApplicationContext context, RequestMatcher matcher) {
this.context = context;
this.matcher = matcher;
}
void matches(String path) {
matches(mockRequest(path));
}
void matches(String servletPath, String path) {
matches(mockRequest(servletPath, path));
}
private void matches(HttpServletRequest request) {
assertThat(this.matcher.matches(request)).as("Matches " + getRequestPath(request)).isTrue();
}
void doesNotMatch(String path) {
doesNotMatch(mockRequest(path));
}
void doesNotMatch(String servletPath, String path) {
doesNotMatch(mockRequest(servletPath, path));
}
private void doesNotMatch(HttpServletRequest request) {
assertThat(this.matcher.matches(request)).as("Does not match " + getRequestPath(request)).isFalse();
}
private MockHttpServletRequest mockRequest(String path) {
return mockRequest(null, path);
}
private MockHttpServletRequest mockRequest(String servletPath, String path) {
MockServletContext servletContext = new MockServletContext();
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
MockHttpServletRequest request = new MockHttpServletRequest(servletContext);
if (servletPath != null) {
request.setServletPath(servletPath);
request.setRequestURI(servletPath + path);
}
else {
request.setRequestURI(path);
}
return request;
}
private String getRequestPath(HttpServletRequest request) {
String url = request.getServletPath();
if (StringUtils.hasText(request.getRequestURI())) {
url += request.getRequestURI();
}
return url;
}
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2012-2025 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.boot.security.autoconfigure.servlet;
import org.springframework.boot.web.context.WebServerApplicationContext;
import org.springframework.boot.web.server.WebServer;
import org.springframework.web.context.support.StaticWebApplicationContext;
/**
* Test {@link StaticWebApplicationContext} that also implements
* {@link WebServerApplicationContext}.
*
* @author Phillip Webb
*/
class TestWebApplicationContext extends StaticWebApplicationContext implements WebServerApplicationContext {
private final String serverNamespace;
TestWebApplicationContext(String serverNamespace) {
this.serverNamespace = serverNamespace;
}
@Override
public WebServer getWebServer() {
return null;
}
@Override
public String getServerNamespace() {
return this.serverNamespace;
}
}

View File

@@ -0,0 +1,382 @@
/*
* Copyright 2012-2025 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.boot.security.autoconfigure.servlet;
import java.util.Collections;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport;
import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport.ConditionAndOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport.ConditionAndOutcomes;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.security.autoconfigure.SecurityProperties;
import org.springframework.boot.security.autoconfigure.servlet.UserDetailsServiceAutoConfiguration.MissingAlternativeOrUserPropertiesConfigured;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.AbstractApplicationContextRunner;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.boot.test.system.CapturedOutput;
import org.springframework.boot.test.system.OutputCaptureExtension;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationManagerResolver;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.TestingAuthenticationProvider;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link UserDetailsServiceAutoConfiguration}.
*
* @author Madhura Bhave
* @author HaiTao Zhang
* @author Lasse Wulff
* @author Moritz Halbritter
*/
@ExtendWith(OutputCaptureExtension.class)
class UserDetailsServiceAutoConfigurationTests {
private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()
.withUserConfiguration(TestSecurityConfiguration.class)
.withConfiguration(AutoConfigurations.of(UserDetailsServiceAutoConfiguration.class));
@Test
void shouldSupplyUserDetailsServiceInServletApp() {
this.contextRunner.with(AlternativeFormOfAuthentication.nonPresent())
.run((context) -> assertThat(context).hasSingleBean(UserDetailsService.class));
}
@Test
void shouldNotSupplyUserDetailsServiceInReactiveApp() {
new ReactiveWebApplicationContextRunner().withUserConfiguration(TestSecurityConfiguration.class)
.withConfiguration(AutoConfigurations.of(UserDetailsServiceAutoConfiguration.class))
.with(AlternativeFormOfAuthentication.nonPresent())
.run((context) -> assertThat(context).doesNotHaveBean(UserDetailsService.class));
}
@Test
void shouldNotSupplyUserDetailsServiceInNonWebApp() {
new ApplicationContextRunner().withUserConfiguration(TestSecurityConfiguration.class)
.withConfiguration(AutoConfigurations.of(UserDetailsServiceAutoConfiguration.class))
.with(AlternativeFormOfAuthentication.nonPresent())
.run((context) -> assertThat(context).doesNotHaveBean(UserDetailsService.class));
}
@Test
void testDefaultUsernamePassword(CapturedOutput output) {
this.contextRunner.with(AlternativeFormOfAuthentication.nonPresent()).run((context) -> {
assertThat(outcomeOfMissingAlternativeCondition(context).isMatch()).isTrue();
UserDetailsService manager = context.getBean(UserDetailsService.class);
assertThat(output).contains("Using generated security password:");
assertThat(manager.loadUserByUsername("user")).isNotNull();
});
}
@Test
void defaultUserNotCreatedIfAuthenticationManagerBeanPresent(CapturedOutput output) {
this.contextRunner.with(AlternativeFormOfAuthentication.nonPresent())
.withUserConfiguration(TestAuthenticationManagerConfiguration.class)
.run((context) -> {
assertThat(outcomeOfMissingAlternativeCondition(context).isMatch()).isTrue();
AuthenticationManager manager = context.getBean(AuthenticationManager.class);
assertThat(manager)
.isEqualTo(context.getBean(TestAuthenticationManagerConfiguration.class).authenticationManager);
assertThat(output).doesNotContain("Using generated security password: ");
TestingAuthenticationToken token = new TestingAuthenticationToken("foo", "bar");
assertThat(manager.authenticate(token)).isNotNull();
});
}
@Test
void defaultUserNotCreatedIfAuthenticationManagerResolverBeanPresent(CapturedOutput output) {
this.contextRunner.with(AlternativeFormOfAuthentication.nonPresent())
.withUserConfiguration(TestAuthenticationManagerResolverConfiguration.class)
.run((context) -> {
assertThat(outcomeOfMissingAlternativeCondition(context).isMatch()).isTrue();
assertThat(output).doesNotContain("Using generated security password: ");
});
}
@Test
void defaultUserNotCreatedIfUserDetailsServiceBeanPresent(CapturedOutput output) {
this.contextRunner.with(AlternativeFormOfAuthentication.nonPresent())
.withUserConfiguration(TestUserDetailsServiceConfiguration.class)
.run((context) -> {
assertThat(outcomeOfMissingAlternativeCondition(context).isMatch()).isTrue();
UserDetailsService userDetailsService = context.getBean(UserDetailsService.class);
assertThat(output).doesNotContain("Using generated security password: ");
assertThat(userDetailsService.loadUserByUsername("foo")).isNotNull();
});
}
@Test
void defaultUserNotCreatedIfAuthenticationProviderBeanPresent(CapturedOutput output) {
this.contextRunner.with(AlternativeFormOfAuthentication.nonPresent())
.withUserConfiguration(TestAuthenticationProviderConfiguration.class)
.run((context) -> {
assertThat(outcomeOfMissingAlternativeCondition(context).isMatch()).isTrue();
AuthenticationProvider provider = context.getBean(AuthenticationProvider.class);
assertThat(output).doesNotContain("Using generated security password: ");
TestingAuthenticationToken token = new TestingAuthenticationToken("foo", "bar");
assertThat(provider.authenticate(token)).isNotNull();
});
}
@Test
void defaultUserNotCreatedIfJwtDecoderBeanPresent() {
this.contextRunner.with(AlternativeFormOfAuthentication.nonPresent())
.withUserConfiguration(TestConfigWithJwtDecoder.class)
.run((context) -> {
assertThat(outcomeOfMissingAlternativeCondition(context).isMatch()).isTrue();
assertThat(context).hasSingleBean(JwtDecoder.class);
assertThat(context).doesNotHaveBean(UserDetailsService.class);
});
}
@Test
void userDetailsServiceWhenPasswordEncoderAbsentAndDefaultPassword() {
this.contextRunner.with(AlternativeFormOfAuthentication.nonPresent())
.withUserConfiguration(TestSecurityConfiguration.class)
.run(((context) -> {
InMemoryUserDetailsManager userDetailsService = context.getBean(InMemoryUserDetailsManager.class);
String password = userDetailsService.loadUserByUsername("user").getPassword();
assertThat(password).startsWith("{noop}");
}));
}
@Test
void userDetailsServiceWhenPasswordEncoderAbsentAndRawPassword() {
testPasswordEncoding(TestSecurityConfiguration.class, "secret", "{noop}secret");
}
@Test
void userDetailsServiceWhenPasswordEncoderAbsentAndEncodedPassword() {
String password = "{bcrypt}$2a$10$sCBi9fy9814vUPf2ZRbtp.fR5/VgRk2iBFZ.ypu5IyZ28bZgxrVDa";
testPasswordEncoding(TestSecurityConfiguration.class, password, password);
}
@Test
void userDetailsServiceWhenPasswordEncoderBeanPresent() {
testPasswordEncoding(TestConfigWithPasswordEncoder.class, "secret", "secret");
}
@ParameterizedTest
@EnumSource
void whenClassOfAlternativeIsPresentUserDetailsServiceBacksOff(AlternativeFormOfAuthentication alternative) {
this.contextRunner.with(alternative.present())
.run((context) -> assertThat(context).doesNotHaveBean(InMemoryUserDetailsManager.class));
}
@ParameterizedTest
@EnumSource
void whenAlternativeIsPresentAndUsernameIsConfiguredThenUserDetailsServiceIsAutoConfigured(
AlternativeFormOfAuthentication alternative) {
this.contextRunner.with(alternative.present())
.withPropertyValues("spring.security.user.name=alice")
.run(((context) -> assertThat(context).hasSingleBean(InMemoryUserDetailsManager.class)));
}
@ParameterizedTest
@EnumSource
void whenAlternativeIsPresentAndPasswordIsConfiguredThenUserDetailsServiceIsAutoConfigured(
AlternativeFormOfAuthentication alternative) {
this.contextRunner.with(alternative.present())
.withPropertyValues("spring.security.user.password=secret")
.run(((context) -> assertThat(context).hasSingleBean(InMemoryUserDetailsManager.class)));
}
private void testPasswordEncoding(Class<?> configClass, String providedPassword, String expectedPassword) {
this.contextRunner.with(AlternativeFormOfAuthentication.nonPresent())
.withUserConfiguration(configClass)
.withPropertyValues("spring.security.user.password=" + providedPassword)
.run(((context) -> {
InMemoryUserDetailsManager userDetailsService = context.getBean(InMemoryUserDetailsManager.class);
String password = userDetailsService.loadUserByUsername("user").getPassword();
assertThat(password).isEqualTo(expectedPassword);
}));
}
private ConditionOutcome outcomeOfMissingAlternativeCondition(ConfigurableApplicationContext context) {
ConditionAndOutcomes conditionAndOutcomes = ConditionEvaluationReport.get(context.getBeanFactory())
.getConditionAndOutcomesBySource()
.get(UserDetailsServiceAutoConfiguration.class.getName());
for (ConditionAndOutcome conditionAndOutcome : conditionAndOutcomes) {
if (conditionAndOutcome.getCondition() instanceof MissingAlternativeOrUserPropertiesConfigured) {
return conditionAndOutcome.getOutcome();
}
}
return null;
}
@Configuration(proxyBeanMethods = false)
static class TestAuthenticationManagerConfiguration {
private AuthenticationManager authenticationManager;
@Bean
AuthenticationManager myAuthenticationManager() {
AuthenticationProvider authenticationProvider = new TestingAuthenticationProvider();
this.authenticationManager = new ProviderManager(Collections.singletonList(authenticationProvider));
return this.authenticationManager;
}
}
@Configuration(proxyBeanMethods = false)
static class TestUserDetailsServiceConfiguration {
@Bean
InMemoryUserDetailsManager myUserDetailsManager() {
return new InMemoryUserDetailsManager(User.withUsername("foo").password("bar").roles("USER").build());
}
}
@Configuration(proxyBeanMethods = false)
static class TestAuthenticationProviderConfiguration {
@Bean
AuthenticationProvider myAuthenticationProvider() {
return new TestingAuthenticationProvider();
}
}
@Configuration(proxyBeanMethods = false)
@EnableWebSecurity
@EnableConfigurationProperties(SecurityProperties.class)
static class TestSecurityConfiguration {
}
@Configuration(proxyBeanMethods = false)
@Import(TestSecurityConfiguration.class)
static class TestConfigWithPasswordEncoder {
@Bean
PasswordEncoder passwordEncoder() {
return mock(PasswordEncoder.class);
}
}
@Configuration(proxyBeanMethods = false)
@Import(TestSecurityConfiguration.class)
static class TestConfigWithClientRegistrationRepository {
@Bean
ClientRegistrationRepository clientRegistrationRepository() {
return mock(ClientRegistrationRepository.class);
}
}
@Configuration(proxyBeanMethods = false)
@Import(TestSecurityConfiguration.class)
static class TestConfigWithJwtDecoder {
@Bean
JwtDecoder jwtDecoder() {
return mock(JwtDecoder.class);
}
}
@Configuration(proxyBeanMethods = false)
@Import(TestSecurityConfiguration.class)
static class TestConfigWithIntrospectionClient {
@Bean
OpaqueTokenIntrospector introspectionClient() {
return mock(OpaqueTokenIntrospector.class);
}
}
@Configuration(proxyBeanMethods = false)
static class TestAuthenticationManagerResolverConfiguration {
@Bean
AuthenticationManagerResolver<?> authenticationManagerResolver() {
return mock(AuthenticationManagerResolver.class);
}
}
private enum AlternativeFormOfAuthentication {
CLIENT_REGISTRATION_REPOSITORY(ClientRegistrationRepository.class),
OPAQUE_TOKEN_INTROSPECTOR(OpaqueTokenIntrospector.class),
RELYING_PARTY_REGISTRATION_REPOSITORY(RelyingPartyRegistrationRepository.class);
private final Class<?> type;
AlternativeFormOfAuthentication(Class<?> type) {
this.type = type;
}
private Class<?> getType() {
return this.type;
}
@SuppressWarnings("unchecked")
private <T extends AbstractApplicationContextRunner<?, ?, ?>> Function<T, T> present() {
return (contextRunner) -> (T) contextRunner
.withClassLoader(new FilteredClassLoader(Stream.of(AlternativeFormOfAuthentication.values())
.filter(Predicate.not(this::equals))
.map(AlternativeFormOfAuthentication::getType)
.toArray(Class[]::new)));
}
@SuppressWarnings("unchecked")
private static <T extends AbstractApplicationContextRunner<?, ?, ?>> Function<T, T> nonPresent() {
return (contextRunner) -> (T) contextRunner
.withClassLoader(new FilteredClassLoader(Stream.of(AlternativeFormOfAuthentication.values())
.map(AlternativeFormOfAuthentication::getType)
.toArray(Class[]::new)));
}
}
}

View File

@@ -0,0 +1,156 @@
/*
* Copyright 2012-2025 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.boot.security.reactive;
import java.util.function.Supplier;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.MockServerHttpResponse;
import org.springframework.mock.web.server.MockServerWebExchange;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebHandler;
import org.springframework.web.server.adapter.HttpWebHandlerAdapter;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link ApplicationContextServerWebExchangeMatcher}.
*
* @author Madhura Bhave
*/
class ApplicationContextServerWebExchangeMatcherTests {
@Test
void createWhenContextClassIsNullShouldThrowException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new TestApplicationContextServerWebExchangeMatcher<>(null))
.withMessageContaining("'contextClass' must not be null");
}
@Test
void matchesWhenContextClassIsApplicationContextShouldProvideContext() {
ServerWebExchange exchange = createExchange();
StaticApplicationContext context = (StaticApplicationContext) exchange.getApplicationContext();
assertThat(new TestApplicationContextServerWebExchangeMatcher<>(ApplicationContext.class)
.callMatchesAndReturnProvidedContext(exchange)
.get()).isEqualTo(context);
}
@Test
void matchesWhenContextClassIsExistingBeanShouldProvideBean() {
ServerWebExchange exchange = createExchange();
StaticApplicationContext context = (StaticApplicationContext) exchange.getApplicationContext();
context.registerSingleton("existingBean", ExistingBean.class);
assertThat(new TestApplicationContextServerWebExchangeMatcher<>(ExistingBean.class)
.callMatchesAndReturnProvidedContext(exchange)
.get()).isEqualTo(context.getBean(ExistingBean.class));
}
@Test
void matchesWhenContextClassIsMissingBeanShouldProvideException() {
ServerWebExchange exchange = createExchange();
Supplier<ExistingBean> supplier = new TestApplicationContextServerWebExchangeMatcher<>(ExistingBean.class)
.callMatchesAndReturnProvidedContext(exchange);
assertThatExceptionOfType(NoSuchBeanDefinitionException.class).isThrownBy(supplier::get);
}
@Test
void matchesWhenContextIsNull() {
MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/path").build());
assertThatIllegalStateException()
.isThrownBy(() -> new TestApplicationContextServerWebExchangeMatcher<>(ExistingBean.class)
.callMatchesAndReturnProvidedContext(exchange))
.withMessageContaining("No ApplicationContext found on ServerWebExchange.");
}
private ServerWebExchange createExchange() {
StaticApplicationContext context = new StaticApplicationContext();
TestHttpWebHandlerAdapter adapter = new TestHttpWebHandlerAdapter(mock(WebHandler.class));
adapter.setApplicationContext(context);
return adapter.createExchange(MockServerHttpRequest.get("/path").build(), new MockServerHttpResponse());
}
static class TestHttpWebHandlerAdapter extends HttpWebHandlerAdapter {
TestHttpWebHandlerAdapter(WebHandler delegate) {
super(delegate);
}
@Override
protected ServerWebExchange createExchange(ServerHttpRequest request, ServerHttpResponse response) {
return super.createExchange(request, response);
}
}
static class ExistingBean {
}
static class NewBean {
private final ExistingBean bean;
NewBean(ExistingBean bean) {
this.bean = bean;
}
ExistingBean getBean() {
return this.bean;
}
}
static class TestApplicationContextServerWebExchangeMatcher<C>
extends ApplicationContextServerWebExchangeMatcher<C> {
private Supplier<C> providedContext;
TestApplicationContextServerWebExchangeMatcher(Class<? extends C> context) {
super(context);
}
Supplier<C> callMatchesAndReturnProvidedContext(ServerWebExchange exchange) {
matches(exchange);
return getProvidedContext();
}
@Override
protected Mono<MatchResult> matches(ServerWebExchange exchange, Supplier<C> context) {
this.providedContext = context;
return MatchResult.match();
}
Supplier<C> getProvidedContext() {
return this.providedContext;
}
}
}

View File

@@ -0,0 +1,239 @@
/*
* Copyright 2012-2025 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.boot.security.servlet;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
import jakarta.servlet.http.HttpServletRequest;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockServletContext;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.StaticWebApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link ApplicationContextRequestMatcher}.
*
* @author Phillip Webb
*/
class ApplicationContextRequestMatcherTests {
@Test
void createWhenContextClassIsNullShouldThrowException() {
assertThatIllegalArgumentException().isThrownBy(() -> new TestApplicationContextRequestMatcher<>(null))
.withMessageContaining("'contextClass' must not be null");
}
@Test
void matchesWhenContextClassIsApplicationContextShouldProvideContext() {
StaticWebApplicationContext context = createWebApplicationContext();
assertThat(new TestApplicationContextRequestMatcher<>(ApplicationContext.class)
.callMatchesAndReturnProvidedContext(context)
.get()).isEqualTo(context);
}
@Test
void matchesWhenContextClassIsExistingBeanShouldProvideBean() {
StaticWebApplicationContext context = createWebApplicationContext();
context.registerSingleton("existingBean", ExistingBean.class);
assertThat(new TestApplicationContextRequestMatcher<>(ExistingBean.class)
.callMatchesAndReturnProvidedContext(context)
.get()).isEqualTo(context.getBean(ExistingBean.class));
}
@Test
void matchesWhenContextClassIsBeanThatDoesNotExistShouldSupplyException() {
StaticWebApplicationContext context = createWebApplicationContext();
Supplier<ExistingBean> supplier = new TestApplicationContextRequestMatcher<>(ExistingBean.class)
.callMatchesAndReturnProvidedContext(context);
assertThatExceptionOfType(NoSuchBeanDefinitionException.class).isThrownBy(supplier::get);
}
@Test // gh-18012
void matchesWhenCalledWithDifferentApplicationContextDoesNotCache() {
StaticWebApplicationContext context1 = createWebApplicationContext();
StaticWebApplicationContext context2 = createWebApplicationContext();
TestApplicationContextRequestMatcher<ApplicationContext> matcher = new TestApplicationContextRequestMatcher<>(
ApplicationContext.class);
assertThat(matcher.callMatchesAndReturnProvidedContext(context1).get()).isEqualTo(context1);
assertThat(matcher.callMatchesAndReturnProvidedContext(context2).get()).isEqualTo(context2);
}
@Test
void initializeAndMatchesAreNotCalledIfContextIsIgnored() {
StaticWebApplicationContext context = createWebApplicationContext();
TestApplicationContextRequestMatcher<ApplicationContext> matcher = new TestApplicationContextRequestMatcher<>(
ApplicationContext.class) {
@Override
protected boolean ignoreApplicationContext(WebApplicationContext webApplicationContext) {
return true;
}
@Override
protected void initialized(Supplier<ApplicationContext> context) {
throw new IllegalStateException();
}
@Override
protected boolean matches(HttpServletRequest request, Supplier<ApplicationContext> context) {
throw new IllegalStateException();
}
};
MockHttpServletRequest request = new MockHttpServletRequest(context.getServletContext());
assertThat(matcher.matches(request)).isFalse();
}
@Test // gh-18211
void matchesWhenConcurrentlyCalledWaitsForInitialize() {
ConcurrentApplicationContextRequestMatcher matcher = new ConcurrentApplicationContextRequestMatcher();
StaticWebApplicationContext context = createWebApplicationContext();
Runnable target = () -> matcher.matches(new MockHttpServletRequest(context.getServletContext()));
List<Thread> threads = new ArrayList<>();
AssertingUncaughtExceptionHandler exceptionHandler = new AssertingUncaughtExceptionHandler();
for (int i = 0; i < 2; i++) {
Thread thread = new Thread(target);
thread.setUncaughtExceptionHandler(exceptionHandler);
threads.add(thread);
}
threads.forEach(Thread::start);
threads.forEach(this::join);
exceptionHandler.assertNoExceptions();
}
private void join(Thread thread) {
try {
thread.join(1000);
}
catch (InterruptedException ex) {
// Ignore
}
}
private StaticWebApplicationContext createWebApplicationContext() {
StaticWebApplicationContext context = new StaticWebApplicationContext();
MockServletContext servletContext = new MockServletContext();
context.setServletContext(servletContext);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, context);
return context;
}
static class ExistingBean {
}
static class NewBean {
private final ExistingBean bean;
NewBean(ExistingBean bean) {
this.bean = bean;
}
ExistingBean getBean() {
return this.bean;
}
}
static class TestApplicationContextRequestMatcher<C> extends ApplicationContextRequestMatcher<C> {
private Supplier<C> providedContext;
TestApplicationContextRequestMatcher(Class<? extends C> context) {
super(context);
}
Supplier<C> callMatchesAndReturnProvidedContext(WebApplicationContext context) {
return callMatchesAndReturnProvidedContext(new MockHttpServletRequest(context.getServletContext()));
}
Supplier<C> callMatchesAndReturnProvidedContext(HttpServletRequest request) {
matches(request);
return getProvidedContext();
}
@Override
protected boolean matches(HttpServletRequest request, Supplier<C> context) {
this.providedContext = context;
return false;
}
Supplier<C> getProvidedContext() {
return this.providedContext;
}
}
static class ConcurrentApplicationContextRequestMatcher extends ApplicationContextRequestMatcher<Object> {
ConcurrentApplicationContextRequestMatcher() {
super(Object.class);
}
private final AtomicBoolean initialized = new AtomicBoolean();
@Override
protected void initialized(Supplier<Object> context) {
try {
Thread.sleep(200);
}
catch (InterruptedException ex) {
// Ignore
}
this.initialized.set(true);
}
@Override
protected boolean matches(HttpServletRequest request, Supplier<Object> context) {
assertThat(this.initialized.get()).isTrue();
return true;
}
}
private static final class AssertingUncaughtExceptionHandler implements UncaughtExceptionHandler {
private volatile Throwable ex;
@Override
public void uncaughtException(Thread thread, Throwable ex) {
this.ex = ex;
}
void assertNoExceptions() {
if (this.ex != null) {
ReflectionUtils.rethrowRuntimeException(this.ex);
}
}
}
}

View File

@@ -0,0 +1,24 @@
-----BEGIN CERTIFICATE-----
MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYD
VQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYD
VQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwX
c2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0Bw
aXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJ
BgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAa
BgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQD
DBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlr
QHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62
E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz
2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWW
RDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQ
nX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5
cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gph
iJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5
ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTAD
AQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduO
nRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+v
ZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLu
xbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6z
V9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3
lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk
-----END CERTIFICATE-----

View File

@@ -0,0 +1,42 @@
<md:EntityDescriptor entityID="https://idp.example.com/idp/shibboleth"
xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:shibmd="urn:mace:shibboleth:metadata:1.0"
xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"
xmlns:mdui="urn:oasis:names:tc:SAML:metadata:ui">
<md:IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<md:KeyDescriptor>
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>
MIIDZjCCAk6gAwIBAgIVAL9O+PA7SXtlwZZY8MVSE9On1cVWMA0GCSqGSIb3DQEB
BQUAMCkxJzAlBgNVBAMTHmlkZW0tcHVwYWdlbnQuZG16LWludC51bmltby5pdDAe
Fw0xMzA3MjQwMDQ0MTRaFw0zMzA3MjQwMDQ0MTRaMCkxJzAlBgNVBAMTHmlkZW0t
cHVwYWdlbnQuZG16LWludC51bmltby5pdDCCASIwDQYJKoZIhvcNAMIIDQADggEP
ADCCAQoCggEBAIAcp/VyzZGXUF99kwj4NvL/Rwv4YvBgLWzpCuoxqHZ/hmBwJtqS
v0y9METBPFbgsF3hCISnxbcmNVxf/D0MoeKtw1YPbsUmow/bFe+r72hZ+IVAcejN
iDJ7t5oTjsRN1t1SqvVVk6Ryk5AZhpFW+W9pE9N6c7kJ16Rp2/mbtax9OCzxpece
byi1eiLfIBmkcRawL/vCc2v6VLI18i6HsNVO3l2yGosKCbuSoGDx2fCdAOk/rgdz
cWOvFsIZSKuD+FVbSS/J9GVs7yotsS4PRl4iX9UMnfDnOMfO7bcBgbXtDl4SCU1v
dJrRw7IL/pLz34Rv9a8nYitrzrxtLOp3nYUCAwEAAaOBhDCBgTBgBgMIIDEEWTBX
gh5pZGVtLXB1cGFnZW50LmRtei1pbnQudW5pbW8uaXSGNWh0dHBzOi8vaWRlbS1w
dXBhZ2VudC5kbXotaW50LnVuaW1vLml0L2lkcC9zaGliYm9sZXRoMB0GA1UdDgQW
BBT8PANzz+adGnTRe8ldcyxAwe4VnzANBgkqhkiG9w0BAQUFAAOCAQEAOEnO8Clu
9z/Lf/8XOOsTdxJbV29DIF3G8KoQsB3dBsLwPZVEAQIP6ceS32Xaxrl6FMTDDNkL
qUvvInUisw0+I5zZwYHybJQCletUWTnz58SC4C9G7FpuXHFZnOGtRcgGD1NOX4UU
duus/4nVcGSLhDjszZ70Xtj0gw2Sn46oQPHTJ81QZ3Y9ih+Aj1c9OtUSBwtWZFkU
yooAKoR8li68Yb21zN2N65AqV+ndL98M8xUYMKLONuAXStDeoVCipH6PJ09Z5U2p
V5p4IQRV6QBsNw9CISJFuHzkVYTH5ZxzN80Ru46vh4y2M0Nu8GQ9I085KoZkrf5e
Cq53OZt9ISjHEw==
</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>
<md:SingleSignOnService
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Location="https://idp.example.com/sso"/>
</md:IDPSSODescriptor>
<md:ContactPerson contactType="technical">
<md:EmailAddress>mailto:technical.contact@example.com</md:EmailAddress>
</md:ContactPerson>
</md:EntityDescriptor>

View File

@@ -0,0 +1,86 @@
<EntitiesDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ID="virtu-20230614094100" Name="virtu" validUntil="2023-07-12T06:41:00Z" xsi:schemaLocation="urn:oasis:names:tc:SAML:2.0:metadata saml-schema-metadata-2.0.xsd http://www.w3.org/2000/09/xmldsig# xmldsig-core-schema.xsd">
<md:EntityDescriptor entityID="https://idp.example.com/idp/shibboleth"
xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:shibmd="urn:mace:shibboleth:metadata:1.0"
xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"
xmlns:mdui="urn:oasis:names:tc:SAML:metadata:ui">
<md:IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<md:KeyDescriptor>
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>
MIIDZjCCAk6gAwIBAgIVAL9O+PA7SXtlwZZY8MVSE9On1cVWMA0GCSqGSIb3DQEB
BQUAMCkxJzAlBgNVBAMTHmlkZW0tcHVwYWdlbnQuZG16LWludC51bmltby5pdDAe
Fw0xMzA3MjQwMDQ0MTRaFw0zMzA3MjQwMDQ0MTRaMCkxJzAlBgNVBAMTHmlkZW0t
cHVwYWdlbnQuZG16LWludC51bmltby5pdDCCASIwDQYJKoZIhvcNAMIIDQADggEP
ADCCAQoCggEBAIAcp/VyzZGXUF99kwj4NvL/Rwv4YvBgLWzpCuoxqHZ/hmBwJtqS
v0y9METBPFbgsF3hCISnxbcmNVxf/D0MoeKtw1YPbsUmow/bFe+r72hZ+IVAcejN
iDJ7t5oTjsRN1t1SqvVVk6Ryk5AZhpFW+W9pE9N6c7kJ16Rp2/mbtax9OCzxpece
byi1eiLfIBmkcRawL/vCc2v6VLI18i6HsNVO3l2yGosKCbuSoGDx2fCdAOk/rgdz
cWOvFsIZSKuD+FVbSS/J9GVs7yotsS4PRl4iX9UMnfDnOMfO7bcBgbXtDl4SCU1v
dJrRw7IL/pLz34Rv9a8nYitrzrxtLOp3nYUCAwEAAaOBhDCBgTBgBgMIIDEEWTBX
gh5pZGVtLXB1cGFnZW50LmRtei1pbnQudW5pbW8uaXSGNWh0dHBzOi8vaWRlbS1w
dXBhZ2VudC5kbXotaW50LnVuaW1vLml0L2lkcC9zaGliYm9sZXRoMB0GA1UdDgQW
BBT8PANzz+adGnTRe8ldcyxAwe4VnzANBgkqhkiG9w0BAQUFAAOCAQEAOEnO8Clu
9z/Lf/8XOOsTdxJbV29DIF3G8KoQsB3dBsLwPZVEAQIP6ceS32Xaxrl6FMTDDNkL
qUvvInUisw0+I5zZwYHybJQCletUWTnz58SC4C9G7FpuXHFZnOGtRcgGD1NOX4UU
duus/4nVcGSLhDjszZ70Xtj0gw2Sn46oQPHTJ81QZ3Y9ih+Aj1c9OtUSBwtWZFkU
yooAKoR8li68Yb21zN2N65AqV+ndL98M8xUYMKLONuAXStDeoVCipH6PJ09Z5U2p
V5p4IQRV6QBsNw9CISJFuHzkVYTH5ZxzN80Ru46vh4y2M0Nu8GQ9I085KoZkrf5e
Cq53OZt9ISjHEw==
</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>
<md:SingleSignOnService
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Location="https://idp.example.com/sso"/>
</md:IDPSSODescriptor>
<md:ContactPerson contactType="technical">
<md:EmailAddress>mailto:technical.contact@example.com</md:EmailAddress>
</md:ContactPerson>
</md:EntityDescriptor>
<md:EntityDescriptor entityID="https://idp2.example.com/idp/shibboleth"
xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:shibmd="urn:mace:shibboleth:metadata:1.0"
xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"
xmlns:mdui="urn:oasis:names:tc:SAML:metadata:ui">
<md:IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<md:KeyDescriptor>
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>
MIIDZjCCAk6gAwIBAgIVAL9O+PA7SXtlwZZY8MVSE9On1cVWMA0GCSqGSIb3DQEB
BQUAMCkxJzAlBgNVBAMTHmlkZW0tcHVwYWdlbnQuZG16LWludC51bmltby5pdDAe
Fw0xMzA3MjQwMDQ0MTRaFw0zMzA3MjQwMDQ0MTRaMCkxJzAlBgNVBAMTHmlkZW0t
cHVwYWdlbnQuZG16LWludC51bmltby5pdDCCASIwDQYJKoZIhvcNAMIIDQADggEP
ADCCAQoCggEBAIAcp/VyzZGXUF99kwj4NvL/Rwv4YvBgLWzpCuoxqHZ/hmBwJtqS
v0y9METBPFbgsF3hCISnxbcmNVxf/D0MoeKtw1YPbsUmow/bFe+r72hZ+IVAcejN
iDJ7t5oTjsRN1t1SqvVVk6Ryk5AZhpFW+W9pE9N6c7kJ16Rp2/mbtax9OCzxpece
byi1eiLfIBmkcRawL/vCc2v6VLI18i6HsNVO3l2yGosKCbuSoGDx2fCdAOk/rgdz
cWOvFsIZSKuD+FVbSS/J9GVs7yotsS4PRl4iX9UMnfDnOMfO7bcBgbXtDl4SCU1v
dJrRw7IL/pLz34Rv9a8nYitrzrxtLOp3nYUCAwEAAaOBhDCBgTBgBgMIIDEEWTBX
gh5pZGVtLXB1cGFnZW50LmRtei1pbnQudW5pbW8uaXSGNWh0dHBzOi8vaWRlbS1w
dXBhZ2VudC5kbXotaW50LnVuaW1vLml0L2lkcC9zaGliYm9sZXRoMB0GA1UdDgQW
BBT8PANzz+adGnTRe8ldcyxAwe4VnzANBgkqhkiG9w0BAQUFAAOCAQEAOEnO8Clu
9z/Lf/8XOOsTdxJbV29DIF3G8KoQsB3dBsLwPZVEAQIP6ceS32Xaxrl6FMTDDNkL
qUvvInUisw0+I5zZwYHybJQCletUWTnz58SC4C9G7FpuXHFZnOGtRcgGD1NOX4UU
duus/4nVcGSLhDjszZ70Xtj0gw2Sn46oQPHTJ81QZ3Y9ih+Aj1c9OtUSBwtWZFkU
yooAKoR8li68Yb21zN2N65AqV+ndL98M8xUYMKLONuAXStDeoVCipH6PJ09Z5U2p
V5p4IQRV6QBsNw9CISJFuHzkVYTH5ZxzN80Ru46vh4y2M0Nu8GQ9I085KoZkrf5e
Cq53OZt9ISjHEw==
</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>
<md:SingleSignOnService
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Location="https://idp2.example.com/sso"/>
</md:IDPSSODescriptor>
<md:ContactPerson contactType="technical">
<md:EmailAddress>mailto:technical.contact2@example.com</md:EmailAddress>
</md:ContactPerson>
</md:EntityDescriptor>
</EntitiesDescriptor>

View File

@@ -0,0 +1,16 @@
-----BEGIN PRIVATE KEY-----
MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBANG7v8QjQGU3MwQE
VUBxvH6Uuiy/MhZT7TV0ZNjyAF2ExA1gpn3aUxx6jYK5UnrpxRRE/KbeLucYbOhK
cDECt77Rggz5TStrOta0BQTvfluRyoQtmQ5Nkt6Vqg7O2ZapFt7k64Sal7AftzH6
Q2BxWN1y04bLdDrH4jipqRj/2qEFAgMBAAECgYEAj4ExY1jjdN3iEDuOwXuRB+Nn
x7pC4TgntE2huzdKvLJdGvIouTArce8A6JM5NlTBvm69mMepvAHgcsiMH1zGr5J5
wJz23mGOyhM1veON41/DJTVG+cxq4soUZhdYy3bpOuXGMAaJ8QLMbQQoivllNihd
vwH0rNSK8LTYWWPZYIECQQDxct+TFX1VsQ1eo41K0T4fu2rWUaxlvjUGhK6HxTmY
8OMJptunGRJL1CUjIb45Uz7SP8TPz5FwhXWsLfS182kRAkEA3l+Qd9C9gdpUh1uX
oPSNIxn5hFUrSTW1EwP9QH9vhwb5Vr8Jrd5ei678WYDLjUcx648RjkjhU9jSMzIx
EGvYtQJBAMm/i9NR7IVyyNIgZUpz5q4LI21rl1r4gUQuD8vA36zM81i4ROeuCly0
KkfdxR4PUfnKcQCX11YnHjk9uTFj75ECQEFY/gBnxDjzqyF35hAzrYIiMPQVfznt
YX/sDTE2AdVBVGaMj1Cb51bPHnNC6Q5kXKQnj/YrLqRQND09Q7ParX0CQQC5NxZr
9jKqhHj8yQD6PlXTsY4Occ7DH6/IoDenfdEVD5qlet0zmd50HatN2Jiqm5ubN7CM
INrtuLp4YHbgk1mi
-----END PRIVATE KEY-----

View File

@@ -0,0 +1,23 @@
-----BEGIN CERTIFICATE-----
MIID1zCCAr+gAwIBAgIUCzQeKBMTO0iHVW3iKmZC41haqCowDQYJKoZIhvcNAQEL
BQAwezELMAkGA1UEBhMCWFgxEjAQBgNVBAgMCVN0YXRlTmFtZTERMA8GA1UEBwwI
Q2l0eU5hbWUxFDASBgNVBAoMC0NvbXBhbnlOYW1lMRswGQYDVQQLDBJDb21wYW55
U2VjdGlvbk5hbWUxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0yMzA5MjAwODI5MDNa
Fw0zMzA5MTcwODI5MDNaMHsxCzAJBgNVBAYTAlhYMRIwEAYDVQQIDAlTdGF0ZU5h
bWUxETAPBgNVBAcMCENpdHlOYW1lMRQwEgYDVQQKDAtDb21wYW55TmFtZTEbMBkG
A1UECwwSQ29tcGFueVNlY3Rpb25OYW1lMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEi
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDUfi4aaCotJZX6OSDjv6fxCCfc
ihSs91Z/mmN+yc1fsxVSs53SIbqUuo+Wzhv34kp8I/r03P9LWVTkFPbeDxAl75Oa
PGggxK55US0Zfy9Hj1BwWIKV3330N61emID1GDEtFKL4yJbJdreQXnIXTBL2o76V
nuV/tYozyZnb07IQ1WhUm5WDxgzM0yFudMynTczCBeZHfvharDtB8PFFhCZXW2/9
TZVVfW4oOML8EAX3hvnvYBlFl/foxXekZSwq/odOkmWCZavT2+0sburHUlOnPGUh
Qj4tHwpMRczp7VX4ptV1D2UrxsK/2B+s9FK2QSLKQ9JzAYJ6WxQjHcvET9jvAgMB
AAGjUzBRMB0GA1UdDgQWBBQjDr/1E/01pfLPD8uWF7gbaYL0TTAfBgNVHSMEGDAW
gBQjDr/1E/01pfLPD8uWF7gbaYL0TTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3
DQEBCwUAA4IBAQAGjUuec0+0XNMCRDKZslbImdCAVsKsEWk6NpnUViDFAxL+KQuC
NW131UeHb9SCzMqRwrY4QI3nAwJQCmilL/hFM3ss4acn3WHu1yci/iKPUKeL1ec5
kCFUmqX1NpTiVaytZ/9TKEr69SMVqNfQiuW5U1bIIYTqK8xo46WpM6YNNHO3eJK6
NH0MW79Wx5ryi4i4C6afqYbVbx7tqcmy8CFeNxgZ0bFQ87SiwYXIj77b6sVYbu32
doykBQgSHLcagWASPQ73m73CWUgo+7+EqSKIQqORbgmTLPmOUh99gFIx7jmjTyHm
NBszx1ZVWuIv3mWmp626Kncyc+LLM9tvgymx
-----END CERTIFICATE-----

View File

@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDUfi4aaCotJZX6
OSDjv6fxCCfcihSs91Z/mmN+yc1fsxVSs53SIbqUuo+Wzhv34kp8I/r03P9LWVTk
FPbeDxAl75OaPGggxK55US0Zfy9Hj1BwWIKV3330N61emID1GDEtFKL4yJbJdreQ
XnIXTBL2o76VnuV/tYozyZnb07IQ1WhUm5WDxgzM0yFudMynTczCBeZHfvharDtB
8PFFhCZXW2/9TZVVfW4oOML8EAX3hvnvYBlFl/foxXekZSwq/odOkmWCZavT2+0s
burHUlOnPGUhQj4tHwpMRczp7VX4ptV1D2UrxsK/2B+s9FK2QSLKQ9JzAYJ6WxQj
HcvET9jvAgMBAAECggEADdeRuZml1F65mDJm1enduaH+NWvEm1yEr3ecr0fbujYI
bQ89+CVx/znvRvPH4aFwQwmgUZl12JrfS05MTectoPMBf/obDwtmPDPmsV2rdEi9
2jEB11vW23T8X7L6hOdzCKHqrd8kkhzK1LuPnhHlaFipU8YlOBOuMYpv8eB78y79
Qkd5/ZEygFhqVGz96R7nT/xS21aPC7OPhicAauLLuguF4caCNhwkjLi3bizLemUn
4i41q69drg7G8WX6BTxzem5FupKfI8rn2EkOjO/biVRknzGxAdqkM8SDHWkqeOuY
8QVhc1kZsMkB0BGPlDPStUwEHSfUiND4GJTcngc++QKBgQD2lyeW3PoPjQ1qzjN4
V/0XE77zpcPE5dW7chLtiWRY1dqk2uOJ32iOtxuqk9Q/YMSZyPJlTkfI5JePuC/B
MB+QXzXuWN03Vn0ZrOpQlxcdA4A1o10NT1nEw8kZlf4+LyUk8GpMGUhjnxFZpZbf
5S3fy0/2V8wGvOmXR65c8m6ASQKBgQDcmfCV5npu1HrtO8jmU9gBIhniNjB4IWue
TSRt3ANDQaVBqsVaIMe/mUEQrZ6MdikMeA4bobOA6bUYwOiq8JGWSenAzGL22TbA
W51q6A8hgDCuH1JnoagqUIbr61kwEVcfbRHEFpuxLURsjoDg/xBtwO96SxWPh5Wr
+f1q8t5/dwKBgGWc+AVk3e6Wk1bVzcPjjjl6O4+vWTLD+wUZBs+3dBBfX4/bWzQv
Sai1r8Lk0+uh9qHgenJghZg1CneA0LztFbSqZ1DmcZIiI7720D+RY0bjcGup++hG
MJmyjCXs9y2sw8OrBkKBkKDspXupjriIehTkdPjwSPTl1+Qs9575j6txAoGAT8n+
ErnCHsQLkjLFf0lkH0TOR9uBvHGaEy+jtXiWVYUw2IeDyg2BMfOkbPvfFL7IKhJi
R+w8mKvvLHzZqrpIbitduLY0NURrYTfBwCEfF+bdtJzvmTwHLwbhRgNhxtj+wgcZ
HetvdK4CyaDhTH/02T2nYHw32CoaIJHS7xPZFhECgYEAv7xRawjlrC4V0BLjP3Ej
pk8BbsRABxN1CrS6nJK+So4u2gKQDsL3WA0oJTS8v8AD5LvQUNr1d57FVlq9lwCd
u623eOIuluCUZBVy1iYdkRXWz9pg5bCidCgEYUpF3SqpsuFou0XFzDD773UVQFVw
VYriYasPwmzS2y2P7PKFzJs=
-----END PRIVATE KEY-----