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

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);

Related

How to obtain HttpSession from within the loadUserByUsername method?

My web app is built with Spring MVC (4.2.9.RELEASE) and Spring Security (3.2.5.RELEASE). I use the loadUserByUsername method to find the user from the database.
public class MyUserDetailsServiceImpl implements UserDetailsService {
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
//how to get the HttpSession from within this methd?
}
....
}
How can I get the HttpSession from within this method?
In the applicationContext.xml file, I have the following for security:
<http create-session="always" use-expressions="true" request-matcher="regex" entry-point-ref="authenticationEntryPoint" >
....
</http>
There is a way to get current request:
Get the request object from RequestHolderContext and from that one get session:
public Optional<HttpServletRequest> getCurrentHttpRequest() {
return Optional.ofNullable(RequestContextHolder.getRequestAttributes())
.filter(requestAttributes -> ServletRequestAttributes.class.isAssignableFrom(requestAttributes.getClass()))
.map(requestAttributes -> ((ServletRequestAttributes) requestAttributes))
.map(ServletRequestAttributes::getRequest);
}
Use it like this:
Optional<HttpServletRequest> request = getCurrentHttpRequest();
HttpServletRequest currentRequest = request.get();
Hope this helps you
There is an answer exactly to your question at Spring Security documentation of more recent version (5.1.5 for instance). In short:
How do I access the HttpSession from a UserDetailsService?
You can’t, since the UserDetailsService has no awareness of the servlet API.
The #Atul's answer can be a good workaround but please be aware that RequestContextHolder internally relies on the current thread and thus may miss the session if called from another thread.
Before making my post, I already have a solution and it seems working, but not sure whether my solution is error-free or Spring already has a more elegant solution. Here is my solution:
I built filter as follows:
public class MyFilter implements Filter {
...
public void doFilter(ServletRequest request, ServletResponse response, FilterChain
chain) throws IOException, ServletException {
//create a thread-safe object and record the request object
}
}
Then, in the loadUserByUsername method, get the request object (thus the HTTP session) from the thread-safe object.
Comments? Please feel free let me know whether this approach has any problem.
UPDATE
MyFilter is declared in web.xml, together with Spring Security's DelegatingFilterProxy.

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 to propagate Spring Security Context in Spring Integration async messaging gateway

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

Logging specific request header using spring security events

In my grails application, failed login attemps get logged using spring security events as shown here http://grails-plugins.github.com/grails-spring-security-core/docs/manual/guide/single.html#7.3%20Registering%20Callback%20Closures
My issue has to do with client ip retrieval. Normally, calling getRemoteAddress from details object of the event should do the job, but my case is that my application is behind a reverse proxy therefore i should get the ip from request header X-Forwarded-For.
Neither event object nor application context parameters of the closuse provide access to the request object. The global request object isn't available either.
Any ideas how to get access to headers or any other way to implement this functionality?
You can get it from RequestContextHolder, if it exists:
GrailsWebRequest request = RequestContextHolder.currentRequestAttributes()
request.getHeader("X-Forwarded-For")
Generally, as you probably know, it isn't considered a very good idea to access the web session from within Services. First of all, you break the abstraction and separation of service logic, and requests might not always be available or associated with the current thread. One way to access the session from a service is to encapsulate the HTTP session in the following manner:
class WebUtilService {
void withSession (Closure closure) {
try {
GrailsWebRequest request = RequestContextHolder.currentRequestAttributes()
GrailsHttpSession session = request.session
closure.call(session)
}
catch (IllegalStateException ise) {
log.warn ("No WebRequest available!")
}
}
}
and you would use it like this:
class MyService {
WebUtilService webUtilService
void doSomething() {
webUtilService.withSession { HttpSession session ->
log.info(session.myValue)
session.newValue = 'Possible, but should be exceptional'
}
}
}
where you could have access to the getHeader() method.
Disclaimer: the code is from Marc-Oliver Scheele's blog.

Resources