mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-08-30 05:07:10 +00:00
Compare commits
71 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
be64c67af5 | ||
|
a58f3282d9 | ||
|
c2ba662b91 | ||
|
49f308adb0 | ||
|
4cbe8de7ea | ||
|
559b73b39f | ||
|
3278f3a410 | ||
|
36f1de945f | ||
|
6663eea65f | ||
|
89b2f9cf54 | ||
|
0e39685b9c | ||
|
9d64880ea9 | ||
|
8b2a453301 | ||
|
d1962201b5 | ||
|
857ca9c412 | ||
|
894105aab5 | ||
|
f7f41ba6c4 | ||
|
f496ded4e5 | ||
|
583e668c6b | ||
|
d6a0e3bf78 | ||
|
29bb4919ca | ||
|
d9210c6596 | ||
|
b8b1a92ad4 | ||
|
bbcdb23698 | ||
|
9bbf837c7c | ||
|
8a1e2a22f9 | ||
|
0404996f87 | ||
|
0f63d98c84 | ||
|
fbfbb1e571 | ||
|
d002e68231 | ||
|
41162aa7e3 | ||
|
d86f2c957d | ||
|
62b5b1a77c | ||
|
523222c24d | ||
|
69f38d4933 | ||
|
0179a811c7 | ||
|
7ce2bdd701 | ||
|
de4ceffc4f | ||
|
8c920a7ee7 | ||
|
b9653346a1 | ||
|
9e6bcbd1d0 | ||
|
8d888edc71 | ||
|
f82fe9c8c6 | ||
|
a8f045eb50 | ||
|
5c5efc9092 | ||
|
5453224377 | ||
|
a14ad770ab | ||
|
806297da23 | ||
|
8846b44b81 | ||
|
7f103b2d0a | ||
|
c06ff59747 | ||
|
f1194de45e | ||
|
1f16009c8d | ||
|
3e13437b1a | ||
|
20a6863e07 | ||
|
e145c07d5b | ||
|
68a7f1702f | ||
|
3e8b606aac | ||
|
ef5c703010 | ||
|
47be93e694 | ||
|
9310153d16 | ||
|
a933089ec2 | ||
|
a8da9ec7f7 | ||
|
3396890d8b | ||
|
c45bc384da | ||
|
4da98dde2b | ||
|
7575e4ef1c | ||
|
acaed1ad96 | ||
|
0549bf566b | ||
|
44037c0ea4 | ||
|
01c8cea00f |
@ -17,8 +17,6 @@ package io.spring.gradle;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.gradle.testkit.runner.GradleRunner;
|
||||
import org.junit.runner.Description;
|
||||
import org.junit.runners.model.Statement;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
@ -23,8 +23,6 @@ import org.gradle.testfixtures.ProjectBuilder;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
|
@ -5,7 +5,6 @@ import org.apache.commons.io.FileUtils;
|
||||
import org.gradle.testkit.runner.BuildResult;
|
||||
import org.gradle.testkit.runner.TaskOutcome;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
|
@ -25,7 +25,6 @@ dependencies {
|
||||
optional project(':spring-security-ldap')
|
||||
optional project(':spring-security-messaging')
|
||||
optional project(path: ':spring-security-saml2-service-provider')
|
||||
opensaml5 project(path: ':spring-security-saml2-service-provider', configuration: 'opensamlFiveMain')
|
||||
optional project(':spring-security-oauth2-client')
|
||||
optional project(':spring-security-oauth2-jose')
|
||||
optional project(':spring-security-oauth2-resource-server')
|
||||
@ -170,15 +169,3 @@ test {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register("opensaml5Test", Test) {
|
||||
filter {
|
||||
includeTestsMatching "org.springframework.security.config.annotation.web.configurers.saml2.*"
|
||||
}
|
||||
useJUnitPlatform()
|
||||
classpath = sourceSets.main.output + sourceSets.test.output + configurations.opensaml5
|
||||
}
|
||||
|
||||
tasks.named("check") {
|
||||
dependsOn opensaml5Test
|
||||
}
|
||||
|
@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.annotation.rsocket;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import io.rsocket.core.RSocketServer;
|
||||
import io.rsocket.exceptions.RejectedSetupException;
|
||||
import io.rsocket.frame.decoder.PayloadDecoder;
|
||||
import io.rsocket.transport.netty.server.CloseableChannel;
|
||||
import io.rsocket.transport.netty.server.TcpServerTransport;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.messaging.handler.annotation.MessageMapping;
|
||||
import org.springframework.messaging.rsocket.RSocketRequester;
|
||||
import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler;
|
||||
import org.springframework.security.authentication.AuthenticationTrustResolver;
|
||||
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
|
||||
import org.springframework.security.authorization.AuthorizationDecision;
|
||||
import org.springframework.security.authorization.ReactiveAuthorizationManager;
|
||||
import org.springframework.security.rsocket.core.PayloadSocketAcceptorInterceptor;
|
||||
import org.springframework.security.rsocket.core.SecuritySocketAcceptorInterceptor;
|
||||
import org.springframework.security.rsocket.util.matcher.PayloadExchangeAuthorizationContext;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
/**
|
||||
* @author Andrey Litvitski
|
||||
*/
|
||||
@ContextConfiguration
|
||||
@ExtendWith(SpringExtension.class)
|
||||
public class AnonymousAuthenticationITests {
|
||||
|
||||
@Autowired
|
||||
RSocketMessageHandler handler;
|
||||
|
||||
@Autowired
|
||||
SecuritySocketAcceptorInterceptor interceptor;
|
||||
|
||||
@Autowired
|
||||
ServerController controller;
|
||||
|
||||
private CloseableChannel server;
|
||||
|
||||
private RSocketRequester requester;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
// @formatter:off
|
||||
this.server = RSocketServer.create()
|
||||
.payloadDecoder(PayloadDecoder.ZERO_COPY)
|
||||
.interceptors((registry) -> registry.forSocketAcceptor(this.interceptor)
|
||||
)
|
||||
.acceptor(this.handler.responder())
|
||||
.bind(TcpServerTransport.create("localhost", 0))
|
||||
.block();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void dispose() {
|
||||
this.requester.rsocket().dispose();
|
||||
this.server.dispose();
|
||||
this.controller.payloads.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWhenAnonymousDisabledThenRespondsWithForbidden() {
|
||||
this.requester = RSocketRequester.builder()
|
||||
.rsocketStrategies(this.handler.getRSocketStrategies())
|
||||
.connectTcp("localhost", this.server.address().getPort())
|
||||
.block();
|
||||
String data = "andrew";
|
||||
assertThatExceptionOfType(RejectedSetupException.class).isThrownBy(
|
||||
() -> this.requester.route("secure.retrieve-mono").data(data).retrieveMono(String.class).block());
|
||||
assertThat(this.controller.payloads).isEmpty();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRSocketSecurity
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
ServerController controller() {
|
||||
return new ServerController();
|
||||
}
|
||||
|
||||
@Bean
|
||||
RSocketMessageHandler messageHandler() {
|
||||
return new RSocketMessageHandler();
|
||||
}
|
||||
|
||||
@Bean
|
||||
PayloadSocketAcceptorInterceptor rsocketInterceptor(RSocketSecurity rsocket) {
|
||||
AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
|
||||
ReactiveAuthorizationManager<PayloadExchangeAuthorizationContext> anonymous = (authentication,
|
||||
exchange) -> authentication.map(trustResolver::isAnonymous).map(AuthorizationDecision::new);
|
||||
rsocket.authorizePayload((authorize) -> authorize.anyExchange().access(anonymous));
|
||||
rsocket.anonymous((anonymousAuthentication) -> anonymousAuthentication.disable());
|
||||
return rsocket.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Controller
|
||||
static class ServerController {
|
||||
|
||||
private List<String> payloads = new ArrayList<>();
|
||||
|
||||
@MessageMapping("**")
|
||||
String retrieveMono(String payload) {
|
||||
add(payload);
|
||||
return "Hi " + payload;
|
||||
}
|
||||
|
||||
private void add(String p) {
|
||||
this.payloads.add(p);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config;
|
||||
|
||||
/**
|
||||
* A {@link Customizer} that allows invocation of code that throws a checked exception.
|
||||
*
|
||||
* @param <T> The type of input.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ThrowingCustomizer<T> extends Customizer<T> {
|
||||
|
||||
/**
|
||||
* Default {@link Customizer#customize(Object)} that wraps any thrown checked
|
||||
* exceptions (by default in a {@link RuntimeException}).
|
||||
* @param t the object to customize
|
||||
*/
|
||||
default void customize(T t) {
|
||||
try {
|
||||
customizeWithException(t);
|
||||
}
|
||||
catch (RuntimeException ex) {
|
||||
throw ex;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the customization on the given object, possibly throwing a checked
|
||||
* exception.
|
||||
* @param t the object to customize
|
||||
* @throws Exception on error
|
||||
*/
|
||||
void customizeWithException(T t) throws Exception;
|
||||
|
||||
}
|
@ -102,9 +102,7 @@ class AuthorizationProxyWebConfiguration implements WebMvcConfigurer {
|
||||
Throwable accessDeniedException = this.throwableAnalyzer
|
||||
.getFirstThrowableOfType(AccessDeniedException.class, causeChain);
|
||||
if (accessDeniedException != null) {
|
||||
return new ModelAndView((model, req, res) -> {
|
||||
throw ex;
|
||||
});
|
||||
throw (AccessDeniedException) accessDeniedException;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -109,6 +109,7 @@ import org.springframework.security.rsocket.util.matcher.RoutePayloadExchangeMat
|
||||
* @author Manuel Tejeda
|
||||
* @author Ebert Toribio
|
||||
* @author Ngoc Nhan
|
||||
* @author Andrey Litvitski
|
||||
* @since 5.2
|
||||
*/
|
||||
public class RSocketSecurity {
|
||||
@ -119,6 +120,8 @@ public class RSocketSecurity {
|
||||
|
||||
private SimpleAuthenticationSpec simpleAuthSpec;
|
||||
|
||||
private AnonymousAuthenticationSpec anonymousAuthSpec = new AnonymousAuthenticationSpec(this);
|
||||
|
||||
private JwtSpec jwtSpec;
|
||||
|
||||
private AuthorizePayloadsSpec authorizePayload;
|
||||
@ -164,6 +167,20 @@ public class RSocketSecurity {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds anonymous authentication
|
||||
* @param anonymous a customizer
|
||||
* @return this instance
|
||||
* @since 7.0
|
||||
*/
|
||||
public RSocketSecurity anonymous(Customizer<AnonymousAuthenticationSpec> anonymous) {
|
||||
if (this.anonymousAuthSpec == null) {
|
||||
this.anonymousAuthSpec = new AnonymousAuthenticationSpec(this);
|
||||
}
|
||||
anonymous.customize(this.anonymousAuthSpec);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds authentication with BasicAuthenticationPayloadExchangeConverter.
|
||||
* @param basic
|
||||
@ -214,7 +231,9 @@ public class RSocketSecurity {
|
||||
if (this.jwtSpec != null) {
|
||||
result.addAll(this.jwtSpec.build());
|
||||
}
|
||||
result.add(anonymous());
|
||||
if (this.anonymousAuthSpec != null) {
|
||||
result.add(this.anonymousAuthSpec.build());
|
||||
}
|
||||
if (this.authorizePayload != null) {
|
||||
result.add(this.authorizePayload.build());
|
||||
}
|
||||
@ -222,12 +241,6 @@ public class RSocketSecurity {
|
||||
return result;
|
||||
}
|
||||
|
||||
private AnonymousPayloadInterceptor anonymous() {
|
||||
AnonymousPayloadInterceptor result = new AnonymousPayloadInterceptor("anonymousUser");
|
||||
result.setOrder(PayloadInterceptorOrder.ANONYMOUS.getOrder());
|
||||
return result;
|
||||
}
|
||||
|
||||
private <T> T getBean(Class<T> beanClass) {
|
||||
if (this.context == null) {
|
||||
return null;
|
||||
@ -283,6 +296,26 @@ public class RSocketSecurity {
|
||||
|
||||
}
|
||||
|
||||
public final class AnonymousAuthenticationSpec {
|
||||
|
||||
private RSocketSecurity parent;
|
||||
|
||||
private AnonymousAuthenticationSpec(RSocketSecurity parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
protected AnonymousPayloadInterceptor build() {
|
||||
AnonymousPayloadInterceptor result = new AnonymousPayloadInterceptor("anonymousUser");
|
||||
result.setOrder(PayloadInterceptorOrder.ANONYMOUS.getOrder());
|
||||
return result;
|
||||
}
|
||||
|
||||
public void disable() {
|
||||
this.parent.anonymousAuthSpec = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public final class BasicAuthenticationSpec {
|
||||
|
||||
private ReactiveAuthenticationManager authenticationManager;
|
||||
|
@ -16,19 +16,24 @@
|
||||
|
||||
package org.springframework.security.config.annotation.web.configuration;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.io.support.SpringFactoriesLoader;
|
||||
import org.springframework.security.authentication.AuthenticationEventPublisher;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.ObjectPostProcessor;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
||||
@ -46,6 +51,7 @@ import org.springframework.security.crypto.factory.PasswordEncoderFactories;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter;
|
||||
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.function.ThrowingSupplier;
|
||||
import org.springframework.web.accept.ContentNegotiationStrategy;
|
||||
import org.springframework.web.accept.HeaderContentNegotiationStrategy;
|
||||
@ -131,6 +137,8 @@ class HttpSecurityConfiguration {
|
||||
// @formatter:on
|
||||
applyCorsIfAvailable(http);
|
||||
applyDefaultConfigurers(http);
|
||||
applyHttpSecurityCustomizers(this.context, http);
|
||||
applyTopLevelCustomizers(this.context, http);
|
||||
return http;
|
||||
}
|
||||
|
||||
@ -160,6 +168,73 @@ class HttpSecurityConfiguration {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies all {@code Customizer<HttpSecurity>} Bean instances to the
|
||||
* {@link HttpSecurity} instance.
|
||||
* @param applicationContext the {@link ApplicationContext} to lookup Bean instances
|
||||
* @param http the {@link HttpSecurity} to apply the Beans to.
|
||||
*/
|
||||
private void applyHttpSecurityCustomizers(ApplicationContext applicationContext, HttpSecurity http) {
|
||||
ResolvableType httpSecurityCustomizerType = ResolvableType.forClassWithGenerics(Customizer.class,
|
||||
HttpSecurity.class);
|
||||
ObjectProvider<Customizer<HttpSecurity>> customizerProvider = this.context
|
||||
.getBeanProvider(httpSecurityCustomizerType);
|
||||
|
||||
// @formatter:off
|
||||
customizerProvider.orderedStream().forEach((customizer) ->
|
||||
customizer.customize(http)
|
||||
);
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies all {@link Customizer} Beans to {@link HttpSecurity}. For each public,
|
||||
* non-static method in HttpSecurity that accepts a Customizer
|
||||
* <ul>
|
||||
* <li>Use the {@link MethodParameter} (this preserves generics) to resolve all Beans
|
||||
* for that type</li>
|
||||
* <li>For each {@link Customizer} Bean invoke the {@link java.lang.reflect.Method}
|
||||
* with the {@link Customizer} Bean as the argument</li>
|
||||
* </ul>
|
||||
* @param context the {@link ApplicationContext}
|
||||
* @param http the {@link HttpSecurity}
|
||||
* @throws Exception
|
||||
*/
|
||||
private void applyTopLevelCustomizers(ApplicationContext context, HttpSecurity http) {
|
||||
ReflectionUtils.MethodFilter isCustomizerMethod = (method) -> {
|
||||
if (Modifier.isStatic(method.getModifiers())) {
|
||||
return false;
|
||||
}
|
||||
if (!Modifier.isPublic(method.getModifiers())) {
|
||||
return false;
|
||||
}
|
||||
if (!method.canAccess(http)) {
|
||||
return false;
|
||||
}
|
||||
if (method.getParameterCount() != 1) {
|
||||
return false;
|
||||
}
|
||||
if (method.getParameterTypes()[0] == Customizer.class) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
ReflectionUtils.MethodCallback invokeWithEachCustomizerBean = (customizerMethod) -> {
|
||||
|
||||
MethodParameter customizerParameter = new MethodParameter(customizerMethod, 0);
|
||||
ResolvableType customizerType = ResolvableType.forMethodParameter(customizerParameter);
|
||||
ObjectProvider<?> customizerProvider = context.getBeanProvider(customizerType);
|
||||
|
||||
// @formatter:off
|
||||
customizerProvider.orderedStream().forEach((customizer) ->
|
||||
ReflectionUtils.invokeMethod(customizerMethod, http, customizer)
|
||||
);
|
||||
// @formatter:on
|
||||
|
||||
};
|
||||
ReflectionUtils.doWithMethods(HttpSecurity.class, invokeWithEachCustomizerBean, isCustomizerMethod);
|
||||
}
|
||||
|
||||
private Map<Class<?>, Object> createSharedObjects() {
|
||||
Map<Class<?>, Object> sharedObjects = new HashMap<>();
|
||||
sharedObjects.put(ApplicationContext.class, this.context);
|
||||
|
@ -33,7 +33,6 @@ import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
@ -79,6 +78,7 @@ import org.springframework.web.filter.ServletRequestPathFilter;
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @author Keesun Baik
|
||||
* @author Yanming Zhou
|
||||
* @since 3.2
|
||||
* @see EnableWebSecurity
|
||||
* @see WebSecurity
|
||||
@ -190,7 +190,7 @@ public class WebSecurityConfiguration implements ImportAware {
|
||||
}
|
||||
|
||||
@Bean
|
||||
public static BeanFactoryPostProcessor conversionServicePostProcessor() {
|
||||
public static RsaKeyConversionServicePostProcessor conversionServicePostProcessor() {
|
||||
return new RsaKeyConversionServicePostProcessor();
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.security.config.annotation.web.reactive;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
@ -28,10 +29,13 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.context.expression.BeanFactoryResolver;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ReactiveAdapterRegistry;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.security.authentication.ReactiveAuthenticationManager;
|
||||
import org.springframework.security.authentication.UserDetailsRepositoryReactiveAuthenticationManager;
|
||||
import org.springframework.security.authentication.password.ReactiveCompromisedPasswordChecker;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.ObjectPostProcessor;
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
||||
import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults;
|
||||
@ -40,6 +44,7 @@ import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.reactive.result.method.annotation.AuthenticationPrincipalArgumentResolver;
|
||||
import org.springframework.security.web.reactive.result.method.annotation.CurrentSecurityContextArgumentResolver;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.web.reactive.config.WebFluxConfigurer;
|
||||
import org.springframework.web.reactive.result.method.annotation.ArgumentResolverConfigurer;
|
||||
|
||||
@ -154,6 +159,83 @@ class ServerHttpSecurityConfiguration {
|
||||
|
||||
@Bean(HTTPSECURITY_BEAN_NAME)
|
||||
@Scope("prototype")
|
||||
ServerHttpSecurity httpSecurity(ApplicationContext context) {
|
||||
ServerHttpSecurity http = httpSecurity();
|
||||
applyServerHttpSecurityCustomizers(context, http);
|
||||
applyTopLevelBeanCustomizers(context, http);
|
||||
return http;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies all {@code Custmizer<ServerHttpSecurity>} Beans to
|
||||
* {@link ServerHttpSecurity}.
|
||||
* @param context the {@link ApplicationContext}
|
||||
* @param http the {@link ServerHttpSecurity}
|
||||
* @throws Exception
|
||||
*/
|
||||
private void applyServerHttpSecurityCustomizers(ApplicationContext context, ServerHttpSecurity http) {
|
||||
ResolvableType httpSecurityCustomizerType = ResolvableType.forClassWithGenerics(Customizer.class,
|
||||
ServerHttpSecurity.class);
|
||||
ObjectProvider<Customizer<ServerHttpSecurity>> customizerProvider = context
|
||||
.getBeanProvider(httpSecurityCustomizerType);
|
||||
|
||||
// @formatter:off
|
||||
customizerProvider.orderedStream().forEach((customizer) ->
|
||||
customizer.customize(http)
|
||||
);
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies all {@link Customizer} Beans to top level {@link ServerHttpSecurity}
|
||||
* method.
|
||||
*
|
||||
* For each public, non-static method in ServerHttpSecurity that accepts a Customizer
|
||||
* <ul>
|
||||
* <li>Use the {@link MethodParameter} (this preserves generics) to resolve all Beans
|
||||
* for that type</li>
|
||||
* <li>For each {@link Customizer} Bean invoke the {@link java.lang.reflect.Method}
|
||||
* with the {@link Customizer} Bean as the argument</li>
|
||||
* </ul>
|
||||
* @param context the {@link ApplicationContext}
|
||||
* @param http the {@link ServerHttpSecurity}
|
||||
* @throws Exception
|
||||
*/
|
||||
private void applyTopLevelBeanCustomizers(ApplicationContext context, ServerHttpSecurity http) {
|
||||
ReflectionUtils.MethodFilter isCustomizerMethod = (method) -> {
|
||||
if (Modifier.isStatic(method.getModifiers())) {
|
||||
return false;
|
||||
}
|
||||
if (!Modifier.isPublic(method.getModifiers())) {
|
||||
return false;
|
||||
}
|
||||
if (!method.canAccess(http)) {
|
||||
return false;
|
||||
}
|
||||
if (method.getParameterCount() != 1) {
|
||||
return false;
|
||||
}
|
||||
if (method.getParameterTypes()[0] == Customizer.class) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
ReflectionUtils.MethodCallback invokeWithEachCustomizerBean = (customizerMethod) -> {
|
||||
|
||||
MethodParameter customizerParameter = new MethodParameter(customizerMethod, 0);
|
||||
ResolvableType customizerType = ResolvableType.forMethodParameter(customizerParameter);
|
||||
ObjectProvider<?> customizerProvider = context.getBeanProvider(customizerType);
|
||||
|
||||
// @formatter:off
|
||||
customizerProvider.orderedStream().forEach((customizer) ->
|
||||
ReflectionUtils.invokeMethod(customizerMethod, http, customizer)
|
||||
);
|
||||
// @formatter:on
|
||||
|
||||
};
|
||||
ReflectionUtils.doWithMethods(ServerHttpSecurity.class, invokeWithEachCustomizerBean, isCustomizerMethod);
|
||||
}
|
||||
|
||||
ServerHttpSecurity httpSecurity() {
|
||||
ContextAwareServerHttpSecurity http = new ContextAwareServerHttpSecurity();
|
||||
// @formatter:off
|
||||
|
@ -21,7 +21,6 @@ import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@ -44,12 +43,13 @@ import static org.springframework.security.config.Customizer.withDefaults;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
* @author Yanming Zhou
|
||||
* @since 5.0
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
class WebFluxSecurityConfiguration {
|
||||
|
||||
public static final int WEB_FILTER_CHAIN_FILTER_ORDER = 0 - 100;
|
||||
public static final int WEB_FILTER_CHAIN_FILTER_ORDER = -100;
|
||||
|
||||
private static final String BEAN_NAME_PREFIX = "org.springframework.security.config.annotation.web.reactive.WebFluxSecurityConfiguration.";
|
||||
|
||||
@ -100,7 +100,7 @@ class WebFluxSecurityConfiguration {
|
||||
}
|
||||
|
||||
@Bean
|
||||
static BeanFactoryPostProcessor conversionServicePostProcessor() {
|
||||
static RsaKeyConversionServicePostProcessor conversionServicePostProcessor() {
|
||||
return new RsaKeyConversionServicePostProcessor();
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,7 @@ import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.aop.Pointcut;
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
@ -37,7 +38,8 @@ class PointcutDelegatingAuthorizationManager implements AuthorizationManager<Met
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthorizationResult authorize(Supplier<Authentication> authentication, MethodInvocation object) {
|
||||
public AuthorizationResult authorize(Supplier<? extends @Nullable Authentication> authentication,
|
||||
MethodInvocation object) {
|
||||
for (Map.Entry<Pointcut, AuthorizationManager<MethodInvocation>> entry : this.managers.entrySet()) {
|
||||
Class<?> targetClass = (object.getThis() != null) ? AopUtils.getTargetClass(object.getThis()) : null;
|
||||
if (entry.getKey().getClassFilter().matches(targetClass)
|
||||
|
@ -1316,6 +1316,10 @@ public class ServerHttpSecurity {
|
||||
return this.context.getBeanNamesForType(beanClass);
|
||||
}
|
||||
|
||||
ApplicationContext getApplicationContext() {
|
||||
return this.context;
|
||||
}
|
||||
|
||||
protected void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
this.context = applicationContext;
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
@ -458,7 +459,7 @@ public final class WebSocketMessageBrokerSecurityBeanDefinitionParser implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthorizationResult authorize(Supplier<Authentication> authentication,
|
||||
public AuthorizationResult authorize(Supplier<? extends @Nullable Authentication> authentication,
|
||||
MessageAuthorizationContext<?> object) {
|
||||
EvaluationContext context = this.expressionHandler.createEvaluationContext(authentication, object);
|
||||
boolean granted = ExpressionUtils.evaluateAsBoolean(this.expression, context);
|
||||
|
@ -29,7 +29,6 @@ import org.springframework.security.config.annotation.web.configurers.AuthorizeH
|
||||
import org.springframework.security.config.core.GrantedAuthorityDefaults
|
||||
import org.springframework.security.core.Authentication
|
||||
import org.springframework.security.web.access.IpAddressAuthorizationManager
|
||||
import org.springframework.security.web.access.intercept.AuthorizationFilter
|
||||
import org.springframework.security.web.access.intercept.RequestAuthorizationContext
|
||||
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher
|
||||
import org.springframework.security.web.util.matcher.AnyRequestMatcher
|
||||
@ -235,13 +234,13 @@ class AuthorizeHttpRequestsDsl : AbstractRequestMatcherDsl {
|
||||
* Specify that URLs are allowed by anyone.
|
||||
*/
|
||||
val permitAll: AuthorizationManager<RequestAuthorizationContext> =
|
||||
AuthorizationManager { _: Supplier<Authentication>, _: RequestAuthorizationContext -> AuthorizationDecision(true) }
|
||||
AuthorizationManager { _: Supplier<out Authentication>, _: RequestAuthorizationContext -> AuthorizationDecision(true) }
|
||||
|
||||
/**
|
||||
* Specify that URLs are not allowed by anyone.
|
||||
*/
|
||||
val denyAll: AuthorizationManager<RequestAuthorizationContext> =
|
||||
AuthorizationManager { _: Supplier<Authentication>, _: RequestAuthorizationContext -> AuthorizationDecision(false) }
|
||||
AuthorizationManager { _: Supplier<out Authentication>, _: RequestAuthorizationContext -> AuthorizationDecision(false) }
|
||||
|
||||
/**
|
||||
* Specify that URLs are allowed by any authenticated user.
|
||||
|
@ -18,14 +18,22 @@ package org.springframework.security.config.annotation.web
|
||||
|
||||
import jakarta.servlet.Filter
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import org.springframework.beans.factory.ObjectProvider
|
||||
import org.springframework.context.ApplicationContext
|
||||
import org.springframework.core.MethodParameter
|
||||
import org.springframework.core.ResolvableType
|
||||
import org.springframework.core.annotation.AnnotationUtils
|
||||
import org.springframework.security.authentication.AuthenticationManager
|
||||
import org.springframework.security.config.Customizer
|
||||
import org.springframework.security.config.annotation.SecurityConfigurerAdapter
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository
|
||||
import org.springframework.security.web.DefaultSecurityFilterChain
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher
|
||||
import org.springframework.util.ReflectionUtils
|
||||
import java.lang.reflect.Method
|
||||
import java.lang.reflect.Modifier
|
||||
|
||||
/**
|
||||
* Configures [HttpSecurity] using a [HttpSecurity Kotlin DSL][HttpSecurityDsl].
|
||||
@ -77,6 +85,117 @@ class HttpSecurityDsl(private val http: HttpSecurity, private val init: HttpSecu
|
||||
var authenticationManager: AuthenticationManager? = null
|
||||
val context: ApplicationContext = http.getSharedObject(ApplicationContext::class.java)
|
||||
|
||||
init {
|
||||
applyFunction1HttpSecurityDslBeans(this.context, this)
|
||||
applyTopLevelFunction1SecurityDslBeans(this.context, this)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Applies all `Function1<HttpSecurity,Unit>` Beans which
|
||||
* allows exposing the DSL as Beans to be applied.
|
||||
*
|
||||
* ```
|
||||
* @Bean
|
||||
* fun httpSecurityDslBean(): HttpSecurityDsl.() -> Unit {
|
||||
* return {
|
||||
* headers {
|
||||
* contentSecurityPolicy {
|
||||
* policyDirectives = "object-src 'none'"
|
||||
* }
|
||||
* }
|
||||
* redirectToHttps { }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
private fun applyFunction1HttpSecurityDslBeans(context: ApplicationContext, http: HttpSecurityDsl) : Unit {
|
||||
val httpSecurityDslFnType = ResolvableType.forClassWithGenerics(Function1::class.java,
|
||||
HttpSecurityDsl::class.java, Unit::class.java)
|
||||
val httpSecurityDslFnProvider = context
|
||||
.getBeanProvider<Function1<HttpSecurityDsl,Unit>>(httpSecurityDslFnType)
|
||||
|
||||
// @formatter:off
|
||||
httpSecurityDslFnProvider.orderedStream().forEach { fn -> fn.invoke(http) }
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies all `Function1<T,Unit>` Beans such that `T` is a top level
|
||||
* DSL on `HttpSecurityDsl`. This allows exposing the top level
|
||||
* DSLs as Beans to be applied.
|
||||
*
|
||||
*
|
||||
* ```
|
||||
* @Bean
|
||||
* fun httpSecurityCustomizer(): ThrowingCustomizer<HttpSecurity> {
|
||||
* return ThrowingCustomizer { http -> http
|
||||
* .headers { headers -> headers
|
||||
* .contentSecurityPolicy { csp -> csp
|
||||
* .policyDirectives("object-src 'none'")
|
||||
* }
|
||||
* }
|
||||
* .redirectToHttps(Customizer.withDefaults())
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param context the [ApplicationContext]
|
||||
* @param http the [HttpSecurity]
|
||||
* @throws Exception
|
||||
*/
|
||||
private fun applyTopLevelFunction1SecurityDslBeans(context: ApplicationContext, http: HttpSecurityDsl) {
|
||||
val isCustomizerMethod = ReflectionUtils.MethodFilter { method: Method ->
|
||||
if (Modifier.isStatic(method.modifiers)) {
|
||||
return@MethodFilter false
|
||||
}
|
||||
if (!Modifier.isPublic(method.modifiers)) {
|
||||
return@MethodFilter false
|
||||
}
|
||||
if (!method.canAccess(http)) {
|
||||
return@MethodFilter false
|
||||
}
|
||||
if (method.parameterCount != 1) {
|
||||
return@MethodFilter false
|
||||
}
|
||||
return@MethodFilter extractDslType(method) != null
|
||||
}
|
||||
val invokeWithEachDslBean = ReflectionUtils.MethodCallback { dslMethod: Method ->
|
||||
val dslFunctionType = firstMethodResolvableType(dslMethod)!!
|
||||
val dslFunctionProvider: ObjectProvider<*> = context.getBeanProvider<Any>(dslFunctionType)
|
||||
|
||||
// @formatter:off
|
||||
dslFunctionProvider.orderedStream().forEach {customizer: Any -> ReflectionUtils.invokeMethod(dslMethod, http, customizer)}
|
||||
}
|
||||
ReflectionUtils.doWithMethods(HttpSecurityDsl::class.java, invokeWithEachDslBean, isCustomizerMethod)
|
||||
}
|
||||
|
||||
/**
|
||||
* From a `Method` with the first argument `Function<T,Unit>` return `T` or `null`
|
||||
* if the first argument is not a `Function`.
|
||||
* @return From a `Method` with the first argument `Function<T,Unit>` return `T`.
|
||||
*/
|
||||
private fun extractDslType(method: Method): ResolvableType? {
|
||||
val functionType = firstMethodResolvableType(method)
|
||||
if (!Function::class.java.isAssignableFrom(functionType.toClass())) {
|
||||
return null
|
||||
}
|
||||
val functionInputType = functionType.getGeneric(0)
|
||||
val securityMarker = AnnotationUtils.findAnnotation(functionInputType.toClass(), SecurityMarker::class.java)
|
||||
val isSecurityDsl = securityMarker != null
|
||||
if (!isSecurityDsl) {
|
||||
return null
|
||||
}
|
||||
return functionInputType
|
||||
}
|
||||
|
||||
private fun firstMethodResolvableType(method: Method): ResolvableType {
|
||||
val parameter = MethodParameter(
|
||||
method, 0
|
||||
)
|
||||
return ResolvableType.forMethodParameter(parameter)
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a [SecurityConfigurerAdapter] to this [HttpSecurity]
|
||||
*
|
||||
|
@ -16,13 +16,23 @@
|
||||
|
||||
package org.springframework.security.config.web.server
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider
|
||||
import org.springframework.context.ApplicationContext
|
||||
import org.springframework.core.MethodParameter
|
||||
import org.springframework.core.ResolvableType
|
||||
import org.springframework.core.annotation.AnnotationUtils
|
||||
import org.springframework.security.authentication.ReactiveAuthenticationManager
|
||||
import org.springframework.security.config.Customizer
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain
|
||||
import org.springframework.security.web.server.context.ServerSecurityContextRepository
|
||||
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher
|
||||
import org.springframework.util.ReflectionUtils
|
||||
import org.springframework.web.server.ServerWebExchange
|
||||
import org.springframework.web.server.WebFilter
|
||||
import java.lang.reflect.Method
|
||||
import java.lang.reflect.Modifier
|
||||
|
||||
/**
|
||||
* Configures [ServerHttpSecurity] using a [ServerHttpSecurity Kotlin DSL][ServerHttpSecurityDsl].
|
||||
@ -68,6 +78,115 @@ class ServerHttpSecurityDsl(private val http: ServerHttpSecurity, private val in
|
||||
var authenticationManager: ReactiveAuthenticationManager? = null
|
||||
var securityContextRepository: ServerSecurityContextRepository? = null
|
||||
|
||||
init {
|
||||
applyFunction1HttpSecurityDslBeans(this.http.applicationContext, this)
|
||||
applyTopLevelFunction1SecurityDslBeans(this.http.applicationContext, this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies all `Function1<ServerHttpSecurityDsl,Unit>` Beans which
|
||||
* allows exposing the DSL as Beans to be applied.
|
||||
*
|
||||
* ```
|
||||
* @Bean
|
||||
* @Order(Ordered.LOWEST_PRECEDENCE)
|
||||
* fun userAuthorization(): ServerHttpSecurityDsl.() -> Unit {
|
||||
* // @formatter:off
|
||||
* return {
|
||||
* authorizeExchange {
|
||||
* authorize("/user/profile", hasRole("USER"))
|
||||
* }
|
||||
* }
|
||||
* // @formatter:on
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
private fun applyFunction1HttpSecurityDslBeans(context: ApplicationContext, http: ServerHttpSecurityDsl) : Unit {
|
||||
val httpSecurityDslFnType = ResolvableType.forClassWithGenerics(Function1::class.java,
|
||||
ServerHttpSecurityDsl::class.java, Unit::class.java)
|
||||
val httpSecurityDslFnProvider = context
|
||||
.getBeanProvider<Function1<ServerHttpSecurityDsl,Unit>>(httpSecurityDslFnType)
|
||||
|
||||
// @formatter:off
|
||||
httpSecurityDslFnProvider.orderedStream().forEach { fn -> fn.invoke(http) }
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies all `Function1<T,Unit>` Beans such that `T` is a top level
|
||||
* DSL on `ServerHttpSecurityDsl`. This allows exposing the top level
|
||||
* DSLs as Beans to be applied.
|
||||
*
|
||||
* ```
|
||||
* @Bean
|
||||
* fun headersSecurity(): Customizer<ServerHttpSecurity.HeaderSpec> {
|
||||
* // @formatter:off
|
||||
* return Customizer { headers -> headers
|
||||
* .contentSecurityPolicy { csp -> csp
|
||||
* .policyDirectives("object-src 'none'")
|
||||
* }
|
||||
* }
|
||||
* // @formatter:on
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param context the [ApplicationContext]
|
||||
* @param http the [HttpSecurity]
|
||||
* @throws Exception
|
||||
*/
|
||||
private fun applyTopLevelFunction1SecurityDslBeans(context: ApplicationContext, http: ServerHttpSecurityDsl) {
|
||||
val isCustomizerMethod = ReflectionUtils.MethodFilter { method: Method ->
|
||||
if (Modifier.isStatic(method.modifiers)) {
|
||||
return@MethodFilter false
|
||||
}
|
||||
if (!Modifier.isPublic(method.modifiers)) {
|
||||
return@MethodFilter false
|
||||
}
|
||||
if (!method.canAccess(http)) {
|
||||
return@MethodFilter false
|
||||
}
|
||||
if (method.parameterCount != 1) {
|
||||
return@MethodFilter false
|
||||
}
|
||||
return@MethodFilter extractDslType(method) != null
|
||||
}
|
||||
|
||||
val invokeWithEachDslBean = ReflectionUtils.MethodCallback { dslMethod: Method ->
|
||||
val dslFunctionType = firstMethodResolvableType(dslMethod)!!
|
||||
val dslFunctionProvider: ObjectProvider<*> = context.getBeanProvider<Any>(dslFunctionType)
|
||||
|
||||
// @formatter:off
|
||||
dslFunctionProvider.orderedStream().forEach {customizer: Any -> ReflectionUtils.invokeMethod(dslMethod, http, customizer)}
|
||||
}
|
||||
ReflectionUtils.doWithMethods(ServerHttpSecurityDsl::class.java, invokeWithEachDslBean, isCustomizerMethod)
|
||||
}
|
||||
|
||||
/**
|
||||
* From a `Method` with the first argument `Function<T,Unit>` return `T` or `null`
|
||||
* if the first argument is not a `Function`.
|
||||
* @return From a `Method` with the first argument `Function<T,Unit>` return `T`.
|
||||
*/
|
||||
private fun extractDslType(method: Method): ResolvableType? {
|
||||
val functionType = firstMethodResolvableType(method)
|
||||
if (!Function::class.java.isAssignableFrom(functionType.toClass())) {
|
||||
return null
|
||||
}
|
||||
val functionInputType = functionType.getGeneric(0)
|
||||
val securityMarker = AnnotationUtils.findAnnotation(functionInputType.toClass(), ServerSecurityMarker::class.java)
|
||||
val isSecurityDsl = securityMarker != null
|
||||
if (!isSecurityDsl) {
|
||||
return null
|
||||
}
|
||||
return functionInputType
|
||||
}
|
||||
|
||||
private fun firstMethodResolvableType(method: Method): ResolvableType {
|
||||
val parameter = MethodParameter(
|
||||
method, 0
|
||||
)
|
||||
return ResolvableType.forMethodParameter(parameter)
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows configuring the [ServerHttpSecurity] to only be invoked when matching the
|
||||
* provided [ServerWebExchangeMatcher].
|
||||
|
@ -78,6 +78,7 @@ import org.springframework.security.authentication.jaas.event.JaasAuthentication
|
||||
import org.springframework.security.authentication.jaas.event.JaasAuthenticationSuccessEvent;
|
||||
import org.springframework.security.authentication.ott.DefaultOneTimeToken;
|
||||
import org.springframework.security.authentication.ott.InvalidOneTimeTokenException;
|
||||
import org.springframework.security.authentication.ott.OneTimeTokenAuthentication;
|
||||
import org.springframework.security.authentication.ott.OneTimeTokenAuthenticationToken;
|
||||
import org.springframework.security.authentication.password.CompromisedPasswordException;
|
||||
import org.springframework.security.authorization.AuthorityAuthorizationDecision;
|
||||
@ -400,6 +401,8 @@ final class SerializationSamples {
|
||||
});
|
||||
generatorByClassName.put(OneTimeTokenAuthenticationToken.class,
|
||||
(r) -> applyDetails(new OneTimeTokenAuthenticationToken("username", "token")));
|
||||
generatorByClassName.put(OneTimeTokenAuthentication.class,
|
||||
(r) -> applyDetails(new OneTimeTokenAuthentication("username", authentication.getAuthorities())));
|
||||
generatorByClassName.put(AccessDeniedException.class,
|
||||
(r) -> new AccessDeniedException("access denied", new RuntimeException()));
|
||||
generatorByClassName.put(AuthorizationServiceException.class,
|
||||
|
@ -33,6 +33,7 @@ import io.micrometer.observation.ObservationHandler;
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
import io.micrometer.observation.ObservationTextPublisher;
|
||||
import jakarta.annotation.security.DenyAll;
|
||||
import jakarta.servlet.RequestDispatcher;
|
||||
import org.aopalliance.aop.Advice;
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
@ -138,6 +139,7 @@ import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatNoException;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.clearInvocations;
|
||||
@ -149,6 +151,7 @@ import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
/**
|
||||
@ -1279,6 +1282,19 @@ public class PrePostMethodSecurityConfigurationTests {
|
||||
this.mvc.perform(requestWithUser).andExpect(status().isForbidden());
|
||||
}
|
||||
|
||||
// gh-17761
|
||||
@Test
|
||||
void getWhenPostAuthorizeAuthenticationNameNotMatchThenNoExceptionExposedInRequest() throws Exception {
|
||||
this.spring.register(WebMvcMethodSecurityConfig.class, BasicController.class).autowire();
|
||||
// @formatter:off
|
||||
MockHttpServletRequestBuilder requestWithUser = get("/authorized-person")
|
||||
.param("name", "john")
|
||||
.with(user("rob"));
|
||||
// @formatter:on
|
||||
this.mvc.perform(requestWithUser)
|
||||
.andExpect(request().attribute(RequestDispatcher.ERROR_EXCEPTION, nullValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWhenPostAuthorizeWithinServiceAuthenticationNameMatchesThenRespondsWithOk() throws Exception {
|
||||
this.spring.register(WebMvcMethodSecurityConfig.class, BasicController.class, BasicService.class).autowire();
|
||||
|
@ -25,6 +25,7 @@ import javax.security.auth.login.LoginContext;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
@ -310,7 +311,7 @@ public class NamespaceHttpTests {
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthorizationResult authorize(Supplier<Authentication> authentication,
|
||||
public AuthorizationResult authorize(Supplier<? extends @Nullable Authentication> authentication,
|
||||
RequestAuthorizationContext object) {
|
||||
HttpServletRequest request = object.getRequest();
|
||||
FilterInvocation invocation = new FilterInvocation(request.getContextPath(), request.getServletPath(),
|
||||
|
@ -28,14 +28,20 @@ import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InOrder;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.core.io.support.SpringFactoriesLoader;
|
||||
import org.springframework.mock.web.MockHttpSession;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
@ -54,6 +60,7 @@ import org.springframework.security.config.annotation.SecurityContextChangedList
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||
import org.springframework.security.config.annotation.web.configurers.AnonymousConfigurer;
|
||||
import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer;
|
||||
import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer;
|
||||
import org.springframework.security.config.test.SpringTestContext;
|
||||
import org.springframework.security.config.test.SpringTestContextExtension;
|
||||
@ -82,12 +89,15 @@ import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.CorsConfigurationSource;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
import org.springframework.web.filter.CorsFilter;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.willAnswer;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.withSettings;
|
||||
import static org.springframework.security.config.Customizer.withDefaults;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
|
||||
@ -425,6 +435,77 @@ public class HttpSecurityConfigurationTests {
|
||||
.andExpectAll(status().isFound(), redirectedUrl("/"), authenticated());
|
||||
}
|
||||
|
||||
@Test
|
||||
void authorizeHttpRequestsCustomizerBean() throws Exception {
|
||||
this.spring.register(AuthorizeRequestsBeanConfiguration.class, UserDetailsConfig.class).autowire();
|
||||
|
||||
Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry> authorizeRequests = this.spring
|
||||
.getContext()
|
||||
.getBean("authorizeRequests", Customizer.class);
|
||||
|
||||
verify(authorizeRequests).customize(any());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void multiAuthorizeHttpRequestsCustomizerBean() throws Exception {
|
||||
this.spring.register(MultiAuthorizeRequestsBeanConfiguration.class, UserDetailsConfig.class).autowire();
|
||||
|
||||
Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry> authorizeRequests0 = this.spring
|
||||
.getContext()
|
||||
.getBean("authorizeRequests0", Customizer.class);
|
||||
Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry> authorizeRequests = this.spring
|
||||
.getContext()
|
||||
.getBean("authorizeRequests", Customizer.class);
|
||||
InOrder inOrder = Mockito.inOrder(authorizeRequests0, authorizeRequests);
|
||||
|
||||
ArgumentCaptor<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry> arg0 = ArgumentCaptor
|
||||
.forClass(AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry.class);
|
||||
ArgumentCaptor<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry> arg1 = ArgumentCaptor
|
||||
.forClass(AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry.class);
|
||||
inOrder.verify(authorizeRequests0).customize(arg0.capture());
|
||||
inOrder.verify(authorizeRequests).customize(arg1.capture());
|
||||
}
|
||||
|
||||
@Test
|
||||
void disableAuthorizeHttpRequestsCustomizerBean() throws Exception {
|
||||
this.spring.register(AuthorizeRequestsBeanConfiguration.class, UserDetailsConfig.class).autowire();
|
||||
|
||||
Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry> authorizeRequests = this.spring
|
||||
.getContext()
|
||||
.getBean("authorizeRequests", Customizer.class);
|
||||
|
||||
verify(authorizeRequests).customize(any());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void httpSecurityCustomizerBean() throws Exception {
|
||||
this.spring.register(HttpSecurityCustomizerBeanConfiguration.class, UserDetailsConfig.class).autowire();
|
||||
|
||||
Customizer<HttpSecurity> httpSecurityCustomizer = this.spring.getContext()
|
||||
.getBean("httpSecurityCustomizer", Customizer.class);
|
||||
|
||||
ArgumentCaptor<HttpSecurity> arg0 = ArgumentCaptor.forClass(HttpSecurity.class);
|
||||
verify(httpSecurityCustomizer).customize(arg0.capture());
|
||||
}
|
||||
|
||||
@Test
|
||||
void multiHttpSecurityCustomizerBean() throws Exception {
|
||||
this.spring.register(MultiHttpSecurityCustomizerBeanConfiguration.class, UserDetailsConfig.class).autowire();
|
||||
|
||||
Customizer<HttpSecurity> httpSecurityCustomizer = this.spring.getContext()
|
||||
.getBean("httpSecurityCustomizer", Customizer.class);
|
||||
Customizer<HttpSecurity> httpSecurityCustomizer0 = this.spring.getContext()
|
||||
.getBean("httpSecurityCustomizer0", Customizer.class);
|
||||
InOrder inOrder = Mockito.inOrder(httpSecurityCustomizer0, httpSecurityCustomizer);
|
||||
|
||||
ArgumentCaptor<HttpSecurity> arg0 = ArgumentCaptor.forClass(HttpSecurity.class);
|
||||
ArgumentCaptor<HttpSecurity> arg1 = ArgumentCaptor.forClass(HttpSecurity.class);
|
||||
inOrder.verify(httpSecurityCustomizer0).customize(arg0.capture());
|
||||
inOrder.verify(httpSecurityCustomizer).customize(arg1.capture());
|
||||
}
|
||||
|
||||
@RestController
|
||||
static class NameController {
|
||||
|
||||
@ -785,6 +866,134 @@ public class HttpSecurityConfigurationTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableWebSecurity
|
||||
@EnableWebMvc
|
||||
static class AuthorizeRequestsBeanConfiguration {
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain noAuthorizeSecurity(HttpSecurity http) throws Exception {
|
||||
http.httpBasic(withDefaults());
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
static Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry> authorizeRequests()
|
||||
throws Exception {
|
||||
Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry> authz = mock(
|
||||
Customizer.class, withSettings().name("authz"));
|
||||
// prevent validation errors of no authorization rules being defined
|
||||
willAnswer(((invocation) -> {
|
||||
AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry requests = invocation
|
||||
.getArgument(0);
|
||||
requests.anyRequest().authenticated();
|
||||
return null;
|
||||
})).given(authz).customize(any());
|
||||
return authz;
|
||||
}
|
||||
|
||||
@RestController
|
||||
static class PublicController {
|
||||
|
||||
@GetMapping("/public")
|
||||
String permitAll() {
|
||||
return "public";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableWebSecurity
|
||||
@EnableWebMvc
|
||||
static class DisableAuthorizeRequestsBeanConfiguration {
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
|
||||
http.httpBasic(withDefaults());
|
||||
// @formatter:off
|
||||
http.authorizeHttpRequests((requests) -> requests
|
||||
.anyRequest().permitAll()
|
||||
);
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
static Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry> authorizeRequests()
|
||||
throws Exception {
|
||||
// @formatter:off
|
||||
return (requests) -> requests
|
||||
.anyRequest().denyAll();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@RestController
|
||||
static class PublicController {
|
||||
|
||||
@GetMapping("/public")
|
||||
String permitAll() {
|
||||
return "public";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Import(AuthorizeRequestsBeanConfiguration.class)
|
||||
static class MultiAuthorizeRequestsBeanConfiguration {
|
||||
|
||||
@Bean
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
static Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry> authorizeRequests0()
|
||||
throws Exception {
|
||||
return mock(Customizer.class, withSettings().name("authz0"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableWebSecurity
|
||||
@EnableWebMvc
|
||||
static class HttpSecurityCustomizerBeanConfiguration {
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
|
||||
http.httpBasic(withDefaults());
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
static Customizer<HttpSecurity> httpSecurityCustomizer() {
|
||||
return mock(Customizer.class, withSettings().name("httpSecurityCustomizer"));
|
||||
}
|
||||
|
||||
@RestController
|
||||
static class PublicController {
|
||||
|
||||
@GetMapping("/public")
|
||||
String permitAll() {
|
||||
return "public";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Import(HttpSecurityCustomizerBeanConfiguration.class)
|
||||
static class MultiHttpSecurityCustomizerBeanConfiguration {
|
||||
|
||||
@Bean
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
static Customizer<HttpSecurity> httpSecurityCustomizer0() throws Exception {
|
||||
return mock(Customizer.class, withSettings().name("httpSecurityCustomizer0"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class TestCompromisedPasswordChecker implements CompromisedPasswordChecker {
|
||||
|
||||
@Override
|
||||
|
@ -24,6 +24,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.security.config.ObjectPostProcessor;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
@ -34,6 +35,7 @@ import org.springframework.security.core.userdetails.AuthenticationUserDetailsSe
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
|
||||
import org.springframework.security.web.authentication.preauth.j2ee.J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource;
|
||||
import org.springframework.security.web.authentication.preauth.j2ee.J2eePreAuthenticatedProcessingFilter;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
@ -64,18 +66,16 @@ public class JeeConfigurerTests {
|
||||
|
||||
@Test
|
||||
public void configureWhenRegisteringObjectPostProcessorThenInvokedOnJ2eePreAuthenticatedProcessingFilter() {
|
||||
ObjectPostProcessorConfig.objectPostProcessor = spy(ReflectingObjectPostProcessor.class);
|
||||
this.spring.register(ObjectPostProcessorConfig.class).autowire();
|
||||
verify(ObjectPostProcessorConfig.objectPostProcessor)
|
||||
.postProcess(any(J2eePreAuthenticatedProcessingFilter.class));
|
||||
ObjectPostProcessor<Object> objectPostProcessor = this.spring.getContext().getBean(ObjectPostProcessor.class);
|
||||
verify(objectPostProcessor).postProcess(any(J2eePreAuthenticatedProcessingFilter.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configureWhenRegisteringObjectPostProcessorThenInvokedOnJ2eeBasedPreAuthenticatedWebAuthenticationDetailsSource() {
|
||||
ObjectPostProcessorConfig.objectPostProcessor = spy(ReflectingObjectPostProcessor.class);
|
||||
this.spring.register(ObjectPostProcessorConfig.class).autowire();
|
||||
verify(ObjectPostProcessorConfig.objectPostProcessor)
|
||||
.postProcess(any(J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource.class));
|
||||
ObjectPostProcessor<Object> objectPostProcessor = this.spring.getContext().getBean(ObjectPostProcessor.class);
|
||||
verify(objectPostProcessor).postProcess(any(J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -135,12 +135,14 @@ public class JeeConfigurerTests {
|
||||
public void requestWhenCustomAuthenticatedUserDetailsServiceInLambdaThenCustomAuthenticatedUserDetailsServiceUsed()
|
||||
throws Exception {
|
||||
this.spring.register(JeeCustomAuthenticatedUserDetailsServiceConfig.class).autowire();
|
||||
AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> userDetailsService = this.spring
|
||||
.getContext()
|
||||
.getBean(AuthenticationUserDetailsService.class);
|
||||
Principal user = mock(Principal.class);
|
||||
User userDetails = new User("user", "N/A", true, true, true, true,
|
||||
AuthorityUtils.createAuthorityList("ROLE_USER"));
|
||||
given(user.getName()).willReturn("user");
|
||||
given(JeeCustomAuthenticatedUserDetailsServiceConfig.authenticationUserDetailsService.loadUserDetails(any()))
|
||||
.willReturn(userDetails);
|
||||
given(userDetailsService.loadUserDetails(any())).willReturn(userDetails);
|
||||
// @formatter:off
|
||||
MockHttpServletRequestBuilder authRequest = get("/")
|
||||
.principal(user)
|
||||
@ -157,7 +159,7 @@ public class JeeConfigurerTests {
|
||||
@EnableWebSecurity
|
||||
static class ObjectPostProcessorConfig {
|
||||
|
||||
static ObjectPostProcessor<Object> objectPostProcessor;
|
||||
ObjectPostProcessor<Object> objectPostProcessor = spy(ReflectingObjectPostProcessor.class);
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
@ -169,8 +171,9 @@ public class JeeConfigurerTests {
|
||||
}
|
||||
|
||||
@Bean
|
||||
static ObjectPostProcessor<Object> objectPostProcessor() {
|
||||
return objectPostProcessor;
|
||||
@Primary
|
||||
ObjectPostProcessor<Object> objectPostProcessor() {
|
||||
return this.objectPostProcessor;
|
||||
}
|
||||
|
||||
}
|
||||
@ -245,7 +248,7 @@ public class JeeConfigurerTests {
|
||||
@EnableWebSecurity
|
||||
public static class JeeCustomAuthenticatedUserDetailsServiceConfig {
|
||||
|
||||
static AuthenticationUserDetailsService authenticationUserDetailsService = mock(
|
||||
private AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> authenticationUserDetailsService = mock(
|
||||
AuthenticationUserDetailsService.class);
|
||||
|
||||
@Bean
|
||||
@ -256,12 +259,17 @@ public class JeeConfigurerTests {
|
||||
.anyRequest().hasRole("USER")
|
||||
)
|
||||
.jee((jee) -> jee
|
||||
.authenticatedUserDetailsService(authenticationUserDetailsService)
|
||||
.authenticatedUserDetailsService(this.authenticationUserDetailsService)
|
||||
);
|
||||
return http.build();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Bean
|
||||
AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> authenticationUserDetailsService() {
|
||||
return this.authenticationUserDetailsService;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -29,12 +29,17 @@ import io.micrometer.observation.ObservationRegistry;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InOrder;
|
||||
import org.mockito.Mockito;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.authentication.password.CompromisedPasswordDecision;
|
||||
@ -46,6 +51,7 @@ import org.springframework.security.config.test.SpringTestContext;
|
||||
import org.springframework.security.config.test.SpringTestContextExtension;
|
||||
import org.springframework.security.config.users.ReactiveAuthenticationTestConfiguration;
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity.AuthorizeExchangeSpec;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
@ -267,6 +273,47 @@ public class ServerHttpSecurityConfigurationTests {
|
||||
assertThat(contexts.next().getContextualName()).isEqualTo("security filterchain after");
|
||||
}
|
||||
|
||||
@Test
|
||||
void authorizeExchangeCustomizerBean() {
|
||||
this.spring.register(AuthorizeExchangeCustomizerBeanConfig.class).autowire();
|
||||
Customizer<AuthorizeExchangeSpec> authzCustomizer = this.spring.getContext().getBean("authz", Customizer.class);
|
||||
|
||||
ArgumentCaptor<AuthorizeExchangeSpec> arg0 = ArgumentCaptor.forClass(AuthorizeExchangeSpec.class);
|
||||
verify(authzCustomizer).customize(arg0.capture());
|
||||
}
|
||||
|
||||
@Test
|
||||
void multiAuthorizeExchangeCustomizerBean() {
|
||||
this.spring.register(MultiAuthorizeExchangeCustomizerBeanConfig.class).autowire();
|
||||
Customizer<AuthorizeExchangeSpec> authzCustomizer = this.spring.getContext().getBean("authz", Customizer.class);
|
||||
|
||||
ArgumentCaptor<AuthorizeExchangeSpec> arg0 = ArgumentCaptor.forClass(AuthorizeExchangeSpec.class);
|
||||
verify(authzCustomizer).customize(arg0.capture());
|
||||
}
|
||||
|
||||
@Test
|
||||
void serverHttpSecurityCustomizerBean() {
|
||||
this.spring.register(ServerHttpSecurityCustomizerConfig.class).autowire();
|
||||
Customizer<ServerHttpSecurity> httpSecurityCustomizer = this.spring.getContext()
|
||||
.getBean("httpSecurityCustomizer", Customizer.class);
|
||||
|
||||
ArgumentCaptor<ServerHttpSecurity> arg0 = ArgumentCaptor.forClass(ServerHttpSecurity.class);
|
||||
verify(httpSecurityCustomizer).customize(arg0.capture());
|
||||
}
|
||||
|
||||
@Test
|
||||
void multiServerHttpSecurityCustomizerBean() {
|
||||
this.spring.register(MultiServerHttpSecurityCustomizerConfig.class).autowire();
|
||||
Customizer<ServerHttpSecurity> httpSecurityCustomizer = this.spring.getContext()
|
||||
.getBean("httpSecurityCustomizer", Customizer.class);
|
||||
Customizer<ServerHttpSecurity> httpSecurityCustomizer0 = this.spring.getContext()
|
||||
.getBean("httpSecurityCustomizer0", Customizer.class);
|
||||
InOrder inOrder = Mockito.inOrder(httpSecurityCustomizer0, httpSecurityCustomizer);
|
||||
ArgumentCaptor<ServerHttpSecurity> arg0 = ArgumentCaptor.forClass(ServerHttpSecurity.class);
|
||||
inOrder.verify(httpSecurityCustomizer0).customize(arg0.capture());
|
||||
inOrder.verify(httpSecurityCustomizer).customize(arg0.capture());
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class SubclassConfig extends ServerHttpSecurityConfiguration {
|
||||
|
||||
@ -474,4 +521,64 @@ public class ServerHttpSecurityConfigurationTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableWebFlux
|
||||
@EnableWebFluxSecurity
|
||||
@Import(UserDetailsConfig.class)
|
||||
static class AuthorizeExchangeCustomizerBeanConfig {
|
||||
|
||||
@Bean
|
||||
SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
static Customizer<AuthorizeExchangeSpec> authz() {
|
||||
return mock(Customizer.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Import(AuthorizeExchangeCustomizerBeanConfig.class)
|
||||
static class MultiAuthorizeExchangeCustomizerBeanConfig {
|
||||
|
||||
@Bean
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
Customizer<AuthorizeExchangeSpec> authz0() {
|
||||
return mock(Customizer.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableWebFlux
|
||||
@EnableWebFluxSecurity
|
||||
@Import(UserDetailsConfig.class)
|
||||
static class ServerHttpSecurityCustomizerConfig {
|
||||
|
||||
@Bean
|
||||
SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
static Customizer<ServerHttpSecurity> httpSecurityCustomizer() {
|
||||
return mock(Customizer.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Import(ServerHttpSecurityCustomizerConfig.class)
|
||||
static class MultiServerHttpSecurityCustomizerConfig {
|
||||
|
||||
@Bean
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
static Customizer<ServerHttpSecurity> httpSecurityCustomizer0() {
|
||||
return mock(Customizer.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -464,7 +464,9 @@ public class MethodSecurityBeanDefinitionParserTests {
|
||||
static class MyAuthorizationManager implements AuthorizationManager<MethodInvocation> {
|
||||
|
||||
@Override
|
||||
public AuthorizationResult authorize(Supplier<Authentication> authentication, MethodInvocation object) {
|
||||
public AuthorizationResult authorize(
|
||||
Supplier<? extends @org.jspecify.annotations.Nullable Authentication> authentication,
|
||||
MethodInvocation object) {
|
||||
return new AuthorizationDecision("bob".equals(authentication.get().getName()));
|
||||
}
|
||||
|
||||
|
@ -30,8 +30,11 @@ import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostP
|
||||
import org.springframework.mock.web.MockServletConfig;
|
||||
import org.springframework.security.config.BeanIds;
|
||||
import org.springframework.security.config.util.InMemoryXmlWebApplicationContext;
|
||||
import org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers;
|
||||
import org.springframework.security.test.web.reactive.server.WebTestClientBuilder;
|
||||
import org.springframework.security.web.servlet.MockServletContext;
|
||||
import org.springframework.test.context.web.GenericXmlWebContextLoader;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.request.RequestPostProcessor;
|
||||
import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder;
|
||||
@ -42,6 +45,7 @@ import org.springframework.web.context.WebApplicationContext;
|
||||
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
|
||||
import org.springframework.web.context.support.XmlWebApplicationContext;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
import org.springframework.web.server.WebFilter;
|
||||
|
||||
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
|
||||
|
||||
@ -156,6 +160,18 @@ public class SpringTestContext implements Closeable {
|
||||
// @formatter:on
|
||||
this.context.getBeanFactory().registerResolvableDependency(MockMvc.class, mockMvc);
|
||||
}
|
||||
String webFluxSecurityBean = "org.springframework.security.config.annotation.web.reactive.WebFluxSecurityConfiguration.WebFilterChainFilter";
|
||||
if (this.context.containsBean(webFluxSecurityBean)) {
|
||||
WebFilter springSecurityFilter = this.context.getBean(webFluxSecurityBean, WebFilter.class);
|
||||
// @formatter:off
|
||||
WebTestClient webTest = WebTestClient
|
||||
.bindToController(new WebTestClientBuilder.Http200RestController())
|
||||
.webFilter(springSecurityFilter)
|
||||
.apply(SecurityMockServerConfigurers.springSecurity())
|
||||
.build();
|
||||
// @formatter:on
|
||||
this.context.getBeanFactory().registerResolvableDependency(WebTestClient.class, webTest);
|
||||
}
|
||||
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
|
||||
bpp.setBeanFactory(this.context.getBeanFactory());
|
||||
bpp.processInjection(this.test);
|
||||
|
@ -26,6 +26,7 @@ import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.assertj.core.api.ThrowableAssert;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
@ -735,7 +736,7 @@ public class WebSocketMessageBrokerConfigTests {
|
||||
}
|
||||
|
||||
@Override
|
||||
public EvaluationContext createEvaluationContext(Supplier<Authentication> authentication,
|
||||
public EvaluationContext createEvaluationContext(Supplier<? extends @Nullable Authentication> authentication,
|
||||
Message<Object> message) {
|
||||
return new StandardEvaluationContext(new MessageSecurityExpressionRoot(authentication, message) {
|
||||
public boolean denyNile() {
|
||||
|
@ -193,7 +193,7 @@ class AuthorizeHttpRequestsDslTests {
|
||||
open class MvcMatcherPathVariablesConfig {
|
||||
@Bean
|
||||
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
val access = AuthorizationManager { _: Supplier<Authentication>, context: RequestAuthorizationContext ->
|
||||
val access = AuthorizationManager { _: Supplier<out Authentication>, context: RequestAuthorizationContext ->
|
||||
AuthorizationDecision(context.variables["userName"] == "user")
|
||||
}
|
||||
http {
|
||||
|
@ -367,7 +367,7 @@ class HttpSecurityDslTests {
|
||||
this.spring.register(CustomFilterConfig::class.java).autowire()
|
||||
|
||||
val filterChain = spring.context.getBean(FilterChainProxy::class.java)
|
||||
val filters: List<Filter> = filterChain.getFilters("/")
|
||||
val filters: List<Filter>? = filterChain.getFilters("/")
|
||||
|
||||
assertThat(filters).anyMatch { it is CustomFilter }
|
||||
}
|
||||
@ -390,7 +390,7 @@ class HttpSecurityDslTests {
|
||||
this.spring.register(CustomFilterConfigReified::class.java).autowire()
|
||||
|
||||
val filterChain = spring.context.getBean(FilterChainProxy::class.java)
|
||||
val filters: List<Filter> = filterChain.getFilters("/")
|
||||
val filters: List<Filter>? = filterChain.getFilters("/")
|
||||
|
||||
assertThat(filters).anyMatch { it is CustomFilter }
|
||||
}
|
||||
@ -413,7 +413,7 @@ class HttpSecurityDslTests {
|
||||
this.spring.register(CustomFilterAfterConfig::class.java).autowire()
|
||||
|
||||
val filterChain = spring.context.getBean(FilterChainProxy::class.java)
|
||||
val filters: List<Class<out Filter>> = filterChain.getFilters("/").map { it.javaClass }
|
||||
val filters: List<Class<out Filter>> = filterChain.getFilters("/")!!.map { it.javaClass }
|
||||
|
||||
assertThat(filters).containsSubsequence(
|
||||
UsernamePasswordAuthenticationFilter::class.java,
|
||||
@ -440,7 +440,7 @@ class HttpSecurityDslTests {
|
||||
this.spring.register(CustomFilterAfterConfigReified::class.java).autowire()
|
||||
|
||||
val filterChain = spring.context.getBean(FilterChainProxy::class.java)
|
||||
val filterClasses: List<Class<out Filter>> = filterChain.getFilters("/").map { it.javaClass }
|
||||
val filterClasses: List<Class<out Filter>> = filterChain.getFilters("/")!!.map { it.javaClass }
|
||||
|
||||
assertThat(filterClasses).containsSubsequence(
|
||||
UsernamePasswordAuthenticationFilter::class.java,
|
||||
@ -467,7 +467,7 @@ class HttpSecurityDslTests {
|
||||
this.spring.register(CustomFilterBeforeConfig::class.java).autowire()
|
||||
|
||||
val filterChain = spring.context.getBean(FilterChainProxy::class.java)
|
||||
val filters: List<Class<out Filter>> = filterChain.getFilters("/").map { it.javaClass }
|
||||
val filters: List<Class<out Filter>> = filterChain.getFilters("/")!!.map { it.javaClass }
|
||||
|
||||
assertThat(filters).containsSubsequence(
|
||||
CustomFilter::class.java,
|
||||
@ -494,7 +494,7 @@ class HttpSecurityDslTests {
|
||||
this.spring.register(CustomFilterBeforeConfigReified::class.java).autowire()
|
||||
|
||||
val filterChain = spring.context.getBean(FilterChainProxy::class.java)
|
||||
val filterClasses: List<Class<out Filter>> = filterChain.getFilters("/").map { it.javaClass }
|
||||
val filterClasses: List<Class<out Filter>> = filterChain.getFilters("/")!!.map { it.javaClass }
|
||||
|
||||
assertThat(filterClasses).containsSubsequence(
|
||||
CustomFilter::class.java,
|
||||
@ -523,7 +523,7 @@ class HttpSecurityDslTests {
|
||||
this.spring.register(CustomSecurityConfigurerConfig::class.java).autowire()
|
||||
|
||||
val filterChain = spring.context.getBean(FilterChainProxy::class.java)
|
||||
val filterClasses: List<Class<out Filter>> = filterChain.getFilters("/").map { it.javaClass }
|
||||
val filterClasses: List<Class<out Filter>> = filterChain.getFilters("/")!!.map { it.javaClass }
|
||||
|
||||
assertThat(filterClasses).contains(
|
||||
CustomFilter::class.java
|
||||
@ -535,7 +535,7 @@ class HttpSecurityDslTests {
|
||||
this.spring.register(CustomSecurityConfigurerConfig::class.java).autowire()
|
||||
|
||||
val filterChain = spring.context.getBean(FilterChainProxy::class.java)
|
||||
val filterClasses: List<Class<out Filter>> = filterChain.getFilters("/").map { it.javaClass }
|
||||
val filterClasses: List<Class<out Filter>> = filterChain.getFilters("/")!!.map { it.javaClass }
|
||||
|
||||
assertThat(filterClasses).contains(
|
||||
CustomFilter::class.java
|
||||
@ -588,7 +588,7 @@ class HttpSecurityDslTests {
|
||||
this.spring.register(CustomDslUsingWithConfig::class.java).autowire()
|
||||
|
||||
val filterChain = spring.context.getBean(FilterChainProxy::class.java)
|
||||
val filterClasses: List<Class<out Filter>> = filterChain.getFilters("/").map { it.javaClass }
|
||||
val filterClasses: List<Class<out Filter>> = filterChain.getFilters("/")!!.map { it.javaClass }
|
||||
|
||||
assertThat(filterClasses).contains(
|
||||
UsernamePasswordAuthenticationFilter::class.java
|
||||
@ -623,5 +623,38 @@ class HttpSecurityDslTests {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `HTTP security when Dsl Bean`() {
|
||||
this.spring.register(DslBeanConfig::class.java).autowire()
|
||||
|
||||
this.mockMvc.get("/")
|
||||
.andExpect {
|
||||
header {
|
||||
string("Content-Security-Policy", "object-src 'none'")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableWebMvc
|
||||
open class DslBeanConfig {
|
||||
@Bean
|
||||
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
http {
|
||||
httpBasic { }
|
||||
}
|
||||
return http.build()
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun headersDsl(): HeadersDsl.() -> Unit {
|
||||
return {
|
||||
contentSecurityPolicy {
|
||||
policyDirectives = "object-src 'none'"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -350,8 +350,8 @@ class LogoutDslTests {
|
||||
|
||||
class NoopLogoutHandler: LogoutHandler {
|
||||
override fun logout(
|
||||
request: HttpServletRequest?,
|
||||
response: HttpServletResponse?,
|
||||
request: HttpServletRequest,
|
||||
response: HttpServletResponse,
|
||||
authentication: Authentication?
|
||||
) { }
|
||||
|
||||
|
@ -22,7 +22,6 @@ import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import jakarta.servlet.http.HttpServletResponse
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
@ -44,7 +43,6 @@ import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequ
|
||||
import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers
|
||||
import org.springframework.security.web.SecurityFilterChain
|
||||
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler
|
||||
import org.springframework.security.web.authentication.ott.DefaultGenerateOneTimeTokenRequestResolver
|
||||
import org.springframework.security.web.authentication.ott.GenerateOneTimeTokenRequestResolver
|
||||
import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler
|
||||
import org.springframework.security.web.authentication.ott.RedirectOneTimeTokenGenerationSuccessHandler
|
||||
@ -53,7 +51,6 @@ import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
|
||||
import org.springframework.test.web.servlet.result.MockMvcResultMatchers
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.time.ZoneOffset
|
||||
|
||||
/**
|
||||
* Tests for [OneTimeTokenLoginDsl]
|
||||
@ -267,7 +264,7 @@ class OneTimeTokenLoginDslTests {
|
||||
)
|
||||
}
|
||||
|
||||
constructor(redirectUrl: String?) {
|
||||
constructor(redirectUrl: String) {
|
||||
this.delegate =
|
||||
RedirectOneTimeTokenGenerationSuccessHandler(
|
||||
redirectUrl
|
||||
|
@ -132,8 +132,8 @@ class RequiresChannelDslTests {
|
||||
|
||||
companion object {
|
||||
val CHANNEL_PROCESSOR: ChannelProcessor = object : ChannelProcessor {
|
||||
override fun decide(invocation: FilterInvocation?, config: MutableCollection<ConfigAttribute>?) {}
|
||||
override fun supports(attribute: ConfigAttribute?): Boolean = true
|
||||
override fun decide(invocation: FilterInvocation, config: MutableCollection<ConfigAttribute>) {}
|
||||
override fun supports(attribute: ConfigAttribute): Boolean = true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -93,7 +93,7 @@ class SecurityContextDslTests {
|
||||
testContext.autowire()
|
||||
val filterChainProxy = testContext.context.getBean(FilterChainProxy::class.java)
|
||||
// @formatter:off
|
||||
val filterTypes = filterChainProxy.getFilters("/").toList()
|
||||
val filterTypes = filterChainProxy.getFilters("/")!!.toList()
|
||||
|
||||
assertThat(filterTypes)
|
||||
.anyMatch { it is SecurityContextHolderFilter }
|
||||
|
@ -270,8 +270,8 @@ class ServerHttpBasicDslTests {
|
||||
|
||||
open class MockServerAuthenticationFailureHandler: ServerAuthenticationFailureHandler {
|
||||
override fun onAuthenticationFailure(
|
||||
webFilterExchange: WebFilterExchange?,
|
||||
exception: AuthenticationException?
|
||||
webFilterExchange: WebFilterExchange,
|
||||
exception: AuthenticationException
|
||||
): Mono<Void> {
|
||||
return Mono.empty()
|
||||
}
|
||||
|
@ -175,8 +175,8 @@ class ServerOAuth2ResourceServerDslTests {
|
||||
|
||||
open class MockServerAuthenticationFailureHandler: ServerAuthenticationFailureHandler {
|
||||
override fun onAuthenticationFailure(
|
||||
webFilterExchange: WebFilterExchange?,
|
||||
exception: AuthenticationException?
|
||||
webFilterExchange: WebFilterExchange,
|
||||
exception: AuthenticationException
|
||||
): Mono<Void> {
|
||||
return Mono.empty()
|
||||
}
|
||||
|
@ -280,11 +280,11 @@ class ServerOneTimeTokenLoginDslTests {
|
||||
this.delegate = ServerRedirectOneTimeTokenGenerationSuccessHandler("/login/ott")
|
||||
}
|
||||
|
||||
constructor(redirectUrl: String?) {
|
||||
constructor(redirectUrl: String) {
|
||||
this.delegate = ServerRedirectOneTimeTokenGenerationSuccessHandler(redirectUrl)
|
||||
}
|
||||
|
||||
override fun handle(exchange: ServerWebExchange?, oneTimeToken: OneTimeToken?): Mono<Void> {
|
||||
override fun handle(exchange: ServerWebExchange, oneTimeToken: OneTimeToken): Mono<Void> {
|
||||
lastToken = oneTimeToken
|
||||
return delegate!!.handle(exchange, oneTimeToken)
|
||||
}
|
||||
|
Binary file not shown.
@ -18,6 +18,8 @@ package org.springframework.security.access;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.jspecify.annotations.NullUnmarked;
|
||||
|
||||
import org.springframework.security.access.intercept.RunAsManager;
|
||||
import org.springframework.security.authorization.AuthorizationManager;
|
||||
import org.springframework.security.core.annotation.SecurityAnnotationScanner;
|
||||
@ -45,6 +47,7 @@ import org.springframework.security.core.annotation.SecurityAnnotationScanner;
|
||||
* {@link AuthorizationManager}.
|
||||
*/
|
||||
@Deprecated
|
||||
@NullUnmarked
|
||||
public interface ConfigAttribute extends Serializable {
|
||||
|
||||
/**
|
||||
|
@ -70,7 +70,7 @@ public abstract class AbstractSecurityExpressionHandler<T>
|
||||
* suitable root object.
|
||||
*/
|
||||
@Override
|
||||
public final EvaluationContext createEvaluationContext(Authentication authentication, T invocation) {
|
||||
public final EvaluationContext createEvaluationContext(@Nullable Authentication authentication, T invocation) {
|
||||
SecurityExpressionOperations root = createSecurityExpressionRoot(authentication, invocation);
|
||||
StandardEvaluationContext ctx = createEvaluationContextInternal(authentication, invocation);
|
||||
if (this.beanResolver != null) {
|
||||
@ -91,7 +91,8 @@ public abstract class AbstractSecurityExpressionHandler<T>
|
||||
* @return A {@code StandardEvaluationContext} or potentially a custom subclass if
|
||||
* overridden.
|
||||
*/
|
||||
protected StandardEvaluationContext createEvaluationContextInternal(Authentication authentication, T invocation) {
|
||||
protected StandardEvaluationContext createEvaluationContextInternal(@Nullable Authentication authentication,
|
||||
T invocation) {
|
||||
return new StandardEvaluationContext();
|
||||
}
|
||||
|
||||
@ -102,8 +103,8 @@ public abstract class AbstractSecurityExpressionHandler<T>
|
||||
* @param invocation the invocation (filter, method, channel)
|
||||
* @return the object
|
||||
*/
|
||||
protected abstract SecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication,
|
||||
T invocation);
|
||||
protected abstract SecurityExpressionOperations createSecurityExpressionRoot(
|
||||
@Nullable Authentication authentication, T invocation);
|
||||
|
||||
protected @Nullable RoleHierarchy getRoleHierarchy() {
|
||||
return this.roleHierarchy;
|
||||
|
@ -18,6 +18,8 @@ package org.springframework.security.access.expression;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.aop.framework.AopInfrastructureBean;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
@ -42,7 +44,7 @@ public interface SecurityExpressionHandler<T> extends AopInfrastructureBean {
|
||||
* Provides an evaluation context in which to evaluate security expressions for the
|
||||
* invocation type.
|
||||
*/
|
||||
EvaluationContext createEvaluationContext(Authentication authentication, T invocation);
|
||||
EvaluationContext createEvaluationContext(@Nullable Authentication authentication, T invocation);
|
||||
|
||||
/**
|
||||
* Provides an evaluation context in which to evaluate security expressions for the
|
||||
@ -55,7 +57,8 @@ public interface SecurityExpressionHandler<T> extends AopInfrastructureBean {
|
||||
* @return the {@link EvaluationContext} to use
|
||||
* @since 5.8
|
||||
*/
|
||||
default EvaluationContext createEvaluationContext(Supplier<Authentication> authentication, T invocation) {
|
||||
default EvaluationContext createEvaluationContext(Supplier<? extends @Nullable Authentication> authentication,
|
||||
T invocation) {
|
||||
return createEvaluationContext(authentication.get(), invocation);
|
||||
}
|
||||
|
||||
|
@ -78,7 +78,7 @@ public abstract class SecurityExpressionRoot implements SecurityExpressionOperat
|
||||
* Creates a new instance
|
||||
* @param authentication the {@link Authentication} to use. Cannot be null.
|
||||
*/
|
||||
public SecurityExpressionRoot(Authentication authentication) {
|
||||
public SecurityExpressionRoot(@Nullable Authentication authentication) {
|
||||
this(() -> authentication);
|
||||
}
|
||||
|
||||
@ -89,7 +89,7 @@ public abstract class SecurityExpressionRoot implements SecurityExpressionOperat
|
||||
* Cannot be null.
|
||||
* @since 5.8
|
||||
*/
|
||||
public SecurityExpressionRoot(Supplier<Authentication> authentication) {
|
||||
public SecurityExpressionRoot(Supplier<? extends @Nullable Authentication> authentication) {
|
||||
this.authentication = SingletonSupplier.of(() -> {
|
||||
Authentication value = authentication.get();
|
||||
Assert.notNull(value, "Authentication object cannot be null");
|
||||
@ -177,7 +177,7 @@ public abstract class SecurityExpressionRoot implements SecurityExpressionOperat
|
||||
this.trustResolver = trustResolver;
|
||||
}
|
||||
|
||||
public void setRoleHierarchy(RoleHierarchy roleHierarchy) {
|
||||
public void setRoleHierarchy(@Nullable RoleHierarchy roleHierarchy) {
|
||||
this.roleHierarchy = roleHierarchy;
|
||||
}
|
||||
|
||||
|
@ -79,12 +79,15 @@ public class DefaultMethodSecurityExpressionHandler extends AbstractSecurityExpr
|
||||
* implementation.
|
||||
*/
|
||||
@Override
|
||||
public StandardEvaluationContext createEvaluationContextInternal(Authentication auth, MethodInvocation mi) {
|
||||
public StandardEvaluationContext createEvaluationContextInternal(@Nullable Authentication auth,
|
||||
MethodInvocation mi) {
|
||||
return new MethodSecurityEvaluationContext(auth, mi, getParameterNameDiscoverer());
|
||||
}
|
||||
|
||||
@Override
|
||||
public EvaluationContext createEvaluationContext(Supplier<Authentication> authentication, MethodInvocation mi) {
|
||||
@SuppressWarnings("NullAway") // FIXME: Dataflow analysis limitation
|
||||
public EvaluationContext createEvaluationContext(Supplier<? extends @Nullable Authentication> authentication,
|
||||
MethodInvocation mi) {
|
||||
MethodSecurityExpressionOperations root = createSecurityExpressionRoot(authentication, mi);
|
||||
MethodSecurityEvaluationContext ctx = new MethodSecurityEvaluationContext(root, mi,
|
||||
getParameterNameDiscoverer());
|
||||
@ -96,13 +99,13 @@ public class DefaultMethodSecurityExpressionHandler extends AbstractSecurityExpr
|
||||
* Creates the root object for expression evaluation.
|
||||
*/
|
||||
@Override
|
||||
protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication,
|
||||
protected MethodSecurityExpressionOperations createSecurityExpressionRoot(@Nullable Authentication authentication,
|
||||
MethodInvocation invocation) {
|
||||
return createSecurityExpressionRoot(() -> authentication, invocation);
|
||||
}
|
||||
|
||||
private MethodSecurityExpressionOperations createSecurityExpressionRoot(Supplier<Authentication> authentication,
|
||||
MethodInvocation invocation) {
|
||||
private MethodSecurityExpressionOperations createSecurityExpressionRoot(
|
||||
Supplier<? extends @Nullable Authentication> authentication, MethodInvocation invocation) {
|
||||
MethodSecurityExpressionRoot root = new MethodSecurityExpressionRoot(authentication);
|
||||
root.setThis(invocation.getThis());
|
||||
root.setPermissionEvaluator(getPermissionEvaluator());
|
||||
|
@ -38,11 +38,11 @@ class MethodSecurityExpressionRoot extends SecurityExpressionRoot implements Met
|
||||
|
||||
private @Nullable Object target;
|
||||
|
||||
MethodSecurityExpressionRoot(Authentication a) {
|
||||
MethodSecurityExpressionRoot(@Nullable Authentication a) {
|
||||
super(a);
|
||||
}
|
||||
|
||||
MethodSecurityExpressionRoot(Supplier<Authentication> authentication) {
|
||||
MethodSecurityExpressionRoot(Supplier<? extends @Nullable Authentication> authentication) {
|
||||
super(authentication);
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,8 @@ package org.springframework.security.access.vote;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.jspecify.annotations.NullUnmarked;
|
||||
|
||||
import org.springframework.security.access.AccessDecisionVoter;
|
||||
import org.springframework.security.access.ConfigAttribute;
|
||||
import org.springframework.security.core.Authentication;
|
||||
@ -53,6 +55,7 @@ import org.springframework.security.core.GrantedAuthority;
|
||||
* instead
|
||||
*/
|
||||
@Deprecated
|
||||
@NullUnmarked
|
||||
public class RoleVoter implements AccessDecisionVoter<Object> {
|
||||
|
||||
private String rolePrefix = "ROLE_";
|
||||
|
@ -16,6 +16,9 @@
|
||||
|
||||
package org.springframework.security.authentication;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.lang.Contract;
|
||||
import org.springframework.security.core.Authentication;
|
||||
|
||||
/**
|
||||
@ -37,7 +40,7 @@ public interface AuthenticationTrustResolver {
|
||||
* @return <code>true</code> the passed authentication token represented an anonymous
|
||||
* principal, <code>false</code> otherwise
|
||||
*/
|
||||
boolean isAnonymous(Authentication authentication);
|
||||
boolean isAnonymous(@Nullable Authentication authentication);
|
||||
|
||||
/**
|
||||
* Indicates whether the passed <code>Authentication</code> token represents user that
|
||||
@ -51,7 +54,7 @@ public interface AuthenticationTrustResolver {
|
||||
* @return <code>true</code> the passed authentication token represented a principal
|
||||
* authenticated using a remember-me token, <code>false</code> otherwise
|
||||
*/
|
||||
boolean isRememberMe(Authentication authentication);
|
||||
boolean isRememberMe(@Nullable Authentication authentication);
|
||||
|
||||
/**
|
||||
* Indicates whether the passed <code>Authentication</code> token represents a fully
|
||||
@ -66,7 +69,7 @@ public interface AuthenticationTrustResolver {
|
||||
* {@link #isRememberMe(Authentication)}, <code>false</code> otherwise
|
||||
* @since 6.1
|
||||
*/
|
||||
default boolean isFullyAuthenticated(Authentication authentication) {
|
||||
default boolean isFullyAuthenticated(@Nullable Authentication authentication) {
|
||||
return isAuthenticated(authentication) && !isRememberMe(authentication);
|
||||
}
|
||||
|
||||
@ -78,7 +81,8 @@ public interface AuthenticationTrustResolver {
|
||||
* {@link Authentication#isAuthenticated()} is true.
|
||||
* @since 6.1.7
|
||||
*/
|
||||
default boolean isAuthenticated(Authentication authentication) {
|
||||
@Contract("null -> false")
|
||||
default boolean isAuthenticated(@Nullable Authentication authentication) {
|
||||
return authentication != null && authentication.isAuthenticated() && !isAnonymous(authentication);
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.security.authentication;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
|
||||
/**
|
||||
@ -44,7 +46,7 @@ public class AuthenticationTrustResolverImpl implements AuthenticationTrustResol
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAnonymous(Authentication authentication) {
|
||||
public boolean isAnonymous(@Nullable Authentication authentication) {
|
||||
if ((this.anonymousClass == null) || (authentication == null)) {
|
||||
return false;
|
||||
}
|
||||
@ -52,7 +54,7 @@ public class AuthenticationTrustResolverImpl implements AuthenticationTrustResol
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRememberMe(Authentication authentication) {
|
||||
public boolean isRememberMe(@Nullable Authentication authentication) {
|
||||
if ((this.rememberMeClass == null) || (authentication == null)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationT
|
||||
|
||||
private static final long serialVersionUID = 620L;
|
||||
|
||||
private final Object principal;
|
||||
private final @Nullable Object principal;
|
||||
|
||||
private @Nullable Object credentials;
|
||||
|
||||
@ -49,7 +49,7 @@ public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationT
|
||||
* will return <code>false</code>.
|
||||
*
|
||||
*/
|
||||
public UsernamePasswordAuthenticationToken(Object principal, @Nullable Object credentials) {
|
||||
public UsernamePasswordAuthenticationToken(@Nullable Object principal, @Nullable Object credentials) {
|
||||
super(null);
|
||||
this.principal = principal;
|
||||
this.credentials = credentials;
|
||||
@ -82,7 +82,8 @@ public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationT
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
public static UsernamePasswordAuthenticationToken unauthenticated(Object principal, @Nullable Object credentials) {
|
||||
public static UsernamePasswordAuthenticationToken unauthenticated(@Nullable Object principal,
|
||||
@Nullable Object credentials) {
|
||||
return new UsernamePasswordAuthenticationToken(principal, credentials);
|
||||
}
|
||||
|
||||
@ -106,7 +107,7 @@ public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationT
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getPrincipal() {
|
||||
public @Nullable Object getPrincipal() {
|
||||
return this.principal;
|
||||
}
|
||||
|
||||
|
@ -178,8 +178,10 @@ public abstract class AbstractJaasAuthenticationProvider implements Authenticati
|
||||
// applied.
|
||||
authorities = getAuthorities(principals);
|
||||
// Convert the authorities set back to an array and apply it to the token.
|
||||
JaasAuthenticationToken result = new JaasAuthenticationToken(request.getPrincipal(),
|
||||
request.getCredentials(), new ArrayList<>(authorities), loginContext);
|
||||
Object principal = request.getPrincipal();
|
||||
Assert.notNull(principal, "The principal cannot be null");
|
||||
JaasAuthenticationToken result = new JaasAuthenticationToken(principal, request.getCredentials(),
|
||||
new ArrayList<>(authorities), loginContext);
|
||||
// Publish the success event
|
||||
publishSuccessEvent(result);
|
||||
// we're done, return the token.
|
||||
|
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.authentication.ott;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
|
||||
/**
|
||||
* The result of a successful one-time-token authentication
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 7.0
|
||||
*/
|
||||
public class OneTimeTokenAuthentication extends AbstractAuthenticationToken {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1195893764725073959L;
|
||||
|
||||
private final Object principal;
|
||||
|
||||
public OneTimeTokenAuthentication(Object principal, Collection<? extends GrantedAuthority> authorities) {
|
||||
super(authorities);
|
||||
this.principal = principal;
|
||||
setAuthenticated(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getPrincipal() {
|
||||
return this.principal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Object getCredentials() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
@ -56,8 +56,7 @@ public final class OneTimeTokenAuthenticationProvider implements AuthenticationP
|
||||
}
|
||||
try {
|
||||
UserDetails user = this.userDetailsService.loadUserByUsername(consumed.getUsername());
|
||||
OneTimeTokenAuthenticationToken authenticated = OneTimeTokenAuthenticationToken.authenticated(user,
|
||||
user.getAuthorities());
|
||||
OneTimeTokenAuthentication authenticated = new OneTimeTokenAuthentication(user, user.getAuthorities());
|
||||
authenticated.setDetails(otpAuthenticationToken.getDetails());
|
||||
return authenticated;
|
||||
}
|
||||
|
@ -40,6 +40,10 @@ public class OneTimeTokenAuthenticationToken extends AbstractAuthenticationToken
|
||||
|
||||
private @Nullable String tokenValue;
|
||||
|
||||
/**
|
||||
* @deprecated Please use constructor that takes a {@link String} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "7.0")
|
||||
public OneTimeTokenAuthenticationToken(@Nullable Object principal, String tokenValue) {
|
||||
super(Collections.emptyList());
|
||||
this.tokenValue = tokenValue;
|
||||
@ -50,6 +54,10 @@ public class OneTimeTokenAuthenticationToken extends AbstractAuthenticationToken
|
||||
this(null, tokenValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Please use {@link OneTimeTokenAuthentication} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "7.0")
|
||||
public OneTimeTokenAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
|
||||
super(authorities);
|
||||
this.principal = principal;
|
||||
@ -60,9 +68,11 @@ public class OneTimeTokenAuthenticationToken extends AbstractAuthenticationToken
|
||||
* Creates an unauthenticated token
|
||||
* @param tokenValue the one-time token value
|
||||
* @return an unauthenticated {@link OneTimeTokenAuthenticationToken}
|
||||
* @deprecated Please use constructor that takes a {@link String} instead
|
||||
*/
|
||||
public static OneTimeTokenAuthenticationToken unauthenticated(String tokenValue) {
|
||||
return new OneTimeTokenAuthenticationToken(null, tokenValue);
|
||||
@Deprecated(forRemoval = true, since = "7.0")
|
||||
public static OneTimeTokenAuthenticationToken unauthenticated(@Nullable String tokenValue) {
|
||||
return new OneTimeTokenAuthenticationToken(null, (tokenValue != null) ? tokenValue : "");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -70,7 +80,9 @@ public class OneTimeTokenAuthenticationToken extends AbstractAuthenticationToken
|
||||
* @param principal the principal
|
||||
* @param tokenValue the one-time token value
|
||||
* @return an unauthenticated {@link OneTimeTokenAuthenticationToken}
|
||||
* @deprecated Please use constructor that takes a {@link String} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "7.0")
|
||||
public static OneTimeTokenAuthenticationToken unauthenticated(Object principal, String tokenValue) {
|
||||
return new OneTimeTokenAuthenticationToken(principal, tokenValue);
|
||||
}
|
||||
@ -80,7 +92,9 @@ public class OneTimeTokenAuthenticationToken extends AbstractAuthenticationToken
|
||||
* @param principal the principal
|
||||
* @param authorities the principal authorities
|
||||
* @return an authenticated {@link OneTimeTokenAuthenticationToken}
|
||||
* @deprecated Please use {@link OneTimeTokenAuthentication} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "7.0")
|
||||
public static OneTimeTokenAuthenticationToken authenticated(Object principal,
|
||||
Collection<? extends GrantedAuthority> authorities) {
|
||||
return new OneTimeTokenAuthenticationToken(principal, authorities);
|
||||
|
@ -22,6 +22,7 @@ import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.security.authentication.ReactiveAuthenticationManager;
|
||||
import org.springframework.security.authentication.ott.InvalidOneTimeTokenException;
|
||||
import org.springframework.security.authentication.ott.OneTimeTokenAuthentication;
|
||||
import org.springframework.security.authentication.ott.OneTimeTokenAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
|
||||
@ -59,10 +60,9 @@ public final class OneTimeTokenReactiveAuthenticationManager implements Reactive
|
||||
.map(onSuccess(otpAuthenticationToken));
|
||||
}
|
||||
|
||||
private Function<UserDetails, OneTimeTokenAuthenticationToken> onSuccess(OneTimeTokenAuthenticationToken token) {
|
||||
private Function<UserDetails, OneTimeTokenAuthentication> onSuccess(OneTimeTokenAuthenticationToken token) {
|
||||
return (user) -> {
|
||||
OneTimeTokenAuthenticationToken authenticated = OneTimeTokenAuthenticationToken.authenticated(user,
|
||||
user.getAuthorities());
|
||||
OneTimeTokenAuthentication authenticated = new OneTimeTokenAuthentication(user, user.getAuthorities());
|
||||
authenticated.setDetails(token.getDetails());
|
||||
return authenticated;
|
||||
};
|
||||
|
@ -18,6 +18,8 @@ package org.springframework.security.authorization;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.security.authentication.AuthenticationTrustResolver;
|
||||
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
|
||||
import org.springframework.security.core.Authentication;
|
||||
@ -111,7 +113,7 @@ public final class AuthenticatedAuthorizationManager<T> implements Authorization
|
||||
* @return an {@link AuthorizationDecision}
|
||||
*/
|
||||
@Override
|
||||
public AuthorizationResult authorize(Supplier<Authentication> authentication, T object) {
|
||||
public AuthorizationResult authorize(Supplier<? extends @Nullable Authentication> authentication, T object) {
|
||||
boolean granted = this.authorizationStrategy.isGranted(authentication.get());
|
||||
return new AuthorizationDecision(granted);
|
||||
}
|
||||
|
@ -19,6 +19,8 @@ package org.springframework.security.authorization;
|
||||
import java.util.Collection;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy;
|
||||
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
|
||||
import org.springframework.security.core.Authentication;
|
||||
@ -55,7 +57,8 @@ public final class AuthoritiesAuthorizationManager implements AuthorizationManag
|
||||
* @return an {@link AuthorityAuthorizationDecision}
|
||||
*/
|
||||
@Override
|
||||
public AuthorizationResult authorize(Supplier<Authentication> authentication, Collection<String> authorities) {
|
||||
public AuthorizationResult authorize(Supplier<? extends @Nullable Authentication> authentication,
|
||||
Collection<String> authorities) {
|
||||
boolean granted = isGranted(authentication.get(), authorities);
|
||||
return new AuthorityAuthorizationDecision(granted, AuthorityUtils.createAuthorityList(authorities));
|
||||
}
|
||||
|
@ -19,6 +19,8 @@ package org.springframework.security.authorization;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy;
|
||||
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
|
||||
import org.springframework.security.core.Authentication;
|
||||
@ -137,7 +139,7 @@ public final class AuthorityAuthorizationManager<T> implements AuthorizationMana
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public AuthorizationResult authorize(Supplier<Authentication> authentication, T object) {
|
||||
public AuthorizationResult authorize(Supplier<? extends @Nullable Authentication> authentication, T object) {
|
||||
return this.delegate.authorize(authentication, this.authorities);
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,8 @@ package org.springframework.security.authorization;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.security.authorization.event.AuthorizationDeniedEvent;
|
||||
import org.springframework.security.authorization.event.AuthorizationGrantedEvent;
|
||||
import org.springframework.security.core.Authentication;
|
||||
@ -46,6 +48,7 @@ public interface AuthorizationEventPublisher {
|
||||
* @param <T> the secured object's type
|
||||
* @since 6.4
|
||||
*/
|
||||
<T> void publishAuthorizationEvent(Supplier<Authentication> authentication, T object, AuthorizationResult result);
|
||||
<T> void publishAuthorizationEvent(Supplier<Authentication> authentication, T object,
|
||||
@Nullable AuthorizationResult result);
|
||||
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ public interface AuthorizationManager<T extends @Nullable Object> {
|
||||
* @param object the {@link T} object to check
|
||||
* @throws AccessDeniedException if access is not granted
|
||||
*/
|
||||
default void verify(Supplier<Authentication> authentication, T object) {
|
||||
default void verify(Supplier<? extends @Nullable Authentication> authentication, T object) {
|
||||
AuthorizationResult result = authorize(authentication, object);
|
||||
if (result != null && !result.isGranted()) {
|
||||
throw new AuthorizationDeniedException("Access Denied", result);
|
||||
@ -54,6 +54,6 @@ public interface AuthorizationManager<T extends @Nullable Object> {
|
||||
* @return an {@link AuthorizationResult}
|
||||
* @since 6.4
|
||||
*/
|
||||
@Nullable AuthorizationResult authorize(Supplier<Authentication> authentication, T object);
|
||||
@Nullable AuthorizationResult authorize(Supplier<? extends @Nullable Authentication> authentication, T object);
|
||||
|
||||
}
|
||||
|
@ -20,6 +20,8 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
|
||||
/**
|
||||
@ -58,7 +60,8 @@ public final class AuthorizationManagers {
|
||||
@SafeVarargs
|
||||
public static <T> AuthorizationManager<T> anyOf(AuthorizationDecision allAbstainDefaultDecision,
|
||||
AuthorizationManager<T>... managers) {
|
||||
return (AuthorizationManagerCheckAdapter<T>) (authentication, object) -> {
|
||||
return (AuthorizationManagerCheckAdapter<T>) (Supplier<? extends @Nullable Authentication> authentication,
|
||||
T object) -> {
|
||||
List<AuthorizationResult> results = new ArrayList<>();
|
||||
for (AuthorizationManager<T> manager : managers) {
|
||||
AuthorizationResult result = manager.authorize(authentication, object);
|
||||
@ -104,7 +107,8 @@ public final class AuthorizationManagers {
|
||||
@SafeVarargs
|
||||
public static <T> AuthorizationManager<T> allOf(AuthorizationDecision allAbstainDefaultDecision,
|
||||
AuthorizationManager<T>... managers) {
|
||||
return (AuthorizationManagerCheckAdapter<T>) (authentication, object) -> {
|
||||
return (AuthorizationManagerCheckAdapter<T>) (Supplier<? extends @Nullable Authentication> authentication,
|
||||
T object) -> {
|
||||
List<AuthorizationResult> results = new ArrayList<>();
|
||||
for (AuthorizationManager<T> manager : managers) {
|
||||
AuthorizationResult result = manager.authorize(authentication, object);
|
||||
@ -133,7 +137,7 @@ public final class AuthorizationManagers {
|
||||
* @since 6.3
|
||||
*/
|
||||
public static <T> AuthorizationManager<T> not(AuthorizationManager<T> manager) {
|
||||
return (authentication, object) -> {
|
||||
return (Supplier<? extends @Nullable Authentication> authentication, T object) -> {
|
||||
AuthorizationResult result = manager.authorize(authentication, object);
|
||||
if (result == null) {
|
||||
return null;
|
||||
@ -182,7 +186,7 @@ public final class AuthorizationManagers {
|
||||
private interface AuthorizationManagerCheckAdapter<T> extends AuthorizationManager<T> {
|
||||
|
||||
@Override
|
||||
AuthorizationResult authorize(Supplier<Authentication> authentication, T object);
|
||||
AuthorizationResult authorize(Supplier<? extends @Nullable Authentication> authentication, T object);
|
||||
|
||||
}
|
||||
|
||||
|
@ -63,9 +63,10 @@ public final class ObservationAuthorizationManager<T>
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable AuthorizationResult authorize(Supplier<Authentication> authentication, T object) {
|
||||
public @Nullable AuthorizationResult authorize(Supplier<? extends @Nullable Authentication> authentication,
|
||||
T object) {
|
||||
AuthorizationObservationContext<T> context = new AuthorizationObservationContext<>(object);
|
||||
Supplier<Authentication> wrapped = () -> {
|
||||
Supplier<@Nullable Authentication> wrapped = () -> {
|
||||
context.setAuthentication(authentication.get());
|
||||
return context.getAuthentication();
|
||||
};
|
||||
|
@ -18,6 +18,8 @@ package org.springframework.security.authorization;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@ -44,7 +46,7 @@ public final class SingleResultAuthorizationManager<C> implements AuthorizationM
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthorizationResult authorize(Supplier<Authentication> authentication, C object) {
|
||||
public AuthorizationResult authorize(Supplier<? extends @Nullable Authentication> authentication, C object) {
|
||||
if (!(this.result instanceof AuthorizationDecision)) {
|
||||
throw new IllegalArgumentException("result should be AuthorizationDecision");
|
||||
}
|
||||
|
@ -19,6 +19,8 @@ package org.springframework.security.authorization;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.security.authorization.event.AuthorizationDeniedEvent;
|
||||
import org.springframework.security.authorization.event.AuthorizationGrantedEvent;
|
||||
@ -57,7 +59,7 @@ public final class SpringAuthorizationEventPublisher implements AuthorizationEve
|
||||
*/
|
||||
@Override
|
||||
public <T> void publishAuthorizationEvent(Supplier<Authentication> authentication, T object,
|
||||
AuthorizationResult result) {
|
||||
@Nullable AuthorizationResult result) {
|
||||
if (result == null) {
|
||||
return;
|
||||
}
|
||||
|
@ -83,7 +83,7 @@ public final class Jsr250AuthorizationManager implements AuthorizationManager<Me
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @Nullable AuthorizationResult authorize(Supplier<Authentication> authentication,
|
||||
public @Nullable AuthorizationResult authorize(Supplier<? extends @Nullable Authentication> authentication,
|
||||
MethodInvocation methodInvocation) {
|
||||
AuthorizationManager<MethodInvocation> delegate = this.registry.getManager(methodInvocation);
|
||||
return delegate.authorize(authentication, methodInvocation);
|
||||
@ -104,8 +104,9 @@ public final class Jsr250AuthorizationManager implements AuthorizationManager<Me
|
||||
return SingleResultAuthorizationManager.permitAll();
|
||||
}
|
||||
if (annotation instanceof RolesAllowed rolesAllowed) {
|
||||
return (a, o) -> Jsr250AuthorizationManager.this.authoritiesAuthorizationManager.authorize(a,
|
||||
getAllowedRolesWithPrefix(rolesAllowed));
|
||||
return (Supplier<? extends @Nullable Authentication> a,
|
||||
MethodInvocation o) -> Jsr250AuthorizationManager.this.authoritiesAuthorizationManager
|
||||
.authorize(a, getAllowedRolesWithPrefix(rolesAllowed));
|
||||
}
|
||||
return NULL_MANAGER;
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ package org.springframework.security.authorization.method;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
@ -73,7 +74,9 @@ public final class MethodExpressionAuthorizationManager implements Authorization
|
||||
* expression
|
||||
*/
|
||||
@Override
|
||||
public AuthorizationResult authorize(Supplier<Authentication> authentication, MethodInvocation context) {
|
||||
@SuppressWarnings("NullAway") // FIXME: Dataflow analysis limitation
|
||||
public AuthorizationResult authorize(Supplier<? extends @Nullable Authentication> authentication,
|
||||
MethodInvocation context) {
|
||||
EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication, context);
|
||||
boolean granted = ExpressionUtils.evaluateAsBoolean(this.expression, ctx);
|
||||
return new ExpressionAuthorizationDecision(granted, this.expression);
|
||||
|
@ -18,6 +18,8 @@ package org.springframework.security.authorization.method;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.security.authorization.AuthorizationEventPublisher;
|
||||
import org.springframework.security.authorization.AuthorizationResult;
|
||||
import org.springframework.security.core.Authentication;
|
||||
@ -32,7 +34,7 @@ final class NoOpAuthorizationEventPublisher implements AuthorizationEventPublish
|
||||
|
||||
@Override
|
||||
public <T> void publishAuthorizationEvent(Supplier<Authentication> authentication, T object,
|
||||
AuthorizationResult result) {
|
||||
@Nullable AuthorizationResult result) {
|
||||
|
||||
}
|
||||
|
||||
|
@ -86,7 +86,8 @@ public final class PostAuthorizeAuthorizationManager
|
||||
* {@link PostAuthorize} annotation is not present
|
||||
*/
|
||||
@Override
|
||||
public @Nullable AuthorizationResult authorize(Supplier<Authentication> authentication, MethodInvocationResult mi) {
|
||||
public @Nullable AuthorizationResult authorize(Supplier<? extends @Nullable Authentication> authentication,
|
||||
MethodInvocationResult mi) {
|
||||
ExpressionAttribute attribute = this.registry.getAttribute(mi.getMethodInvocation());
|
||||
if (attribute == null) {
|
||||
return null;
|
||||
|
@ -78,7 +78,8 @@ public final class PreAuthorizeAuthorizationManager
|
||||
* {@link PreAuthorize} annotation is not present
|
||||
*/
|
||||
@Override
|
||||
public @Nullable AuthorizationResult authorize(Supplier<Authentication> authentication, MethodInvocation mi) {
|
||||
public @Nullable AuthorizationResult authorize(Supplier<? extends @Nullable Authentication> authentication,
|
||||
MethodInvocation mi) {
|
||||
ExpressionAttribute attribute = this.registry.getAttribute(mi);
|
||||
if (attribute == null) {
|
||||
return null;
|
||||
|
@ -68,7 +68,8 @@ public final class SecuredAuthorizationManager implements AuthorizationManager<M
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable AuthorizationResult authorize(Supplier<Authentication> authentication, MethodInvocation mi) {
|
||||
public @Nullable AuthorizationResult authorize(Supplier<? extends @Nullable Authentication> authentication,
|
||||
MethodInvocation mi) {
|
||||
Set<String> authorities = getAuthorities(mi);
|
||||
return authorities.isEmpty() ? null
|
||||
: this.authoritiesAuthorizationManager.authorize(authentication, authorities);
|
||||
|
@ -76,7 +76,7 @@ public abstract class AuthenticationException extends RuntimeException {
|
||||
* authentication attempt
|
||||
* @since 6.5
|
||||
*/
|
||||
public void setAuthenticationRequest(Authentication authenticationRequest) {
|
||||
public void setAuthenticationRequest(@Nullable Authentication authenticationRequest) {
|
||||
Assert.notNull(authenticationRequest, "authenticationRequest cannot be null");
|
||||
this.authenticationRequest = authenticationRequest;
|
||||
}
|
||||
|
@ -31,8 +31,6 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
*/
|
||||
public final class SecurityAnnotationScanners {
|
||||
|
||||
private static final Map<Class<? extends Annotation>, SecurityAnnotationScanner<? extends Annotation>> uniqueScanners = new ConcurrentHashMap<>();
|
||||
|
||||
private static final Map<Class<? extends Annotation>, SecurityAnnotationScanner<? extends Annotation>> uniqueTemplateScanners = new ConcurrentHashMap<>();
|
||||
|
||||
private static final Map<List<Class<? extends Annotation>>, SecurityAnnotationScanner<? extends Annotation>> uniqueTypesScanners = new ConcurrentHashMap<>();
|
||||
@ -48,8 +46,7 @@ public final class SecurityAnnotationScanners {
|
||||
* @return the default {@link SecurityAnnotationScanner}
|
||||
*/
|
||||
public static <A extends Annotation> SecurityAnnotationScanner<A> requireUnique(Class<A> type) {
|
||||
return (SecurityAnnotationScanner<A>) uniqueScanners.computeIfAbsent(type,
|
||||
(t) -> new UniqueSecurityAnnotationScanner<>(type));
|
||||
return requireUnique(type, new AnnotationTemplateExpressionDefaults());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -68,9 +65,6 @@ public final class SecurityAnnotationScanners {
|
||||
*/
|
||||
public static <A extends Annotation> SecurityAnnotationScanner<A> requireUnique(Class<A> type,
|
||||
AnnotationTemplateExpressionDefaults templateDefaults) {
|
||||
if (templateDefaults == null) {
|
||||
return requireUnique(type);
|
||||
}
|
||||
return (SecurityAnnotationScanner<A>) uniqueTemplateScanners.computeIfAbsent(type,
|
||||
(t) -> new ExpressionTemplateSecurityAnnotationScanner<>(t, templateDefaults));
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
@ -69,8 +70,7 @@ public class OneTimeTokenAuthenticationProviderTests {
|
||||
.willReturn(new User(USERNAME, PASSWORD, List.of()));
|
||||
OneTimeTokenAuthenticationToken token = new OneTimeTokenAuthenticationToken(TOKEN);
|
||||
|
||||
OneTimeTokenAuthenticationToken authentication = (OneTimeTokenAuthenticationToken) this.provider
|
||||
.authenticate(token);
|
||||
Authentication authentication = this.provider.authenticate(token);
|
||||
|
||||
User user = (User) authentication.getPrincipal();
|
||||
assertThat(authentication.isAuthenticated()).isTrue();
|
||||
|
@ -88,13 +88,12 @@ public class OneTimeTokenReactiveAuthenticationManagerTests {
|
||||
this.authenticationManager = new OneTimeTokenReactiveAuthenticationManager(oneTimeTokenService,
|
||||
userDetailsService);
|
||||
|
||||
Authentication auth = this.authenticationManager
|
||||
Authentication token = this.authenticationManager
|
||||
.authenticate(OneTimeTokenAuthenticationToken.unauthenticated(TOKEN))
|
||||
.block();
|
||||
|
||||
OneTimeTokenAuthenticationToken token = (OneTimeTokenAuthenticationToken) auth;
|
||||
UserDetails user = (UserDetails) token.getPrincipal();
|
||||
Collection<GrantedAuthority> authorities = token.getAuthorities();
|
||||
Collection<? extends GrantedAuthority> authorities = token.getAuthorities();
|
||||
|
||||
assertThat(user).isNotNull();
|
||||
assertThat(user.getUsername()).isEqualTo(USERNAME);
|
||||
|
@ -1,3 +1,7 @@
|
||||
plugins {
|
||||
id 'security-nullability'
|
||||
}
|
||||
|
||||
apply plugin: 'io.spring.convention.spring-module'
|
||||
|
||||
dependencies {
|
||||
|
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2004-present 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* AOT integration for Spring Security's Data integration.
|
||||
*/
|
||||
@NullMarked
|
||||
package org.springframework.security.data.aot.hint;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.security.data.repository.query;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.data.spel.spi.EvaluationContextExtension;
|
||||
import org.springframework.security.access.PermissionEvaluator;
|
||||
import org.springframework.security.access.expression.DenyAllPermissionEvaluator;
|
||||
@ -93,7 +95,7 @@ public class SecurityEvaluationContextExtension implements EvaluationContextExte
|
||||
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
|
||||
.getContextHolderStrategy();
|
||||
|
||||
private Authentication authentication;
|
||||
private @Nullable Authentication authentication;
|
||||
|
||||
private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
|
||||
|
||||
@ -114,7 +116,7 @@ public class SecurityEvaluationContextExtension implements EvaluationContextExte
|
||||
* Creates a new instance that always uses the same {@link Authentication} object.
|
||||
* @param authentication the {@link Authentication} to use
|
||||
*/
|
||||
public SecurityEvaluationContextExtension(Authentication authentication) {
|
||||
public SecurityEvaluationContextExtension(@Nullable Authentication authentication) {
|
||||
this.authentication = authentication;
|
||||
}
|
||||
|
||||
@ -146,7 +148,7 @@ public class SecurityEvaluationContextExtension implements EvaluationContextExte
|
||||
this.securityContextHolderStrategy = securityContextHolderStrategy;
|
||||
}
|
||||
|
||||
private Authentication getAuthentication() {
|
||||
private @Nullable Authentication getAuthentication() {
|
||||
if (this.authentication != null) {
|
||||
return this.authentication;
|
||||
}
|
||||
|
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2004-present 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Spring Security extensions for Spring Data queries.
|
||||
*/
|
||||
@NullMarked
|
||||
package org.springframework.security.data.repository.query;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
@ -242,3 +242,72 @@ It matches the requests in order by the `securityMatcher` definition.
|
||||
In this case, that means that, if the URL path starts with `/api`, Spring Security uses `apiHttpSecurity`.
|
||||
If the URL does not start with `/api`, Spring Security defaults to `webHttpSecurity`, which has an implied `securityMatcher` that matches any request.
|
||||
|
||||
|
||||
[[modular-serverhttpsecurity-configuration]]
|
||||
== Modular ServerHttpSecurity Configuration
|
||||
|
||||
Many users prefer that their Spring Security configuration lives in a centralized place and will choose to configure it within the `SecurityWebFilterChain` Bean declaration.
|
||||
However, there are times that users may want to modularize the configuration.
|
||||
This can be done using:
|
||||
|
||||
* xref:#serverhttpsecurity-customizer-bean[Customizer<ServerHttpSecurity> Beans]
|
||||
* xref:#top-level-customizer-bean[Top Level ServerHttpSecurity Customizer Beans]
|
||||
|
||||
// FIXME: this needs to link to appropriate spot
|
||||
// NOTE: If you are using Spring Security's xref:servlet/configuration/kotlin.adoc[], then you can also expose `*Dsl -> Unit` Beans as outlined in xref:./kotlin.adoc#modular-httpsecuritydsl-configuration[Modular HttpSecurityDsl Configuration].
|
||||
|
||||
|
||||
[[serverhttpsecurity-customizer-bean]]
|
||||
=== Customizer<ServerHttpSecurity> Beans
|
||||
|
||||
If you would like to modularize your security configuration you can place logic in a `Customizer<ServerHttpSecurity>` Bean.
|
||||
For example, the following configuration will ensure all `ServerHttpSecurity` instances are configured to:
|
||||
|
||||
include-code::./ServerHttpSecurityCustomizerBeanConfiguration[tag=httpSecurityCustomizer,indent=0]
|
||||
|
||||
<1> Set the xref:servlet/exploits/headers.adoc#servlet-headers-csp[Content Security Policy] to `object-src 'none'`
|
||||
<2> xref:servlet/exploits/http.adoc#servlet-http-redirect[Redirect any request to https]
|
||||
|
||||
|
||||
[[top-level-customizer-bean]]
|
||||
=== Top Level ServerHttpSecurity Customizer Beans
|
||||
|
||||
If you prefer to have further modularization of your security configuration, Spring Security will automatically apply any top level `HttpSecurity` `Customizer` Beans.
|
||||
|
||||
A top level `HttpSecurity` `Customizer` type can be summarized as any `Customizer<T>` that matches `public HttpSecurity.*(Customizer<T>)`.
|
||||
This translates to any `Customizer<T>` that is a single argument to a public method on javadoc:org.springframework.security.config.annotation.web.builders.HttpSecurity[].
|
||||
|
||||
A few examples can help to clarify.
|
||||
If `Customizer<ContentTypeOptionsConfig>` is published as a Bean, it will not be be automatically applied because it is an argument to javadoc:org.springframework.security.config.annotation.web.configurers.HeadersConfigurer#contentTypeOptions(org.springframework.security.config.Customizer)[] which is not a method defined on `HttpSecurity`.
|
||||
However, if `Customizer<HeadersConfigurer<HttpSecurity>>` is published as a Bean, it will be automatically applied because it is an argument to javadoc:org.springframework.security.config.annotation.web.builders.HttpSecurity#headers(org.springframework.security.config.Customizer)[].
|
||||
|
||||
For example, the following configuration will ensure that the xref:servlet/exploits/headers.adoc#servlet-headers-csp[Content Security Policy] is set to `object-src 'none'`:
|
||||
|
||||
include-code::./TopLevelCustomizerBeanConfiguration[tag=headersCustomizer,indent=0]
|
||||
|
||||
[[customizer-bean-ordering]]
|
||||
=== Customizer Bean Ordering
|
||||
|
||||
First each xref:#httpsecurity-customizer-bean[Customizer<HttpSecurity> Bean] is applied using https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/ObjectProvider.html#orderedStream()[ObjectProvider#orderedStream()].
|
||||
This means that if there are multiple `Customizer<HttpSecurity>` Beans, the https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/annotation/Order.html[@Order] annotation can be added to the Bean definitions to control the ordering.
|
||||
|
||||
Next every xref:#top-level-customizer-bean[Top Level HttpSecurity Customizer Beans] type is looked up and each is is applied using `ObjectProvider#orderedStream()`.
|
||||
If there is are two `Customizer<HeadersConfigurer<HttpSecurity>>` beans and two `Customizer<HttpsRedirectConfigurer<HttpSecurity>>` instances, the order that each `Customizer` type is invoked is undefined.
|
||||
However, the order that each instance of `Customizer<HttpsRedirectConfigurer<HttpSecurity>>` is defined by `ObjectProvider#orderedStream()` and can be controlled using `@Order` on the Bean the definitions.
|
||||
|
||||
Finally, the `HttpSecurity` Bean is injected as a Bean.
|
||||
All `Customizer` instances are applied before the `HttpSecurity` Bean is created.
|
||||
This allows overriding the customizations provided by the `Customizer` Beans.
|
||||
|
||||
You can find an example below that illustrates the ordering:
|
||||
|
||||
include-code::./CustomizerBeanOrderingConfiguration[tag=sample,indent=0]
|
||||
|
||||
<1> First all `Customizer<HttpSecurity>` instances are applied.
|
||||
The `adminAuthorization` Bean has the highest `@Order` so it is applied first.
|
||||
If there are no `@Order` annotations on the `Customizer<HttpSecurity>` Beans or the `@Order` annotations had the same value, then the order that the `Customizer<HttpSecurity>` instances are applied is undefined.
|
||||
<2> The `userAuthorization` is applied next due to being an instance of `Customizer<HttpSecurity>`
|
||||
<3> The order that the `Customizer` types are undefined.
|
||||
In this example, the order of `contentSecurityPolicy`, `contentTypeOptions`, and `httpsRedirect` are undefined.
|
||||
If `@Order(Ordered.HIGHEST_PRECEDENCE)` was added to `contentTypeOptions`, then we would know that `contentTypeOptions` is before `contentSecurityPolicy` (they are the same type), but we do not know if `httpsRedirect` is before or after the `Customizer<HeadersConfigurer<HttpSecurity>>` Beans.
|
||||
<4> After all of the `Customizer` Beans are applied, the `HttpSecurity` is passed in as a Bean.
|
||||
|
@ -546,6 +546,14 @@ open class BankService {
|
||||
The result is that the above method will only return the `Account` if its `owner` attribute matches the logged-in user's `name`.
|
||||
If not, Spring Security will throw an `AccessDeniedException` and return a 403 status code.
|
||||
|
||||
[NOTE]
|
||||
=====
|
||||
Note that `@PostAuthorize` is not recommended for classes that perform database writes since that typically means that a database change was made before the security invariants were checked.
|
||||
A common example of doing this is if you have `@Transactional` and `@PostAuthorize` on the same method.
|
||||
Instead, read the value first, using `@PostAuthorize` on the read, and then perform the database write, should that read is authorized.
|
||||
If you must do something like this, you can <<changing-the-order, ensure that `@EnableTransactionManagement` comes before `@EnableMethodSecurity`>>.
|
||||
=====
|
||||
|
||||
[[use-prefilter]]
|
||||
=== Filtering Method Parameters with `@PreFilter`
|
||||
|
||||
@ -1797,39 +1805,7 @@ As already noted, there is a Spring AOP method interceptor for each annotation,
|
||||
|
||||
Namely, the `@PreFilter` method interceptor's order is 100, ``@PreAuthorize``'s is 200, and so on.
|
||||
|
||||
The reason this is important to note is that there are other AOP-based annotations like `@EnableTransactionManagement` that have an order of `Integer.MAX_VALUE`.
|
||||
In other words, they are located at the end of the advisor chain by default.
|
||||
|
||||
At times, it can be valuable to have other advice execute before Spring Security.
|
||||
For example, if you have a method annotated with `@Transactional` and `@PostAuthorize`, you might want the transaction to still be open when `@PostAuthorize` runs so that an `AccessDeniedException` will cause a rollback.
|
||||
|
||||
To get `@EnableTransactionManagement` to open a transaction before method authorization advice runs, you can set ``@EnableTransactionManagement``'s order like so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@EnableTransactionManagement(order = 0)
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@EnableTransactionManagement(order = 0)
|
||||
----
|
||||
|
||||
Xml::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<tx:annotation-driven ref="txManager" order="0"/>
|
||||
----
|
||||
======
|
||||
|
||||
Since the earliest method interceptor (`@PreFilter`) is set to an order of 100, a setting of zero means that the transaction advice will run before all Spring Security advice.
|
||||
You can use the `offset` parameter on `@EnableMethodSecurity` to move all interceptors en masse to provide their advice earlier or later in a method invocation.
|
||||
|
||||
[[authorization-expressions]]
|
||||
== Expressing Authorization with SpEL
|
||||
|
@ -664,6 +664,75 @@ class Config {
|
||||
----
|
||||
======
|
||||
|
||||
[[modular-httpsecurity-configuration]]
|
||||
== Modular HttpSecurity Configuration
|
||||
|
||||
Many users prefer that their Spring Security configuration lives in a centralized place and will choose to configure it in a single `SecurityFilterChain` instance.
|
||||
However, there are times that users may want to modularize the configuration.
|
||||
This can be done using:
|
||||
|
||||
* xref:#httpsecurity-customizer-bean[Customizer<HttpSecurity> Beans]
|
||||
* xref:#top-level-customizer-bean[Top Level HttpSecurity Customizer Beans]
|
||||
|
||||
NOTE: If you are using Spring Security's xref:servlet/configuration/kotlin.adoc[], then you can also expose `*Dsl -> Unit` Beans as outlined in xref:./kotlin.adoc#modular-httpsecuritydsl-configuration[Modular HttpSecurityDsl Configuration].
|
||||
|
||||
|
||||
[[httpsecurity-customizer-bean]]
|
||||
=== Customizer<HttpSecurity> Beans
|
||||
|
||||
If you would like to modularize your security configuration you can place logic in a `Customizer<HttpSecurity>` Bean.
|
||||
For example, the following configuration will ensure all `HttpSecurity` instances are configured to:
|
||||
|
||||
include-code::./HttpSecurityCustomizerBeanConfiguration[tag=httpSecurityCustomizer,indent=0]
|
||||
|
||||
<1> Set the xref:servlet/exploits/headers.adoc#servlet-headers-csp[Content Security Policy] to `object-src 'none'`
|
||||
<2> xref:servlet/exploits/http.adoc#servlet-http-redirect[Redirect any request to https]
|
||||
|
||||
|
||||
[[top-level-customizer-bean]]
|
||||
=== Top Level HttpSecurity Customizer Beans
|
||||
|
||||
If you prefer to have further modularization of your security configuration, Spring Security will automatically apply any top level `HttpSecurity` `Customizer` Beans.
|
||||
|
||||
A top level `HttpSecurity` `Customizer` type can be summarized as any `Customizer<T>` that matches `public HttpSecurity.*(Customizer<T>)`.
|
||||
This translates to any `Customizer<T>` that is a single argument to a public method on javadoc:org.springframework.security.config.annotation.web.builders.HttpSecurity[].
|
||||
|
||||
A few examples can help to clarify.
|
||||
If `Customizer<ContentTypeOptionsConfig>` is published as a Bean, it will not be be automatically applied because it is an argument to javadoc:org.springframework.security.config.annotation.web.configurers.HeadersConfigurer#contentTypeOptions(org.springframework.security.config.Customizer)[] which is not a method defined on `HttpSecurity`.
|
||||
However, if `Customizer<HeadersConfigurer<HttpSecurity>>` is published as a Bean, it will be automatically applied because it is an argument to javadoc:org.springframework.security.config.annotation.web.builders.HttpSecurity#headers(org.springframework.security.config.Customizer)[].
|
||||
|
||||
For example, the following configuration will ensure that the xref:servlet/exploits/headers.adoc#servlet-headers-csp[Content Security Policy] is set to `object-src 'none'`:
|
||||
|
||||
include-code::./TopLevelCustomizerBeanConfiguration[tag=headersCustomizer,indent=0]
|
||||
|
||||
[[customizer-bean-ordering]]
|
||||
=== Customizer Bean Ordering
|
||||
|
||||
First each xref:#httpsecurity-customizer-bean[Customizer<HttpSecurity> Bean] is applied using https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/ObjectProvider.html#orderedStream()[ObjectProvider#orderedStream()].
|
||||
This means that if there are multiple `Customizer<HttpSecurity>` Beans, the https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/annotation/Order.html[@Order] annotation can be added to the Bean definitions to control the ordering.
|
||||
|
||||
Next every xref:#top-level-customizer-bean[Top Level HttpSecurity Customizer Beans] type is looked up and each is is applied using `ObjectProvider#orderedStream()`.
|
||||
If there is are two `Customizer<HeadersConfigurer<HttpSecurity>>` beans and two `Customizer<HttpsRedirectConfigurer<HttpSecurity>>` instances, the order that each `Customizer` type is invoked is undefined.
|
||||
However, the order that each instance of `Customizer<HttpsRedirectConfigurer<HttpSecurity>>` is defined by `ObjectProvider#orderedStream()` and can be controlled using `@Order` on the Bean the definitions.
|
||||
|
||||
Finally, the `HttpSecurity` Bean is injected as a Bean.
|
||||
All `Customizer` instances are applied before the `HttpSecurity` Bean is created.
|
||||
This allows overriding the customizations provided by the `Customizer` Beans.
|
||||
|
||||
You can find an example below that illustrates the ordering:
|
||||
|
||||
include-code::./CustomizerBeanOrderingConfiguration[tag=sample,indent=0]
|
||||
|
||||
<1> First all `Customizer<HttpSecurity>` instances are applied.
|
||||
The `adminAuthorization` Bean has the highest `@Order` so it is applied first.
|
||||
If there are no `@Order` annotations on the `Customizer<HttpSecurity>` Beans or the `@Order` annotations had the same value, then the order that the `Customizer<HttpSecurity>` instances are applied is undefined.
|
||||
<2> The `userAuthorization` is applied next due to being an instance of `Customizer<HttpSecurity>`
|
||||
<3> The order that the `Customizer` types are undefined.
|
||||
In this example, the order of `contentSecurityPolicy`, `contentTypeOptions`, and `httpsRedirect` are undefined.
|
||||
If `@Order(Ordered.HIGHEST_PRECEDENCE)` was added to `contentTypeOptions`, then we would know that `contentTypeOptions` is before `contentSecurityPolicy` (they are the same type), but we do not know if `httpsRedirect` is before or after the `Customizer<HeadersConfigurer<HttpSecurity>>` Beans.
|
||||
<4> After all of the `Customizer` Beans are applied, the `HttpSecurity` is passed in as a Bean.
|
||||
|
||||
|
||||
[[post-processing-configured-objects]]
|
||||
== Post Processing Configured Objects
|
||||
|
||||
|
@ -346,3 +346,76 @@ class BankingSecurityConfig {
|
||||
This configuration will handle requests not covered by the other filter chains and will be processed last (no `@Order` defaults to last).
|
||||
Requests that match `/`, `/user-login`, `/user-logout`, `/notices`, `/contact` and `/register` allow access without authentication.
|
||||
Any other requests require the user to be authenticated to access any URL not explicitly allowed or protected by other filter chains.
|
||||
|
||||
|
||||
[[modular-httpsecuritydsl-configuration]]
|
||||
== Modular HttpSecurityDsl Configuration
|
||||
|
||||
Many users prefer that their Spring Security configuration lives in a centralized place and will choose to configure it in a single `SecurityFilterChain` instance.
|
||||
However, there are times that users may want to modularize the configuration.
|
||||
This can be done using:
|
||||
|
||||
* xref:#httpsecuritydsl-bean[HttpSecurityDsl.() -> Unit Beans]
|
||||
* xref:#top-level-dsl-bean[Top Level Security Dsl Beans]
|
||||
|
||||
NOTE: Since the Spring Security Kotlin Dsl (`HttpSecurityDsl`) uses `HttpSecurity`, all of the Java xref:./kotlin.adoc#modular-bean-configuration[Modular Bean Customization] is applied before xref:#modular-httpsecuritydsl-configuration[Modular HttpSecurity Configuration].
|
||||
|
||||
[[httpsecuritydsl-bean]]
|
||||
=== HttpSecurityDsl.() -> Unit Beans
|
||||
|
||||
If you would like to modularize your security configuration you can place logic in a `HttpSecurityDsl.() -> Unit` Bean.
|
||||
For example, the following configuration will ensure all `HttpSecurityDsl` instances are configured to:
|
||||
|
||||
include-code::./HttpSecurityDslBeanConfiguration[tag=httpSecurityDslBean,indent=0]
|
||||
|
||||
<1> Set the xref:servlet/exploits/headers.adoc#servlet-headers-csp[Content Security Policy] to `object-src 'none'`
|
||||
<2> xref:servlet/exploits/http.adoc#servlet-http-redirect[Redirect any request to https]
|
||||
|
||||
|
||||
[[top-level-dsl-bean]]
|
||||
=== Top Level Security Dsl Beans
|
||||
|
||||
If you prefer to have further modularization of your security configuration, Spring Security will automatically apply any top level Security Dsl Beans.
|
||||
|
||||
A top level Security Dsl can be summarized as any class Dsl class that matches `public HttpSecurityDsl.*(<Dsl>)`.
|
||||
This translates to any Security Dsl that is a single argument to a public method on `HttpSecurityDsl`.
|
||||
|
||||
A few examples can help to clarify.
|
||||
If `ContentTypeOptionsDsl.() -> Unit` is published as a Bean, it will not be be automatically applied because it is an argument to `HeadersDsl#contentTypeOptions(ContentTypeOptionsDsl.() -> Unit)` and is not an argument to a method defined on `HttpSecurityDsl`.
|
||||
However, if `HeadersDsl.() -> Unit` is published as a Bean, it will be automatically applied because it is an argument to `HttpSecurityDsl.headers(HeadersDsl.() -> Unit)`.
|
||||
|
||||
For example, the following configuration ensure all `HttpSecurityDsl` instances are configured to:
|
||||
|
||||
include-code::./TopLevelDslBeanConfiguration[tag=headersSecurity,indent=0]
|
||||
|
||||
<1> Set the xref:servlet/exploits/headers.adoc#servlet-headers-csp[Content Security Policy] to `object-src 'none'`
|
||||
|
||||
[[dsl-bean-ordering]]
|
||||
=== Dsl Bean Ordering
|
||||
|
||||
First, all xref:servlet/configuration/java.adoc#modular-httpsecurity-configuration[Modular HttpSecurity Configuration] is applied since the Kotlin Dsl uses an `HttpSecurity` Bean.
|
||||
|
||||
Second, each xref:#httpsecuritydsl-bean[HttpSecurityDsl.() -> Unit Beans] is applied using https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/ObjectProvider.html#orderedStream()[ObjectProvider#orderedStream()].
|
||||
This means that if there are multiple `HttpSecurity.() -> Unit` Beans, the https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/annotation/Order.html[@Order] annotation can be added to the Bean definitions to control the ordering.
|
||||
|
||||
Next, every xref:#top-level-dsl-bean[Top Level Security Dsl Beans] type is looked up and each is is applied using `ObjectProvider#orderedStream()`.
|
||||
If there is are differt types of top level security Beans (.e.g. `HeadersDsl.() -> Unit` and `HttpsRedirectDsl.() -> Unit`), then the order that each Dsl type is invoked is undefined.
|
||||
However, the order that each instance of of the same top level security Bean type is defined by `ObjectProvider#orderedStream()` and can be controlled using `@Order` on the Bean the definitions.
|
||||
|
||||
Finally, the `HttpSecurityDsl` Bean is injected as a Bean.
|
||||
All `*Dsl.() -> Unit` Beans are applied before the `HttpSecurityDsl` Bean is created.
|
||||
This allows overriding the customizations provided by the `*Dsl.() -> Unit` Beans.
|
||||
|
||||
You can find an example below that illustrates the ordering:
|
||||
|
||||
include-code::./DslBeanOrderingConfiguration[tag=sample,indent=0]
|
||||
|
||||
<1> All xref:servlet/configuration/java.adoc#modular-httpsecurity-configuration[Modular HttpSecurity Configuration] is applied since the Kotlin Dsl uses an `HttpSecurity` Bean.
|
||||
<2> All `HttpSecurity.() -> Unit` instances are applied.
|
||||
The `adminAuthorization` Bean has the highest `@Order` so it is applied first.
|
||||
If there are no `@Order` annotations on the `HttpSecurity.() -> Unit` Beans or the `@Order` annotations had the same value, then the order that the `HttpSecurity.() -> Unit` instances are applied is undefined.
|
||||
<3> The `userAuthorization` is applied next due to being an instance of `HttpSecurity.() -> Unit`
|
||||
<4> The order that the `*Dsl.() -> Unit` types are undefined.
|
||||
In this example, the order of `contentSecurityPolicy`, `contentTypeOptions`, and `httpsRedirect` are undefined.
|
||||
If `@Order(Ordered.HIGHEST_PRECEDENCE)` was added to `contentTypeOptions`, then we would know that `contentTypeOptions` is before `contentSecurityPolicy` (they are the same type), but we do not know if `httpsRedirect` is before or after the `HeadersDsl.() -> Unit` Beans.
|
||||
<5> After all of the `*Dsl.() -> Unit` Beans are applied, the `HttpSecurityDsl` is passed in as a Bean.
|
||||
|
@ -4,36 +4,7 @@
|
||||
This section demonstrates how to use Spring Security's Test support to test method-based security.
|
||||
We first introduce a `MessageService` that requires the user to be authenticated to be able to access it:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
public class HelloMessageService implements MessageService {
|
||||
|
||||
@PreAuthorize("authenticated")
|
||||
public String getMessage() {
|
||||
Authentication authentication = SecurityContextHolder.getContext()
|
||||
.getAuthentication();
|
||||
return "Hello " + authentication;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
class HelloMessageService : MessageService {
|
||||
@PreAuthorize("authenticated")
|
||||
fun getMessage(): String {
|
||||
val authentication: Authentication = SecurityContextHolder.getContext().authentication
|
||||
return "Hello $authentication"
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
include-code::./HelloMessageService[tag=authenticated,indent=0]
|
||||
|
||||
The result of `getMessage` is a `String` that says "`Hello`" to the current Spring Security `Authentication`.
|
||||
The following listing shows example output:
|
||||
@ -48,30 +19,8 @@ Hello org.springframework.security.authentication.UsernamePasswordAuthentication
|
||||
|
||||
Before we can use the Spring Security test support, we must perform some setup:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@ExtendWith(SpringExtension.class) // <1>
|
||||
@ContextConfiguration // <2>
|
||||
public class WithMockUserTests {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
include-code::./WithMockUserTests[tag=setup,indent=0]
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
class WithMockUserTests {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> `@ExtendWith` instructs the spring-test module that it should create an `ApplicationContext`. For additional information, refer to the {spring-framework-reference-url}testing.html#testcontext-junit-jupiter-extension[Spring reference].
|
||||
<2> `@ContextConfiguration` instructs the spring-test the configuration to use to create the `ApplicationContext`. Since no configuration is specified, the default configuration locations will be tried. This is no different than using the existing Spring Test support. For additional information, refer to the {spring-framework-reference-url}testing.html#spring-testing-annotation-contextconfiguration[Spring Reference].
|
||||
|
||||
@ -87,28 +36,7 @@ If you need only Spring Security related support, you can replace `@ContextConfi
|
||||
Remember, we added the `@PreAuthorize` annotation to our `HelloMessageService`, so it requires an authenticated user to invoke it.
|
||||
If we run the tests, we expect the following test will pass:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Test(expected = AuthenticationCredentialsNotFoundException.class)
|
||||
public void getMessageUnauthenticated() {
|
||||
messageService.getMessage();
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Test(expected = AuthenticationCredentialsNotFoundException::class)
|
||||
fun getMessageUnauthenticated() {
|
||||
messageService.getMessage()
|
||||
}
|
||||
----
|
||||
======
|
||||
include-code::./WithMockUserSampleTests[tag=snippet,indent=0]
|
||||
|
||||
[[test-method-withmockuser]]
|
||||
== @WithMockUser
|
||||
@ -117,32 +45,7 @@ The question is "How could we most easily run the test as a specific user?"
|
||||
The answer is to use `@WithMockUser`.
|
||||
The following test will be run as a user with the username "user", the password "password", and the roles "ROLE_USER".
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Test
|
||||
@WithMockUser
|
||||
public void getMessageWithMockUser() {
|
||||
String message = messageService.getMessage();
|
||||
...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Test
|
||||
@WithMockUser
|
||||
fun getMessageWithMockUser() {
|
||||
val message: String = messageService.getMessage()
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
include-code::./WithMockUserTests[tag=mock-user,indent=0]
|
||||
|
||||
Specifically the following is true:
|
||||
|
||||
@ -157,168 +60,28 @@ The preceding example is handy, because it lets us use a lot of defaults.
|
||||
What if we wanted to run the test with a different username?
|
||||
The following test would run with a username of `customUser` (again, the user does not need to actually exist):
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Test
|
||||
@WithMockUser("customUsername")
|
||||
public void getMessageWithMockUserCustomUsername() {
|
||||
String message = messageService.getMessage();
|
||||
...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Test
|
||||
@WithMockUser("customUsername")
|
||||
fun getMessageWithMockUserCustomUsername() {
|
||||
val message: String = messageService.getMessage()
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
include-code::./WithMockUserTests[tag=custom-user,indent=0]
|
||||
|
||||
We can also easily customize the roles.
|
||||
For example, the following test is invoked with a username of `admin` and roles of `ROLE_USER` and `ROLE_ADMIN`.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Test
|
||||
@WithMockUser(username="admin",roles={"USER","ADMIN"})
|
||||
public void getMessageWithMockUserCustomUser() {
|
||||
String message = messageService.getMessage();
|
||||
...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Test
|
||||
@WithMockUser(username="admin",roles=["USER","ADMIN"])
|
||||
fun getMessageWithMockUserCustomUser() {
|
||||
val message: String = messageService.getMessage()
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
include-code::./WithMockUserTests[tag=custom-roles,indent=0]
|
||||
|
||||
If we do not want the value to automatically be prefixed with `ROLE_` we can use the `authorities` attribute.
|
||||
For example, the following test is invoked with a username of `admin` and the `USER` and `ADMIN` authorities.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Test
|
||||
@WithMockUser(username = "admin", authorities = { "ADMIN", "USER" })
|
||||
public void getMessageWithMockUserCustomAuthorities() {
|
||||
String message = messageService.getMessage();
|
||||
...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Test
|
||||
@WithMockUser(username = "admin", authorities = ["ADMIN", "USER"])
|
||||
fun getMessageWithMockUserCustomUsername() {
|
||||
val message: String = messageService.getMessage()
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
include-code::./WithMockUserTests[tag=custom-authorities,indent=0]
|
||||
|
||||
It can be a bit tedious to place the annotation on every test method.
|
||||
Instead, we can place the annotation at the class level. Then every test uses the specified user.
|
||||
The following example runs every test with a user whose username is `admin`, whose password is `password`, and who has the `ROLE_USER` and `ROLE_ADMIN` roles:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
@WithMockUser(username="admin",roles={"USER","ADMIN"})
|
||||
public class WithMockUserTests {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
@WithMockUser(username="admin",roles=["USER","ADMIN"])
|
||||
class WithMockUserTests {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
include-code::./WithMockUserClassTests[tag=snippet,indent=0]
|
||||
|
||||
If you use JUnit 5's `@Nested` test support, you can also place the annotation on the enclosing class to apply to all nested classes.
|
||||
The following example runs every test with a user whose username is `admin`, whose password is `password`, and who has the `ROLE_USER` and `ROLE_ADMIN` roles for both test methods.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
@WithMockUser(username="admin",roles={"USER","ADMIN"})
|
||||
public class WithMockUserTests {
|
||||
|
||||
@Nested
|
||||
public class TestSuite1 {
|
||||
// ... all test methods use admin user
|
||||
}
|
||||
|
||||
@Nested
|
||||
public class TestSuite2 {
|
||||
// ... all test methods use admin user
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@ExtendWith(SpringExtension::class)
|
||||
@ContextConfiguration
|
||||
@WithMockUser(username = "admin", roles = ["USER", "ADMIN"])
|
||||
class WithMockUserTests {
|
||||
@Nested
|
||||
inner class TestSuite1 { // ... all test methods use admin user
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class TestSuite2 { // ... all test methods use admin user
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
include-code::./WithMockUserNestedTests[tag=snippet,indent=0]
|
||||
|
||||
By default, the `SecurityContext` is set during the `TestExecutionListener.beforeTestMethod` event.
|
||||
This is the equivalent of happening before JUnit's `@Before`.
|
||||
@ -337,55 +100,7 @@ Using `@WithAnonymousUser` allows running as an anonymous user.
|
||||
This is especially convenient when you wish to run most of your tests with a specific user but want to run a few tests as an anonymous user.
|
||||
The following example runs `withMockUser1` and `withMockUser2` by using <<test-method-withmockuser,@WithMockUser>> and `anonymous` as an anonymous user:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@WithMockUser
|
||||
public class WithUserClassLevelAuthenticationTests {
|
||||
|
||||
@Test
|
||||
public void withMockUser1() {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withMockUser2() {
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithAnonymousUser
|
||||
public void anonymous() throws Exception {
|
||||
// override default to run as anonymous user
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@WithMockUser
|
||||
class WithUserClassLevelAuthenticationTests {
|
||||
@Test
|
||||
fun withMockUser1() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun withMockUser2() {
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithAnonymousUser
|
||||
fun anonymous() {
|
||||
// override default to run as anonymous user
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
include-code::./WithUserClassLevelAuthenticationTests[tag=snippet,indent=0]
|
||||
|
||||
By default, the `SecurityContext` is set during the `TestExecutionListener.beforeTestMethod` event.
|
||||
This is the equivalent of happening before JUnit's `@Before`.
|
||||
@ -410,92 +125,17 @@ That is exactly what `@WithUserDetails` does.
|
||||
|
||||
Assuming we have a `UserDetailsService` exposed as a bean, the following test is invoked with an `Authentication` of type `UsernamePasswordAuthenticationToken` and a principal that is returned from the `UserDetailsService` with the username of `user`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Test
|
||||
@WithUserDetails
|
||||
public void getMessageWithUserDetails() {
|
||||
String message = messageService.getMessage();
|
||||
...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Test
|
||||
@WithUserDetails
|
||||
fun getMessageWithUserDetails() {
|
||||
val message: String = messageService.getMessage()
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
include-code::./WithUserDetailsTests[tag=user-details,indent=0]
|
||||
|
||||
We can also customize the username used to lookup the user from our `UserDetailsService`.
|
||||
For example, this test can be run with a principal that is returned from the `UserDetailsService` with the username of `customUsername`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Test
|
||||
@WithUserDetails("customUsername")
|
||||
public void getMessageWithUserDetailsCustomUsername() {
|
||||
String message = messageService.getMessage();
|
||||
...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Test
|
||||
@WithUserDetails("customUsername")
|
||||
fun getMessageWithUserDetailsCustomUsername() {
|
||||
val message: String = messageService.getMessage()
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
include-code::./WithUserDetailsTests[tag=user-details-custom-username,indent=0]
|
||||
|
||||
We can also provide an explicit bean name to look up the `UserDetailsService`.
|
||||
The following test looks up the username of `customUsername` by using the `UserDetailsService` with a bean name of `myUserDetailsService`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Test
|
||||
@WithUserDetails(value="customUsername", userDetailsServiceBeanName="myUserDetailsService")
|
||||
public void getMessageWithUserDetailsServiceBeanName() {
|
||||
String message = messageService.getMessage();
|
||||
...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Test
|
||||
@WithUserDetails(value="customUsername", userDetailsServiceBeanName="myUserDetailsService")
|
||||
fun getMessageWithUserDetailsServiceBeanName() {
|
||||
val message: String = messageService.getMessage()
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
include-code::./WithCustomUserDetailsTests[tag=custom-user-details-service,indent=0]
|
||||
|
||||
As we did with `@WithMockUser`, we can also place our annotation at the class level so that every test uses the same user.
|
||||
However, unlike `@WithMockUser`, `@WithUserDetails` requires the user to exist.
|
||||
@ -519,128 +159,21 @@ We now see an option that allows the most flexibility.
|
||||
We can create our own annotation that uses the `@WithSecurityContext` to create any `SecurityContext` we want.
|
||||
For example, we might create an annotation named `@WithMockCustomUser`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory.class)
|
||||
public @interface WithMockCustomUser {
|
||||
|
||||
String username() default "rob";
|
||||
|
||||
String name() default "Rob Winch";
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory::class)
|
||||
annotation class WithMockCustomUser(val username: String = "rob", val name: String = "Rob Winch")
|
||||
----
|
||||
======
|
||||
include-code::./WithMockCustomUser[tag=snippet,indent=0]
|
||||
|
||||
You can see that `@WithMockCustomUser` is annotated with the `@WithSecurityContext` annotation.
|
||||
This is what signals to Spring Security test support that we intend to create a `SecurityContext` for the test.
|
||||
The `@WithSecurityContext` annotation requires that we specify a `SecurityContextFactory` to create a new `SecurityContext`, given our `@WithMockCustomUser` annotation.
|
||||
The following listing shows our `WithMockCustomUserSecurityContextFactory` implementation:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
public class WithMockCustomUserSecurityContextFactory
|
||||
implements WithSecurityContextFactory<WithMockCustomUser> {
|
||||
@Override
|
||||
public SecurityContext createSecurityContext(WithMockCustomUser customUser) {
|
||||
SecurityContext context = SecurityContextHolder.createEmptyContext();
|
||||
|
||||
CustomUserDetails principal =
|
||||
new CustomUserDetails(customUser.name(), customUser.username());
|
||||
Authentication auth =
|
||||
UsernamePasswordAuthenticationToken.authenticated(principal, "password", principal.getAuthorities());
|
||||
context.setAuthentication(auth);
|
||||
return context;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
class WithMockCustomUserSecurityContextFactory : WithSecurityContextFactory<WithMockCustomUser> {
|
||||
override fun createSecurityContext(customUser: WithMockCustomUser): SecurityContext {
|
||||
val context = SecurityContextHolder.createEmptyContext()
|
||||
val principal = CustomUserDetails(customUser.name, customUser.username)
|
||||
val auth: Authentication =
|
||||
UsernamePasswordAuthenticationToken(principal, "password", principal.authorities)
|
||||
context.authentication = auth
|
||||
return context
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
include-code::./WithMockCustomUserSecurityContextFactory[tag=snippet,indent=0]
|
||||
|
||||
We can now annotate a test class or a test method with our new annotation and Spring Security's `WithSecurityContextTestExecutionListener` to ensure that our `SecurityContext` is populated appropriately.
|
||||
|
||||
When creating your own `WithSecurityContextFactory` implementations, it is nice to know that they can be annotated with standard Spring annotations.
|
||||
For example, the `WithUserDetailsSecurityContextFactory` uses the `@Autowired` annotation to acquire the `UserDetailsService`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
final class WithUserDetailsSecurityContextFactory
|
||||
implements WithSecurityContextFactory<WithUserDetails> {
|
||||
|
||||
private UserDetailsService userDetailsService;
|
||||
|
||||
@Autowired
|
||||
public WithUserDetailsSecurityContextFactory(UserDetailsService userDetailsService) {
|
||||
this.userDetailsService = userDetailsService;
|
||||
}
|
||||
|
||||
public SecurityContext createSecurityContext(WithUserDetails withUser) {
|
||||
String username = withUser.value();
|
||||
Assert.hasLength(username, "value() must be non-empty String");
|
||||
UserDetails principal = userDetailsService.loadUserByUsername(username);
|
||||
Authentication authentication = UsernamePasswordAuthenticationToken.authenticated(principal, principal.getPassword(), principal.getAuthorities());
|
||||
SecurityContext context = SecurityContextHolder.createEmptyContext();
|
||||
context.setAuthentication(authentication);
|
||||
return context;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
class WithUserDetailsSecurityContextFactory @Autowired constructor(private val userDetailsService: UserDetailsService) :
|
||||
WithSecurityContextFactory<WithUserDetails> {
|
||||
override fun createSecurityContext(withUser: WithUserDetails): SecurityContext {
|
||||
val username: String = withUser.value
|
||||
Assert.hasLength(username, "value() must be non-empty String")
|
||||
val principal = userDetailsService.loadUserByUsername(username)
|
||||
val authentication: Authentication =
|
||||
UsernamePasswordAuthenticationToken(principal, principal.password, principal.authorities)
|
||||
val context = SecurityContextHolder.createEmptyContext()
|
||||
context.authentication = authentication
|
||||
return context
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
include-code::./WithUserDetailsSecurityContextFactory[tag=snippet,indent=0]
|
||||
|
||||
By default, the `SecurityContext` is set during the `TestExecutionListener.beforeTestMethod` event.
|
||||
This is the equivalent of happening before JUnit's `@Before`.
|
||||
@ -658,46 +191,12 @@ You can change this to happen during the `TestExecutionListener.beforeTestExecut
|
||||
If you reuse the same user within your tests often, it is not ideal to have to repeatedly specify the attributes.
|
||||
For example, if you have many tests related to an administrative user with a username of `admin` and roles of `ROLE_USER` and `ROLE_ADMIN`, you have to write:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@WithMockUser(username="admin",roles={"USER","ADMIN"})
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@WithMockUser(username="admin",roles=["USER","ADMIN"])
|
||||
----
|
||||
======
|
||||
include-code::./WithMockUserTests[tag=snippet,indent=0]
|
||||
|
||||
Rather than repeating this everywhere, we can use a meta annotation.
|
||||
For example, we could create a meta annotation named `WithMockAdmin`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@WithMockUser(value="rob",roles="ADMIN")
|
||||
public @interface WithMockAdmin { }
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@WithMockUser(value = "rob", roles = ["ADMIN"])
|
||||
annotation class WithMockAdmin
|
||||
----
|
||||
======
|
||||
include-code::./WithMockAdmin[tag=snippet,indent=0]
|
||||
|
||||
Now we can use `@WithMockAdmin` in the same way as the more verbose `@WithMockUser`.
|
||||
|
||||
|
@ -15,6 +15,7 @@ Each section that follows will indicate the more notable removals as well as the
|
||||
|
||||
== Config
|
||||
|
||||
* Support modular configuration in xref::servlet/configuration/java.adoc#modular-httpsecurity-configuration[Servlets] and xref::reactive/configuration/webflux.adoc#modular-serverhttpsecurity-configuration[WebFlux]
|
||||
* Removed `and()` from the `HttpSecurity` DSL in favor of using the lambda methods
|
||||
* Removed `authorizeRequests` in favor of `authorizeHttpRequests`
|
||||
* Simplified expression migration for `authorizeRequests`
|
||||
@ -48,6 +49,7 @@ http.csrf((csrf) -> csrf.spa());
|
||||
* Removed GET request support from `Saml2AuthenticationTokenConverter`
|
||||
* Added JDBC-based `AssertingPartyMetadataRepository`
|
||||
* Made so that SLO still returns `<saml2:LogoutResponse>` even when validation fails
|
||||
* Removed Open SAML 4 support; applications should migrate to Open SAML 5
|
||||
|
||||
== Web
|
||||
|
||||
|
@ -31,7 +31,6 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.security.config.oauth2.client.CommonOAuth2Provider;
|
||||
import org.springframework.security.docs.features.integrations.rest.clientregistrationid.UserService;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
|
||||
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
|
@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.reactive.configuration.customizerbeanordering;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||
import org.springframework.web.reactive.config.EnableWebFlux;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@EnableWebFlux
|
||||
@EnableWebFluxSecurity
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
class CustomizerBeanOrderingConfiguration {
|
||||
|
||||
// tag::sample[]
|
||||
@Bean // <4>
|
||||
SecurityWebFilterChain springSecurity(ServerHttpSecurity http) {
|
||||
// @formatter:off
|
||||
http
|
||||
.authorizeExchange((exchange) -> exchange
|
||||
.anyExchange().authenticated()
|
||||
);
|
||||
return http.build();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(Ordered.LOWEST_PRECEDENCE) // <2>
|
||||
Customizer<ServerHttpSecurity> userAuthorization() {
|
||||
// @formatter:off
|
||||
return (http) -> http
|
||||
.authorizeExchange((exchange) -> exchange
|
||||
.pathMatchers("/users/**").hasRole("USER")
|
||||
);
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE) // <1>
|
||||
Customizer<ServerHttpSecurity> adminAuthorization() {
|
||||
// @formatter:off
|
||||
return (http) -> http
|
||||
.authorizeExchange((exchange) -> exchange
|
||||
.pathMatchers("/admins/**").hasRole("ADMIN")
|
||||
);
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
// <3>
|
||||
|
||||
@Bean
|
||||
Customizer<ServerHttpSecurity.HeaderSpec> contentSecurityPolicy() {
|
||||
// @formatter:off
|
||||
return (headers) -> headers
|
||||
.contentSecurityPolicy((csp) -> csp
|
||||
.policyDirectives("object-src 'none'")
|
||||
);
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Bean
|
||||
Customizer<ServerHttpSecurity.HeaderSpec> contentTypeOptions() {
|
||||
// @formatter:off
|
||||
return (headers) -> headers
|
||||
.contentTypeOptions(Customizer.withDefaults());
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Bean
|
||||
Customizer<ServerHttpSecurity.HttpsRedirectSpec> httpsRedirect() {
|
||||
// @formatter:off
|
||||
return Customizer.withDefaults();
|
||||
// @formatter:on
|
||||
}
|
||||
// end::sample[]
|
||||
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.reactive.configuration.customizerbeanordering;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.config.test.SpringTestContext;
|
||||
import org.springframework.security.config.test.SpringTestContextExtension;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.mockUser;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@ExtendWith(SpringTestContextExtension.class)
|
||||
public class CustomizerBeanOrderingTests {
|
||||
public final SpringTestContext spring = new SpringTestContext(this);
|
||||
|
||||
@Autowired
|
||||
private WebTestClient webTest;
|
||||
|
||||
@Test
|
||||
void authorizationOrdered() throws Exception {
|
||||
this.spring.register(
|
||||
CustomizerBeanOrderingConfiguration.class).autowire();
|
||||
// @formatter:off
|
||||
this.webTest.mutateWith(mockUser("admin").roles("ADMIN"))
|
||||
.get()
|
||||
.uri("https://localhost/admins/1")
|
||||
.exchange()
|
||||
.expectStatus().isOk();
|
||||
this.webTest.mutateWith(mockUser("user").roles("USER"))
|
||||
.get()
|
||||
.uri("https://localhost/admins/1")
|
||||
.exchange()
|
||||
.expectStatus().isForbidden();
|
||||
this.webTest.mutateWith(mockUser("user").roles("USER"))
|
||||
.get()
|
||||
.uri("https://localhost/users/1")
|
||||
.exchange()
|
||||
.expectStatus().isOk();
|
||||
this.webTest.mutateWith(mockUser("user").roles("OTHER"))
|
||||
.get()
|
||||
.uri("https://localhost/users/1")
|
||||
.exchange()
|
||||
.expectStatus().isForbidden();
|
||||
this.webTest.mutateWith(mockUser("authenticated").roles("OTHER"))
|
||||
.get()
|
||||
.uri("https://localhost/other")
|
||||
.exchange()
|
||||
.expectStatus().isOk();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.reactive.configuration.serverhttpsecuritycustomizerbean;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@EnableWebFluxSecurity
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
class ServerHttpSecurityCustomizerBeanConfiguration {
|
||||
|
||||
@Bean
|
||||
SecurityWebFilterChain springSecurity(ServerHttpSecurity http) {
|
||||
// @formatter:off
|
||||
http
|
||||
.authorizeExchange((exchange) -> exchange
|
||||
.anyExchange().authenticated()
|
||||
);
|
||||
return http.build();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
// tag::httpSecurityCustomizer[]
|
||||
@Bean
|
||||
Customizer<ServerHttpSecurity> httpSecurityCustomizer() {
|
||||
// @formatter:off
|
||||
return (http) -> http
|
||||
.headers((headers) -> headers
|
||||
.contentSecurityPolicy((csp) -> csp
|
||||
// <1>
|
||||
.policyDirectives("object-src 'none'")
|
||||
)
|
||||
)
|
||||
// <2>
|
||||
.redirectToHttps(Customizer.withDefaults());
|
||||
// @formatter:on
|
||||
}
|
||||
// end::httpSecurityCustomizer[]
|
||||
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.reactive.configuration.serverhttpsecuritycustomizerbean;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.config.test.SpringTestContext;
|
||||
import org.springframework.security.config.test.SpringTestContextExtension;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@ExtendWith(SpringTestContextExtension.class)
|
||||
public class ServerHttpSecurityCustomizerBeanTests {
|
||||
public final SpringTestContext spring = new SpringTestContext(this);
|
||||
|
||||
@Autowired
|
||||
private WebTestClient webTest;
|
||||
|
||||
@Test
|
||||
void httpSecurityCustomizer() throws Exception {
|
||||
this.spring.register(
|
||||
ServerHttpSecurityCustomizerBeanConfiguration.class).autowire();
|
||||
// @formatter:off
|
||||
this.webTest
|
||||
.get()
|
||||
.uri("http://localhost/")
|
||||
.exchange()
|
||||
.expectHeader().location("https://localhost/")
|
||||
.expectHeader()
|
||||
.value("Content-Security-Policy", csp ->
|
||||
assertThat(csp).isEqualTo("object-src 'none'")
|
||||
);
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.reactive.configuration.toplevelcustomizerbean;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||
import org.springframework.web.reactive.config.EnableWebFlux;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@EnableWebFluxSecurity
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class TopLevelCustomizerBeanConfiguration {
|
||||
|
||||
@Bean
|
||||
SecurityWebFilterChain springSecurity(ServerHttpSecurity http) {
|
||||
// @formatter:off
|
||||
http
|
||||
.authorizeExchange((exchange) -> exchange
|
||||
.anyExchange().authenticated()
|
||||
);
|
||||
return http.build();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
// tag::headersCustomizer[]
|
||||
@Bean
|
||||
Customizer<ServerHttpSecurity.HeaderSpec> headersSecurity() {
|
||||
// @formatter:off
|
||||
return (headers) -> headers
|
||||
.contentSecurityPolicy((csp) -> csp
|
||||
// <1>
|
||||
.policyDirectives("object-src 'none'")
|
||||
);
|
||||
// @formatter:on
|
||||
}
|
||||
// end::headersCustomizer[]
|
||||
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.reactive.configuration.toplevelcustomizerbean;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.config.test.SpringTestContext;
|
||||
import org.springframework.security.config.test.SpringTestContextExtension;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import org.springframework.test.web.servlet.ResultMatcher;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@ExtendWith(SpringTestContextExtension.class)
|
||||
public class TopLevelCustomizerBeanTests {
|
||||
public final SpringTestContext spring = new SpringTestContext(this);
|
||||
|
||||
@Autowired
|
||||
private WebTestClient webTest;
|
||||
|
||||
|
||||
@Test
|
||||
void headersCustomizer() throws Exception {
|
||||
this.spring.register(TopLevelCustomizerBeanConfiguration.class).autowire();
|
||||
// @formatter:off
|
||||
this.webTest
|
||||
.get()
|
||||
.uri("http://localhost/")
|
||||
.exchange()
|
||||
.expectHeader()
|
||||
.value("Content-Security-Policy", csp ->
|
||||
assertThat(csp).isEqualTo("object-src 'none'")
|
||||
);
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
private static @NotNull ResultMatcher cspIsObjectSrcNone() {
|
||||
return header().string("Content-Security-Policy", "object-src 'none'");
|
||||
}
|
||||
}
|
@ -20,8 +20,6 @@ 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.config.annotation.web.configuration.EnableWebSecurity;
|
||||
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.core.userdetails.UserDetailsService;
|
||||
|
@ -16,33 +16,18 @@
|
||||
|
||||
package org.springframework.security.docs.servlet.authentication.servletx509config;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.http.client.reactive.ClientHttpConnector;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.security.config.test.SpringTestContext;
|
||||
import org.springframework.security.config.test.SpringTestContextExtension;
|
||||
import org.springframework.security.web.authentication.preauth.x509.X509TestUtils;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import org.springframework.test.web.reactive.server.WebTestClientConfigurer;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebFilter;
|
||||
import org.springframework.web.server.WebFilterChain;
|
||||
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
|
||||
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.x509;
|
||||
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
|
||||
|
@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.servlet.configuration.customizerbeanordering;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.ThrowingCustomizer;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
|
||||
import org.springframework.security.config.annotation.web.configurers.HttpsRedirectConfigurer;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@EnableWebMvc
|
||||
@EnableWebSecurity
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
class CustomizerBeanOrderingConfiguration {
|
||||
|
||||
// tag::sample[]
|
||||
@Bean // <4>
|
||||
SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.authorizeHttpRequests((requests) -> requests
|
||||
.anyRequest().authenticated()
|
||||
);
|
||||
return http.build();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(Ordered.LOWEST_PRECEDENCE) // <2>
|
||||
ThrowingCustomizer<HttpSecurity> userAuthorization() {
|
||||
// @formatter:off
|
||||
return (http) -> http
|
||||
.authorizeHttpRequests((requests) -> requests
|
||||
.requestMatchers("/users/**").hasRole("USER")
|
||||
);
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE) // <1>
|
||||
ThrowingCustomizer<HttpSecurity> adminAuthorization() {
|
||||
// @formatter:off
|
||||
return (http) -> http
|
||||
.authorizeHttpRequests((requests) -> requests
|
||||
.requestMatchers("/admins/**").hasRole("ADMIN")
|
||||
);
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
// <3>
|
||||
|
||||
@Bean
|
||||
Customizer<HeadersConfigurer<HttpSecurity>> contentSecurityPolicy() {
|
||||
// @formatter:off
|
||||
return (headers) -> headers
|
||||
.contentSecurityPolicy((csp) -> csp
|
||||
.policyDirectives("object-src 'none'")
|
||||
);
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Bean
|
||||
Customizer<HeadersConfigurer<HttpSecurity>> contentTypeOptions() {
|
||||
// @formatter:off
|
||||
return (headers) -> headers
|
||||
.contentTypeOptions(Customizer.withDefaults());
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Bean
|
||||
Customizer<HttpsRedirectConfigurer<HttpSecurity>> httpsRedirect() {
|
||||
// @formatter:off
|
||||
return Customizer.withDefaults();
|
||||
// @formatter:on
|
||||
}
|
||||
// end::sample[]
|
||||
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.servlet.configuration.customizerbeanordering;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.config.test.SpringTestContext;
|
||||
import org.springframework.security.config.test.SpringTestContextExtension;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@ExtendWith(SpringTestContextExtension.class)
|
||||
public class CustomizerBeanOrderingTests {
|
||||
public final SpringTestContext spring = new SpringTestContext(this).mockMvcAfterSpringSecurityOk();
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Test
|
||||
void authorizationOrdered() throws Exception {
|
||||
this.spring.register(
|
||||
CustomizerBeanOrderingConfiguration.class).autowire();
|
||||
// @formatter:off
|
||||
this.mockMvc
|
||||
.perform(get("https://localhost/admins/1").with(user("admin").roles("ADMIN")))
|
||||
.andExpect(status().isOk());
|
||||
this.mockMvc
|
||||
.perform(get("https://localhost/admins/1").with(user("user").roles("USER")))
|
||||
.andExpect(status().isForbidden());
|
||||
this.mockMvc
|
||||
.perform(get("https://localhost/users/1").with(user("user").roles("USER")))
|
||||
.andExpect(status().isOk());
|
||||
this.mockMvc
|
||||
.perform(get("https://localhost/users/1").with(user("user").roles("OTHER")))
|
||||
.andExpect(status().isForbidden());
|
||||
this.mockMvc
|
||||
.perform(get("https://localhost/other").with(user("authenticated").roles("OTHER")))
|
||||
.andExpect(status().isOk());
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.servlet.configuration.httpsecuritycustomizerbean;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.ThrowingCustomizer;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
|
||||
import org.springframework.security.config.test.SpringTestContext;
|
||||
import org.springframework.security.config.test.SpringTestContextExtension;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.ResultMatcher;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@EnableWebSecurity
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
class HttpSecurityCustomizerBeanConfiguration {
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.authorizeHttpRequests((requests) -> requests
|
||||
.anyRequest().authenticated()
|
||||
);
|
||||
return http.build();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
// tag::httpSecurityCustomizer[]
|
||||
@Bean
|
||||
ThrowingCustomizer<HttpSecurity> httpSecurityCustomizer() {
|
||||
// @formatter:off
|
||||
return (http) -> http
|
||||
.headers((headers) -> headers
|
||||
.contentSecurityPolicy((csp) -> csp
|
||||
// <1>
|
||||
.policyDirectives("object-src 'none'")
|
||||
)
|
||||
)
|
||||
// <2>
|
||||
.redirectToHttps(Customizer.withDefaults());
|
||||
// @formatter:on
|
||||
}
|
||||
// end::httpSecurityCustomizer[]
|
||||
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.servlet.configuration.httpsecuritycustomizerbean;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.ThrowingCustomizer;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
|
||||
import org.springframework.security.config.test.SpringTestContext;
|
||||
import org.springframework.security.config.test.SpringTestContextExtension;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.ResultMatcher;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@ExtendWith(SpringTestContextExtension.class)
|
||||
public class HttpSecurityCustomizerBeanTests {
|
||||
public final SpringTestContext spring = new SpringTestContext(this);
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Test
|
||||
void httpSecurityCustomizer() throws Exception {
|
||||
this.spring.register(HttpSecurityCustomizerBeanConfiguration.class).autowire();
|
||||
// @formatter:off
|
||||
this.mockMvc
|
||||
.perform(get("/"))
|
||||
.andExpect(redirectsToHttps());
|
||||
// headers are not sent back as a part of the redirect to https, so a separate request is necessary
|
||||
this.mockMvc.perform(get("https://localhost/"))
|
||||
.andExpect(cspIsObjectSrcNone());
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
private static @NotNull ResultMatcher redirectsToHttps() {
|
||||
return mvcResult -> assertThat(
|
||||
mvcResult.getResponse().getRedirectedUrl()).startsWith("https://");
|
||||
}
|
||||
|
||||
private static @NotNull ResultMatcher cspIsObjectSrcNone() {
|
||||
return header().string("Content-Security-Policy", "object-src 'none'");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.servlet.configuration.toplevelcustomizerbean;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.ThrowingCustomizer;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
|
||||
import org.springframework.security.config.test.SpringTestContext;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.ResultMatcher;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@EnableWebSecurity
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class TopLevelCustomizerBeanConfiguration {
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.authorizeHttpRequests((requests) -> requests
|
||||
.anyRequest().authenticated()
|
||||
);
|
||||
return http.build();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
// tag::headersCustomizer[]
|
||||
@Bean
|
||||
Customizer<HeadersConfigurer<HttpSecurity>> headersSecurity() {
|
||||
// @formatter:off
|
||||
return (headers) -> headers
|
||||
.contentSecurityPolicy((csp) -> csp
|
||||
// <1>
|
||||
.policyDirectives("object-src 'none'")
|
||||
);
|
||||
// @formatter:on
|
||||
}
|
||||
// end::headersCustomizer[]
|
||||
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.servlet.configuration.toplevelcustomizerbean;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.ThrowingCustomizer;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
|
||||
import org.springframework.security.config.test.SpringTestContext;
|
||||
import org.springframework.security.config.test.SpringTestContextExtension;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.ResultMatcher;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@ExtendWith(SpringTestContextExtension.class)
|
||||
public class TopLevelCustomizerBeanTests {
|
||||
public final SpringTestContext spring = new SpringTestContext(this);
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
|
||||
@Test
|
||||
void headersCustomizer() throws Exception {
|
||||
this.spring.register(TopLevelCustomizerBeanConfiguration.class).autowire();
|
||||
// @formatter:off
|
||||
this.mockMvc
|
||||
.perform(get("/"))
|
||||
.andExpect(cspIsObjectSrcNone());
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
private static @NotNull ResultMatcher cspIsObjectSrcNone() {
|
||||
return header().string("Content-Security-Policy", "object-src 'none'");
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.servlet.test.testmethod;
|
||||
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.config.core.MessageService;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
|
||||
/**
|
||||
* A message service for demonstrating test support for method-based security.
|
||||
*/
|
||||
// tag::authenticated[]
|
||||
public class HelloMessageService implements MessageService {
|
||||
|
||||
@Override
|
||||
@PreAuthorize("isAuthenticated()")
|
||||
public String getMessage() {
|
||||
Authentication authentication = SecurityContextHolder.getContext()
|
||||
.getAuthentication();
|
||||
return "Hello " + authentication;
|
||||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("isAuthenticated()")
|
||||
public String getJsrMessage() {
|
||||
Authentication authentication = SecurityContextHolder.getContext()
|
||||
.getAuthentication();
|
||||
return "Hello JSR " + authentication;
|
||||
}
|
||||
}
|
||||
// end::authenticated[]
|
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.servlet.test.testmethod;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||
import org.springframework.security.config.core.MessageService;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
class HelloServiceTests {
|
||||
|
||||
@Autowired
|
||||
MessageService messageService;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
UsernamePasswordAuthenticationToken user = UsernamePasswordAuthenticationToken.authenticated("user", "password", AuthorityUtils.createAuthorityList("ROLE_USER"));
|
||||
SecurityContextHolder.getContext().setAuthentication(user);
|
||||
}
|
||||
|
||||
@Test
|
||||
void helloServiceTest() {
|
||||
assertThat(messageService.getMessage())
|
||||
.contains("user")
|
||||
.contains("ROLE_USER");
|
||||
}
|
||||
|
||||
@EnableMethodSecurity(prePostEnabled = true, jsr250Enabled = true)
|
||||
@Configuration
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
MessageService messageService() {
|
||||
return new HelloMessageService();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.servlet.test.testmethodmetaannotations;
|
||||
|
||||
import org.springframework.security.test.context.support.WithMockUser;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
// tag::snippet[]
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@WithMockUser(value="rob",roles={"USER","ADMIN"})
|
||||
public @interface WithMockAdmin { }
|
||||
// end::snippet[]
|
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.servlet.test.testmethodmetaannotations;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||
import org.springframework.security.config.core.MessageService;
|
||||
import org.springframework.security.docs.servlet.test.testmethod.HelloMessageService;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
public class WithMockAdminTests {
|
||||
|
||||
@Autowired
|
||||
MessageService messageService;
|
||||
|
||||
@Test
|
||||
@WithMockAdmin
|
||||
void getMessageWithMockUserAdminRoles() {
|
||||
String message = messageService.getMessage();
|
||||
assertThat(message)
|
||||
.contains("rob")
|
||||
.contains("ROLE_ADMIN")
|
||||
.contains("ROLE_USER");
|
||||
}
|
||||
|
||||
@EnableMethodSecurity
|
||||
@Configuration
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
MessageService messageService() {
|
||||
return new HelloMessageService();
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user