My Spring Boot 2.7.1 application needs to use 2 different Oauth2 webclients, that each has its own identity provider. One of them needs to go through a proxy, but not the other.
For the one going through the proxy, I build it like this :
#Bean
#Qualifier("systemA")
WebClient getWebClientForSystemA(OAuth2AuthorizedClientManager authorizedClientManager,
#Value("${asset-sync-service.systemA-proxy.host}") String proxyHost,
#Value("${asset-sync-service.systemA-proxy.port}") int proxyPort) {
var oauth = new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
oauth.setDefaultClientRegistrationId("systemA");
var webClientBuilder=WebClient.builder()
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.apply(oauth.oauth2Configuration());
if(StringUtils.isNotEmpty(proxyHost)){
log.info("setting proxy setting ({}:{}) on webclient for systemA webclient..",proxyHost,proxyPort);
var httpClientWithSystemAProxy=HttpClient.create()
.wiretap("systemAWebClient",LogLevel.DEBUG, AdvancedByteBufFormat.TEXTUAL)
.proxy(proxy -> proxy.type(Proxy.HTTP)
.host(proxyHost)
.port(proxyPort));
webClientBuilder=webClientBuilder
.clientConnector(new ReactorClientHttpConnector(httpClientWithSystemAProxy));
}
return webClientBuilder.build();
}
The first time the webClient is called and tries to get a token, it fails with :
Caused by: org.springframework.security.oauth2.core.OAuth2AuthorizationException: [invalid_token_response] An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: I/O error on POST request for "https://systemA.my.company/connect/oauth2/access_token": systemA.my.company; nested exception is java.net.UnknownHostException: systemA.my.company
(when I try access https://systemA.my.company/connect/oauth2/access_token in my browser, it gets resovled)
I added the wiretap in the HttpClient, because I have the feeling it's not going through the proxy, and I want to see more logs. But unfortunately, I don't see anything in my logs, despite setting Logback root logger at DEBUG level.
am I doing something wrong, either ín the wiretap config or in the config of the proxy ?
I haven't been able to fix the missing logs that I expect with wiretap, but the proxy issue is solved, thanks to
https://stackoverflow.com/a/65790535/3067542
https://blog.doubleslash.de/spring-oauth2-client-authorization-request-proxy-support/
we need to configure the proxy also in the underlying restTemplate that is used by OAuth2AuthorizedClientManager, in addition to configuring it in the webClient. Not obvious at all !
Related
I'm using Spring Cloud API Gateway, the app has yaml based configuration and it works well since all routes are internal links, means there is no need for any proxy.
But, there a dependency on Spring Security which is used to validate JWT token and the URL of issuer is the resource that can be reached only via proxy.
spring:
security:
oauth2:
resourceserver:
jwt:
jwk-set-uri: https://some-site.com/itmp/v1/.well-known/jwks.json
issuer-uri: https://some-site.com/itmp/v1/
And here is the problem: I can not force WebClient using the http.proxyHost parameter. I tried many techniques that I found all around the Internet like setting them implicitly in app properties file, or hard coded in application itself. But nothing worked out for me. Maybe I'm missing something, maybe Spring Cloud has some special tweaks for that behavior?
FYI Project is based on Spring Boot 2.6.6 (reactor-netty 1.0.17 which is normally supports proxyWithSystemProperties )
I recently had this exact problem myself. After a lot of back and forth I ended up creating my own ReactiveJwtDecoder bean and setting the HttpClient/WebClient it uses internally (using NimbusReactiveJwtDecoder.webClient(WebClient)). Something along the lines of:
#Bean
ReactiveJwtDecoder jwtDecoder(#Value("${ISSUER_URI}") String issuerUri,
#Value("${JWKS_URI}") String jwksUri) {
HttpClient httpClient = HttpClient.create().proxyWithSystemProperties();
WebClient webClient = WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();
var jwtDecoder = NimbusReactiveJwtDecoder
.withJwkSetUri(jwksUri)
.webClient(webClient)
.build();
// import org.springframework.security.oauth2.jwt.JwtValidators;
jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(issuerUri));
return jwtDecoder;
}
You can probably inject the OAuth2ResourceServerProperties-bean instead of those two #Value-annotated Strings to get the URLs.
Also, be aware that if you application already does more customized JWT claim validation than merely checking iss, exp and nbf, you must remember to inject the existing JwtValidator and use that instead of the JwtValidators.createDefaultWithIssuer(String) one.
I want to secure a GraphQL API using authentication against Keycloak. This works fine by using the "quarkus-oidc" extension as described in the corresponding guide. I simply added an "#Authenticated" annotation to the same method that has the "#Query" annotation. However, I also need to customize the 401 response in case of an authorization failure (because my client expects a JSON with some details). How can I do that?
I already tried adding an ExceptionMapper (as described here), but it is not invoked. (It works for a standard REST endpoint, though.)
I also tried doing the authentication manually: I removed the "#Authenticated" annotation and instead injected a "SecurityIdentity" object. I then used the "hasRole" method, which throws an "AuthenticationFailedException" if authentication fails. I can catch this exception, but the default 401 response is already sent to the client in the background, and when I try sending any other response, I get an IllegalStateException ("Response head already sent")
We are using Spring Boot in 2.4.2 with Spring WebFlux.
I want the Spring Boot application to terminate all requests to the application that take longer than say 3 seconds to process.
There is server.netty.connection-timeout, but that doesn't seem to do the trick.
Is there a way to specify such a server request timeout?
I was also facing the same issue i.e. even after configuring server.netty.connection-timeout request would get canceled. So, after some debugging found that timeout was getting set to '30000' by AsyncContext.
So, I configured the following property spring.mvc.async.request-timeout which change the timeout being set in AsyncContext and the request stopped getting canceled.
TL;DR:
Netty has no request timeout*. Add this WebFilter to set a request-timeout of 3 seconds using the reactor timeout on every request (here in kotlin, but in Java it works accordingly):
#Component
class RequestTimeoutWebFilter : WebFilter {
override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> {
return chain
.filter(exchange)
.timeout(Duration.ofSeconds(3))
}
}
* at least I could not find any information in the netty docs.
Detailed answer
The connection-timeout does not refer to the duration that a request is allowed to take for processing, but it refers to the time it takes for establishing the connection.
First, I could not find any spring configuration option that allows setting the request timeout for netty. Then I went through the netty documentation to find out that there is no concept of request timeouts on the http server (only on the http client).
Wondering about why such important feature would not be supported, I remembered that we often cannot apply the same concepts as in blocking servers for good reasons. Next, I remembered, that in the reactive world we do not directly implement the handling of the request, but how the handling is assembled - i.e. we hold a Mono<Void> that will handle the request. Hence, we can just look at reactor and how to timeout a Mono, which is very easy:
Mono.create(...)
.timeout(Duration.ofSeconds(3))
Next, we just need to figure out, how to apply this to all requests. This is easy as well, because we can just use a WebFilter to intercept all requests to apply our timeout (here in kotlin, but in Java it works accoringly):
#Component
class RequestTimeoutWebFilter : WebFilter {
override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> {
return chain
.filter(exchange)
.timeout(Duration.ofSeconds(3))
}
}
This effectively cancels a request within the set timeout with the following error:
2022-10-21 00:08:00.981 ERROR 6289 --- [ parallel-4] a.w.r.e.AbstractErrorWebExceptionHandler : [59dfa990-7] 500 Server Error for HTTP GET "/some/route"
java.util.concurrent.TimeoutException: Did not observe any item or terminal signal within 3000ms in 'source(MonoDefer)' (and no fallback has been configured)
at reactor.core.publisher.FluxTimeout$TimeoutMainSubscriber.handleTimeout(FluxTimeout.java:295) ~[reactor-core-3.4.22.jar:3.4.22]
More tips and hints
To make the timeout configurable, we can use a custom config variable instead of the hard-coded duration.
To custimize the 500 status code we can either change the exception by providing a fallback to the timeout as 2nd argument and handle that exception in a controller advice - or we can just use reactors onErrorReturn.
The documentation for WebFilter actually states that they should be used to implement timeouts:
Contract for interception-style, chained processing of Web requests that may be used to implement cross-cutting, application-agnostic requirements such as security, timeouts, and others.
Still I think it is expected that spring provides such implementation out-of-the box that can be easily configured. Maybe we oversaw that it is there, but then I would argue it is too hard to find. ^^
Alternative solution path
As an alternative, we could use circuit breakers. However, those are implemented on the readers side and conceptually are used to protect the reading side against failure of the downstream - rather than protecting the internal processing within the downstream from running too long. They can only be applied to mimic a request timeout when applying them in a dedicated server (e.g. a spring cloud gateway server) that sits between the actual client and the actual service. When using Resilience4j as implementation, you can use TimeLimiter to achieve it.
I am confused about how an infinite loop of feign calls might behave.
An example:
Assume I have 2 APIs, A & B.
if I call API A, which in turn calls API B via a feign HTTP call, which in turn calls API A again via feign, will it recognize this and break the call chain?
Quick flowchart of calls:
A -> B -> A -> B ... Repeat infinitely?
I have not tried this code, it is just an idea。
But I am assuming that spring-cloud-starter-feign will provide some methods to resolve this problem? Is this assumption correct?
#PostMapping(RestJsonPath.API_A)
ResponseEntity<byte[]> apiA();
#PostMapping(RestJsonPath.API_B)
ResponseEntity<byte[]> apiB();
Will it execute until it times out or hystrix will stop it?
TL;DR:
Feign will keep the connection open on the initial request from A to B until the pre-configured timeout kicks in. At this point, Feign will time out the request and if you have specified a Hystrix fallback, Spring will use your Hystrix fallback as the response.
Explanation:
spring-boot-starter-feign provides an abstraction layer for writing the HTTP request code. It will not handle potential loops or cycles in your code.
Here is an example spring boot feign client from their tutorials website for demonstration:
#FeignClient(value = "jplaceholder",
url = "https://jsonplaceholder.typicode.com/",
configuration = ClientConfiguration.class,
fallback = JSONPlaceHolderFallback.class)
public interface JSONPlaceHolderClient {
#RequestMapping(method = RequestMethod.GET, value = "/posts")
List<Post> getPosts();
#RequestMapping(method = RequestMethod.GET, value = "/posts/{postId}", produces = "application/json")
Post getPostById(#PathVariable("postId") Long postId);
}
Notice first that this is an interface - all the code is auto generated by Spring at startup time, and that code will make RESTful requests to the urls configured via the annotations. For instance, the 2nd request allows us to pass in a path variable, which Spring will ensure makes it on the URL path of the outbound request.
The important thing to stress here is that this interface is only responsible for the HTTP calls, not any potential loops. Logic using this interface (which I can inject to any other Spring Bean as I would any other Spring Bean), is up to you the developer.
Github repo where this example came from.
Spring Boot Docs on spring-boot-starter-openfeign.
Hope this helps you understand the purpose of the openfeign project, and helps you understand that it's up to you to deal with cycles and infinite loops in your application code.
As for Hystrix, that framework comes in to play (if it is enabled) only if one of these generated HTTP requests fails, whether it's a timeout, 4xx error, 5xx error, or a response deserialization error. You configure Hystrix, as a sensible default or fallback for when the HTTP request fails.
This is a decent tutorial on Hystrix.
Some points to call out is that a Hystrix fallback must implement your Feign client interface, and you must specify this class as your Hysterix fallback in the #FeignClient annotation. Spring and Hystrix will call your Hystrix class automatically if a Feign request fails.
I'm trying to enable role based access control on a rest end point that I've setup using undertow, jersey and CDI. I initialize the servlet deployment as follows:
DeploymentInfo servletBuilder = Servlets.deployment()
.setClassLoader(Main.class.getClassLoader())
.setContextPath("/rest")
.setDeploymentName("sv.war")
.addListeners(listener(Listener.class))
.setLoginConfig(new LoginConfig("KEYCLOAK", "some-realm"))
.setAuthorizationManager(auth) // my dummy for testing
.addServlets(servlet("jerseyServlet", ServletContainer.class)
.setLoadOnStartup(1)
.addInitParam("javax.ws.rs.Application", SystemViewApplication.class.getName())
.addMapping("/api/*"));
I enabled kecloak authentication based on this example code.
So, my server is started as:
DeploymentManager manager = Servlets.defaultContainer().addDeployment(servletBuilder);
manager.deploy();
PathHandler path = Handlers.path(Handlers.resource(staticResources).setDirectoryListingEnabled(false).setWelcomeFiles("index.html"))
.addPrefixPath("/rest", manager.start());
Undertow server = Undertow.builder()
.addHttpListener(8087, "localhost")
.setHandler(sessionHandling(addSecurity(exchange -> {
final SecurityContext context = exchange.getSecurityContext();
if (!context.isAuthenticated()) {
exchange.endExchange();
return;
}
log.info("Authenticated: {} {} {}", context.getMechanismName(), context.getAuthenticatedAccount().getPrincipal().getName(), context.getAuthenticatedAccount().getRoles());
// propagate the request
path.handleRequest(exchange);
})))
.build();
server.start();
Where the two methods sessionHandling() and addSecurity() are lifted from the example I've linked above.
The authentication works, I am forced to log in, and the Authenticated: .. logging line is printed out with the correct details. But, once it hits the servlet handling, the security context (and account) is lost. I've traced this call and I can see that at some point along the path, it's replaced by brand new SecurityContext which has a null account.
Now my question - is there some authentication mechanism that I am missing that would propagate the state after the keycloak authentication or can I just fix the undertow code and in the SecurityContext, if the passed in context is already correctly authenticated, accept that state and move on? (the latter doesn't seem right, I'm guessing it's because the could be different authentication for the servlet deployment?) If so, is there any way to connect the servlet deployment to see the keycloak authentication has already happened?
Incase anyone comes looking here on how to authenticate servlets properly with keycloak and use role based authentication, this worked for me (note, this worked for me without the requirement of any xml files, purely with annotations.
First in the servlet application (wherever you extended ResourceConfig) register() the RolesAllowedDynamicFeature.class.
Also enable "use-resource-role-mappings": true in keycloak.json.
Next, instantiate the servlet deployment with an initial security wrapper:
DeploymentInfo servletBuilder = Servlets.deployment()
.setClassLoader(Main.class.getClassLoader())
.setContextPath("/")
.setDeploymentName("sv.war")
.addListeners(listener(Listener.class))
.setIdentityManager(idm)
.setSessionManagerFactory(new InMemorySessionManagerFactory())
.setInitialSecurityWrapper(handler -> sessionHandling(addSecurity(handler)))
.setResourceManager(staticResources)
.addWelcomePage("index.html")
.addServlets(servlet("jerseyServlet", ServletContainer.class)
.setLoadOnStartup(1)
.addInitParam("javax.ws.rs.Application", SystemViewApplication.class.getName())
.addMapping("/api/*"));
DeploymentManager manager = Servlets.defaultContainer().addDeployment(servletBuilder);
manager.deploy();
Undertow server = Undertow.builder()
.addHttpListener(8087, "localhost")
.setHandler(Handlers.path(manager.start()))
.build();
server.start();
Where sessionHandling(addSecurity(handler)) is basically the code from the linked github repo.
Now authentication via keycloak will work, and also role based authentication will work, so for example, if you have a CDI injected rest end point, such as:
#RolesAllowed({"admin", "guest"})
#GET
#Path("/{id}")
public Response findById(#PathParam("id") #NotNull Integer id){
// some method
}
As long as the roles are configured in keycloak, it should work.