Wednesday, February 18, 2009

GWT and Spring Security

Update: I have put together a sample app to demonstrate the integration technique: http://seewah.blogspot.com/2009/06/gwt-and-spring-security-sample-demo.html

As promised, I am posting this article to share my experience with using Spring Security (formerly ACEGI) with GWT. Apologies for the delay. I guess I have just been busy + lazy!

First of all, for those of you who haven't, please read my previous blogs GWT Spring Integration and GWT Spring Integration - using ContextLoaderListener to see how to use Spring-managed service beans in GWT.

Again it is not my intention to provide a complete solution for GWT / Spring Security integration as there are so many ways of doing it, and which method to choose depend hugely on how you want to actually use Spring Security inside GWT. By sharing my limited experience here, I hope to give some ideas to those of you who are trying to come up with a solution that fits your needs.

So how do I want to use Spring Security in my GWT application?

I want to:

1) leverage Spring Security's SecurityContext mechanism to store authenticated token, which I can then access anywhere in my code
2) be able to annotate my service classes, so that Spring can apply method security using AOP to my service beans

I DO NOT want to:

1) let Spring Security automatically handle form authentication for me
2) rely on Spring Security to automatically redirect user to login page

In other words, using Spring Security, I want to develop some form of RPC service which my GWT client can call to authenticate the username and password supplied by the user at the login screen. Once authentication has succeeded, a token which identifies the current user as being authenticated will be available to all subsequent RPC requests, so that I can either manually inspect the token inside the RPC request to enforce some form of authorization, or I can rely on Spring Security's off-the-shelf method security mechanism to allow or disallow user call certain methods in my Spring managed service beans.

I am going to break up the rest of the article into three different sections: An authentication service, HTTP Session Integration and Method Security

An Authentication service

The following is a simple implementation of such a service:


import org.springframework.security.Authentication;
import org.springframework.security.context.SecurityContext;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.security.context.SecurityContextImpl;
import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
import org.springframework.security.userdetails.User;

public class AuthenticationServiceImpl implements AuthenticationService {

/**
* Authenticates against the supplied credentials,
* populating the Spring SecurityContext if
* credentials are OK.
*
* @param username
* @param password
* @return
* whether authentication is successful
*/
public boolean authenticate(String username, String password) {

// check credentials, e.g., by querying a database...
boolean authenticated = checkCredentials();

// look up authorities by, e.g., querying the database...
GrantedAuthority[] authorities = getGrantedAuthorities(username);

if (authenticated) {
User user = new User(username, password, true, true, true, true, authorities);
Authentication auth = new UsernamePasswordAuthenticationToken(user, password, authorities);
SecurityContext sc = new SecurityContextImpl();
sc.setAuthentication(auth);
SecurityContextHolder.setContext(sc);
}

return authenticated;
}

public void logout() {
SecurityContextHolder.clearContext();
}
}


This of course is a very simple implementation, but the point to note is within the authenticate method, an Spring Authentication object is created and inserted into the SecurityContext programmatically.

We can simply turn this into a RPC service by creating a com.google.gwt.user.server.rpc.RemoteServiceServlet implementation in which we either access AuthenticationServiceImpl directly or via a Spring ApplicationContext as described in my previous blogs GWT Spring Integration and GWT Spring Integration - using ContextLoaderListener.

Now we have a service that effectively let us "log into" the system. However, this on its own is rather useless, as the token is not available to subsequent request threads. If we have a RPC service for retrieving documents, and we would like to protect the documents, we really would want to be able to, inside the service, inspect the SecurityContext to see whether an authenticated token exists. We want be able to do the following, which we cannot do... yet:


if (SecurityContextHolder.getContext() == null
SecurityContextHolder.getContext().getAuthentication() == null
!SecurityContextHolder.getContext().getAuthentication().isAuthenticated) {

// no authenticated token found, stop user from retrieving documents
}


HTTP Session Integration

Luckily, Spring Security provides a filter, org.springframework.security.context.HttpSessionContextIntegrationFilter, which automatically synchronises the current SecurityContext with a http session object, thus, making the token available to all subsequent request threads.

We can enable this filter in a number of ways. We can create the filter directly in web.xml (host-mode or standalone mode). Or we can create a org.springframework.security.util.FilterToBeanProxy in web.xml and define a FilterChainProxy in the Spring ApplicationContext. Or we can, as most people do nowadays, create a org.springframework.web.filter.DelegatingFilterProxy called springSecurityFilterChain (note: the naming is crucial) in web.xml and use Spring Security Namespace Configuration in the ApplicationContext to create the necessary beans. We will have a look at this last approach.

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>/*</url-pattern>
</filter-mapping>

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
<listener>

<listener>
<listener-class>org.springframework.security.ui.session.HttpSessionEventPublisher</listener-class>
</listener>


In Application Context:


<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"....>

<security:http entry-point-ref="dummyEntryPoint" create-session="always" />

<bean id="dummyEntryPoint" class="com.seewah.blog.DummyEntryPoint" />
...
<beans>


This is pretty neat, except for the fact that we have to create a "dummy" org.springframework.security.ui.AuthenticationEntryPoint, even though we are not replying on Spring Security to redirect user to login page. Unfortunately, by design, the security:http tag requires an AuthenticationEntryPoint, so I created this class:


public class DummyEntryPoint implements AuthenticationEntryPoint {

public void commence(ServletRequest request, ServletResponse response, AuthenticationException e) throws IOException, ServletException {

throw new IllegalStateException("This implementation is a dummy class, created purely so that spring security namespace tags can be used in application context, and this method should never be called");
}
}


OK, so now we have a way to "log into" the system, and we can inspect the SecurityContext in subsequent RPC requests to grant or deny users access to certain services. However, we would like to go one step further. We would like to leverage the excellent method security feature provided by Spring Security because we like our security layer to be non-invasive, and we like AOP.

Method Security

We configure method security as follows:

In Application Context:



<security:global-method-security secured-annotations="enabled" jsr250-annotations="disabled" />

<bean id="dummyAuthenticationProvider" class="com.seewah.blog.DummyAuthenticationProvider">
<security:custom-authentication-provider />
</bean>


Unfortunately, again, because of the way Spring Security Namespace Configuration works, we have to introduce another dummy class, even though it will never be used. Remember our AuthenticationServiceImpl creates authenticated token programmatically and method security will simply grant / deny access based on the presence of this token. Hence the authencationProvider here becomes redundant. We implement the dummy class as follows:


public class DummyAuthenticationProvider implements AuthenticationProvider {

public Authentication authenticate(Authentication authentication) throws AuthenticationException {

throw new IllegalStateException("This implementation is a dummy class, created purely so that spring security namespace tags can be used in application context, and this method should never be called");
}

@SuppressWarnings("unchecked")
public boolean supports(Class clazz) {

throw new IllegalStateException("This implementation is a dummy class, created purely so that spring security namespace tags can be used in application context, and this method should never be called");
}
}


Of course, as method security is implemented using AOP in Spring, only Spring-managed beans are protected, which means in order to use this mechanism to protect our service layer, we MUST access these services via the Spring ApplicationContext (GWT Spring Integration and GWT Spring Integration - using ContextLoaderListener) and NOT directly.

When a protected method is accessed without an authenticated token being present in the SecurityContext, Spring throws a SpringSecurityException. If we let this exception propagate all the way to the GWT client code, we are most certainly going to be greeted with the dreaded SerializableException error. Instead we should catch this exception and rethrow an exception that GWT can safely serialize and pass to the client side. We define such an exception, ServiceSecurityException, in our GWT client package, and now we have a mechanism that translates a Spring Security method security exception into a GWT client exception:

On the server


public class DocumentServiceImpl implements DocumentService {

@org.springframework.security.annotation.Secured("ROLE_ADMIN")
public void delete(Long id) {
...
}
}

public class DocumentServiceServlet extends DependencyInjectionRemoteServiceServlet implements ... {

@Autowired
private DocumentService documentService;

public void delete(Long id) throws ServiceSecurityException {
try {
documentService.delete(id);
} catch (SpringSecurityException e) {
throw new ServiceSecurityException(e.getMessage());
}
}
....
}



Inside the client


documentServiceAsync.delete(id, new AsyncCallback<Object>() {
public void onFailure(Throwable throwable) {
if (throwable instanceof ServiceSecurityException) {
// client side logic to redirect user to login page, for example
} else {
...
}
}
...
});


Of course, seeing that we need client-side exception handling whenever we invoke an "secured" RPC call, we may as well create an AutoErrorHandlingAsyncCallback and invoke all our RPC calls passing through an instance of this callback instead of the normal AsyncCallback object:


public abstract class AutoErrorHandlingAsyncCallback<T> implements AsyncCallback<T> {

final public void onFailure(Throwable throwable) {
if (throwable instanceof ServiceSecurityException) {
// client side logic to redirect user to login page, for example
} else {
...
}
}
}


OK this way of using Spring Security method security is not as non-invasive as we have perhaps hoped, but IMHO this is far better than writing code to check for authenticated token in SecurityContext in the services themselves.

23 comments:

jtyrrell said...

So is a token passed to the client application, which it then sends back to the server with every RPC call?

See Wah Cheng said...

Yes, except that the client does not explicitly handle the token. Spring Security provides a servlet filter to put the token into the HTTP session. The browser automatically sends the HTTP session cookie to the server for every subsequent HTTP request. From the session, the server can then retrieve the corresponding token when handling these HTTP (in effect, RPC) requests. See the HTTP Session Integration section in the article.

Anonymous said...

can you help me look at this problem


http://forum.springsource.org/showthread.php?p=242557#post242557


i tried your tutorial on applicationContext.xml and get error
The prefix "security" for element "security:http" is not bound.

Anonymous said...

in authenticateServiceAsync how to I do callback?


public interface AuthenticationServiceAsync {


public void authenticate(String username, String password ,AsyncCallback < boolean > callback) ;



}


the AsyncCallback cannot use primitive "boolean" as callback right?

See Wah Cheng said...

Sorry for the delay. I have been away.

Yeah you missed out the namespace declaration, as pointed out by the two replies.

GWT RPC handles primitive return types - while the actual method returns a primitive boolean, you just have to specify a Boolean object in the asyncCallback:

public void authenticate(String username, String password, AsyncCallback<Boolean> callback);

cometta said...

inside applicationContext.xml, is there any extra things you specified beside below...



http://pastebin.com/m49fad44d



is there a need to specify intercept point with security:http ?
i have a problem, even though my method inside DocumentServiceImpl is annotated with @Secured , no exception is throws when user not yet login

See Wah Cheng said...

Hi your applicationContext looks fine to me. Have you remembered to include

<filter>
<filtername>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>

<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

in your web.xml? (see the first code snippet after the heading "HTTP Session Integration")

cometta said...

yes, i already put that in web.xml

below is my startup log

http://pastebin.com/m66c58959

noticed anything that i missed out?


@Autowired

private DocumentService documentService;

try{

documentService.doSomething();
}
catch (SpringSecurityException e) {

throw new ServiceSecurityException(e.getMessage());
}


//your serviceSecurityException extends RuntimeException right?

See Wah Cheng said...

I am just putting together a sample app at the moment and I will add a download link to it in the blog article as soon as it is ready (maybe today or next monday...)

you can then compare your code with my sample and hopefully work out the differences!

See Wah Cheng said...

I have put together a sample app: http://seewah.blogspot.com/2009/06/gwt-and-spring-security-sample-demo.html

hope you will find it useful!

cometta said...

See Wah, thank you for the demo app. I already found out the problem and i am posting to share with the rest of the people who reading your article .


public void delete(Long id) throw ServiceSecurityException {
try {
documentService.delete(id);
} catch (SpringSecurityException e) {
throw new ServiceSecurityException();
}

also remember in the interface remember to put public void delete(Long id) throw ServiceSecurityException


thank you to you See Wah.it works great

See Wah Cheng said...

Oh yeah! Spring AOP does not support private method proxying...

I am glad your code is working now :-)

hugo said...

Hi,
I followed your example and it seems to work. In fact it works. Except that my callback on client side don't receive the security exception but a generic exception :

"see server log for details"

The exception is correctly sent but catched on server side and I receive an error from the server which is not what I expected :

com.google.gwt.user.server.rpc.UnexpectedException: Service method 'public abstract java.util.List com.hakanai.i18n.client.services.ProjectService.fetch()' threw an unexpected exception: com.hakanai.i18n.client.services.auth.ServiceSecurityException
at com.google.gwt.user.server.rpc.RPC.encodeResponseForFailure(RPC.java:360)
at com.google.gwt.user.server.rpc.RPC.invokeAndEncodeResponse(RPC.java:546)
at com.google.gwt.user.server.rpc.RemoteServiceServlet.processCall(RemoteServiceServlet.java:166)


This UnexpectedException is quite confusing. I don't understand what's wrong.

I use gwt 1.6.4 and smartGWT on client side (but it shouldn't interfere on server side).

Any idea ?

See Wah Cheng said...

Hi

I suppose you followed my demo on http://seewah.blogspot.com/2009/06/gwt-and-spring-security-sample-demo.html. Is that right?

Personally I have not tried it on 1.6.4, but can you just check that your GWT ProjectService interface throws ServiceSecurityException in its signature? i.e.

abstract List<?> fetch() throws ServiceSecurityException

?

hugo said...

yes, it was my problem, I've forgeted to add this declaration on the interface. Thanks for you help.

See Wah Cheng said...

Cool I am glad that solved it :-)

bighead said...

Hi Wah Cheng thanks for your good works!
Hope you dont mind that I do some amendment to your DependencyRemoteServiceServlet so that it allow to inject inherit fields incase we need to build another base servlet by extending your dependency servlet.
Here is the code :

/**
* Carries out dependency injection. This implementation uses Spring IoC
* container.
*
* @exception NoSuchBeanDefinitionException
* if a suitable bean cannot be found in the Spring
* application context. The current implementation looks up
* beans by name
*/
protected void doDependencyInjection() {
for (Field field : getFieldsToDependencyInject(this.getClass())) {
try {
boolean isFieldAccessible = field.isAccessible();
if (!isFieldAccessible) {
field.setAccessible(true);
}
field.set(this, WebApplicationContextUtils.getWebApplicationContext(getServletContext()).getBean(field.getName()));
if (!isFieldAccessible) {
field.setAccessible(false);
}
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}

/**
* Find annotated fields to inject.
*
* @return a list of all the annotated fields
*/
private Set getFieldsToDependencyInject(Class clazz) {

Set fieldsToInject = new HashSet();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (field.getAnnotation(Autowired.class) != null) {
fieldsToInject.add(field);
}
}

Class parent = clazz.getSuperclass();
if (parent != null && !(parent == DependencyInjectionRemoveServiceServlet.class)) {
fieldsToInject.addAll(getFieldsToDependencyInject(parent));
}

return fieldsToInject;
}

See Wah Cheng said...

Neat!

May incorporate this into my demo when I get round to updating it. I thought I should update it to work with GWT 1.6+ at some point.

Cheers!

Александр said...

Thanks a lot. It works great. In fact I was using GWT + GAE + Spring Security. And the only thing I have to do to make it work was changing appengine-web.xml. Just add an extra line <sessions-enabled>true</sessions-enabled>.

ravi said...

I am very new to Spring and I am getting the following exception when I tried your blog.


invalid; nested exception is org.xml.sax.SAXParseException: cvc-complex-type.2.4.c: The matching wildcard is strict, but no declaration can be found for element 'bean'.:

Here is my applicationContext.xml

ravi said...

Here is my applicationContext.xml
I removed the " < " and " >" to make it visible.

beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:osgi="http://www.springframework.org/schema/osgi"
xmlns:security="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">
<security:global-method-security secured-annotations="enabled" jsr250-annotations="disabled"

bean id="dummyAuthenticationProvider" class="com.xxx.xxx.DummyAuthenticationProvider"

security:custom-authentication-provider
bean

beans:beans

Stephan Beutel said...

Hello,

thanks for this howto.
I'm working an a solution using this demo together with a dispatcher servlet to handle the rcp request in an easier way.

But there are some problems using a dispatcher servlet:
- The init method of DependencyInjectionRemoteServiceServlet isn't called anymore
- The ServletContext isn't available in DependencyInjectionRemoteServiceServlet

The way how to integrate the dispatcher servlet I described here:
http://www.i-net-design.com/2011/02/18/problem-including-dispatcher-servlet-with-spring-security/

Perhaps someone knows how handle the spring injection of the servlet. I think it isn't a good solution to set the ServletContext call the init method by myself.

Thanks

dhoffer said...

Very interesting, would this still be the recommended way with GWT 2.4 & Spring 3.1?