7

I want to write a servlet that wraps around a set of resources and needs to protect them with basic HTTP auth; the submitted username/pass will be checked against the backend database before serving the file.

Does anyone have any working examples of this? I tried the sample at http://www.coderanch.com/t/352345/Servlets/java/HTTP-basic-authentication-Web-Applications but it kept returning an IllegalStateException in the sendError call.

Beryllium
  • 12,808
  • 10
  • 56
  • 86
Roy Tang
  • 5,643
  • 9
  • 44
  • 74
  • I just used the sample in the link. – Roy Tang Mar 25 '13 at 11:13
  • 1
    @Roy, example in your post worked fine for me. I'm not sure why its giving error for you. could you please update your post with the stacktrace? – Avinash K.P Mar 25 '13 at 11:28
  • IMO, a servlet filter is more appropriate for this, as it is independent of the servlet and can be applied to multiple servlets if necessary. This may be more advanced than what you need presently, so please consider this only as a friendly pointer, not a critique. – Taylor Aug 20 '13 at 14:21

1 Answers1

21

Here is some code that returns a Credential object (bean object holding login and password).

public Credentials credentialsWithBasicAuthentication(HttpServletRequest req) {
    String authHeader = req.getHeader("Authorization");
    if (authHeader != null) {
        StringTokenizer st = new StringTokenizer(authHeader);
        if (st.hasMoreTokens()) {
            String basic = st.nextToken();

            if (basic.equalsIgnoreCase("Basic")) {
                try {
                    String credentials = new String(Base64.decodeBase64(st.nextToken()), "UTF-8");
                    LOG.debug("Credentials: " + credentials);
                    int p = credentials.indexOf(":");
                    if (p != -1) {
                        String login = credentials.substring(0, p).trim();
                        String password = credentials.substring(p + 1).trim();

                        return new Credentials(login, password);
                    } else {
                        LOG.error("Invalid authentication token");
                    }
                } catch (UnsupportedEncodingException e) {
                    LOG.warn("Couldn't retrieve authentication", e);
                }
            }
        }
    }

    return null;
}

It works well, even with a password as funky as :&=/?é$£.

Here is a basic unit test for the class, using jMock:

public void testCredentialsWithBasicAuthentication() {
    // Setup
    final HttpServletRequest request = context.mock(HttpServletRequest.class);

    AuthentificationHelper helper = new AuthentificationHelper();
    String login = "mickael";
    String password = ":&=/?é$£";
    String base64Hash = Base64.encodeString(login + ":" + password);
    final String authHeader = "Basic " + base64Hash;

    // Expectations
    context.checking(new Expectations() {
        {
            oneOf (request).getHeader("Authorization");
            will(returnValue(authHeader));
        }   
    });

    // Execute
    Credentials credentials = helper.credentialsWithBasicAuthentication(request);

    // Verify
    assertNotNull(credentials);
    assertEquals(login, credentials.getLogin());
    assertEquals(password, credentials.getPassword());

    context.assertIsSatisfied();
}
Mick F
  • 7,312
  • 6
  • 51
  • 98
  • 1
    A little note on the use of the class `StringTokenizer` taken from official Java documentation: "`StringTokenizer` is a legacy class that is retained for compatibility reasons although its use is discouraged in new code. It is recommended that anyone seeking this functionality use the `split` method of `String` or the java.util.regex package instead." – xonya Oct 28 '15 at 08:35
  • Where does Base64 class come from? – Mateus Viccari Nov 04 '16 at 11:50
  • I haven't coded with Java in a while but isn't Base64 part of J2SE? as in https://docs.oracle.com/javase/8/docs/api/java/util/Base64.html – Mick F Nov 09 '16 at 17:03