Support BeanResolver for Reactive AuthenticationPrincipal
Fixes: gh-4326
This commit is contained in:
parent
d816af2337
commit
a2073b2b91
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2017 the original author or authors.
|
* Copyright 2002-2018 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,9 +16,11 @@
|
||||||
|
|
||||||
package org.springframework.security.config.annotation.web.reactive;
|
package org.springframework.security.config.annotation.web.reactive;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.BeanFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Scope;
|
import org.springframework.context.annotation.Scope;
|
||||||
|
import org.springframework.context.expression.BeanFactoryResolver;
|
||||||
import org.springframework.core.ReactiveAdapterRegistry;
|
import org.springframework.core.ReactiveAdapterRegistry;
|
||||||
import org.springframework.security.authentication.ReactiveAuthenticationManager;
|
import org.springframework.security.authentication.ReactiveAuthenticationManager;
|
||||||
import org.springframework.security.authentication.UserDetailsRepositoryReactiveAuthenticationManager;
|
import org.springframework.security.authentication.UserDetailsRepositoryReactiveAuthenticationManager;
|
||||||
|
@ -51,6 +53,9 @@ class ServerHttpSecurityConfiguration implements WebFluxConfigurer {
|
||||||
@Autowired(required = false)
|
@Autowired(required = false)
|
||||||
private PasswordEncoder passwordEncoder;
|
private PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
|
@Autowired(required = false)
|
||||||
|
private BeanFactory beanFactory;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void configureArgumentResolvers(ArgumentResolverConfigurer configurer) {
|
public void configureArgumentResolvers(ArgumentResolverConfigurer configurer) {
|
||||||
configurer.addCustomResolver(authenticationPrincipalArgumentResolver());
|
configurer.addCustomResolver(authenticationPrincipalArgumentResolver());
|
||||||
|
@ -58,7 +63,12 @@ class ServerHttpSecurityConfiguration implements WebFluxConfigurer {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public AuthenticationPrincipalArgumentResolver authenticationPrincipalArgumentResolver() {
|
public AuthenticationPrincipalArgumentResolver authenticationPrincipalArgumentResolver() {
|
||||||
return new AuthenticationPrincipalArgumentResolver(this.adapterRegistry);
|
AuthenticationPrincipalArgumentResolver resolver = new AuthenticationPrincipalArgumentResolver(
|
||||||
|
this.adapterRegistry);
|
||||||
|
if(this.beanFactory != null) {
|
||||||
|
resolver.setBeanResolver(new BeanFactoryResolver(this.beanFactory));
|
||||||
|
}
|
||||||
|
return resolver;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean(HTTPSECURITY_BEAN_NAME)
|
@Bean(HTTPSECURITY_BEAN_NAME)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2017 the original author or authors.
|
* Copyright 2002-2018 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,8 +16,17 @@
|
||||||
|
|
||||||
package org.springframework.security.config.annotation.web.reactive;
|
package org.springframework.security.config.annotation.web.reactive;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.csrf;
|
||||||
|
import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.basicAuthentication;
|
||||||
|
import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.Credentials.basicAuthenticationCredentials;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.Principal;
|
||||||
|
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.ConfigurableApplicationContext;
|
import org.springframework.context.ConfigurableApplicationContext;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
|
@ -32,40 +41,43 @@ import org.springframework.security.config.test.SpringTestRule;
|
||||||
import org.springframework.security.config.users.ReactiveAuthenticationTestConfiguration;
|
import org.springframework.security.config.users.ReactiveAuthenticationTestConfiguration;
|
||||||
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||||
import org.springframework.security.core.context.SecurityContext;
|
import org.springframework.security.core.context.SecurityContext;
|
||||||
import org.springframework.security.core.context.SecurityContextImpl;
|
import org.springframework.security.core.context.SecurityContextImpl;
|
||||||
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
|
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
|
||||||
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
|
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
|
||||||
import org.springframework.security.core.userdetails.User;
|
import org.springframework.security.core.userdetails.User;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
|
||||||
|
import org.springframework.security.test.context.support.WithMockUser;
|
||||||
import org.springframework.security.test.web.reactive.server.WebTestClientBuilder;
|
import org.springframework.security.test.web.reactive.server.WebTestClientBuilder;
|
||||||
import org.springframework.security.web.reactive.result.view.CsrfRequestDataValueProcessor;
|
import org.springframework.security.web.reactive.result.view.CsrfRequestDataValueProcessor;
|
||||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||||
import org.springframework.security.web.server.WebFilterChainProxy;
|
import org.springframework.security.web.server.WebFilterChainProxy;
|
||||||
import org.springframework.security.web.server.context.WebSessionServerSecurityContextRepository;
|
import org.springframework.security.web.server.context.WebSessionServerSecurityContextRepository;
|
||||||
import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher;
|
import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher;
|
||||||
|
import org.springframework.test.context.junit4.SpringRunner;
|
||||||
import org.springframework.test.web.reactive.server.FluxExchangeResult;
|
import org.springframework.test.web.reactive.server.FluxExchangeResult;
|
||||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.reactive.config.EnableWebFlux;
|
||||||
import org.springframework.web.reactive.function.BodyInserters;
|
import org.springframework.web.reactive.function.BodyInserters;
|
||||||
import org.springframework.web.reactive.result.view.AbstractView;
|
import org.springframework.web.reactive.result.view.AbstractView;
|
||||||
|
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.security.Principal;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.csrf;
|
|
||||||
import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.Credentials.basicAuthenticationCredentials;
|
|
||||||
import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.basicAuthentication;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Rob Winch
|
* @author Rob Winch
|
||||||
* @since 5.0
|
* @since 5.0
|
||||||
*/
|
*/
|
||||||
|
@RunWith(SpringRunner.class)
|
||||||
|
@SecurityTestExecutionListeners
|
||||||
public class EnableWebFluxSecurityTests {
|
public class EnableWebFluxSecurityTests {
|
||||||
@Rule
|
@Rule
|
||||||
public final SpringTestRule spring = new SpringTestRule();
|
public final SpringTestRule spring = new SpringTestRule();
|
||||||
|
@ -288,6 +300,46 @@ public class EnableWebFluxSecurityTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@WithMockUser
|
||||||
|
public void authenticationPrincipalArgumentResolverWhenSpelThenWorks() {
|
||||||
|
this.spring.register(AuthenticationPrincipalConfig.class).autowire();
|
||||||
|
|
||||||
|
WebTestClient client = WebTestClient.bindToApplicationContext(this.spring.getContext()).build();
|
||||||
|
|
||||||
|
client.get()
|
||||||
|
.uri("/spel")
|
||||||
|
.exchange()
|
||||||
|
.expectStatus().isOk()
|
||||||
|
.expectBody(String.class).isEqualTo("user");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@EnableWebFluxSecurity
|
||||||
|
@EnableWebFlux
|
||||||
|
@Import(ReactiveAuthenticationTestConfiguration.class)
|
||||||
|
static class AuthenticationPrincipalConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public PrincipalBean principalBean() {
|
||||||
|
return new PrincipalBean();
|
||||||
|
}
|
||||||
|
|
||||||
|
static class PrincipalBean {
|
||||||
|
public String username(UserDetails user) {
|
||||||
|
return user.getUsername();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public static class AuthenticationPrincipalResolver {
|
||||||
|
@GetMapping("/spel")
|
||||||
|
String username(@AuthenticationPrincipal(expression = "@principalBean.username(#this)") String username) {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static DataBuffer toDataBuffer(String body) {
|
private static DataBuffer toDataBuffer(String body) {
|
||||||
DataBuffer buffer = new DefaultDataBufferFactory().allocateBuffer();
|
DataBuffer buffer = new DefaultDataBufferFactory().allocateBuffer();
|
||||||
buffer.write(body.getBytes(StandardCharsets.UTF_8));
|
buffer.write(body.getBytes(StandardCharsets.UTF_8));
|
||||||
|
|
|
@ -412,6 +412,7 @@ Below are the highlights of the release.
|
||||||
For example, `@WithMockUser(setupBefore = TestExecutionEvent.TEST_EXECUTION)` will setup a user after JUnit's `@Before` and before the test executes.
|
For example, `@WithMockUser(setupBefore = TestExecutionEvent.TEST_EXECUTION)` will setup a user after JUnit's `@Before` and before the test executes.
|
||||||
** `@WithUserDetails` now works with `ReactiveUserDetailsService`
|
** `@WithUserDetails` now works with `ReactiveUserDetailsService`
|
||||||
* <<jackson>> - added support for `BadCredentialsException`
|
* <<jackson>> - added support for `BadCredentialsException`
|
||||||
|
* <<mvc-authentication-principal>> - Supports resolving beans in WebFlux (was already supported in Spring MVC).
|
||||||
|
|
||||||
|
|
||||||
[[samples]]
|
[[samples]]
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2017 the original author or authors.
|
* Copyright 2002-2018 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -49,6 +49,14 @@ public class AuthenticationPrincipalArgumentResolver extends HandlerMethodArgume
|
||||||
super(adapterRegistry);
|
super(adapterRegistry);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@link BeanResolver} to be used on the expressions
|
||||||
|
* @param beanResolver the {@link BeanResolver} to use
|
||||||
|
*/
|
||||||
|
public void setBeanResolver(BeanResolver beanResolver) {
|
||||||
|
this.beanResolver = beanResolver;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsParameter(MethodParameter parameter) {
|
public boolean supportsParameter(MethodParameter parameter) {
|
||||||
return findMethodAnnotation(AuthenticationPrincipal.class, parameter) != null;
|
return findMethodAnnotation(AuthenticationPrincipal.class, parameter) != null;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2017 the original author or authors.
|
* Copyright 2002-2018 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -23,6 +23,7 @@ import org.mockito.Mock;
|
||||||
import org.mockito.junit.MockitoJUnitRunner;
|
import org.mockito.junit.MockitoJUnitRunner;
|
||||||
import org.springframework.core.MethodParameter;
|
import org.springframework.core.MethodParameter;
|
||||||
import org.springframework.core.ReactiveAdapterRegistry;
|
import org.springframework.core.ReactiveAdapterRegistry;
|
||||||
|
import org.springframework.expression.BeanResolver;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||||
import org.springframework.security.web.method.ResolvableMethod;
|
import org.springframework.security.web.method.ResolvableMethod;
|
||||||
|
@ -33,6 +34,8 @@ import reactor.core.publisher.Mono;
|
||||||
import java.lang.annotation.*;
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
|
||||||
|
@ -48,16 +51,20 @@ public class AuthenticationPrincipalArgumentResolverTests {
|
||||||
BindingContext bindingContext;
|
BindingContext bindingContext;
|
||||||
@Mock
|
@Mock
|
||||||
Authentication authentication;
|
Authentication authentication;
|
||||||
|
@Mock
|
||||||
|
BeanResolver beanResolver;
|
||||||
|
|
||||||
ResolvableMethod authenticationPrincipal = ResolvableMethod.on(getClass()).named("authenticationPrincipal").build();
|
ResolvableMethod authenticationPrincipal = ResolvableMethod.on(getClass()).named("authenticationPrincipal").build();
|
||||||
ResolvableMethod spel = ResolvableMethod.on(getClass()).named("spel").build();
|
ResolvableMethod spel = ResolvableMethod.on(getClass()).named("spel").build();
|
||||||
ResolvableMethod meta = ResolvableMethod.on(getClass()).named("meta").build();
|
ResolvableMethod meta = ResolvableMethod.on(getClass()).named("meta").build();
|
||||||
|
ResolvableMethod bean = ResolvableMethod.on(getClass()).named("bean").build();
|
||||||
|
|
||||||
AuthenticationPrincipalArgumentResolver resolver;
|
AuthenticationPrincipalArgumentResolver resolver;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setup() {
|
public void setup() {
|
||||||
resolver = new AuthenticationPrincipalArgumentResolver(new ReactiveAdapterRegistry());
|
resolver = new AuthenticationPrincipalArgumentResolver(new ReactiveAdapterRegistry());
|
||||||
|
this.resolver.setBeanResolver(this.beanResolver);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -126,6 +133,19 @@ public class AuthenticationPrincipalArgumentResolverTests {
|
||||||
assertThat(argument.block()).isEqualTo(user.getId());
|
assertThat(argument.block()).isEqualTo(user.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void resolveArgumentWhenBeanThenObtainsPrincipal() throws Exception {
|
||||||
|
MyUser user = new MyUser(3L);
|
||||||
|
MethodParameter parameter = this.bean.arg(Long.class);
|
||||||
|
when(authentication.getPrincipal()).thenReturn(user);
|
||||||
|
when(exchange.getPrincipal()).thenReturn(Mono.just(authentication));
|
||||||
|
when(this.beanResolver.resolve(any(), eq("beanName"))).thenReturn(new Bean());
|
||||||
|
|
||||||
|
Mono<Object> argument = resolver.resolveArgument(parameter, bindingContext, exchange);
|
||||||
|
|
||||||
|
assertThat(argument.block()).isEqualTo(user.getId());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void resolveArgumentWhenMetaThenObtainsPrincipal() throws Exception {
|
public void resolveArgumentWhenMetaThenObtainsPrincipal() throws Exception {
|
||||||
MethodParameter parameter = this.meta.arg(String.class);
|
MethodParameter parameter = this.meta.arg(String.class);
|
||||||
|
@ -142,8 +162,16 @@ public class AuthenticationPrincipalArgumentResolverTests {
|
||||||
|
|
||||||
void spel(@AuthenticationPrincipal(expression = "id") Long id) {}
|
void spel(@AuthenticationPrincipal(expression = "id") Long id) {}
|
||||||
|
|
||||||
|
void bean(@AuthenticationPrincipal(expression = "@beanName.methodName(#this)") Long id) {}
|
||||||
|
|
||||||
void meta(@CurrentUser String principal) {}
|
void meta(@CurrentUser String principal) {}
|
||||||
|
|
||||||
|
static class Bean {
|
||||||
|
public Long methodName(MyUser user) {
|
||||||
|
return user.getId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static class MyUser {
|
static class MyUser {
|
||||||
private final Long id;
|
private final Long id;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue