I had previously used a Spring-centered stack in Google App Engine. These days I’m on more of a Google Guice kick. There are some nice advantages to Guice, but some drawbacks as well. One of the latter is the lack of an out-of-the-box integrated security solution. I am currently using Apache Shiro wired in with Guice as my security solution. In this post, I’ll tell you how to do the same.
Getting Started
If you want to setup Shiro with Guice, you’ll need Shiro (I’m using 1.1.0) and Guice (I’m using 3.0). Also, this post just tells you how to get started. You should read all of the Shiro Reference Manual so you can apply Shiro appropriately to your situation. Only then can you have confidence your app is secure.
As always, your feedback is appreciated. If I’m doing something wrong, please let me know!
Shiro Realm
To adapt Shiro to your application, the first thing you need is to implement a “realm”. The realm is the core adaptation mechanism between Shiro and your particular datasource (where you store your authentication and authorization information). This is a straightforward process: just follow the documentation, perhaps extending AuthorizingRealm, which is the logical extension point if you’re providing authentication and authorization context. You might consider using SimpleAuthorizationInfo and SimpleAccount if you just need basic authentication and authorization in your realm.
The following is a simple realm example. The reference manual has information on writing a better realm than what you see here. For example, you may want to use a HashedCredentialsMatcher instead of just matching clear character arrays.
public class MyRealm extends AuthorizingRealm {
private static Logger logger = Logger.getLogger(MyRealm.class.getName());
private MySecurityManagerService mySecurityManagerService;
@Inject
public MyRealm(MySecurityManagerService mySecurityManagerService) {
// This is the thing that knows how to find user creds and roles
this.mySecurityManagerService = mySecurityManagerService;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
String username = (String) principalCollection.getPrimaryPrincipal();
// Find the thing that stores your user's roles.
MyPrincipal principal = mySecurityManagerService.findMyPrincipalByUsername(username);
if (principal == null) {
if (logger.isLoggable(Level.FINE)) {
logger.fine("Principal not found for authorizing user with username: " + username);
}
return null;
} else {
if (logger.isLoggable(Level.FINE)) {
logger.fine(String.format("Authoriziong user %s with roles: %s", username, principal.getRoles()));
}
SimpleAuthorizationInfo result = new SimpleAuthorizationInfo(principal.getRoles());
return result;
}
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)
throws AuthenticationException {
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
if (usernamePasswordToken.getUsername() == null || usernamePasswordToken.getUsername().isEmpty()) {
throw new AuthenticationException("Authentication failed");
}
// Find the thing that stores your user's credentials. This may be the same or different than
// the thing that stores the roles.
MyPrincipal principal = mySecurityManagerService.findMyPrincipalByUsername(usernamePasswordToken.getUsername());
if (principal == null) {
if (logger.isLoggable(Level.FINE)) {
logger.fine("Principal not found for user with username: " + usernamePasswordToken.getUsername());
}
return null;
}
if (logger.isLoggable(Level.FINE)) {
logger.fine("Principal found for authenticating user with username: " + usernamePasswordToken.getUsername());
}
return new SimpleAccount(principal.getUsername(), principal.getPassword(), getName(),
principal.getRoles(), new HashSet());
}
}
That should get you started anyway.
Shiro Filter
Shiro has something of it’s own mini dependency injection facility with the IniShiroFilter. There is good documentation on that, and I think it might be the default configuration facility for most people using Shiro. However, if you’re using Guice, that’s not the way you want to go. You want to leverage Guice to wire everything together, and just do in Shiro what Shiro is meant for: security.
So you need another, more basic, filter. Shiro could have just provided such a basic, essential module. But they don’t. I don’t know why. Nevertheless, it’s easy to make your own. Really easy. Prepare yourself. Here it is:
@Singleton
public class ShiroFilter extends AbstractShiroFilter {
@Inject
public ShiroFilter(WebSecurityManager webSecurityManager) {
setSecurityManager(webSecurityManager);
}
}
Once you have your mind around that, you’ll need to do yourself some AOP.
Guice Interceptor
Shiro uses AOP to intercept method invocations for access control. Generally, that’s what you want: it’s non-intrusive and extensible. But it doesn’t use standard APIs to do that. I don’t know why. Guice, on the other hand, uses the AOP Alliance API for interception. So you need to make the one fit into the other.
The APIs are very similar, though (again, why not just use the AOP Alliance APIs? …), so it’s easy to adapt them:
public class ShiroMethodInterceptor implements MethodInterceptor {
private RoleAnnotationMethodInterceptor roleAnnotationMethodInterceptor = new RoleAnnotationMethodInterceptor();
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
return roleAnnotationMethodInterceptor.invoke(new ShiroMethodInvocation(methodInvocation));
}
private static class ShiroMethodInvocation implements org.apache.shiro.aop.MethodInvocation {
private MethodInvocation methodInvocation;
public ShiroMethodInvocation(MethodInvocation methodInvocation) {
this.methodInvocation = methodInvocation;
}
...
Easy stuff, right?
Wiring it All Together
Shiro is very modular. And if you’re using the default configuration, they really take care of things for you. But who does that? Not me. So that means you need to assemble the parts you need for your particular environment. This includes:
- Creating a Realm for your particular environment
- Selecting a session manager
- Selecting a remember-me manager
- Selecting a SecurityManager and telling it about your realm, session manager and remember-me manager
You should know what all of those things are because you should have read the Shiro documentation, which is not bad at all. I created a custom realm and then did the following inside one of my Guice modules:
public class MyModule extends AbstractModule {
@Override
protected void configure() {
...
bind(Realm.class).to(MyRealm.class);
bindInterceptor(Matchers.any(), Matchers.annotatedWith(RequiresRoles.class),
new ShiroMethodInterceptor());
}
@Provides
@Singleton
WebSecurityManager provideSecurityManager(Realm realm, SessionManager sessionManager, RememberMeManager rememberMeManager) {
DefaultWebSecurityManager result = new DefaultWebSecurityManager(realm);
result.setSessionManager(sessionManager);
result.setRememberMeManager(rememberMeManager);
return result;
}
@Provides
@Singleton
SessionManager provideSessionManager() {
return new ServletContainerSessionManager();
}
@Provides
@Singleton
RememberMeManager provideRememberMeManager() {
return new CookieRememberMeManager();
}
}
I love how easy it is to do AOP in Guice. So nice.
In Google App Engine, it’s important to use the ServletContainerSessionManager instead of something like DefaultWebSessionManager because the latter tries to start threads, which GAE doesn’t care for at all.
The last thing you need to wire in is that filter, so Shiro will actually get involved in your web requests. I again do that with Guice:
public class MyServletModule extends ServletModule {
@Override
protected void configureServlets() {
filter("/*").through(ShiroFilter.class);
}
}
To make that work, you need to have configured Guice in your web.xml.
Securing Services
Authentication with Shiro is nice and simple. The 10 Minute Tutorial will get you started. In the simplest case might do something like this:
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
token.setRememberMe(true);
try {
subject.login(token);
} catch (AuthenticationException ae) {
// handle failed logins here ... see the docs for more exceptions
}
Authorization is even easier. If you’re doing something basic, you will probably want some role-based security on your services. Shiro provides nice APIs for checking roles and permissions. I currently only care about roles. And that’s why we setup that aspect. To add role access control to your service method (on the server side of course!), just do something like this:
@RequiresRoles("importantRole")
public void doSomethingImportant(...) {
...
I’m confident most everyone reading this knows that checking permissions on the client in a web app is not enough. So you need to do your security on the server side. Once something is on the client computer all bets are off!
In my app, I return some data useful for predicating particular permissions so I can toggle buttons, show panels, etc, on the client. But I do all of that an more on the server.
Conclusion
I think Apache Shiro is a good choice for adding security to a Guice-based app. In particular it’s working well for me in Google App Engine with a GWT front-end. I hope this guide helps some if you’re working a similar environment. As always, if you see something I’m doing wrong, let me know!