I am trying to get the IP Address of the client of my JAX-WS SOAP Web Service (an alternative solution is appreciated).
I am using the following code, which works on another project, however this project (integrating with Spring 3.2.4) is returning a null HttpServletRequest when I fetch it from the javax.xml.ws.WebServiceContext (the WebServiceContext is not null):
#Resource
WebServiceContext wsContext;
private String getRemoteIpAddress()
{
MessageContext context = this.wsContext.getMessageContext();
HttpServletRequest httpRequest = (HttpServletRequest) context.get(MessageContext.SERVLET_REQUEST);
String remoteIpAddress = httpRequest.getRemoteAddr();
For further clarity, this is this Web Service class looks like:
#WebService(name = "PService", targetNamespace = "http://server.ps/")
#SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.WRAPPED)
public class PSystemServiceEndpoint extends SpringBeanAutowiringSupport
Important to note, the application is running as a standalone Java application and includes a Jetty Embeeded Server.
I also use Spring WS and when I need to get IP from remote client I use this function
private String getIpRequest(){
String result = "none";
TransportContext context = TransportContextHolder.getTransportContext();
if(context != null){
HttpServletConnection connection = (HttpServletConnection)context.getConnection();
if(connection != null){
HttpServletRequest request = connection.getHttpServletRequest();
if(request != null){
result = request.getRemoteAddr();
}
}
}
return result;
}
As you can see I don't use WebServiceContext, instead I use WebServiceTemplate so I am not sure if this code is right for you, but I hope it helps.
Related
Problem
How to forward requests in Spring Cloud application? I need to forward requests to other services depending on the part of uri.
For example
HTTP GET http://user-application/api/users, returns users JSON.
HTTP GET http://user-application/api/proxy/jobs-application/api/jobs, returns jobs JSON, but this request should be forwarded to another application:
HTTP GET http://jobs-application/api/jobs.
Any HTTP method is allowed, not only GET.
Context
I have a SpringBoot Application, User application which has REST end-points which return data.
For example GET http://user-application/api/users would return users in the JSON format.
User application also has an HTTP end-point which should forward the request to other applications - let's call one of them Jobs application.
This end-point is HTTP {ANY_METHOD} /api/proxy/{dynamic-service}/{dynamic-path} as an example,
GET http://user-application/api/proxy/jobs-application/api/jobs
Please, note, initial request comes to the User application, while then it is forwarded to the Jobs application.
Approaches
I put some my approaches which I think about. Maybe you have done similar things in the past, so you could share your experience doing so. Or even improve one of my approaches.
ProxyController approach
I would create a ProxyController in User application with mapping /proxy
#Controller
#RequestMaping("/proxy/**")
ProxyController
public void proxy(final HttpServletRequest request, HttpResponse response) {
final String requestUri = request.getRequestUri();
if (!requestUri.startsWith("/api/proxy/")) {
return null; // Do not proxy
}
final int proxyIndex = "/api/proxy/".lenght(); // Can be made a constant
final String proxiedUrl = requestUri.subString(proxyIndex, requestUri.lenght());
final Optional<String> payload = retrievePayload(request);
final Headers headers = retrieveHeaders(request);
final HttpRequest proxyRequest = buildProxyRequest(request, headers);
payload.ifPresent(proxyRequest::setPayload);
final HttpResponse proxyResponse = httpClient.execute(proxyRequest)
pdateResponse(response, proxyResponse);
}
The problem with this approach, I have to write a lot of code t build a proxy request, to check if it has payload and if it has, copy it into proxy request, then copy headers, cookies etc to the proxy request, copy HTTP verb into proxy request. Then when I get proxy response, I have to populate its details into the response.
Zuul approach
I was inspired by ZuulFilters:
https://www.baeldung.com/spring-rest-with-zuul-proxy
https://stackoverflow.com/a/47856576/4587961
#Component
public class ProxyFilter extends ZuulFilter {
private static final String PROXY_PART = "/api/proxy";
private static final int PART_LENGTH = PROXY_PART.length();
#Autowired
public ProxyFilter() {
}
#Override
public boolean shouldFilter() {
final RequestContext context = RequestContext.getCurrentContext();
final String requestURI = retrieveRequestUri(context);
return requestURI.startsWith(PROXY_PART);
}
#Override
public Object run() {
final RequestContext context = RequestContext.getCurrentContext();
final String requestURI = retrieveRequestUri(context);
final String forwardUri = requestURI.substring(PART_LENGTH);
context.setRouteHost(buildUrl(forwardUri));
return null;
}
#Override
public String filterType() {
return "proxy";
}
#Override
public int filterOrder() {
return 0;
}
private String retrieveRequestUri(final RequestContext context) {
final HttpServletRequest request = context.getRequest();
return request.getRequestURI();
}
private URL buildUrl(final String uri) {
try {
return new URL(uri);
} catch (MalformedURLException e) {
throw new RuntimeException(String.format("Failed to forward request uri %s}.", uri), e);
}
}
}
This code allows me to forward requests with less effort. However, we also use client side load balancer Ribbon and circuit breaker Hystrix in Spring Cloud Zuul out of box. How to enable these features? Will they be enabled out of box in context.setRouteHost(forwardUrl);
I would like to add another approach, maybe it can also work.
Static application.yml file to configure Zuul proxy approach
This approach does not requre dynamic Zuul Filters.
application.yml
zuul:
routes:
user-application:
path: /api/users/**
serviceId: user-service
stripPrefix: false
sensitiveHeaders:
# I have to define all other services similarly.
jobs-application:
path: /api/proxy/jobs/**
serviceId: jobs-application
stripPrefix: true
sensitiveHeaders:
It will work only if I know all the services my clients need to call before I deploy the User application. What if a new application is added dynamically? Then I will have to update the configuration.
I have a web service hosted for sharing transaction details. There are two clients connected with my web service.
User setup at my end is like below
KEJESTORE ==== 201.XXX.XX.XX
MARIOBROS ==== 81.XX.XX.XX
I get their Username and ip address of the client server for security reasons using the below method in each time when they call transaction method;
MessageContext msgCtxt = wsCtxt.getMessageContext();
HttpServletRequest req = (HttpServletRequest) msgCtxt.get(MessageContext.SERVLET_REQUEST);
String clientIp = req.getRemoteAddr();
String user = wsCtxt.getUserPrincipal().getName();
But I get results like below some times (This happens very rarely).
KEJESTORE === 81.XX.XX.XX
MARIOBROS === 201.XXX.XX.XX
I can not figure out if there is any problem with my above code which i'm using for this purpose.
Please advice.
Edit:
Method
public TranResponse sendTransaction(WebServiceContext wsCtxt, Transaction tran){
MessageContext msgCtxt = wsCtxt.getMessageContext();
HttpServletRequest req = (HttpServletRequest) msgCtxt.get(MessageContext.SERVLET_REQUEST);
String clientIp = req.getRemoteAddr();
String user = wsCtxt.getUserPrincipal().getName();
// more code
}
I have an extension of this question. I have that exact code running on a Jetty Server, and other SOAP web services work perfectly. However, on this line:
HttpServletRequest req = (HttpServletRequest)mc.get(MessageContext.SERVLET_REQUEST);
System.out.println("Client IP = " + req.getRemoteAddr());
The server crashes with a null pointer exception. mc.get(MessageContext.SERVLET_REQUEST) is returning null.
By comparison, mc.get(MessageContext.HTTP_REQUEST_METHOD) returns "POST", so I assume that's working.
What can I do to fix this?
EDIT:
I've tried this fix to no avail.
I've also tried using the #Context annotation instead and got the same issue.
A System.out.println(mc) yields this:
{javax.xml.ws.wsdl.port={http://my.test.namespace.com/}testWSDLPort,
javax.xml.ws.soap.http.soapaction.uri="",
com.sun.xml.internal.ws.server.OneWayOperation=null,
javax.xml.ws.http.request.pathinfo=null,
...
...
and so on, and the list of values does NOT include javax.xml.ws.servlet.request, which is the value of MessageContext.SERVLET_REQUEST. What do I need to do to make sure the MessageContext has this value?
Currently the Jetty HTTP SPI JAX-WS implementation doesn't appear to properly inject the MessageContext into a web service. Try switching to Apache CXF instead. Once you have
cxf-2.6.2.jar
neethi-3.0.2.jar
xmlschema-core-2.0.3.jar
on your project build path, you have to create a servlet class that extends the CXFNonSpringServlet and overrides the loadBus function like so:
public class SOAPServlet extends CXFNonSpringServlet {
private static final long serialVersionUID = 1L;
private Map<String, Object> endpoints;
public SOAPServlet(){
endpoints = new HashMap<String, Object>();
}
#Override
public void loadBus(ServletConfig servletConfig) {
super.loadBus(servletConfig);
// You could add the endpoint publish codes here
Bus bus = getBus();
BusFactory.setDefaultBus(bus);
Set s = endpoints.entrySet();
Iterator p = s.iterator();
while(p.hasNext()){
Map.Entry m = (Map.Entry)p.next();
String address = (String)m.getKey();
Object impl = (Object)m.getValue();
System.out.println("Publishing " + address);
Endpoint.publish(address, impl);
}
}
public void publish(String address, Object impl){
endpoints.put(address, impl);
}
}
And then where you are configuring your server, add these lines:
Server server = new Server(8080);
// Configure SOAP servlet
SOAPServlet servlet = new SOAPServlet();
ServletHolder SOAPServletHolder = new ServletHolder(servlet);
ServletContextHandler SOAPContext = new ServletContextHandler(server,"/",ServletContextHandler.SESSIONS);
SOAPContext.addServlet(SOAPServletHolder, "/*");
// Set server context handlers
ContextHandlerCollection contexts = new ContextHandlerCollection();
contexts.setHandlers(new Handler []{SOAPContext});
server.setHandler(contexts);
// Publish SOAP Web service endpoints
servlet.publish("/MyWebServiceRelativeURL", new MyWebServiceImpl());
server.start();
server.join();
I encountered the same issue - instead of retrieving the SERVLET_REQUEST like that and getting null, I used the following:
com.sun.net.httpserver.HttpExchange server = (com.sun.net.httpserver.HttpExchange) mc.get("com.sun.xml.internal.ws.http.exchange");
System.out.println("Client IP = " + server.getRemoteAddress().toString());
This allows for retrieving of the IP address, port, etc.
I am Writting a Restful webservice with Jersey, this is the sample code:
#GET
#Produce(MediaType.APPLICATION_JSON)
public String findItems(){
...
}
and the url of findItem is localhost:8080/items
the method should verify the http authentication info(digest or basic) of this url before excutes, how to access authentication from the a url request first?
I would not put this in the controller itself, but in a com.sun.jersey.spi.container.ContainerRequestFilter around the resource classes you wish to protect, but should give you the basic idea.
#Context
private HttpServletRequest request;
#GET
#Produce(MediaType.APPLICATION_JSON)
public String findItems(){
String auth = request.getHeader("authorization");
if (auth != null) {
String basic_prefix = "Basic ";
if (auth.startsWith(basic_prefix)) {
String auth_data = new String(Base64.decode(auth.substring(basic_prefix.length())));
String [] user_and_key = auth_data.split(":", 2);
// user and password available here
} else {
// reject access
}
} else {
// reject access
}
}
Usually the authentication is handled by the container, you just have to add the corresponding constraint to web.xml to indicate what Uris should be protected and what kind of auth is required. Then in jersey you can get the roles and principal info from the SecurityContext you can inject to your resource.
I have define a Spring bean.
<beans>
<bean id="remoteService" class="edu.wustl.catissuecore.CaTissueApplictionServicImpl" />
</beans>
Is there any way to get the IP address of client in this class? Similarly as available in the servlet request.getRemoteAddr();
The simplest (and ugliest) approach is to use RequestContextHolder:
String remoteAddress = ((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes())
.getRequest().getRemoteAddr();
Without knowing more about your bean and how it's wired up, that's the best I can suggest. If your bean is a controller (either subclassing AbstractController or being annotated with #Controller) then it should be able to get direct access to the request object.
The best way to get client ip is to loop through the headers
private static final String[] IP_HEADER_CANDIDATES = {
"X-Forwarded-For",
"Proxy-Client-IP",
"WL-Proxy-Client-IP",
"HTTP_X_FORWARDED_FOR",
"HTTP_X_FORWARDED",
"HTTP_X_CLUSTER_CLIENT_IP",
"HTTP_CLIENT_IP",
"HTTP_FORWARDED_FOR",
"HTTP_FORWARDED",
"HTTP_VIA",
"REMOTE_ADDR" };
public static String getClientIpAddress(HttpServletRequest request) {
for (String header : IP_HEADER_CANDIDATES) {
String ip = request.getHeader(header);
if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
return ip;
}
}
return request.getRemoteAddr();
}
Construct this:
#Autowired(required = true)
private HttpServletRequest request;
and use like this:
request.getRemoteAddr()