Create spring-boot-security module
This commit is contained in:
committed by
Phillip Webb
parent
63255f3c62
commit
f53b9786e5
@@ -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 {
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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-----
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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-----
|
||||
@@ -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-----
|
||||
@@ -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-----
|
||||
Reference in New Issue
Block a user