12

I have the following scenario:

Server: Jetty (with configured JAAS)

Client: Jersey invoked via JUnit (via Maven)

I have JAAS set up in the web server. I am using the client part as a test.

On the server side users are authenticated through a form with Basic authentication handled via JAAS. Obviously, users need to be authenticated before being able to view certain pages.

I would like to be able to login via the Jersey before trying to access a secured page. How can this be done? I have checked that you can define a filter, but I'm not quite sure how to use that. And -- once the user is logged in via the form, how can I proceed (from the client-side) to the page I'm actually interested in?

I would really appreciate it, if somebody could show me an example how this is done on the client side with Jersey.

I have the following JUnit test case method:

@Test
public void testLogin()
        throws IOException
{
    String URL_LOGIN = "http://localhost:9080/foo/auth.html";
    Client client = Client.create();

    String username = "me";
    String password = "me";

    final HTTPBasicAuthFilter authFilter = new HTTPBasicAuthFilter(username, password);
    client.addFilter(authFilter);
    client.addFilter(new LoggingFilter());

    WebResource webResource = client.resource(URL_LOGIN);
    // I even tried:
    // webResource.header("Authorization", "Basic " + "base64encoded_userid:password").type("application/xml");

    String page = webResource.post(String.class);

    System.out.println(page);
}

Please, note:

1) http://localhost:9080/foo/auth.html is the page I am supposed to be seeing upon successful auth.

2) I am actually seeing the output of http://localhost:9080/foo/login.html.

3) Obviously, through a browser, I can successfully login via the login.html page.

What do I seem to be missing out here?

carlspring
  • 31,231
  • 29
  • 115
  • 197

5 Answers5

23

With Basic auth you don't need to go to any login page at all. If the server is configured to use Basic auth, then you can make requests to any protected page if you include the basic auth header in your requests. The Jersey filter takes care of that. So, if Basic auth would really be what the server is using, then your code should work.

Given it does not work and the way it does not work I am pretty sure the server is configured to use form-based authentication instead of the basic authentication.

For the form-based authentication to work, you'll have to send a post request with form data including user name and password to log in and then set cookies you receive from the server to your subsequent requests (since the server - once you log in - will set the session cookie).

Look at how the login.html looks like - it should contain a form. If it is using the standard servlet form auth., the action URL of that form should be "j_security_check" and there should be two form parameters: j_username and j_password. If that is the case, you can try something like the following:

String URL_LOGIN = "http://localhost:9080/foo/j_security_check";
String URL_DATA = "http://localhost:9080/foo/auth.html";
Client client = Client.create();

// add a filter to set cookies received from the server and to check if login has been triggered
client.addFilter(new ClientFilter() {
    private ArrayList<Object> cookies;

    @Override
    public ClientResponse handle(ClientRequest request) throws ClientHandlerException {
        if (cookies != null) {
            request.getHeaders().put("Cookie", cookies);
        }
        ClientResponse response = getNext().handle(request);
        // copy cookies
        if (response.getCookies() != null) {
            if (cookies == null) {
                cookies = new ArrayList<Object>();
            }
            // A simple addAll just for illustration (should probably check for duplicates and expired cookies)
            cookies.addAll(response.getCookies());
        }
        return response;
    }
});

String username = "me";
String password = "me";

// Login:
WebResource webResource = client.resource(URL_LOGIN);

com.sun.jersey.api.representation.Form form = new Form();
form.putSingle("j_username", username);
form.putSingle("j_password", password);
webResource.type("application/x-www-form-urlencoded").post(form);

// Get the protected web page:
webResource = client.resource(URL_DATA);
String response = webResource.get(String.class);

I haven't tested this, so maybe there will be some typos or bugs.

carlspring
  • 31,231
  • 29
  • 115
  • 197
Martin Matula
  • 7,969
  • 1
  • 31
  • 35
  • Martin, thanks for this example! This seems to be what I'm after. However, in the `response` result I keep getting the login page (`/foo/login.html`), instead of the `/foo/auth.html` page which should print that I'm logged in. What am I missing here? I tried your example exactly as it is, except for correcting com.sun.jersey.representation.Form to com.sun.jersey.representation.api.Form. – carlspring Mar 14 '12 at 20:43
  • Use logging filter and check how requests and responses look like - particularly the response to the post. See if the server sends Set-Cookie in the response and if the subsequent client requests have the cookie. Also is what response you get from post - if the login succeeded or not. Not sure if for form login you need to first try to request the protected page and only then do the login - try that. Also see what login.html page looks like - if it really uses j_security_check and j_username and j_password form params. – Martin Matula Mar 14 '12 at 21:23
  • Martin, it uses j_security_check and j_username/j_password. I have enabled logging in your example in order to see the headers. I have the following output which I've posted on pastebin: http://pastebin.com/dGMHpk7g Perhaps you could have a look as well...? – carlspring Mar 14 '12 at 21:58
  • OK. So, before the "// login" comment line from the snippet above, try to do this: `client.resource(URL_DATA).get(String.class);` – Martin Matula Mar 15 '12 at 10:10
  • Martin, I am quite sure your answer is correct. I appear to be having problems with my app server's JAAS setup which prevents me from logging in, thus the code you showed is currently failing. I will re-try it once I have sorted out my setup problem. Thanks! – carlspring Mar 19 '12 at 14:04
  • 2
    Good solution for Jersey 1.x but what about Jersey 2.x? – jon Jul 28 '14 at 17:51
10

I do not use Jersey but this is not a Jersey issue per se.

First, you must be using either FORM login or BASIC authentication; unless this is a custom development I doubt you are using a 'Form with Basic Authentication'.

If you are using Basic Authentication, then things are pretty simple (though inefficient): BASIC authentication is stateless and you must send the Authorization: Basic xxxx HTTP header on EVERY request.

If you are using FORM login, things a re a bit more involved. On every request, you are supposed to send the Session Id (stored in a Cookie or using URL rewriting). If you do not send one, or if the associated session is invalidated (because it is expired for instance), the server will send a 302 (redirect) and the Login Form. You are then supposed to perform a FORM POST to the URL indicated in the form with the username and password as parameters. If authentication is successful, the server will then send the response to the original request (and a new session id). In this scenario, programmatic request must therefore be able to

  • 1. Handle cookies (unless you force URL rewriting which is unlikely)
  • 2. Detect when they get a 302 and login form back on the request and complete the required Form post before they continue.
This is true for any HTTP call (AJAX, REST, etc...) and please note that the fact that your server is using JAAS or another authentication and authorization mechanism has no bearing: it is a session management issue.

Alternatively, a Jersey specific solution is available by intercepting the calls, here:

How to get jersey logs at server?

Community
  • 1
  • 1
Bruno Grieder
  • 28,128
  • 8
  • 69
  • 101
  • Thanks! This is some rather useful info. Much appreciated, but I am trying to figure out how to properly do this with the Jersey Client. – carlspring Mar 14 '12 at 03:08
3

Autorization for JAAS:

    String URL_DATA = "http://localhost:9080/foo/auth.html";
    Client client = Client.create();

    String username = "me";
    String password = "me";

    client.addFilter(new HTTPBasicAuthFilter(username, password));

    // Get the protected web page:
    WebResource webResource = client.resource(URL_DATA);
    String response = webResource.get(String.class);
    System.out.println(response);
milosz
  • 650
  • 8
  • 14
2

To access Rest End Point from Form Based Authentication One need to register a Form Authentication Filter with the JAX-RS Jersey client. One useful filter class is given at the following git hub link.

https://github.com/imixs/imixs-melman/blob/master/src/main/java/org/imixs/melman/FormAuthenticator.java

use FormAuthenticator.java in your project and in Rest Jersey client do the following:

// Constructor of jersey Rest client
public RestClass(String username, String password)
{
    private WebTarget webTarget;
    private Client client;

    private static final String BASE_URI = "http://localhost:8080/SampleRest/webresources";

    client = javax.ws.rs.client.ClientBuilder.newClient();
    client.register(new FormAuthenticator(BASE_URI, user,  password));

    webTarget = client.target(BASE_URI).path("restpath");
}

And everything works fine. I have used it and this issue is solved. JAX-RS API must give this authenticator in their release.

Note: same username and password which were used in form based authentication are to be supplied from calling client in servlets.

as RestClient rs = new RestClient(username, password);

** Aplication class should register RolesAllowedDynamicFeature for roles to take effect ** MyApplication.java in Web Project where Rest class and FormAuthenticator.java resides rest package

@ApplicationPath("webresources")

public class MyApplication extends ResourceConfig {

    public MyApplication() {
        packages("entity.service");
        packages("rest");
        register(RolesAllowedDynamicFeature.class);
    }
}
weaknespase
  • 1,014
  • 8
  • 15
1

For me these changes solved the issue:

String URL_LOGIN = "http://localhost:9080/foo/j_security_check";
String URL_DATA = "http://localhost:9080/foo/auth.html";
Client client = Client.create();

// add a filter to set cookies received from the server and to check if login has been triggered
client.addFilter(new ClientFilter() {
    private ArrayList<Object> cookies;

    @Override
    public ClientResponse handle(ClientRequest request) throws ClientHandlerException {
        if (cookies != null) {
            request.getHeaders().put("Cookie", cookies);
        }
        ClientResponse response = getNext().handle(request);
        // copy cookies
        if (response.getCookies() != null) {
            if (cookies == null) {
                cookies = new ArrayList<Object>();
            }
            // A simple addAll just for illustration (should probably check for duplicates and expired cookies)
            cookies.addAll(response.getCookies());
        }
        return response;
    }
});

String username = "me";
String password = "me";


// Get the protected web page: (this will make the server know that someone will try to access the protected resource)
WebResource  webResource = client.resource(URL_DATA);
String response = webResource.get(String.class);


// Login:
webResource = client.resource(URL_LOGIN);

com.sun.jersey.api.representation.Form form = new Form();
form.putSingle("j_username", username);
form.putSingle("j_password", password);
webResource.type("application/x-www-form-urlencoded").post(form);


// Get the protected web page: (this time the service will return the data)
webResource = client.resource(URL_DATA);
response = webResource.get(String.class);
tomrozb
  • 25,773
  • 31
  • 101
  • 122