How to propagate Spring Security Context in Spring Integration async messaging gateway - spring-security

I am trying to get spring security context to propagate through an spring integration async message flow, but have found that even though I added SecurityContextPropagationChannelInterceptor the security context always ends up null in my message handler.
#Bean
#GlobalChannelInterceptor(patterns = {"*"})
public ChannelInterceptor securityContextPropagationInterceptor()
{
return new SecurityContextPropagationChannelInterceptor();
}
I initiate my flow from a service that has a populated security context by making a call to my gateway interface:
#MessagingGateway
public interface AssignmentsService
{
#Gateway(requestChannel = "applyAssignmentsFlow.input")
ListenableFuture<AssignmentResult> applyAssignments( AssignmentRequest assignmentRequest );
}
On further debugging I have found that the GatewayProxyFactoryBean creates a new thread when initiating my flow, but does not propagate the security context.
I have searched but have been unable to find out how to configure this to propagate the security context.

That's pretty interesting task. Indeed :) !
But anyway you can do it like this:
#Bean
public AsyncTaskExecutor securityContextExecutor() {
return new DelegatingSecurityContextAsyncTaskExecutor(new SimpleAsyncTaskExecutor());
}
...
#MessagingGateway(asyncExecutor = "securityContextExecutor")
public interface AssignmentsService
The main trick here is from Spring Security and its concurrency utils, where we should use TaskExecutor wrappers to pick up the current SecurityContext and propagate it into newly spawned Thread.
There is nothing about Spring Integration, though - just the proper way to work with Security.
Will add such a trick into Reference Manual soon.
Pull request on the matter: https://github.com/spring-projects/spring-integration/pull/2015

Related

Readiness for Spring Cloud Data Flow

Spring Cloud Data Flow's documentation describes how to integrate with kubernetes Readiness probes. I'm developing my dataflow locally and running it in a docker-compose configuration, while we wait for our k8s SCDF environment to be stood up.
Is there another way to implement a readiness / do not send data context for SCDF? Upon component spin-up, I need to make a RESTful call and then run some computations on the results. Things attempted unsuccessfully:
use of ApplicationAvailabilityEvents - publishing a ReadinessState.ACCEPTING_TRAFFIC after the load + compilation is complete, after publishing a ReadinessState.REFUSING_TRAFFIC. When Spring completes its own load, it publishes an ACCEPTING_TRAFFIC, and so doesn't wait for mine from my service.
setting up an ApplicationRunner which also serves as an ApplicationListener for custom events which I throw when the computations are complete. Effectively, the run() method looks like:
public class ApplicationStartupRunner implements ApplicationRunner, ApplicationListener {
private boolean sessionLoaded = false;
public void run(ApplicationArguments args) {
doTimeExpensiveThing();
while (!sessionLoaded) {
TimeUnit.MILLISECONDS.sleep(150);
}
}
public void onApplicationEvent(SessionLoadEvent event) {
this.sessionLoaded = true;
}
}
Additional technical note: the Spring Boot application is built as a processor, which is using a function exposed as a Bean to provide its capability, ala
public Function<Flux<ChangeEvent>, Flux<Alert>> processChangeEvents()
Optimally, whatever approach I use which works in docker-compose, I'll wire into an indicator which'll be picked up by k8s and its readiness probe. Given that SCDF can be deployed on k8s, docker-compose (locally), or CloudFoundry, hoping that there's a model I can hook into that I've just overlooked.
Potential answer: instead of using the ApplicationRunner, wait in the processChangeEvents method and do not return the function until startup processing is complete.
In our case, because the doTimeExpensiveThing is an asynchronous activity, I use the technique of watching/waiting for the sessionLoaded flag, but now within the processChangeEvents method itself.
#Configuration
public class ConfigurationForProcessor implements ApplicationEventListener<SessionLoadEvent> {
boolean sessionLoaded;
Function<Flux<ChangeEvent>, Flux<Alert>> processChangeEvents() {
doTimeExpensiveAsynchronousThing();
while (!sessionLoaded) {
TimeUnit.MILLISECONDS.sleep(150);
}
return (Flux<ChangeEvent>) -> ... code which returns a Flux of Alert
}
public void onApplicationEvent(SessionLoadEvent event) {
this.sessionLoaded = true;
}
}
Very open to guidance on other approaches. This appears like it's working, though not sure there aren't gotchas I haven't caught yet.

The OAuth2 authenticated feign client doesn't support be invoked in an asynchronous method?

I am developing a spring cloud project with Feign and OAuth2.In the project, there are some time-consuming operations and some requests will be sent when these operations is finished. In order to achieve a better user experience, these operations was moved into an asynchronous method(with #Async). But there arise a problem.
I added the OAuth2FeignRequestInterceptor as a bean and has make sure that the Feign Client can work properly in the synchronous method(which thread has correct RequestAttributes in RequestContextHolder).
#Configuration
public class SomeConfiguration{
#Bean
public RequestInterceptor oauth2FeignRequestInterceptor(OAuth2ClientContext oAuth2ClientContext, BaseOAuth2ProtectedResourceDetails resource){
return new OAuth2FeignRequestInterceptor(oAuth2ClientContext,resource);
}
}
But if I move these operations into an asynchronous method, there will be throws an exception that scopedTarget.oauth2ClientContext cannot be created due to the absent of RequestContext. I have searched the stackoverflow.com and found a solution:
How to enable request scope in async task executor
With a RequestContextListener bean and these code, the RequestContextHolder belongs to the child thread would be filled with the parent thread's (the request thread) RequestAttributes.
Because the asynchronous method will cost some time before invoke the feign client, the request will be reponded before the feign client be invoked. When the request be responded, the RequestContextListener will reset the RequestAttributes in RequestContextHolder by invoke RequestContextHolder.resetRequestAttributes();(RequestContextListener.java:76) and make the request inside the RequestAttributes inactive. When it finished the time-consuming tasks and try to send something by feign client, the feign client try to get the oAuth2ClientContext from the request and throws the exception:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.oauth2ClientContext': Scope 'request' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: Cannot ask for request attribute - request is not active anymore!
I'm not sure if it is appropriate for OAuth2FeignRequestInterceptor to retrieve the Authorization information from the RequestContext in an asynchronous scenario.
Thank you for reading my issue and hoping for your reply.
If you're using spring, you can bind the spring security context to a sub thread.
SecurityContext context = SecurityContextHolder.getContext();
ExecutorService delegateExecutor = new ExecutorServiceAdapter(this.taskExecutor);
DelegatingSecurityContextExecutorService executor = new DelegatingSecurityContextExecutorService(delegateExecutor, context);
executor.invokeAll(tasks).stream()...
Need to define the taskExecutor bean:
#Configuration
public class ThreadConfig {
#Bean
public TaskExecutor threadPoolTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(4);
executor.setMaxPoolSize(10);
executor.setThreadNamePrefix("task_thread");
executor.initialize();
return executor;
}
}
Last, most importantly, you need enable setThreadContextInheritable when the servlet startup:
DispatcherServlet dispatcherServlet = (DispatcherServlet)ctx.getBean("dispatcherServlet");
dispatcherServlet.setThreadContextInheritable(true);

How to enable GlobalMethodsSecurity when unit test with MockMvc of standaloneSetup

SpringSecurity's #PreAuthorize and #PostAuthorize is ignored when unit testing with MockMvc. But it's OK when access by browser of Postman while normally started the application
I am using Spring 4.3 and Spring security 4.2, not the spring boot. I am using MockMvcBuilders.standaloneSetup to test the controller only. and don't want to use webAppContextSetup to involve the entire application to test.
After check the spring security's source code, I found that the Pre and PostAuthorize is checking by org.springframework.security.access.expression.method.ExpressionBasedPreInvocationAdvice and org.springframework.security.access.expression.method.ExpressionBasedPostInvocationAdvice. But the controller is not include by org.springframework.security.access.prepost.PrePostAnnotationSecurityMetadataSource.
I think this is caused by the controller is not initialized by Spring, so I try to register it to the BeanFactory, but it also fail.
Testing code:
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
mockMvc = standaloneSetup(controllers)
.setValidator(validator)
.apply(springSecurity(filterChainProxy))
.alwaysDo(print())
.build();
}
public void itWillFailWhenUpdateOtherOrg() {
CurrentUser user = new CurrentUser();
user.setOrgId(1);
user.setUsername("testuser");
mockMvc.perform(put("/orgs/-1")
.contentType(APPLICATION_JSON)
.content("{\"name\":\"RootOrg\",\"parent\":100}")
.with(user(user))).andExpect(status().isForbidden());
verify(orgService, never()).update(any());
}
Controller code:
#PutMapping("/org/{id}")
#PreAuthorize("principal.orgId == #orgDO.parent")
public OrgDO update(#PathVariable Integer id, #RequestBody OrgDO orgDO) {
}
When testing, the status code is 200, but not 403.
java.lang.AssertionError: Status
Expected :403
Actual :200
I expect the put request will fail and return status code 403, because of the principal.orgId != #orgDO.parent.
Be sure to NOT include all class to the Spring context, I just want to test the controller class.
Thank you very much.
After few hours of digging here is why:
MockMvcBuilders.standaloneSetup normally get passed a controller instantiated manually (not with Spring and therefore not with AOP). Therefore the PreAuthorize is not intercepted and security check is skipped. You can therefore either #Autowire your controller and pass it to MockMvcBuilders.standaloneSetup (which maybe kind of defies the purpose of using standalone setup since it's alos create the rest controller...) or simply use a WebApplicationContext: MockMvcBuilders.webAppContextSetup with an autowired WepAppContext.

Changing the Order of the Spring Security WebFilter

Changing the Order of the Spring Security WebFilter
I have an API Gateway implemented using Spring Cloud Gateway that uses Spring Security. Spring Security for WebFlux is implemented as a WebFilter right at the beginning of the filter chain. So after successful authentication the request would be forwarded to Spring Cloud Gateway's RoutePredicateHandlerMapping, which would try to deduce the destination based on the URL pattern, and then it would go to a FilteringWebHandler to execute the other filters of Spring Cloud Gateway.
My problem is the following: I have implemented a customized authentication algorithm which uses query string and header variables as credentials for authentication according to the requirements of the project, an this is working without any problem. The problem occurred when we needed to add a small customization for the authentication algorithm that is path independent. When the request reaches the WebFilter of Spring Security, pattern matching is not yet done so I do not know which application does it point to, for example:
app1:
-Path: /app1/**
app2:
-Path: /app2/**
Which means that instead of having authentication -> route mapping -> filtering web handler I should do route mapping -> authentication -> filtering web handler. Not that these three components are not similar, one of them is a filter another is a mapper and the last one is web handler. Now I know how to customize them but the problem is that I do not know how to intercept the Netty server building process in order to change the order of these operations. I need to wait for the building process to end and alter the content of the server before it starts. How can I do that?
EDIT: here is the final solution:
So here is how I did it:
Goal: removing the WebFilter of Spring Security from the default HttpHandler, and inserting it between RoutePredicateRouteMapping and the FilteringWebHandler of Spring Cloud Gateway
Why: Because I need to know the Application ID while carrying on my customized authentication process. This Application ID is attached to the request by the RoutePredicateRouteMapping by matching the request's URL to a predefined list.
How did I do it:
1- Removing the WebFilter of Spring Security
I created an HttpHandler bean that invokes the default WebHttpHandlerBuilder and then customize the filters. As a bonus, I removed unneeded filters in order to increase the performance of my API Gateway
#Bean
public HttpHandler httpHandler() {
WebHttpHandlerBuilder webHttpHandlerBuilder = WebHttpHandlerBuilder.applicationContext(this.applicationContext);
MyAuthenticationHandlerAdapter myAuthenticationHandlerAdapter = this.applicationContext.getBean(MY_AUTHENTICATED_HANDLER_BEAN_NAME, MyAuthenticationHandlerAdapter.class);
webHttpHandlerBuilder
.filters(filters ->
myAuthenticationHandlerAdapter.setSecurityFilter(
Collections.singletonList(filters.stream().filter(f -> f instanceof WebFilterChainProxy).map(f -> (WebFilterChainProxy) f).findFirst().orElse(null))
)
);
return webHttpHandlerBuilder.filters(filters -> filters
.removeIf(f -> f instanceof WebFilterChainProxy || f instanceof WeightCalculatorWebFilter || f instanceof OrderedHiddenHttpMethodFilter))
.build();
}
2- Wrapping Spring Cloud Gateway's FilteringWebHandler with Spring Web's FilteringWebHandler with the added WebFilter
I created my own HandlerAdapter which would match against Spring Cloud Gateway's FilteringWebHandler and wrap it with Spring Web's FilteringWebHandler plus the security filter I extracted in the first step
#Bean
public MyAuthenticationHandlerAdapter myAuthenticationHandlerAdapter() {
return new MyAuthenticationHandlerAdapter();
}
public class MyAuthenticationHandlerAdapter implements HandlerAdapter {
#Setter
private List<WebFilter> securityFilter = new ArrayList<>();
#Override
public boolean supports(Object handler) {
return handler instanceof FilteringWebHandler;
}
#Override
public Mono<HandlerResult> handle(ServerWebExchange exchange, Object handler) {
org.springframework.web.server.handler.FilteringWebHandler filteringWebHandler = new org.springframework.web.server.handler.FilteringWebHandler((WebHandler) handler, securityFilter);
Mono<Void> mono = filteringWebHandler.handle(exchange);
return mono.then(Mono.empty());
}
}
This way I could achieve better performance with highly customized HttpHandler pipeline that I suppose to be future-proof
END EDIT
Spring Security for WebFlux is implemented as a WebFilter which is executed almost as soon as a request is received. I have implemented custom authentication converter and authentication manager which would extract some variables from the header and URL and use them for authentication. This is working without any problem.
Now I needed to add another variable taken from RoutePredicateRouteMapping before authentication is done. What I want exactly is to remove the WebFilter (called WebFilterChainProxy) from its current position and put it between the RoutePredicateRouteMapping and the FilteringWeHandler.
Here is how the default process goes:
ChannelOperations calls ReactorHttpHandlerAdapter which calls HttpWebHandlerAdapter, ExceptionHandlingWebHandler, and then org.springframework.web.server.handler.FilterWebHandler.
This WebHandler would invoke its filters and then call the DispatchHandler. One of those filters is the WebFilterChainProxy that does the authentication for Spring Security. So first step is removing the filter from here.
Now the DispatchHandler which is called after the filters would invoke RoutePredicateHandlerMapping, which would analyze the routes and give me the route ID that I need, and then it would call the org.springframework.cloud.gateway.handler.FilteringHandler (this is not the same FilteringHandler above), and that in turn would call the other filters of the Spring Cloud Gateway. What I want here is to invoke the filter after RoutePredicatehandlerMapping and before org.springframework.cloud.gateway.handler.FilteringHandler.
What I ended doing was the following:
I created and WebHttpHandlerBuilder that would remove WebFilterChainProxy and pass it as a parameter to a customized DispatcherHandler. Now that the filter is removed the request would pass the first layers without requiring authentication. In my customized DispatcherHandler I would invoke the RoutePredicateHandlerMapping and then pass the exchange variable to the WebFilterChainProxy to do the authentication before passing it to the org.springframework.cloud.gateway.handler.FilteringHandler, which worked perfectly!
I still think that I'm over engineering it and I hope that there is a way to do it using annotations and configuration beans instead of all these customized classes (WebHttpHandlerBuilder and DispatcherHandler).
You should probably implement that security filter as a proper GatewayFilter, since only those are aware of the other GatewayFilter instances and can be ordered accordingly. In your case, you probably want to order it after the routing one.
Also, please don't cross-post, the Spring team is actively monitoring StackOverflow.
I had a similar problem. The accepted solution, while interesting, was a bit drastic for me. I was able to make it work simply by adding my custom filter before SecurityWebFiltersOrder.AUTHENTICATION in the security configuration. This is similar to what I've done with success in a regular Spring mvc application.
Here's an example using oauth authentication. tokenIntrospector is my custom introspector, and requestInitializationFilter is the filter that grabs the tenant id and stashes it in the context.
#AllArgsConstructor
#Configuration
#EnableWebFluxSecurity
public class WebApiGatewaySecurityConfiguration {
private final GatewayTokenIntrospector tokenIntrospector;
private final GatewayRequestInitializationFilter requestInitializationFilter;
#Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
// #formatter:off
http
.formLogin().disable()
.csrf().disable()
.oauth2ResourceServer(oauth2ResourceServer ->
oauth2ResourceServer.opaqueToken(c -> c.introspector(tokenIntrospector)))
.addFilterBefore(requestInitializationFilter, SecurityWebFiltersOrder.AUTHENTICATION);
return http.build();
// #formatter:on
}
}

How #PreAuthorize is working in an Reactive Application or how to live without ThreadLocal?

Can you explain where the advice handling #PreAuthorize("hasRole('ADMIN')") retrieves the SecurityContext in a Reactive application?
The following Spring Security example is a good illustration of this kind of usage: https://github.com/spring-projects/spring-security/tree/5.0.0.M4/samples/javaconfig/hellowebflux-method
After checking the Spring Security Webflux source code, I've found some implementations of SecurityContextRepository but the load method needs the ServerWebExchange as a parameter.
I'm trying to understand how to replace SecurityContextHolder.getContext().getAuthentication() call in a standard service (because ThreadLocal is no longer an option in a Reactive Application), but I don't understand how to replace this with a call to a SecurityContextRepository without a reference on the ServerWebExchange.
The ReactiveSecurityContextHolder provides the authentication in a reactive way, and is analogous to SecurityContextHolder.
Its getContext() method provides a Mono<SecurityContext>, just like SecurityContextHolder.getContext() provides a SecurityContext.
ReactiveSecurityContextHolder
.getContext()
.map(context ->
context.getAuthentication()
You're right, ThreadLocal is no longer an option because the processing of a request is not tied to a particular thread.
Currently, Spring Security is storing the authentication information as a ServerWebExchange attribute, so tied to the current request/response pair. But you still need that information when you don't have direct access to the current exchange, like #PreAuthorize.
The authentication information is stored in the Reactive pipeline itself (so accessible from your Mono or Flux), which is a very interesting Reactor feature - managing a context tied to a particular Subscriber (in a web application, the HTTP client is pulling data from the server and acts as such).
I'm not aware of an equivalent of SecurityContextHolder, or some shortcut method to get the Authentication information from the context.
See more about Reactor Context feature in the reference documentation.
You can also see an example of that being used in Spring Security here.
I implemented a JwtAuthenticationConverter (kotlin):
#Component
class JwtAuthenticationConverter : Function<ServerWebExchange,
Mono<Authentication>> {
#Autowired
lateinit var jwtTokenUtil: JwtTokenUtil
#Autowired
lateinit var userDetailsService: ReactiveUserDetailsService
private val log = LogFactory.getLog(this::class.java)
override fun apply(exchange: ServerWebExchange): Mono<Authentication> {
val request = exchange.request
val token = getJwtFromRequest(request)
if ( token != null )
try {
return userDetailsService.findByUsername(jwtTokenUtil.getUsernameFromToken(token))
.map { UsernamePasswordAuthenticationToken(it, null, it.authorities) }
} catch ( e: Exception ) {
exchange.response.statusCode = HttpStatus.UNAUTHORIZED
exchange.response.headers["internal-message"] = e.message
log.error(e)
}
return Mono.empty()
}
private fun getJwtFromRequest(request: ServerHttpRequest): String? {
val bearerToken = request.headers[SecurityConstants.TOKEN_HEADER]?.first {
it.startsWith(SecurityConstants.TOKEN_PREFIX, true)}
return if (bearerToken.isNullOrBlank()) null else bearerToken?.substring(7, bearerToken.length)
}
And then I set a SecurityConfig like this:
val authFilter = AuthenticationWebFilter(ReactiveAuthenticationManager {
authentication: Authentication -> Mono.just(authentication)
})
authFilter.setAuthenticationConverter(jwtAuthenticationConverter)
http.addFilterAt( authFilter, SecurityWebFiltersOrder.AUTHENTICATION)
You can use this approach to customize your AuthenticationConverter as I did to jwt based authentication to set the desired authentication object.

Resources