I have some servlets running in Jetty, deployed on Heroku, handling POST requests.
Some, but not all, POST requests MUST come over https. Whether or not a request should be forced to be on https depends on the http body of the POST request.
I need to figure out, from inside the servlet, whether the incoming request used https (SSL) or not, so that I can send the appropriate response. However, nothing I have tried seems to work.
I tried the obvious HttpServletRequest.getProtocol() but that apparently returns the same constant whether the protocol was http or https.
I tried HttpServletRequest.isSecure() however that is returning false even though my test request was sent to a url starting with https://
When I call HttpUtils.getRequestURL( HttpServletRequest ).toString(); I get an apparrently reconstructed url that starts with "http://" even though my test request was sent to a url starting with "https://"
According to the post "Enforce HTTPS with Embedded Jetty on Heroku" heroku has some load balancers, and I should get the value of the "x-forwarded-proto" header. That header is blank.
FYI I am using the default SSL endpoint provided by the heroku api -- I am not using their SSL Endpoint extension, because this url is not being loaded in a browser (so I don't need a custom domain in the url).
Can anyone tell me how to tell if HTTPS was used in the incoming request?
I know nothing about Heroku, but if you're programmatically configuring Jetty (as opposed to using the XML configuration), you likely need to add the SecureRequestCustomizer to your HttpConfiguration. It sets the secure flag on the requests, as well as setting the scheme to HTTPS. You can find examples here, but briefly:
final HttpConfiguration httpConfig = new HttpConfiguration();
httpConfig.setSecurePort(httpsPort);
final ServerConnector httpConnector = new ServerConnector(server,
new HttpConnectionFactory(httpConfig));
httpConnector.setPort(httpPort);
server.addConnector(httpConnector);
final HttpConfiguration httpsConfig = new HttpConfiguration(httpConfig);
httpsConfig.addCustomizer(new SecureRequestCustomizer()); // !!!
final HttpConnectionFactory httpsFactory = new HttpConnectionFactory(httpsConfig);
final SslConnectionFactory sslFactory = new SslConnectionFactory(sslCtx,
httpsFactory.getProtocol());
final ServerConnector httpsConnector = new ServerConnector(server,
sslFactory, httpsFactory);
httpsConnector.setPort(httpsPort);
server.addConnector(httpsConnector);
I too found it rather surprising that this poorly documented step was necessary.
Related
Starting with Jetty 9.4.? it is possible to run HTTP and HTTPs on the same port. The gist of it is:
HttpConnectionFactory http = new HttpConnectionFactory();
SslConnectionFactory https = new SslConnectionFactory(sslCtxFactory, http.getProtocol());
DetectorConnectionFactory conFactory = new DetectorConnectionFactory(https);
ServerConnector connector = new ServerConnector(server, conFactory, http);
Since I only want to serve HTTPS, I would like to redirect each and every http://host:port/stuff to https://host:port/stuff. I know how to redirect with either subclass of RedirectRule or just with a handler called early.
The thing I am struggling with is: how do I figure out from the request that the connection is HTTP and not HTTPS?
When I look at the Request in the debugger, I found no hints, everything looks as if it is http even if the connection is https, Request.isSecure() is false, scheme is http and so on. The best thing I could come up with was:
if (Request.getHttpChannel().getEndPoint() instanceof SslConnection.DecryptedEndPoint())
Here is an annotated and clipped stack trace showing how my handlers are wrapped into each other:
at server.HttpToHttpsRedirectRule.matchAndApply(HttpToHttpsRedirectRule.java:34)
"^^ Here I do the matchAndApply myself and then use a jetty RedirectRule.apply"
"The redirect works OK, but figuring whether it is HTTPS does not work"
at org.eclipse.jetty.rewrite.handler.RuleContainer.apply(RuleContainer.java:166)
at org.eclipse.jetty.rewrite.handler.RuleContainer.matchAndApply(RuleContainer.java:145)
at org.eclipse.jetty.rewrite.handler.RewriteHandler.handle(RewriteHandler.java:317)
at org.eclipse.jetty.server.handler.gzip.GzipHandler.handle(GzipHandler.java:766)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127)
at server.LogHandler.handle(LogHandler.java:33)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127)
"^^^ the outermost handler"
[clip]
at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:380)
"^^^ we decoded the ssl and feed the lower levels plain HTTP"
[clip]
at org.eclipse.jetty.io.ssl.SslConnection$1.run(SslConnection.java:146)
"^^^ we are in SSL"
It is in my HttpToHttpsRedirectRule where I would like to figure out which connection it is. Is there a saner solution than the instanceof mentioned above?
To make sure the original scheme (and possibly more) information is kept, it is necessary to add a SecureRequestCustomizer to the http configuration like this:
HttpConfiguration httpConf = new HttpConfiguration();
httpConf.addCustomizer(new SecureRequestCustomizer());
HttpConnectionFactory http = new HttpConnectionFactory(httpConf);
SslConnectionFactory https = new SslConnectionFactory(sslCtxFactory, http.getProtocol());
DetectorConnectionFactory conFactory = new DetectorConnectionFactory(https);
ServerConnector connector = new ServerConnector(server, conFactory, http);
The crucial hint was found in the jetty mailing list.
I wrote a sample Java REST Client application to access a resource, but it gives the 500 error. Here resource can be accessed through HTTPS only.
Client client = ClientBuilder.newClient();
WebTarget target = client.target("https://<location>").path("<path>");
MultivaluedMap<String, String> formData = new MultivaluedHashMap<String, String>();
formData.add("Amount", "100.00");
formData.add("Currency", "NZD");
formData.add("EmailAddress", "test#gmail.com");
Response response = target.request(MediaType.APPLICATION_FORM_URLENCODED_TYPE).post(Entity.form(formData));
When i invoked the request from Web Browser, it works fine.
What is the problem with my code?
Since Protocol "HTTPS" requires some additional configurations?
http://www.bhaveshthaker.com/24/calling-invoking-secure-restful-web-service-over-https-with-jax-rs-in-java-without-keystore-truststore-information/
For your reference..
Yes, HTTP and HTTPS cannot be used interchangeably. You need different client configuration for same if your server code is using SSL certificate/ user created certificate.
I have a dot net application that call a java web service. I am trying to implement authentication by passing credentials to the java service. Here is the dot net code setting the credentials. How can I get these credentials in my java application? They aren't set in the headers...
System.Net.NetworkCredential serviceCredentials = new NetworkCredential("user", "pass");
serviceInstance.Credentials = serviceCredentials;
serviceInstance is an instance of SoapHttpClientProtocol.
I've tried injecting the WebServiceContext like so
#Resource
WebServiceContext wsctx;
and pulling the crentials from the headers but they aren't there.
You are not passing the credentials to your service the correct way. In order to get the Authorize http request header do the following:
// Create the network credentials and assign
// them to the service credentials
NetworkCredential netCredential = new NetworkCredential("user", "pass");
Uri uri = new Uri(serviceInstance.Url);
ICredentials credentials = netCredential.GetCredential(uri, "Basic");
serviceInstance.Credentials = credentials;
// Be sure to set PreAuthenticate to true or else
// authentication will not be sent.
serviceInstance.PreAuthenticate = true;
Note: Be sure to set PreAuthenticate to true or else authentication will not be sent.
see this article for more information.
I had to dig-up some old code for this one :)
Update:
After inspecting the request/response headers using fiddler as suggested in the comments below a WWW-Authenticate header was missing at the Java Web Service side.
A more elegant way of implementing "JAX-WS Basic authentication" can be found in this article here using a SoapHeaderInterceptor (Apache CXF Interceptors)
I am having a problem getting the Apache HttpClient to connect to a service external to my virtualised development environment.
To access the internet (e.g. api.twitter.com) I need to call a local URL (e.g. api.twitter.com.dev.mycompany.net), which then forwards the request to real host.
The problem is, that to whatever request I send, I get a 404 Not Found response.
I have tried debugging it using wget, and it appears the problem is, that the destination server identifies the desired resource by using both the request URL and the hostname in the Host header. Since the hostname does not match, it is unable to locate the resource.
I have (unsuccessfully) tried to override the Host header by setting the http.virtual-host parameter on the client like this:
HttpClient client = new DefaultHttpClient();
if (envType.isWithProxy()) {
client.getParams().setParameter(ClientPNames.VIRTUAL_HOST, "api.twitter.com");
}
Technical details:
Client is used as an executor in RESTeasy to call the REST API. So "manually" setting the virtual host (as described here) is not an option.
Everything is done via HTTPS/SSL - not that I think it makes a difference.
Edit 1: Using a HttpHost instead of a String does not have the desired effect either:
HttpClient client = new DefaultHttpClient();
if (envType.isWithProxy()) {
HttpHost realHost = new HttpHost("api.twitter.com", port, scheme);
client.getParams().setParameter(ClientPNames.VIRTUAL_HOST, realHost);
}
Edit 2: Further investigation has revealed, that the parameter needs to be set on the request object. The following is the code v. 4.2-aplha1 of HttpClient setting the virtual host:
HttpRequest orig = request;
RequestWrapper origWrapper = wrapRequest(orig);
origWrapper.setParams(params);
HttpRoute origRoute = determineRoute(target, origWrapper, context);
virtualHost = (HttpHost) orig.getParams().getParameter(
ClientPNames.VIRTUAL_HOST);
paramsare the parameters passed from the client. But the value for 'virtualHost' is read from the request parameters.
So this changes the nature of the question to: How do I set the VIRTUAL_HOST property on the requests?
ClientPNames.VIRTUAL_HOST is the right parameter for overriding physical host name in HTTP requests. I would just recommend setting this parameter on the request object instead of the client object. If that does not produce the desired effect please post the complete wire / context log of the session (see logging guide for instructions) either here or to the HttpClient user list.
Follow-up
OK. Let's take a larger sledge hammer. One can override content of the Host header using an interceptor.
DefaultHttpClient client = new DefaultHttpClient();
client.addRequestInterceptor(new HttpRequestInterceptor() {
public void process(
final HttpRequest request,
final HttpContext context) throws HttpException, IOException {
request.setHeader(HTTP.TARGET_HOST, "www.whatever.com");
}
});
One can make the interceptor clever enough to override the header selectively, only for specific hosts.
Im using Metro 2.0 and J2SE5. The application I have written does not know about the external WebService at compile time, it finds them at runtime based on a business logic XML file, therefore I perform a WSDL request.
The sample code I have written is as follows:
String wsdlServiceName = ...;
String wsdlURL = ...;
Document payload = ...;
final String nsURI = ...;
final QName serviceName = new QName(nsURI, wsdlServiceName + "Service");
final QName servicePort = new QName(nsURI, wsdlServiceName + "Port");
// Create service and the dispatcher for the SOAP message
Service service = Service.create(new URL(wsdlURL), serviceName);
dispatch = service.createDispatch(servicePort, SOAPMessage.class, Service.Mode.MESSAGE);
// Set timeouts
dispatch.getRequestContext().put("com.sun.xml.internal.ws.request.timeout", 3000);
dispatch.getRequestContext().put("com.sun.xml.internal.ws.connect.timeout", 3000);
// Create the outgoing SOAP request
SOAPBinding soapBinding = (SOAPBinding) dispatch.getBinding();
request = soapBinding.getMessageFactory().createMessage();
SOAPBody requestBody = request.getSOAPBody();
requestBody.addDocument(payload);
// Invoke web service operation
SOAPMessage response = dispatch.invoke(request);
The timeout works correctly when the Web Service is invoked ( dispatcher.invoke(request) )
HOWEVER the WSDL is requested before the timeouts are set, and if the Web Service is not responding it takes 90 seconds before the connection is timed-out.
Is it possible to set the timeouts before the WSDL is requested ? You need a dispatcher to set the timeouts, but that is done AFTER the Service is created that requests the WSDL?! (ie. Service.create() )
Try the setting system property
sun.net.client.defaultConnectTimeout
but from Networking Properties it says it may not be supported for in future releases
However I would suggest to cache the WSDL and not access it remotely.
It is better performance wise especially if you are working with a WSDL that is not expected to change frequently.
We just ran into this same issue, and tried all of the settings mentioned above - likewise, to no avail.
Our solution was to download the WSDL to a temporary file first, using URL.openConnection() (setting the timeouts on the connection with: URLConnection.setConnectionTimeout(), and URLConnection.setReadTimeout()). We then generate a url for this file with: File.toURI().toURL(), which we pass to the service constructor that takes a URL.
This approach lets you dynamically fetch the current WSDL, while explicitly controlling the timeout. We then set the timeout for subsequent calls to the service as you show in the original post.