I implement rate limiting in Spring Cloud Gateway (SCG). I get client IP address with below code
#Component
public class RemoteAddressKeyResolver implements KeyResolver {
#Override
public Mono<String> resolve(ServerWebExchange exchange) {
return Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
}
}
My SCG is behind a proxy so it gets address of proxy not real client address. How can I get real client address?
I found a solution!. There is an implementation of RemoteAddressResolver is XForwardedRemoteAddressResolver. Just use it, don't need to implement logic again.
#Component
public class RemoteAddressKeyResolver implements KeyResolver {
#Override
public Mono<String> resolve(ServerWebExchange exchange) {
XForwardedRemoteAddressResolver resolver = XForwardedRemoteAddressResolver.maxTrustedIndex(1);
InetSocketAddress inetSocketAddress = resolver.resolve(exchange);
return Mono.just(inetSocketAddress.getAddress().getHostAddress());
}
}
That's all, so simple!
you could check your request headers key, like X-Forwarded-For (depends on your proxy settings)
The X-Forwarded-For (XFF) header is a de-facto standard header for identifying the originating IP address of a client connecting to a web server through an HTTP proxy or a load balancer.
getFirst will return the origin ip
exchange.getRequest().getHeaders().getFirst("X-Forwarded-For")
return exchange -> {
// String origin = exchange.getRequest().getHeaders().getFirst("X-Forwarded-For");
// if (origin == null)
// origin = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
// return Mono.just(origin);
XForwardedRemoteAddressResolver resolver = XForwardedRemoteAddressResolver.maxTrustedIndex(1);
InetSocketAddress inetSocketAddress = resolver.resolve(exchange);
logger.trace("inetSocketAddress {}", inetSocketAddress);
logger.trace(".getHostName() {}", inetSocketAddress.getHostName());
return Mono.just(inetSocketAddress.getHostName());
};
Related
I have a Micronaut application running with the below configuration:
micronaut:
server:
cors:
enabled: true
port: 8080
Now I have an enhancement where I want to call a 3rd party URL and get the response in my application (one of the module in my application). I used the below code snippet:
EmbeddedServer server = ApplicationContext.run(EmbeddedServer.class);
HttpClient client = server .getApplicationContext() .createBean(HttpClient.class, server.getURL());
HttpRequest req = HttpRequest.GET(urlHost);
HttpResponse<String> response = client.toBlocking().exchange(req, String.class);
But this is not working. I get port already in use. I did not find much help in google because Micronaut's HttpClient is usually used in Micronaut Test which is not in my case. Is this possible to use it in my application? If so how? Thanks in Advance.
It is because you are starting another server by ApplicationContext.run(EmbeddedServer.class).
You don't need it. It is enough to inject HttpClient into your class by constructor:
#Singleton
public class MyClient {
private final RxHttpClient client;
public MyClient(#Client("https://some.third-party.com") RxHttpClient client) {
this.client = client;
}
HttpResponse<String> getSomething(Integer id) {
URI uri = UriBuilder.of("/some-objects").path(id).build();
return client.toBlocking().exchange(HttpRequest.GET(uri), String.class);
}
}
If you have third-party server URL in application configuration under some-service.url path for example, then you can use #Client("${some-service.url}")
Another option is to define declarative client for the third-party server and then inject it in your classes where needed.
First define client interface for your third-party service:
#Client("some-service")
public interface SomeServiceClient {
#Get("/api/some-objects/{id}")
String getSomeObject(#QueryValue("id") Integer id);
}
Add client configuration for that service in application configuration (application.yaml):
micronaut:
http:
services:
some-service:
url: "https://some.third-party.com"
read-timeout: 1m
And then you can inject the SomeServiceClient where you need it:
#Singleton
public class SomeServiceConsumer {
private final SomeServiceClient client;
public SomeServiceConsumer(SomeServiceClient client) {
this.client = client;
}
void doWithSomething(Integer id) {
String object = client.getSomeObject(id);
... // processing of object here
}
}
You can find more information in a Micronaut documentation
https://guides.micronaut.io/latest/micronaut-http-client-gradle-java.html
I want to enable http on some endpoints and https on another set of endpoints.
I got solutions like configure https through application.properties and http by programmatically creating an extra connector, but all the results enable both http and https for all endpoints.
Can someone let me know how to configure some endpoints with https and some end points with http?
I figured this out for Jetty servlet which I use. If you use the default TomCat servlet you will have to do something similar that works for TomCat I suppose.
So to start with I have a ssl port as default that is activated. To also allow http you need to configure an additional http port in your config. Then you need to add a server Handler. You could add the Handler SecuredRedirectHandler to redirect ALL http requests to the https port. Since we don't want to redirect ALL http requests we make our own CustomRedirectHandler that extends SecuredRedirectHandler.
#Bean
public ConfigurableServletWebServerFactory webServerFactory() {
JettyServletWebServerFactory factory = new JettyServletWebServerFactory();
factory.addServerCustomizers(new JettyServerCustomizer() {
#Override
public void customize(Server server) {
final HttpConnectionFactory httpConnectionFactory = server.getConnectors()[0].getConnectionFactory(HttpConnectionFactory.class);
// Enable HTTP for assigned port
final ServerConnector httpConnector = new ServerConnector(server, httpConnectionFactory);
httpConnector.setPort(serverProperties.intHttpPort() /* HTTP */);
server.addConnector(httpConnector);
// Add a CustomRedirectHandler to Server Handlers
final HandlerList handlerList = new HandlerList();
handlerList.addHandler(new CustomRedirectHandler());
for(Handler handler : server.getHandlers()) {
handlerList.addHandler(handler);
}
server.setHandler(handlerList);
}
});
return factory;
}
In our CustomRedirectHandler we can check if the requested endpoint is in our "allowed http" array. If it already request https or is allowed http then we do nothing, else redirect to https. My example allows http only for the endpoint that starts with "/.well-known/acme-challenge/" to allow requests to http://example.com/.well-known/acme-challenge/TOKEN for example.
public class CustomRedirectHandler extends SecuredRedirectHandler {
private final String[] allowedHttp = {"/.well-known/acme-challenge/"};
#Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
HttpChannel channel = baseRequest.getHttpChannel();
if (baseRequest.isSecure() || channel == null) {
// nothing to do, already requested https
return;
}
// Check if request is for allowed http
if (allowHttp(baseRequest)) {
return;
}
// Else Redirect to https
super.handle(target, baseRequest, request, response);
}
public boolean allowHttp(Request baseRequest) {
String pathInfo = baseRequest.getPathInfo();
if (pathInfo == null) {
return false;
}
for (String allowed : allowedHttp) {
if (pathInfo.startsWith(allowed)) {
return true;
}
}
return false;
}
}
The following code is from spring mvc documentation:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/portfolio");
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/app");
registry.enableSimpleBroker("/topic");
}
}
#Controller
public class GreetingController {
#MessageMapping("/greeting") {
public String handle(String greeting) {
return "[" + getTimestamp() + ": " + greeting;
}
}
The client connects to http://localhost:8080/portfolio to establish WebSocket connection, I wonder what's the exact url of client sending request?
http://localhost:8080/portfolio/app
or
http://localhost:8080/app?
and in actual WebSocket frame, does the destination header contain relative url like /app, /topic or absolute url?
[Android] https://github.com/NaikSoftware/StompProtocolAndroid
[Spring] https://docs.spring.io/spring/docs/5.1.9.RELEASE/spring-framework-reference/web.html#websocket-stomp
Just set the end point by using
addEndpoint("/portfolio");
Use the following Url to connect to websocket
ws://localhost:8080/portfolio
But remember you have to connect to socket only once and after that just invoke the endpoints without URL. Beacause socket is streamline connection and you have to establish connection only once.
setApplicationDestinationPrefixes("/app");
Above line will set the end point /app using this you can only publish over the socket. However all who has subscribed to this topic will get notified.
enableSimpleBroker("/topic");
Broker are responsible for handling subscribe and publish for both as they listen and send data in dual way means publish and subscribe both unlike /app.
private var mStompClient: StompClient? = null
mStompClient = Stomp.over(Stomp.ConnectionProvider.OKHTTP, "ws://localhost:8080/portfolio")
Connect to websocket using the above line. since we have to connect to socket end point only once write this in singleton.
val response = stomp.topic("/topic")
.subscribe { topicMessage -> }
Now above line will subscribe to your socket client means anytime you pushed the data from /topic this will this response variable will notified.
stompClient.send(StompMessage(StompCommand.SEND,
listOf(StompHeader(StompHeader.DESTINATION, "/topic")),
gson.toJson(myDataModel)))?
.subscribe()
Using above line you will you will you will send data to the socket which is specified as /topic.
#MessageMapping("/action")
fun performDeviceAction(#Payload myDataModel: MyDataModel) {}
Use the above line to receive the data from client on socket /action
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new SocketTextHandler(), "/user");
}
In order to tell Spring to forward client requests to the endpoint , we need to register the handler. Above snipplet will register a client.
Use below link and download source code for more information
https://www.javainuse.com/spring/boot-websocket
I've created a Grizzly server that returns JSON. It works nicely:
final ResourceConfig resourceConfig = new ResourceConfig().packages("com.mypackage");
resourceConfig.setApplicationName("My-Service");
resourceConfig.register(JacksonJaxbJsonProvider.class);
server = GrizzlyHttpServerFactory.createHttpServer(uri, resourceConfig);
I need filters that
Check if the Authorization header contains a valid key
Responds with 302/redirect if x-forwarded-proto is http (with location set to the https equivalent)
Responds with 403/forbidden if x-forwarded-for is not on a white list
Right now I do these checks like this:
#Path("v1")
public class Facade {
#GET
#Path("/getinfo/{infotype}")
#Produces(MediaType.APPLICATION_JSON)
public Response getNextPwc(#HeaderParam("authorization") String authorizationKey,
#Context Request request,
#HeaderParam("x-forwarded-proto") String xForwardedProto,
#HeaderParam("x-forwarded-for") String xForwardedFor,
#PathParam("infotype") String infoType) {
Response errorResponse = getErrorResponse(authorizationKey, request, xForwardedFor, xForwardedProto);
if (errorResponse != null) {
return errorResponse;
}
... start work with infoType here ...
It works - the server behaves correctly - but the code is in the wrong place (and I have to remember to add it to all new methods) so I would like to move it to a filter mechanism instead.
Any help is very appreciated.
(I know the x-forwarded-proto and x-forwarded-for checks are not bullet proof, but it is better than nothing.)
updated:
this works, although it's probably not how it was supposed to be set up:
final HttpServer server = new HttpServer();
final NetworkListener networkListener = new NetworkListener(
"secured-listener",
"0.0.0.0",
9191
);
server.addListener(networkListener);
server.start();
networkListener.getFilterChain().add(5, new BaseFilter(){
#Override
public NextAction handleRead(FilterChainContext ctx) throws IOException {
return super.handleRead(ctx);
}
});
System.in.read();
Notice index in getFilterChain().add( - the filter should be before HttpServerFilter cause it returns STOP action.
I use Spring integration for Web service message handling. Unfortunately the Message does not contains the sender IP Address. How can I get this information?
#Bean
public SimpleWebServiceInboundGateway myInboundGateway() {
SimpleWebServiceInboundGateway simpleWebServiceInboundGateway = new SimpleWebServiceInboundGateway();
simpleWebServiceInboundGateway.setRequestChannelName("testChannel");
simpleWebServiceInboundGateway.setReplyChannelName("testResponseChannel");
return simpleWebServiceInboundGateway;
}
#ServiceActivator(inputChannel = "testChannel", outputChannel = "testResponseChannel")
public Message getHeaders(Message message) {
// how can I reach the sender IP address here
return message;
}
The SimpleWebServiceInboundGateway doesn't map transport headers by default.
See DefaultSoapHeaderMapper.
Of course you can implement your own, but that really might be enough for you to use:
((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
.getRequest()
.getRemoteAddr();
in that your target #ServiceActivator.
Of course that will work if you don't shift message to a different thread before the service activator. The RequestContextHolder.currentRequestAttributes() is tied with ThreadLocal.
You can retrieve it from HttpServletRequest, using getRemoteAddr() to get access to user IP address and getHeader() to get header value. This is assuming you can modify your controller class.
Perhaps this will help:
#Controller
public class MyController {
#RequestMapping(value="/do-something")
public void doSomething(HttpServletRequest request) {
final String userIpAddress = request.getRemoteAddr();
final String userAgent = request.getHeader("user-agent");
System.out.println (userIpAddress);
}
}