Thursday, June 2, 2011

GWT and Spring Security integration: as easy as pie

There is very easy way of integrating GWT remote services with Spring Security method level security but I never seen such solution on the internet.
This solution utilizes AOP, and I will use aspectJ for compile-time weaving but you can use spring-aop if there are reasons not to use aspectJ in your project.
Let's start with security exception that will work on GWT client side.
package com.mm.client;

import java.io.Serializable;

public class AppSecurityException extends RuntimeException implements Serializable {
    public AppSecurityException() {
        super();
    }
}
There are 2 things to note: this exception should be unchecked exception and you should specify it in throws declaration of secured GWT RPC method despite it is unchecked exception.
I'm leaving up to you adding reasons why exception is thrown (Not logged in, not authorized, session expired etc.)
Next, create Spring context configuration that will configure Spring Security to use method level security only, without URL filtering because you don't need it in GWT RPC.
I also including here configuration for annotation-driven context and aspectJ configuration.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:security="http://www.springframework.org/schema/security"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd">
    <context:annotation-config/>
    <context:spring-configured/>
    <context:component-scan base-package="com.mm"/>
    <security:global-method-security secured-annotations="enabled" mode="aspectj"/>
    <bean id="springSecurityFilterChain"
          class="org.springframework.security.web.FilterChainProxy">
        <security:filter-chain-map path-type="ant">
            <security:filter-chain pattern="/app/*"
                                   filters="securityContextPersistenceFilter"/>
        </security:filter-chain-map>
    </bean>
    <bean id="securityContextPersistenceFilter"
          class="org.springframework.security.web.context.SecurityContextPersistenceFilter">
        <property name="forceEagerSessionCreation" value="true"/>
    </bean>
</beans>
Do not forget to include filter in web.xml
<filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/app/*</url-pattern>
    </filter-mapping>
Next step is to define marker annotation for secured methods
package com.mm.server;

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
public @interface GwtMethod {
}
And last part of setup is to define aspect that will translate Spring Security exception to your app exception:
package com.mm.server;

import com.mm.client.AppSecurityException;
import org.springframework.security.core.AuthenticationException;

public aspect SecurityExceptionAspect {
    declare precedence:SecurityExceptionAspect,*;

    private pointcut executionOfGwtMethod():
        execution(* *(..)) && @annotation(GwtMethod);

    Object around(): executionOfGwtMethod() {
        try {
            return proceed();
        } catch (AuthenticationException e) {
            throw new AppSecurityException();
        }
    }
}
That's all.
You can use this in following way:
package com.mm.server;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.mm.client.AppSecurityException;
import com.mm.client.SecurityTestService;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.security.access.annotation.Secured;

@Configurable
public class SecurityTestServiceImpl extends RemoteServiceServlet implements SecurityTestService {
    @Override
    @Secured("ROLE_LOGGED_IN")
    @GwtMethod
    public String securedMethod() throws AppSecurityException {
        return "Ok";
    }
}
And catch it in client code like this:
new AsyncCallback<String>() {
        @Override
        public void onFailure(Throwable caught) {
            if (caught instanceof AppSecurityException) {
                //Security error, handle it accordingly
            } else {
                //Some other error
            }
        }

        @Override
        public void onSuccess(String result) {
            // Handle result
        }
    }
You can download working example here.

12 comments:

  1. Very interesting articel indeed, would be a great help if you post the loggin part, where we should assign the role

    ReplyDelete
  2. There is an example in a project linked in the end of article (very-very basic though)

    ReplyDelete
  3. Возник возможно очень глупый вопрос в силу незнания aspectj и spring aop.

    Можно ли достичь описанного выше эффекта без компиляции с aspectj?

    спасибо.

    ReplyDelete
  4. Можно, например, используя Java Agent. Это довольно хорошо описано в документации на спринг, так же как и плюсы/минусы обоих подходов. В общих чертах - это даст оверхед в рантайме, может быть сложно для конфигурирования на некоторых серверах и вообще не наш путь. Не стоит бояться AspectJ компилятора, нет в нем ничего страшного и это давно и без проблем используется во "взрослых" системах, в т.ч. и у нас в банке.

    ReplyDelete
  5. Great Post! Works like a charm :)

    ReplyDelete
  6. Hello. First of all, thanks for the guide!
    But I am struggling to make it work. I did what was written above, I get no errors, but unfortunately it always grants access without me doing any sort of authentication.

    Now I am very, very new to Spring, and I found out about aspectJ just now, and I fear I'm doing something wrong. How do i create that .aj file? I just created a new file and written into it. (I have downloaded the jars). Do i link it to the project somehow? Maybe it's a silly question, sorry for that :/

    I'm searching google as we speak, but I seem not to have much luck.

    Please try to help me if you can.

    Thank you in advance.

    ReplyDelete
  7. This is very interesting, I'm going to give it a try, I think it's just what I've been looking for. I do have some initial questions, not being a Spring or AOP guru.

    I notice you have context.xml in META-INF/spring folder is that equivilent of applicationContext.xml in Web-INF? In which case...


    contextConfigLocation
    classpath:/META-INF/spring/context.xml


    Isn't needed, right? Is there a reason for the way you do it?

    It sure would be nice to have derived AppSecurityException types, i.e. NotLoggedSecurityException, etc and/or have text messages in these too for details. How should this be done?

    ReplyDelete
  8. I got this working...seems to work very well...but I have a few questions.

    At first I was confused how to make it throw an AppSecurityException because if I just changed ROLE_LOGGED_IN to null or if I set an Authentication with null GrantedAuthoritys it threw an AccessDeniedException instead. Only by not setting any Authentication does it throw AppSecurityException. So I'm thinking maybe to be safe the aj file should handle AccessDeniedException as well as AuthenticationException?

    So what I have working now is a way to set ROLE_LOGGED_IN once the user logs in and then all methods can check for this but really need it to do more.

    For instance once that user is logged in, I'd like the system (my app) to detect if he logs in again and if so, log out of the first session. So how could I do that with this because I think all this is stored in thread local storage so one thread has no way to get at data in another, right?

    Also how could I tie this in with HttpSession timeouts, invalidate, etc.

    E.g. I think this is a great solution as all these errors are pushed to the client via exceptions but I'm just not clear on how to wire up everything on the server to cause all the exceptions one would need to handle.

    Plus I do need to send data in those exceptions to the client, e.g. if being forced logged out I might want to explain why, etc.

    Also I see you use "Authorized", "None" in the Authentication instance. What's really the purpose of these fields? Would there be a benefit for me to store the username and/or password here?

    ReplyDelete
  9. I have googled many page and have visited your article http://blog.maxmatveev.com/2011/06/gwt-and-spring-security-integration-as.html.
    It's wonderful thing I really need. But I always get aspectJ argument errors in SecurityExceptionAspect.aj , it seems the aspectj did not work properly, my gwt remote method isn't cheked the role by @Secure annotation.

    I want to download your source code to compare what I go wrong in this project. But link http://dl.dropbox.com/u/7556109/gwt-spring-security.zip is died.
    Can you place it again?

    I have wasted 1 day to try, I really appreciate if you reply.

    ReplyDelete
  10. The download link is not working!

    ReplyDelete
    Replies
    1. I've re-uploaded it, try again now

      Delete