I am trying to make my web service secure by making one of the methods require HTTP Basic authentication. In order to do this, I've implemented a custom Interceptor called LoginInterceptor that checks the requested URL, and if it corresponds to a method called open, it checks the message header for the username and password.
If there are no authentication fields in the header, the response code is set to HTTP_UNAUTHORIZED, and if the username or password is incorrect, the response code is set to HTTP_FORBIDDEN. Here's the code:
public LoginInterceptor() {
super(Phase.RECEIVE);
addAfter(RequestInterceptor.class.getName()); //another custom interceptor, for some simple redirections.
}
public void handleMessage(Message message) throws Fault {
String requestURI = message.get(Message.REQUEST_URI).toString();
String methodKeyword = requestURI.substring("localhost".length()+1).split("/")[0];
if(methodKeyword.equals("open")) {
AuthorizationPolicy policy = message.get(AuthorizationPolicy.class);
if(policy == null) {
sendErrorResponse(message, HttpURLConnection.HTTP_UNAUTHORIZED);
return;
}
//userPasswords is a hashmap of usernames and passwords.
String realPassword = userPasswords.get(policy.getUserName());
if (realPassword == null || !realPassword.equals(policy.getPassword())) {
sendErrorResponse(message, HttpURLConnection.HTTP_FORBIDDEN);
}
}
}
//This is where the response code is set, and this is where I'd like to write the response message.
private void sendErrorResponse(Message message, int responseCode) {
Message outMessage = getOutMessage(message);
outMessage.put(Message.RESPONSE_CODE, responseCode);
// Set the response headers
Map responseHeaders = (Map) message.get(Message.PROTOCOL_HEADERS);
if (responseHeaders != null) {
responseHeaders.put("Content-Type", Arrays.asList("text/html"));
responseHeaders.put("Content-Length", Arrays.asList(String.valueOf("0")));
}
message.getInterceptorChain().abort();
try {
getConduit(message).prepare(outMessage);
close(outMessage);
} catch (IOException e) {
e.printStackTrace();
}
}
private Message getOutMessage(Message inMessage) {
Exchange exchange = inMessage.getExchange();
Message outMessage = exchange.getOutMessage();
if (outMessage == null) {
Endpoint endpoint = exchange.get(Endpoint.class);
outMessage = endpoint.getBinding().createMessage();
exchange.setOutMessage(outMessage);
}
outMessage.putAll(inMessage);
return outMessage;
}
//Not actually sure what this does. Copied from a tutorial online. Any explanation is welcome
private Conduit getConduit(Message inMessage) throws IOException {
Exchange exchange = inMessage.getExchange();
Conduit conduit = exchange.getDestination().getBackChannel(inMessage);
exchange.setConduit(conduit);
return conduit;
}
private void close(Message outMessage) throws IOException {
OutputStream os = outMessage.getContent(OutputStream.class);
os.flush();
os.close();
}
This works fine, however, I want to also return a message in the response, something like "incorrect username or password". I've tried, from within the sendErrorResponse method, doing:
outMessage.setContent(String.class, "incorrect username or password")
and I set the content-length to "incorrect username or password".length(). This doesn't work, I guess because the Apache CXF Messages use InputStreams and OutputStreams.
So I tried:
OutputStream os = outMessage.getContent(OutputStream.class);
try {
os.write("incorrect username or password".getBytes() );
outMessage.setContent(OutputStream.class, os);
} catch (IOException e) {
e.printStackTrace();
}
This doesn't work either. When stepping through with a debugger, I notice that os is null When testing with Postman, I get:
Could not get any response This seems to be like an error connecting
to http://localhost:9090/launcher/open. The response status was 0.
Check out the W3C XMLHttpRequest Level 2 spec for more details about
when this happens.
Pressing ctrl+shif+c (opening up dev tools) in Chrome, and checking the networks tab, I see:
"ERR_CONTENT_LENGTH_MISMATCH"
I've tried using an XMLStreamWriter, but that wans't any better.
Questions:
I can return the correct response code (401 Unauthorized and 403 forbidden), but how do I return a message in the response body?
Do I need to specifically extend a particular OutInterceptor like JASRXOutInterceptor in order to modify the message content?
I tried using a JAASInterceptor before, but I didn't manage to get that working. Could someone show me how to implement it that way, if that's somehow easier?
I could also just throw a fault like this: throw new Fault("incorrect username or password", Logger.getGlobal());, but then the HTTP response code would be 500. I'd prefer to return a proper 401 or 403 response.
Note:
Right now I'm still using HTTP for the transport layer. Once I fix this, I'll change to HTTPS.
Basically, what I wanted to do is return a fault with a HTTP response code of 401 (unauthorized) or 403 (forbidden) instead of 500 (server error). Turns out Apache CXF provides a simple way of doing that, using the Fault.setStatusCode(int) method, as I found from this question on Stack Overflow: how to throw a 403 error in Apache CXF - Java
So this is what my handleMessage method looks like now:
public void handleMessage(Message message) throws Fault {
String requestURI = message.get(Message.REQUEST_URI).toString();
String methodKeyword = requestURI.substring("localhost".length()+1).split("/")[0];
if(methodKeyword.equals("open")) {
AuthorizationPolicy policy = message.get(AuthorizationPolicy.class);
if(policy == null) {
Fault fault = new Fault("incorrect username or password", Logger.getGlobal());
fault.setStatusCode(401);
throw fault;
}
String realPassword = userPasswords.get(policy.getUserName());
if (realPassword == null || !realPassword.equals(policy.getPassword())) {
Fault fault = new Fault("incorrect username or password", Logger.getGlobal());
fault.setStatusCode(403);
throw fault;
}
}
}
I removed the other methods, they were unnecessary.
Related
I am using a method where it calls another REST API to retrieve an ID from the DB. When I run the veracode scan for the class I am getting Security flaw "Server-side Request Forgery" at below line.
response = resttemplate.getForEntity(resturl, String.class);
Not sure How to fix this issue. Any help is appreciated. Below is my full code for that method.
public static String getIDFromDB(String resturl) {
String id = null;
RestTemplate resttemplate = new RestTemplate();
ResponseEntity<String> response = new ResponseEntity<>(HTTPStatus.OK)
try {
response = resttemplate.getForEntity(resturl, String.class);
if (response.getStatusCode == HTTPStatus.OK && response.getBody.trim() != null) {
id = response.getBody.trim() ;
}
} Catch(Exception e) {
log.error("failed to get msgID: {}", e);
}
}
This is because you are allowing in your code to pass the resturl completely in your code, so it enables the attacker to bypass and route the URL to their intended destination.
To avoid this, so should externalise and refer the URL having domain and the application contexts with operation name in config files or dB
I'm calling a REST Service that returns a JSON String. It works, but I'm not sure how to handle the exceptions and return values. Here are my two methods I wrote:
public static String callRestService(String id) {
try {
URL url = new URL("http://"localhost:8080/rest/api/2/issue/" + id);
String basicAuth = ConnectionHelper.getServerAuthentication(serverConfig.get("authenticationType"),
serverConfig.get("username"), serverConfig.get("password"));
HttpURLConnection connection = ConnectionHelper.getHttpURLConnection(url, "GET", "Accept", basicAuth);
if (connection != null) {
InputStream responseStream = connection.getInputStream();
String response = StringHelper.convertInputStreamToString(responseStream);
connection.disconnect();
return response;
}
return "";
} catch (Exception e) {
return "";
}
}
public static HttpURLConnection getHttpURLConnection(URL url, String requestMethod, String requestProperty,
String authentication) {
try {
HttpURLConnection connection = (HttpURLConnection)url.openConnection();
if (authentication != null && !authentication.isEmpty()) {
connection.addRequestProperty("Authorization", authentication);
}
connection.setRequestMethod(requestMethod);
connection.addRequestProperty(requestProperty, "application/json");
return connection;
} catch (Exception e) {
return null;
}
}
Is my return value and exception handling ok? Or is there a better way to do this?
For better client side handling you should have an Enum with return cases
for example if we are building a registration module your enum should be like the following :
public enum RestResponseEnum{
DONE(1,"done"),DUPLICATE_RECORD(2,"Sorry this is a duplicate record"),ERROR(3,"There is an error happened")
//Getter & Setter
private int code;
//Getter & Setter
private String msg;
private(int code,String msg){
this.code=code;
this.msg=msg;
}
public static String getAsJson(RestResponseEnum restResponseEnum){
JSONObject jsonObject=new JSONObject();
jsonObject.put("code", restResponseEnum.getCode());
jsonObject.put("message", restResponseEnum.getMsg());
return jsonObject.toString();
}
}
Use it like this :
{
// Your function code
if(registeredEmailIsFoundInDatabase){
return RestResponseEnum.getAsJson(RestResponseEnum.DUPLICATE_RECORD);
}
}
You should always faclitate and clearify the response to the client
you can see this methodology from dealing with most of apis like this one from github : https://api.github.com/users/any/any
If it is a proper REST service it will add additional information about a call in the http response code. So if it doesnt start with 2, there is no point in parsing the response body at all (in case there is no contract to return error details in the body).
How to handle your exception much depends on your current application. General rules of thumb are:
Log exceptions
Handle them on an appropriate level
Sometimes you need to ensure encapsulation and handle them where they occur, sometimes it's okay to rethrow them an catch them globally. E.g. you are using a framework like JSF, user has triggered an external service call, log the exception, rethrow it, catch it and inform the user about it without sharing too much technical details. Like:
Error: YOUR_ERROR_CODE has occured. Please contact technical support
if this keeps happening.
Example:
if (connection.getResponseCode().startsWith("2") {
// do stuff
// if any checked exception occurs here, add it to throws clause and let the caller catch it
}
else if connection.getResponseCode().equals("404") {
throw new EntityNotFoundRuntimeException(...);
}
...
But whether or not this solution is good for your case depends on your architecture.
Using Retrofit 1.6.0, OkHTTP 2.0.0, and OkHTTP-UrlConnection 2.0.0.
I am doing a POST to a service using Retrofit to a URL that does not exist. The failure callback is called, as expected. However, the RetrofitError parameter does not have a response. I would really like to grab the HTTP status code that was returned by using
error.getResponse().getStatus()
but since getResponse() returns null, I can't.
Why is getResponse() null and how can I get the status?
Thanks.
Also, the error I am receiving is UnknownHostException, as expected. Repeat: I am expecting this error. I want to know how to get the HTTP status code or why error.getResponse() returns null.
Edit: Here's some code:
RestAdapterBuilderClass.java
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint("http://badURL.DoesntMatter/");
.setRequestInterceptor(sRequestInterceptor)
.setLogLevel(RestAdapter.LogLevel.FULL)
.build();
sService = restAdapter.create(ServiceInterface.class);
ServiceInterface.java
#POST("/Login")
void login(#Body JsonObject body, Callback<String> callback);
CallbackClass.java
#Override
public void failure(RetrofitError error) {
if (error.getResponse() == null) {
// error.getResponse() is null when I need to get the status code
// from it.
return;
}
}
When you get an UnknownHostException it means, that you were not able to establish a connection to the server. You cannot, in fact, expect a HTTP status in that case.
Naturally you only get a Http response (and with that a status) when you can connect to a server.
Even when you get a 404 status code, you made a connection to the server. That is not the same as a UnknownHostException.
The getResponse() can return null if you didn't get a response.
RetrofitError has a method called isNetworkError() that you can use to detect a failed request due to network problems. I usually add a small helper method like this:
public int getStatusCode(RetrofitError error) {
if (error.isNetworkError()) {
return 503; // Use another code if you'd prefer
}
return error.getResponse().getStatus();
}
and then use that result to handle any additional failure logic (logout on 401, display error message on 500, etc).
Just to expand on #lyio's answer, I found from Fabric logging that getKind() sometimes returns UNEXPECTED and then if you parse the message you get timeouts and connection issues so I wrote the utility class below.
public class NetworkUtils {
// Compiled from Fabric events
private static final List<String> networkErrorStrings = new ArrayList<>(Arrays.asList(
"Unable to resolve host",
"Connection closed by peer",
"Failed to connect",
"timeout",
"Connection timed out"));
public static boolean isNetworkError(#Nullable RetrofitError retrofitError) {
if (retrofitError != null) {
if (retrofitError.getKind() != RetrofitError.Kind.NETWORK) {
for (String error : networkErrorStrings) {
if (retrofitError.getMessage().contains(error)) {
return true;
}
}
} else {
return true;
}
}
return false;
}
}
I am using Retrofit 2. When endpoint url end with "/" as in your case and again in your interface it starts with "/" [#POST("/Login")] causes this problem. Remove the "/" from .setEndpoint() method
I've a SOAP web service built in Java.
If my method runs into an exception I want to return a "HTTP CODE 500".
Is it possible? If yes how?
(Web service is running on Tomcat 6)
maybe you should simply throw a qualified Exception yourself which then will be sent back to the client as a soap fault.
W3C tells us this:
In case of a SOAP error while processing the request, the SOAP HTTP
server MUST issue an HTTP 500 "Internal Server Error" response and
include a SOAP message in the response containing a SOAP Fault element
(see section 4.4) indicating the SOAP processing error.
http://www.w3.org/TR/2000/NOTE-SOAP-20000508/
Messing with http response codes could be dangerous as some other client might expect a different response. In your case you'd be lucky because you want exactly the the behaviour as specified by W3C. So throw an Exception ;)
How to do that? Take a look here:
How to throw a custom fault on a JAX-WS web service?
Greetings
Bastian
Since the JAX-WS is based on servlets, you can do it. You can try the next:
#WebService
public class Calculator {
#Resource
private WebServiceContext ctx;
public int division (int a, int b) {
try {
return a / b;
} catch (ArithmeticException e) {
sendError(500, "Service unavailable for you.");
return -1; // never send
}
}
private void sendError(int status, String msg) {
try {
MessageContext msgCtx = ctx.getMessageContext();
HttpServletResponse response =
(HttpServletResponse) msgCtx.get(MessageContext.SERVLET_RESPONSE);
response.sendError(status, msg);
} catch (IOException e) {
// Never happens or yes?
}
}
}
However, I prefer to use JAX-RS to do something similar.
#PUT
#Path("test")
#Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response update( //
#FormParam("id") int id,
#FormParam("fname") String fname,
#FormParam("lname") String lname
) {
try {
// do something
return Response.ok("Successfully updated",
MediaType.TEXT_PLAIN_TYPE).build();
} catch (Exception e) {
LOG.error("An error occurred", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity("An error occurred")
.type(MediaType.TEXT_PLAIN_TYPE).build();
}
}
I have written a SOAP web-service using CXF which is being called by a SAP system, in the payload there is a word with a special character which occurs multiple times. However, I read this word differently in some random cases, i.e. in a one payload I see the word as Kliëntbestuurder and in another as Kli��ntbestuurder.
The SAP system calling my service via SAP PI only have the one word.
UPDATE:
So it seems that it was not the web-service communication that was getting confused but rather the interceptor that I had written to dump the soap envelope for me to be able to scrutinise. The interceptor is as follows:
public class WebServiceMessageInterceptor extends AbstractPhaseInterceptor<Message> {
public WebServiceMessageInterceptor() {
super(Phase.RECEIVE);
}
#Override
public void handleMessage(Message message) throws Fault {
final LoggingMessage buffer = new LoggingMessage("", "");
String encoding = (String) message.get(Message.ENCODING);
if (encoding != null) {
buffer.getEncoding().append(encoding);
}
Object headers = message.get(Message.PROTOCOL_HEADERS);
if (headers != null) {
buffer.getHeader().append(headers);
}
InputStream is = message.getContent(InputStream.class);
if (is != null) {
CachedOutputStream outputStream = new CachedOutputStream();
try {
IOUtils.copy(is, outputStream);
outputStream.flush();
is.close();
message.setContent(InputStream.class, outputStream.getInputStream());
outputStream.writeCacheTo(buffer.getPayload(), "UTF-8", -1);
outputStream.close();
FileUtils.writeStringToFile(new File("/tmp/soap" + System.currentTimeMillis() + ".log"), buffer.toString(), "UTF-8");
} catch (IOException e) {
e.printStackTrace();
throw new Fault(e);
}
}
}
Any further ideas why my interceptor is not using UTF-8?
This might be related to not using encoding consistently across and within the services. I suggest you help yourself by reading this excellent tutorial - Unicode - How to get the characters right? end to end. Then ask follow up questions once you narrowed down the scope of the error.
Check the http headers on the response you are sending back from your web services. You can use the Raw tab in soapUI to view the headers. If you don't see something like
Content-Type: text/xml;charset=UTF-8
then you can force CXF to add it to the response by doing something like this in your WebMethods:
MessageContext ctx = context.getMessageContext();
ctx.put(Message.CONTENT_TYPE, "text/xml;charset=UTF-8");
where context is the javax.xml.ws.WebServiceContext injected into your class.
You should also verify that the client to your web service is also using the correct encoding. You may be sending a valid response to him.