Sunday, July 13, 2008

GWT + j_security_check

I went out looking for a solution to this recently and found very little information. A lot of questions unanswered led me to believe I was not the only one who wanted to do this. I present here a small sample on how to accomplish this.

 

Before presenting the sample, let's go over a few things first on why you would want to do this and more importantly why you still need an Ajax style security token for all your GWT services even after this is implemented.


The reasons you would want to do this? One is automatic single sign-on (SSO) if supported by your container, e.g., Glassfish supports SSO out of the box for its web, EJB and web services tiers. If you use j_security_check, you're in; you're into all these services. Furthermore, if you use a J2EE authorization agent like OpenSSO inside Glassfish, you're into any services or sites provided by that federation. So even if you’re fronting your application with GWT, your back end might be composed of all these elements and more.


This also allows you to use any standard security annotations in your EJBs or, if you're orchestrating like me, in OpenESB. You can do this because you now have a Java security principal setup inside the container.


This also allows you to leverage the clustering features, if any, of your application server. Glassfish, again, contains great out of the box support for clustering. Session replication and expiration is managed for you and it just works. One call can proceed on one server while the other can proceed on another while the caller remains completely oblivious to where its work is actually being executed.

 

What you can’t do however is assume that you should no longer have an object representing your logged in user in your GWT client code. GWT RPC calls are still out in the open with this technique, you should pass in at the very least your session id value as a parameter to the GWT remote call so that the RCP service can do validations.

 

You still need SSL. Use SSL to make sure your password is transmitted securely over the wire. Hashing on the client side is completely useless. First, because MD5 is usually about the only thing available in JavaScript and that is easily broken. Second, because you should be counting on SSL to do its job correctly. Third, once on the server, you should hash your password with something that is secure, i.e., not MD5 and throw away the clear text representation of the password.

 

I use NetBeans 6.1 and release candidate 1 of GWT 1.5 for this example. The idea is to use the RequestBuilder class to touch a resource that has been marked off limits by the container. We do this because the j_security_check, in Java EE 5 at least, cannot be invoked directly in a portable manner. When the server sees the access to the protected resource by an unknown, it redirects you to the login page. When this occurs, the method “onResponseReceived” is called. We ignore the redirect and submit our own form. The form just needs to have at least 3 named elements present:

  • The form name, j_security_check
  • The user name field, j_username
  • The password field, j_password

If you don’t touch a protected resource first and simply submit your form immediately, you will find that you still don’t have a security principal since no login actually occurred in the container.

 

You would apply the same rules for defining a protected area than you would for a normal non-GWT application. You can see on how to do this in Glassfish and Tomcat by following the tutorial here.

 

Here is some sample code that fulfills a login on Java application server. I originally had more here in terms of error handling but it seemed it got to be too large for presentation purposes.

 

package org.codepimps.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.http.client.RequestCallback;
import com.google.gwt.http.client.RequestException;
import com.google.gwt.http.client.Response;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.FormHandler;
import com.google.gwt.user.client.ui.FormPanel;
import com.google.gwt.user.client.ui.FormSubmitCompleteEvent;
import com.google.gwt.user.client.ui.FormSubmitEvent;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.PasswordTextBox;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;

public class MainEntryPoint implements EntryPoint {
  public void onModuleLoad() {
    final FormPanel formPanel = new FormPanel();
    VerticalPanel vpanel = new VerticalPanel();
    HorizontalPanel hpanel = new HorizontalPanel();
    TextBox userName;
    TextBox password;

    formPanel.setAction("j_security_check");
    formPanel.setMethod(FormPanel.METHOD_POST);
    hpanel.add(new Label("User name:"));
    hpanel.add(userName = new TextBox());
    userName.setName("j_username");
    vpanel.add(hpanel);

    hpanel = new HorizontalPanel();
    hpanel.add(new Label("Password:"));
    hpanel.add(password = new PasswordTextBox());
    password.setName("j_password");
    vpanel.add(hpanel);

    vpanel.add(new Button("Login", new ClickListener() {
      public void onClick(Widget arg0) {
        String url = GWT.getHostPageBaseURL() + "protected/protectedText.txt";
        RequestBuilder rb = new RequestBuilder(RequestBuilder.GET, url);
       
        rb.setCallback(new RequestCallback() {
          public void onResponseReceived(Request request, Response response) {
            formPanel.submit();
          }

          public void onError(Request request, Throwable caught) {
            throw new UnsupportedOperationException("Not supported yet.");
          }
        });

        try {
          rb.send();
        } catch (RequestException ex) {
          // TODO present the error in a text area or something
        }
      }
    }));
   
    formPanel.addFormHandler(new FormHandler() {
      public void onSubmit(FormSubmitEvent event) {
      }

      public void onSubmitComplete(FormSubmitCompleteEvent event) {
        // TODO actually make sure the login succeeded.  Present error in a text area
        // for the user to see
        Window.Location.assign(GWT.getHostPageBaseURL() + "/protected/protectedText.txt");
      }
    });

    formPanel.add(vpanel);
    RootPanel.get().add(formPanel);
  }
}

Java2html

19 comments:

Abhijeet said...

Hi, nice article there.

Could you elaborate a bit more on how MD5 is easily broken? What are the drawbacks if I am hashing on the client and sending only the hash across the wire?

Thanks,
Abhijeet.

pjulien said...

I will not try to address the shortcomings of MD5 here. The wikipedia article found at:

http://en.wikipedia.org/wiki/MD5

is a good place to start. In other words, hashing to MD5 on the client doesn't buy you anything but encrypting the password with something like blowfish on the server does: http://www.mindrot.org/projects/jBCrypt/

That being said, the only good way to provide over the wire security is to use public key infrastructure. If you only need trust, then you sign each packets with an HMAC. If you also need privacy, you fully encrypt the content instead of just signing it.

For an overview of HMAC, see here http://en.wikipedia.org/wiki/Hmac.

Unfortunately, this is impractical for end users but usually fine for web services of the RESTful kind.

bsevindi said...

"you should pass in at the very least your session id value as a parameter to the GWT remote call so that the RCP service can do validations"

How could I do that? BTW I am new to GWT and I need to integrate an existing GWT 1.5 app with container managed security (both the servlets and EJBs are secured)

pjulien said...

RemoteServiceServlet is used to implement RPC services on GWT. You can use the getThreadLocalRequest method of that class to get your request, i.e., the HttpServletRequest instance.

From HttpServletRequest, you can call the getSession method which will yield an HttpSession object.

The HttpSession class has a getId method that you can use to obtain your session id. This session id is a string and can be returned at any time via an RPC call to your client code.

Assuming you accomplish this task at login, any subsequent RPC call would feature a "request context" class that you would make and that you would store your session id in. Any server code would look inside your request context for your session id and perform validations against the HTTP request object.

bsevindi said...

Thanks for your advice. That works for servlet authentication, but as I have said before my enterprise beans are also secured, i.e they are annoted with RolesAllowed. As you may also know, servlets propagate user principal to ejb container. But in this situation, it just doesn't work; calls to EJBs fail and throw "java.rmi.AccessException: CORBA NO_PERMISSION" exception

pjulien said...

You mentioned both servlets and EJBs in your post. The "RolesAllowed" annotation works for both servlets and EJBs. Once your user is authenticated at the servlet level, he's also authenticated at the EJB level. If you're asking me how to call EJBs directly from GWT client code, well, you can't, you would need the full EJB client stack source code and compile it using the GWT compiler.

You would still need to have RemoteServiceServlet based servlets front your EJBs. Your HTTP request can be sent to the EJBs at the same time if you want to do any validations there.

bsevindi said...

I have secured my servlets by their url mapping, I mean I have added security constraint elements to web.xml. But I cannot call EJB methods from those servlets even the user is authenticated.

pjulien said...

Then you have a bug completely unrelated to this blog post.

bsevindi said...

No, my pb exactly relates to this blog post.

When I authenticate my users using j_security_check, it works fine. My users are authenticated and GWT modules be able to call any secured RemoteServiceServlet method. But those secured-remoteserviceservlet-methods cannot call secured-ejb-methods because glassfish doesn't propagate any user principal when the servlet recieves an RPC call.

I have checked that in an unsecured EJB method, I cannot manage to obtain any principal when I called SessionContext.getCallerPrincipal method.

BTW have you considered session timeouts? When the session becomes invalid, further rpc calls receive the html of the login page in return and display that in a dialog box.

pjulien said...

Do you have a principal in your servlet? If you don't have one in your EJB, I would imagine the problem is you don't have a principal in your servlet either.

My applications implement session timeouts yes.

bsevindi said...

Yes, I can obtain a principal in servlets. So what is this? Do you have any idea?

Also, how do you handle session time outs?

pjulien said...

I don't know but if you have a principal in your servlet and not in your in EJB, then this problem is all server and doesn't relate at all to GWT.

Session timeouts need to be implemented in both the server and client. On the server, this is part of your RPC validations. On the client, you just clear out the UI or something once you determine the session has been idle for too long.

Laird Nelson said...

Hello; I'm thick this morning. How is it that once the J2EE cookie has been set--i.e. once the form has been submitted successfully--how is it that other AJAX calls from within GWT do not also send that cookie along? You mention that one still has to store a security token on the GWT side and explicitly send it through with every call. I believe you, but I can't see why the jsessionid cookie (or whatever it is) doesn't get sent with every subsequent GWT RPC request.

Thanks,
Laird

pjulien said...

The goal here is to achieve protection from XSRF.

If you're using RequestBuilder and RequestCallback a cookie is all you need.

If you're using GWT RPC, you don't have any cookies set since this is GWT RPC hence an extra parameter acts as your "security anchor".

Laird Nelson said...

Thank you; I guess the missing part for me was that GWT RPC doesn't use cookies (I am brand new to GWT). Thanks again.

Best,
Laird

Komal said...

Hi,nice article ..
I`m not clear as to how a gwt project be modified to work with josso or opensso with tomcat as a container can you out some light on this.

Thanks

pjulien said...

I don't use Tomcat

Komal said...

Hey thanks for the reply.. So which application server or web server should be used and would be good?


Thanks..

pjulien said...

I use glassfish, up to you what you want to use.