RestEasy client spring integration: can not auto follow redirects - spring

Problem: I can not get RestEasy to automatically follow redirects
I'm using the RestEasy client framework 2.3.4 to consume RESTful JSON services. I'm using the rest easy client spring integration. If I wasn't using spring RestClientProxyFactoryBean to create my services I would set the auto redirect flag on the client request factory
I have tried setting the follow redirect on my HTTP client and following the debug I can see this value is overridden to false by Rest Easy.
Looking at the source code I need to get access to the client invoker that the spring proxy factory creates but it doesn't expose this.
This is like a very common task, surely I am missing something? Cheers.

You should be able to set a custom client executor on the proxybean factory but that also didn't work e.g
#Override
public ClientRequest createRequest(String uriTemplate) {
ClientRequest clientRequest = new ClientRequest(uriTemplate, this);
clientRequest.followRedirects(true);
return clientRequest;
}
#Override
public ClientRequest createRequest(UriBuilder uriBuilder) {
ClientRequest clientRequest = super.createRequest(uriBuilder);
clientRequest.followRedirects(true);
return clientRequest;
}
}
proxyFactoryBean.setClientExecutor(new FollowRedirectsClientExecutor());
In end extending and overriding the Http client (in this case HTTP Component) was needed to make this work e.g.
public HttpUriRequest followRedirects(HttpUriRequest request) {
if (logger.isDebugEnabled()) {
logger.debug("Setting allow redirects");
}
HttpParams p = request.getParams();
HttpClientParams.setRedirecting(p, true);
request.setParams(p);
return request;
}
}
...
#Override
public <T> T execute(HttpUriRequest request, ResponseHandler<? extends T> responseHandler) throw
s IOException,
ClientProtocolException { ClientProtocolException {
request = followRedirects(request);
...

Related

Is there support for multiple feign.Client beans within Spring

Some context
The project is a spring boot application (v 2.3.12) using spring-cloud-openfeign-core (v 2.2.8) to make REST requests.
The service has 2 clients
one that needs to make REST requests using a proxy
one that needs to make REST request without a proxy
I'm unable to get both client to work simultaneously, i.e. have requests work for both proxied and non-proxied resources.
Is it possible to use different configuration files to support this, I have tried something like this
Configuration class X
#bean
public Client client1() {
return new Client.Default(null, null);
}
Configuration class Y
#bean
public Client client2() {
return new Client.Proxied(null, null,
new Proxy(Proxy.Type.HTTP, new InetSocketAddress("10.0.0.1", 8080)));
}
Feign interface code looks something like this
#Service
#FeignClient(name = "service1", url = "internal-url", configuration = X.class)
#Headers("Content-Type: application/json")
public interface serviceClient1 {
#PostMapping(value = "/v1/path}")
Response1 update(#RequestBody Request1 request1);
}
#Service
#FeignClient(name = "service2", url = "external-url", configuration = Y.class)
#Headers("Content-Type: application/json")
public interface serviceClient2 {
#PostMapping(value = "/v1/path}")
Response2 update(#RequestBody Request2 request2);
}
It seems only one of the client beans is used when making requests.

Spring RSocket over WebSocket - Access user information from HTTP session

In my web application, users login using a username/password combination and get a session cookie. When initiating a WebSocket connection, I can easily access the user information in the WebSocketHandler, for example:
#Component
public class MyWebSocketHandler implements WebSocketHandler {
#Override
public Mono<Void> handle(WebSocketSession session) {
// two ways to access security context information, either like this:
Mono<Principal> principal = session.getHandshakeInfo().getPrincipal();
// or like this
Mono<SecurityContext> context = ReactiveSecurityContextHolder.getContext();
//...
return Mono.empty();
}
}
Both reuse the HTTP session from the WebSocket handshake, I don't have to send additional authentication over the WebSocket itself. With STOMP the same thing applies: I can just reuse the information of the HTTP session.
How do I achieve the same thing using RSocket? For example, how would I get information about the user inside a MessageMapping method like this?:
#Controller
public class RSocketController {
#MessageMapping("test-stream")
public Flux<String> streamTest(RSocketRequester requester) {
// this mono completes empty, no security context available :(
Mono<SecurityContext> context = ReactiveSecurityContextHolder.getContext();
return Flux.empty();
}
}
I found many resources how to setup authentication with RSocket, but they all rely on an additional authentication after the WebSocket connection is established, but I specifically want to reuse the web session and don't want to send additional tokens over the websocket.
Have you tried the following? I found it in the documentation: 2.2 Secure Your RSocket Methods (might have to scroll down a bit) https://spring.io/blog/2020/06/17/getting-started-with-rsocket-spring-security
#PreAuthorize("hasRole('USER')") // (1)
#MessageMapping("fire-and-forget")
public Mono<Void> fireAndForget(final Message request, #AuthenticationPrincipal UserDetails user) { // (2)
log.info("Received fire-and-forget request: {}", request);
log.info("Fire-And-Forget initiated by '{}' in the role '{}'", user.getUsername(), user.getAuthorities());
return Mono.empty();
}
You can get the user information using #AuthenticationPrincipal Mono<UserDetails> userDetails.
In case someone use JWT authentication as me you need to add #AuthenticationPrincipal Mono<Jwt> jwt to your method arguments.
But for this to work, you need to configure the RSocketMessageHandler bean, that resolvs the argument.
#Bean
public RSocketMessageHandler rSocketMessageHandler(RSocketStrategies strategies) {
RSocketMessageHandler handler = new RSocketMessageHandler();
handler.getArgumentResolverConfigurer()
.addCustomResolver(new AuthenticationPrincipalArgumentResolver());
handler.setRSocketStrategies(strategies);
return handler;
}
Important you have to use org.springframework.security.messaging.handler.invocation.reactive.AuthenticationPrincipalArgumentResolver() class as the resolver, and for that you need spring-security-messaging dependency.

How to resolve authentication parameter with Spring messaging RSocket #ConnectMapping

#ConnectMapping("setup")
public void setup(#AuthenticationPrincipal Principal principal) {
}
#MessageMapping("hello")
public void hello(#AuthenticationPrincipal Principal principal) {
}
The two mappings are on the server side.
When the RSocket client setup a connection with 'message/x.rsocket.authentication.v0' metadata,
and then send request to hello mapping.
The first principal is null.
The second principal is the expected authentication data.
How to resolve the principal in #ConnectMapping?
Work properly with spring-security-rsocket version 5.3.4.RELEASE
You need to add message handler with custom resolver which will include AuthenticationPrincipalArgumentResolver from package org.springframework.security.messaging.handler.invocation.reactive
may need to add dependency
org.springframework.security:spring-security-messaging
to your maven/gradle
my example for kotlin:
#Bean
fun messageHandler(strategies: RSocketStrategies?): RSocketMessageHandler? {
val handler = RSocketMessageHandler()
handler.argumentResolverConfigurer.addCustomResolver(AuthenticationPrincipalArgumentResolver())
handler.rSocketStrategies = strategies
return handler
}

Spring Cloud : Using routing type filter in Zuul

I have 2 micro-services (Service A and Service B) built using Spring Boot, which gets routed through a Zuul Proxy also built as a Spring Boot app and I have checked that the Zuul proxy works just fine. However, what I am trying to do is to write a custom routing type ZuulFilter which should first route to Service A when a request comes in for Service B. Here is what I need assistance for:
I would like to know an example of how a routing filter looks like as I do not see anything after searching the internet. What I get are some examples of pre-filter and Netflix's documentation doesn't help much as well on that aspect.
Whether writing a custom route filter would mess up the original routing behavior of Zuul
I would construct a Feign client in the Zuul filter and make the call to service A using it. Feign will populate a ribbon load balancer to make the call in just the same way that Zuul does when proxying.
I had the same issue and this is what I came up with.
public class ServicesLegacyRouteFilter extends ZuulFilter {
private ServiceB serviceB;
public ServiceLegacyRouteFilter(ServiceB serviceB) {
this.serviceB = serviceB;
}
#Override
public String filterType() {
return ROUTE_TYPE;
}
#Override
public int filterOrder() {
return 10;
}
#Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
if ("serviceA".equals(ctx.get("serviceId"))) {
//call Service B here and use return type to set
//the final destination service
String destination = serviceB.routeWhere();
ctx.set("serviceId", destination);
return true;
}
return false;
}
#Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
// Or call ServiceB here to make your determination on
// the final destination.
String destination = serviceB.routeWhere();
ctx.set("serviceId", destination);
return null;
}
}
My actual production use case was more complicated on the routing of course, but this is the basics of how I was able to change routes based on what was coming in and how to take advantage of Zuul to get it out to the correct service.

WebSocket Stomp over SockJS - http custom headers

I'm using stomp.js over SockJS in my javascript client.
I'm connecting to websocket using
stompClient.connect({}, function (frame) {
stomp over sockJS connection has 2 http requests:
request to /info
http upgrade request
the client sends all cookies. I would like to also send custom headers (e.g. XSRF header) but didn't find a way to do that. Will appreciate any help.
#Rohitdev
So basically you can't send any HTTP headers using stompClient, because STOMP is layer over websockets, and only when websockets handshake happen we have possibility to send custom headers.
So only SockJS can send this headers, but for some reasons don't do this: https://github.com/sockjs/sockjs-client/issues/196
Custom headers:
stompClient.connect({token: "ABC123"}, function(frame) { ... code ...});
Without Custom headers:
stompClient.connect({}, function(frame) { ... code ...});
In Javascript, you can extract an STOMP header using:
username = frame.headers['user-name'];
In the server side, if you are using Spring Framework you can implementa an Interceptor to copy the HTTP parmeters to WebSockets STOMP headers.
public class HttpSessionHandshakeInterceptor_personalised implements HandshakeInterceptor {
#Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
// Set ip attribute to WebSocket session
attributes.put("ip", request.getRemoteAddress());
// ============================================= CODIGO PERSONAL
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
HttpServletRequest httpServletRequest = servletRequest.getServletRequest();
// httpServletRequest.getCookies();
// httpServletRequest.getParameter("inquiryId");
// httpServletRequest.getRemoteUser();
String token = httpServletRequest.getParameter("token");
...
}
}
And for send messages without STOMP parameters:
function sendMessage() {
var from = document.getElementById('from').value;
var text = document.getElementById('text').value;
stompClient.send("/app/chatchannel", {},
JSON.stringify({'from':from, 'text':text}));
}
and here you are passing parameters into the STOMP headers.
function sendMessage() {
var from = document.getElementById('from').value;
var text = document.getElementById('text').value;
stompClient.send("/app/chatchannel", {'token':'AA123'},
JSON.stringify({'from':from, 'text':text}));
}
Use #Header(name = "token") annotation inside the method if you are using Spring boot at the server.
Usage -
#Controller
public class SocketController {
static final String token = "1234";
#MessageMapping("/send")
#SendTo("/receive/changes")
public Object notify(MessageModel message, #Header(name = "token") String header)throws Exception {
if(!header.equals(token)) {
// return when headers do not match
return("Unauthorized");
}
// return the model object with associated sent message
return new MessageModel(message.getMessage());
}
}
You should have a MessageModel class with message variable and required getters, setters and contructor.
In frontend use Stomp
Usage -
function sendMessage() {
var text = document.getElementById('text').value;
stompClient.send("/send/message", {'token':'1234'},
JSON.stringify({'message':text}));
}
To add more security you an use CORS in Spring
You must use query as parameter instead to use Authorization in Header.
(?query=token_2222)
example: var socket = new SockJS('/ws?query=token_2222');
then read it in HandshakeInterceptor as Sergio wrote
SockJS JavaScript client does not support sending authorization header with a SockJS request.
Spring Java’s STOMP client allows to set headers for the handshake:
WebSocketHttpHeaders handshakeHeaders = new WebSocketHttpHeaders();
handshakeHeaders.add(principalRequestHeader, principalRequestValue);
Additional information: https://www.toptal.com/java/stomp-spring-boot-websocket
And you can find a lot of infоrmation about this point on Spring documentation:
https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#websocket-stomp-authentication
Short conclusion: for applications using cookies, integration is very good (Spring Security and other).
For applications using JWT, possible options are:
1. Add as a request parameter and process in the implementation of DefaultHandshakeHandler
var socket = new SockJS('/our-websocket?query=token_2222');
2. OR add directly to the STOMP header of the message:
//add
var headers = {
login: 'mylogin',
passcode: 'mypasscode',
// additional header
'client-id': 'my-client-id'
};
stompClient.connect(headers, function (frame) {}
//Place of processing
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(final MessageBrokerRegistry registry) {
registry.enableStompBrokerRelay("/topic")
.setRelayHost("127.0.0.1")
.setRelayPort(61613) //rabbitmq-plugins enable rabbitmq_stomp ; docker exec -it ID bash
.setClientLogin("guest")
.setClientPasscode("guest")
.setUserRegistryBroadcast("/topic/registry") //позволяет отправлять сообщеня всем приватные сообщения всем юзерам
;
registry.setApplicationDestinationPrefixes("/ws");
}
#Override
public void registerStompEndpoints(final StompEndpointRegistry registry) {
registry.addEndpoint("/our-websocket")
.setHandshakeHandler(new UserHandshakeHandler())
.setAllowedOriginPatterns("*")
.withSockJS()
;
}
#Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(new ChannelInterceptor() {
#Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
if (StompCommand.CONNECT.equals(accessor.getCommand())) {
// 1) Perform authentication here using standard authentication providers (managers).
//2) Install the user in case of successful authentication or throw an error
accessor.setUser(new UserPrincipal("TEST USER 2"));
}
return message;
}
});
}
}

Resources