I'am building an API-Gateway that proxies HTTP traffic to Grpc services. All incoming HTTP requests can have JWT in Authorization header. I need to transcode this JWT to Grpc metadata at each request and send it with Grpc request. I am using grpc-kotlin library with grpc code generator for kotlin suspend functions for client stub.
I have write this WebFilter to put header into ReactorContext:
#Component
class UserMetadataWebFilter : WebFilter {
override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> {
exchange.request.headers[HttpHeaders.AUTHORIZATION]?.firstOrNull()?.let { authorizationHeader ->
return chain.filter(exchange).contextWrite { Context.of("myHeader", authorizationHeader) }
}
return chain.filter(exchange)
}
}
And it can be used in controller methods like this:
identityProviderClient.createUser(protobufRequest,
coroutineContext[ReactorContext]?.context?.get("myHeader") ?: Metadata())
I want to create Grpc client interceptor or something another to automaticly set Grpc metadata from coroutine context. I have many Grpc client calls and I believe that is to write this code for every call is not good practice.
I know about envoy-proxy, but I need apply specific logic to my requests, that's why envoy-proxy is not my choice.
How should I transcode Http header(s) into grpc client call metadata? Thanks.
ClientInterceptor seems appropriate. Intercept the channel, see utility function:
https://grpc.github.io/grpc-java/javadoc/io/grpc/ClientInterceptors.html#intercept-io.grpc.Channel-io.grpc.ClientInterceptor...-
Related
I'm trying to build an application that periodically fetches data from a Third-Party API that demands a reCAPTCHA protected OAuth 2.0 Authorization Code Flow with PKCE for authentication.
I guess, it wouldn't be a big deal to implement the authorization protocol manually but I'm willing to do that using the Spring Security OAuth Client in the reactive manner.
The goal is to have a scheduled task that fetches the data from the API only being blocked until I manually open up a login page (currently a REST endpoint) in the browser that forwards me to the login page of the API vendor. After successful authentication, the scheduled task should also be able to access the API.
Currently the class structure looks like this:
MyController#showData and MyScheduler#fetchData both call ApiClient#retrieveData which does the final API call using the reactive WebClient from Spring.
The WebClient configuration looks like this:
#Configuration
#EnableWebFluxSecurity
class WebClientConfiguration {
#Bean
WebClient webClient(ReactiveClientRegistrationRepository clientRegs,
ReactiveOAuth2AuthorizedClientService authClientService) {
ReactiveOAuth2AuthorizedClientManager authClientManager =
new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(clientRegs, authClientService);
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth =
new ServerOAuth2AuthorizedClientExchangeFilterFunction(authClientManager);
oauth.setDefaultOAuth2AuthorizedClient(true);
oauth.setDefaultClientRegistrationId("test");
return WebClient.builder()
.filter(oauth)
.build();
}
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http,
ServerOAuth2AuthorizationRequestResolver resolver) {
http.authorizeExchange()
.anyExchange()
.authenticated()
.and()
.oauth2Login(auth -> auth.authorizationRequestResolver(resolver));
return http.build();
}
#Bean
public ServerOAuth2AuthorizationRequestResolver pkceResolver(
ReactiveClientRegistrationRepository repo) {
DefaultServerOAuth2AuthorizationRequestResolver resolver =
new DefaultServerOAuth2AuthorizationRequestResolver(repo);
resolver.setAuthorizationRequestCustomizer(OAuth2AuthorizationRequestCustomizers.withPkce());
return resolver;
}
}
The authorization works fine. When I open /showData in the browser, I'm redirected to the vendor's login page and when I come back, the requested data is displayed as it should be.
But the Scheduler is still blocked. I guess that has something to do with the Security Context which is linked only to the browser session, but I'm not so familiar with Spring Security to understand how to share the access (and refresh) token within the whole application.
Disclaimer: The Third-Party API has specific endpoints which are explicitly meant to be called periodically and not only on a user's request, but they still demand authorization by Authorization Code instead of Client Credential.
If you want to call third-party API from the scheduler you should pass OAuth2AuthorizedClient to it. That class represent an OAuth2 authorized client and has information about access/refresh token.
Here is the documentation describing how to use it.
We are using the Netflix DGS framework to build our backend to provide a GraphQL API.
In addition to that we use Keykloak as an identity provider which comes with a handy Spring module to add support for authentication and authorization out of the box.
Every request contains a JWT token, which gets validated and from there a SecurityContext object is being generated which is then available in every endpoint.
This is working great for HTTP requests. GraphQL queries and mutations are sent via HTTP, therefore no problem here.
Subscriptions on the other hand use the web socket protocol. A WS request does not contain additional headers, therefore no JWT token is sent with the request.
We can add the token via a payload, the question is now how to set up a Spring Security Filter which creates a Security Context out of the payload.
I guess this is rather Spring specific, basically a filter which intercepts any web socket request (ws://... or wss://...) is needed.
Any help or hint is very much appreciated!
The only way to use headers in web socket messages is in the connection_init message. the headers will be sent by the client in the payload of the message.
The solution I propose is done in 2 steps (We will assume that the name of the header element is "token"):
Intercept the connection_init message, then force the insertion of a new element (token) in the subscription request.
Retrieve the element (token) of the header during the interception of the subscription and feed the context.
Concretely, the solution is the implementation of WebSocketGraphQlInterceptor interface
#Configuration
class SubscriptionInterceptor implements WebSocketGraphQlInterceptor {
#Override
public Mono<Object> handleConnectionInitialization(WebSocketSessionInfo sessionInfo, Map<String, Object> connectionInitPayload) {
sessionInfo.getHeaders().add("token", connectionInitPayload.get("token").toString());
return Mono.just(connectionInitPayload);
}
#Override
public Mono<WebGraphQlResponse> intercept(WebGraphQlRequest request, Chain chain) {
List<String> token = request.getHeaders().getOrEmpty("token");
return chain.next(request).contextWrite(context -> context. Put("token", token.isEmpty() ? "" : token.get(0)));
}
}
I have an OAuth2 enabled WebClient configured using Spring's very convenient spring-boot-starter-oauth2-client library. It works perfectly in terms of getting and managing tokens when I make requests to my OAuth2 secured resource server API
I'd like to add global exception handling to the WebClient to handle exceptions arising from requests to my resource server. An ExchangeFilterFunction following something like the approach outlined in this article looks good.
I'm just wondering, as the WebClient already has a ServletOAuth2AuthorizedClientExchangeFilterFunction applied, would adding additional filters have any impact on the interactions with the OAuth2 authorization server?
Here is my current WebClient config:
#Bean
public WebClient edmsDataIntegrationServiceWebClient(OAuth2AuthorizedClientManager authorizedClientManager) {
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
oauth2Client.setDefaultClientRegistrationId("my-auth-server");
return WebClient.builder()
.baseUrl(baeUrl)
.apply(oauth2Client.oauth2Configuration())
.build();
}
So the additional filter would be something like the approach outlined here, where handleResourceServerError() would return an ExchangeFilterFunction that handles exceptions caused by my resource server response:
return WebClient.builder()
.baseUrl(baeUrl)
.apply(oauth2Client.oauth2Configuration())
.filter(WebClientFilter.handleResourceServerError()) //handles exceptions caused by resource server response
.build();
Is the above approach ok without breaking any of the ServletOAuth2AuthorizedClientExchangeFilterFunction functionality?
we want to integrate third party library(Eclipse XText LSP) into our SpringBoot webapp.
This library works "interactively" with the user (like chat). XText API requires input and output stream to work. We want to use WebSocket to let users interact with this library smoothly (send/retrieve json messages).
We have a problem with SpringBoot because SpringBoot support for WebSocket doesn't expose input/output streams. We wrote custom TextWebSocketHandler (subclass) but none of it's methods provide access to in/out streams.
We also tried with HandshakeInterceptor (to obtain in/out streams after handshake ) but with no success.
Can we use SpringBoot WebSocket API in this scenario or should we use some lower level (Servlet?) API ?
Regards Daniel
I am not sure if this will fit your architecture or not, but I have achieved this by using Spring Boot's STOMP support and wiring it into a custom org.eclipse.lsp4j.jsonrpc.RemoteEndpoint, rather than using a lower level API.
The approach was inspired by reading through the code provided in org.eclipse.lsp4j.launch.LSPLauncher.
JSON handler
Marhalling and unmarshalling the JSON needs to be done with the API provided with the xtext language server, rather than Jackson (which would be used by the Spring STOMP integration)
Map<String, JsonRpcMethod> supportedMethods = new LinkedHashMap<String, JsonRpcMethod>();
supportedMethods.putAll(ServiceEndpoints.getSupportedMethods(LanguageClient.class));
supportedMethods.putAll(languageServer.supportedMethods());
jsonHandler = new MessageJsonHandler(supportedMethods);
jsonHandler.setMethodProvider(remoteEndpoint);
Response / notifications
Responses and notifications are sent by a message consumer which is passed to the remoteEndpoint when constructed. The message must be marshalled by the jsonHandler so as to prevent Jackson doing it.
remoteEndpoint = new RemoteEndpoint(new MessageConsumer() {
#Override
public void consume(Message message) {
simpMessagingTemplate.convertAndSendToUser('user', '/lang/message',
jsonHandler.serialize(message));
}
}, ServiceEndpoints.toEndpoint(languageServer));
Requests
Requests can be received by using a #MessageMapping method that takes the whole #Payload as a String to avoid Jackson unmarshalling it. You can then unmarshall yourself and pass the message to the remoteEndpoint.
#MessageMapping("/lang/message")
public void incoming(#Payload String message) {
remoteEndpoint.consume(jsonHandler.parseMessage(message));
}
There may be a better way to do this, and I'll watch this question with interest, but this is an approach that I have found to work.
I have a service that gets http request with an authorization header.
When processing the request, I want to use a Feign Client to query another service. The query to the other service should include the same authorization header.
Currently I use a Filter to extract the authorization header from the incoming request, store the header in a ThreadLocal.
When building the Feign Client I use a RequestInterceptor to read the authorization header from the ThreadLocal and put it into the request to the other service.
This approach is not ideal, because when I start using things like RxJava or Hystrix, threads are changed while processing the request and I have to move the authorization header ThreadLocal from one thread to another.
What are other options to solve this?
One way that I am thinking about is to create a new FeignClient for each request, this way I would no longer need to store the authorization in a thread local. But is this a good idea?
I think I found a solution for my problem. Using RequestContextHolder I can get a reference to the original request (also from spawned child threads) and copy the header from there:
public class AuthForwardInterceptor implements RequestInterceptor {
#Override
public void apply(RequestTemplate template) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
template.header(HttpHeaders.AUTHORIZATION, request.getHeader(HttpHeaders.AUTHORIZATION));
}
}