Secure Your Java and Android Clients

This topic provides information on how to add user authentication functionality to Java and Android clients. The Java and Android Client APIs use the same authentication classes and methods.

A challenge handler is a constructor used in an application to respond to authentication challenges from the Gateway when the application attempts to access a protected resource. Each of the resources protected by the Gateway is configured with a different authentication scheme (for example, Basic, Application Basic, Application Negotiate, or Application Token), and your application requires a challenge handler for each of the schemes that it will encounter or a single challenge handler that will respond to all challenges. Also, you can add a dispatch challenge handler to route challenges to specific challenge handlers according to the URI of the requested resource.

For information about each authentication scheme type, see Configure the HTTP Challenge Scheme.

Before you add security to your clients, follow the steps in Checklist: Secure Network Traffic with the Gateway and Checklist: Configure Authentication and Authorization to set up security on Kaazing WebSocket Gateway for your client. The authentication and authorization methods configured on the Gateway influence your client security implementation. In this procedure, we provide an example of the most common implementation.

Before You Begin

This procedure is part of Checklist: How to Build Java JMS Clients Using Kaazing WebSocket Gateway, that includes the following steps:

  1. Use the Kaazing WebSocket Gateway Java JMS Client Library
  2. Secure Your Java and Android Clients
  3. Troubleshoot Java JMS Clients
  4. Display Logs for the Java JMS Client

To Secure Your Java and Android Clients

This section includes the following topics:

Overview of Challenge Handlers

A challenge handler is responsible for producing responses to authentication challenges from the Gateway. The challenge handler process is as follows:

  1. When an attempt to access a URI protected by the Gateway is made, the Gateway responds with an authentication request, indicating that credentials need to be provided before access to the resource is granted. The specific type of challenge is indicated in a HTTP header called "WWW-Authenticate".
  2. The authentication request and the header are converted into a ChallengeRequest (as defined in RFC 2617) and sent to a challenge handler registered in the client application for authentication challenge responses.
  3. The ChallengeResponse credentials generated by a registered challenge handler are included in a replay of the original request to the Gateway, which allows access to the resource (assuming the credentials are sufficient).

Authenticating your Java client involves implementing a challenge handler to respond to authentication challenges from the Gateway. If your challenge handler is responsible for obtaining user credentials, then implement a login handler.

Challenge Handler Class Imports

To use a challenge handler in your Java client you must add the following imports:

import com.kaazing.net.auth.BasicChallengeHandler;
import com.kaazing.net.ws.WebSocket;
import com.kaazing.net.ws.WebSocketFactory;

Here is an example of all the WebSocket imports, including challenge handlers and kerberos challenge handlers:

import com.kaazing.net.auth.BasicChallengeHandler;
import com.kaazing.net.auth.LoginHandler;
import com.kaazing.net.http.HttpRedirectPolicy;
import com.kaazing.net.ws.WebSocket;
import com.kaazing.net.ws.WebSocketFactory;
import com.kaazing.net.ws.WebSocketMessageReader;
import com.kaazing.net.ws.WebSocketMessageType;
import com.kaazing.net.ws.WebSocketMessageWriter;

Creating a Basic Challenge Handler

Clients with a single challenge handling strategy for authentication requests can set a specific challenge handler as the default using the setDefaultChallengeHandler() method in the WebSocketFactory class. For example:

private WebSocketFactory wsFactory;

wsFactory = WebSocketFactory.createWebSocketFactory();
.
.
.  
BasicChallengeHandler challengeHandler = BasicChallengeHandler.create();
challengeHandler.setLoginHandler(loginHandler);
wsFactory.setDefaultChallengeHandler(challengeHandler);

Each WebSocket created from the factory can have its own Challenge Handler associated with it:

wsFactory = WebSocketFactory.createWebSocketFactory();
WebSocket ws = wsFactory.createWebSocket(location);
BasicChallengeHandler challengeHandler = BasicChallengeHandler.create();
challengeHandler.setLoginHandler(loginHandler);
ws.setChallengeHandler(challengeHandler);

Note: The challenge handler API is very flexible and there are many different ways to implement challenge handlers to suit the needs of your client application. For more detailed information on challenge handlers, see the Java Client API.

Creating a Login Handler

A login handler is responsible for obtaining credentials from an arbitrary source, such as a dialog presented to the user. Login handlers can be associated with one or more challenge handlers (ChallengeHandler objects) to ensure that when a challenge handler requires credentials for a challenge response (ChallengeResponse), the work is delegated to a login handler.

Here is an example using a login dialog to respond to login challenges and obtain user credentials as part of the authentication challenge:

final LoginHandler loginHandler = new LoginHandler() {
        private String username;
        private char[] password;

        @Override
        public PasswordAuthentication getCredentials() {
                try {
            LoginDialog dialog = new LoginDialog(parentFrame);
            if (dialog.isCanceled()) {
                return null;
            }
            username = dialog.getUsername();
            password = dialog.getPassword();
                } catch (Exception e) {
                        e.printStackTrace();
                }
                return new PasswordAuthentication(username, password);
        }
};
   
wsFactory = WebSocketFactory.createWebSocketFactory();
wsFactory.setDefaultChallengeHandler(
        BasicChallengeHandler.create().setLoginHandler(loginHandler);

Note: This example is taken from the out of the box Java Demo at http://localhost:8001/demo/ and uses an extension of JDialog (javax.swing.JDialog). The source code for the out of the box demo is available in your installation of the Gateway: GATEWAY_HOME/demo/java/.

Creating a Custom Challenge Handler

There are two methods used in ChallengeHandler:

  • canHandle(ChallengeRequest challengeRequest) determines if the challenge handler can handle the authentication scheme required by the Gateway (for example, Basic, Application Basic, Negotiate, Application Negotiate, or Application Token). The method takes a ChallengeRequest object containing a challenge and returns true if the challenge handler has the potential to respond meaningfully to the challenge. If this method determines that the challenge handler can handle the authentication scheme, it returns true and the handle() method is used. If this method returns false, the ChallengeHandler class (that contains all of the registered individual ChallengeHandler objects) continues looking for a ChallengeHandler to handle the request.
  • handle(ChallengeRequest challengeRequest) handles the authentication challenge by returning a challenge response. Typically, the challenge response invokes a login handler to collect user credentials and transforms that information into a ChallengeResponse object. The ChallengeResponse sends the credentials to the Gateway in an Authorization header and notifies the Gateway on what challenge handler to use for future requests. If handle() cannot create a challenge response, it returns null.

For information about each authentication scheme type, see Configure the HTTP Challenge Scheme.

Overriding Default Challenge Handler Implementations

After you have developed your own challenge handler, you can install it for future use. For example, to install your own implementation of BasicChallengeHandler for a Java client:

  1. Add a JAR file with your BasicChallengeHandler implementation to your classpath parameter before the Kaazing WebSocket Gateway Java client libraries.
  2. Ensure the JAR file contains the following file inside:
    META-INF/services/com.kaazing.gateway.client.security.BasicChallengeHander. The contents of the file should consist of a single line listing the fully-qualified name of your new implementation class (for example, fully.qualified.challenge.handler.impl.MyChallengeHandler). For more information, see the Service Loader documentation.

Managing Log In Attempts

When it is not possible for the Kaazing WebSocket Gateway client to create a challenge response, the client must return null to the Gateway to stop the Gateway from continuing to issue authentication challenges.

The following example demonstrates how to stop the Gateway from issuing further challenges.

/**
* Sets up the login handler for responding to "Application Basic" or "Application Negotiate" challenges.
*/
private static int maxRetries = 2; //max retries allowed for wrong credentials
private int retry = 0;    // retry counter

private void setupLoginHandler(final Frame parentFrame, String locStr) {
  wsFactory = WebSocketFactory.createWebSocketFactory();
  int index = locStr.indexOf("://");
  @Override
  public PasswordAuthentication getCredentials() {
    try {
      if (retry++ >= maxRetries) {
        return null;    // stop authentication process if max retry reached
      }
      LoginDialog dialog = new LoginDialog(parentFrame);
      if (dialog.isCanceled()) {
        retry = 0;    // user stopped authentication, reset retry counter
        return null;  // stop authentication process
      }
      username = dialog.getUsername();
      password = dialog.getPassword();

      updateButtonsForConnected();
      log("CONNECTED");
      retry = 0;    //reset retry counter;

      // Receive messages using WebSocketMessageReader.
      final WebSocketMessageReader messageReader = webSocket.getMessageReader();
    }
  } 
  catch (Exception e1) {
    retry = 0;     //reset retry counter
    e1.printStackTrace();
    log("EXCEPTION: "+e1.getMessage());  }
...

Authentication and Connections

Both WebSocketFactory and JMSConnectionFactory are used when adding a challenge handler to a Java or Android client's JMS connection to the Gateway. In the following code example, the challenge handler is initiated during the connect event for the JMS connection (lines 27-29, 45-52):

...
import com.kaazing.net.auth.BasicChallengeHandler;
import com.kaazing.net.auth.ChallengeHandler;
import com.kaazing.net.auth.LoginHandler;
...
public class JmsPanel extends javax.swing.JPanel implements ActionListener, MessageListener, ExceptionListener {
  ...
  private ChallengeHandler createChallengeHandler(String location) {
    final LoginHandler loginHandler = new LoginHandler() {
      private String username;
      private char[] password;
      @Override
      public PasswordAuthentication getCredentials() {
        try {
          LoginDialog dialog = new LoginDialog(Frame.getFrames()[0]);
          if (dialog.isCanceled()) {
            return null;
          }
          username = dialog.getUsername();
          password = dialog.getPassword();
        } catch (Exception e) {
          e.printStackTrace();
        }
        return new PasswordAuthentication(username, password);
      }
    };
    BasicChallengeHandler challengeHandler = BasicChallengeHandler.create();
    challengeHandler.setLoginHandler(loginHandler);
    return challengeHandler;
  }
  ...
  public void actionPerformed(ActionEvent arg0) {
    try {
      if (arg0.getSource() == connect) {

        final ExceptionListener applet = this;
        Thread connectThread = new Thread() {

          @Override
          public void run() {
            try {
              String url = location.getText();
              logMessage("CONNECT: " + url);
                          
              if (connectionFactory instanceof JmsConnectionFactory) {
                JmsConnectionFactory stompConnectionFactory = (JmsConnectionFactory)connectionFactory;
                // initialize the login handler for the target location
                ChallengeHandler challengeHandler = createChallengeHandler(url);
                stompConnectionFactory.setGatewayLocation(new URI(url));
                WebSocketFactory webSocketFactory = stompConnectionFactory.getWebSocketFactory();
                webSocketFactory.setDefaultChallengeHandler(challengeHandler);
                webSocketFactory.setDefaultRedirectPolicy(HttpRedirectPolicy.SAME_DOMAIN);
              }
              ...

Registering Challenge Handlers at Locations

When authentication challenges arrive for specific URI locations, the DispatchChallengeHandler is used to route challenges to the appropriate challenge handlers. This allows clients to use specific challenge handlers to handle specific types of challenges at different URI locations.

Here is an example of registering a specific location for a challenge handler:

LoginHandler someServerLoginHandler = ...
NegotiateChallengeHandler  nch = NegotiateChallengeHandler.create();
NegotiableChallengeHandler nblch = NegotiableChallengeHandler.create();
DispatchChallengeHandler   dch = DispatchChallengeHandler.create();

WebSocketFactory                wsFactory = WebSocketFactory.createWebSocketFactory();
wsFactory.setDefaultChallengeHandler(dch.register("ws://host.example.com",
        nch.register(nblch).setLoginHandler(someServerLoginHandler)
);
 // register more alternatives to negotiate here.
)

Using Wildcards to Match Sub Domains and Paths

You can use wildcards (“*”) when registering locations using locationDescription in DispatchChallengeHandler. Some examples of locationDescription values with wildcards are:

  • */ matches all requests to any host on port 80 (default port), with no user information or path specified.
  • *.hostname.com:8000 matches all requests to port 8000 on any sub domain of hostname.com, but not hostname.com itself.
  • server.hostname.com:*/* matches all requests to a particular server on any port on any path but not the empty path.

Creating Kerberos Challenge Handlers

The following examples demonstrate different implementations of Kerberos challenge handlers. When registered with the DispatchChallengeHandler, a KerberosChallengeHandler directly responds to Negotiate challenges where Kerberos-generated authentication credentials are required. In addition, you can use a KerberosChallengeHandler indirectly in conjunction with a NegotiateChallengeHandler to assist in the construction of a challenge response using object identifiers. For more information, see the Java Client API.

This abstract class captures common requirements for a number of implementation flavors for Kerberos, including Microsoft's SPNEGO implementation, and a SPNEGO Kerberos v5 GSS implementation.

To successfully use a KerberosChallengeHandler, one must know one or more Kerberos KDC service locations and optionally (if not defaulted to "HTTP/requestedURIHostname") provide the name of the specific service being requested.

For the KDC service location, one must establish either:

  • a default Kerberos KDC service location, using setDefaultLocation(java.net.URI), or
  • a mapping from a Kerberos Realm to at least one Kerberos KDC service location using
    setRealmLocation(String, java.net.URI).

For the non-defaulted service name being requested, one can configure the service name using setServiceName(String).

For example, one may install negotiate and a kerberos challenge handler that work together to handle a challenge as:

import com.kaazing.net.auth.BasicChallengeHandler;
import com.kaazing.net.auth.DispatchChallengeHandler;
import com.kaazing.net.auth.KerberosChallengeHandler;
import com.kaazing.net.auth.LoginHandler;
import com.kaazing.net.auth.NegotiateChallengeHandler;
.
.
.
LoginHandler someServerLoginHandler = ...; // perhaps this pops a dialog

KerberosChallengeHandler kerberosChallengeHandler =
        KerberosChallengeHandler.create()
                .setDefaultLocation(URI.create("ws://kb.hostname.com/kerberos5"))
                .setRealmLocation("ATHENA.MIT.EDU", URI.create("ws://athena.hostname.com/kerberos5"))
                .setServiceName("HTTP/servergw.hostname.com")
                        .setLoginHandler(someServerLoginHandler)

NegotiateChallengeHandler negotiateChallengeHandler = NegotiateChallengeHandler.create()
        .register(kerberosChallengeHandler);

WebSocketFactory wsFactory = WebSocketFactory.createWebSocketFactory();
wsFactory.setDefaultChallengeHandler(WebSocketDemoChallengeHandler.create()
        .register("ws://gateway.kaazing.wan:8001/echo", negotiateChallengeHandler)
        .register("ws://gateway.kaazing.wan:8001/echo/*", negotiateChallengeHandler));

At this point, any user attempting to access servergw.hostname.com:8000/echo will be challenged using a KerberosChallengeHandler instance. If the user enters credentials with the ATHENA.MIT.EDU realm the realm-specific athena.hostname.com KDC will be used to ask for Kerberos credentials for the challenge response. If the user enters credentials with any other realm the kb.hostname.com KDC will be used to ask for Kerberos credentials. All requests to either KDC will be for the service name HTTP/servergw.hostname.com (indicating access to that HTTP server is the service for which Kerberos credentials are being requested).

TOP