I'm adding Apache Shiro to my application and I'm wondering if the following error message is truly accurate:
org.apache.shiro.UnavailableSecurityManagerException: No SecurityManager accessible to the calling code, either bound to the org.apache.shiro.util.ThreadContext or as a vm static singleton. This is an invalid application configuration.
I've looked through the source code a bit and the impression that I get is that as long as I'm not using SecurityUtils
and I'm willing to pass a SecurityManager
to components that need it, I don't actually need to assign the SecurityManager
to the static singleton used by SecurityUtils
.
The specific thing I want to avoid is having Shiro put anything into ThreadLocal
or having Shiro use its ThreadContext
support class. I'm using Apache Thrift and don't want to commit myself to a one-thread-per-request network design. My requirements from Shiro are pretty minimal, so I'll show what I'm doing below.
I'm using Guice in my application, but I'm not using shiro-guice
because the Shiro AOP stuff depends on having a Subject
associated with a ThreadContext
. Instead, I start with an extremely simple Guice module.
public class ShiroIniModule extends AbstractModule {
@Override
protected void configure() {}
@Provides
@Singleton
public SecurityManager provideSecurityManager() {
return new DefaultSecurityManager(new IniRealm("classpath:shiro.ini"));
}
}
That's not exactly a production quality realm / security manager setup, but it's good enough for me to test with. Next, I create my own manager classes with a very limited scope that are used by the components of my application. I have two of these; a ThriftAuthenticationManager
and a ThriftAuthorizationManager
. Here is the former:
@Singleton
public class ThriftAuthenticationManager {
private final Logger log = LoggerFactory.getLogger(ThriftAuthenticationManager.class);
private final SecurityManager securityManager;
@Inject
public ThriftAuthenticationManager(SecurityManager securityManager) {
this.securityManager = securityManager;
}
public String authenticate(String username, String password) throws TException {
try {
Subject currentUser = new Subject.Builder(securityManager).buildSubject();
if (!currentUser.isAuthenticated()) {
currentUser.login(new UsernamePasswordToken(username, password));
}
String authToken = currentUser.getSession().getId().toString();
Preconditions.checkState(!Strings.isNullOrEmpty(authToken));
return authToken;
}
catch (AuthenticationException e) {
throw Exceptions.security(SecurityExceptions.AUTHENTICATION_EXCEPTION);
}
catch(Throwable t) {
log.error("Unexpected error during authentication.", t);
throw new TException("Unexpected error during authentication.", t);
}
}
}
And the latter:
@Singleton
public class ThriftAuthorizationManager {
private final Logger log = LoggerFactory.getLogger(ThriftAuthorizationManager.class);
private final SecurityManager securityManager;
@Inject
public ThriftAuthorizationManager(SecurityManager securityManager) {
this.securityManager = securityManager;
}
public void checkPermissions(final String authToken, final String permissions)
throws TException {
withThriftExceptions(new Callable<Void>() {
@Override
public Void call() throws Exception {
securityManager.checkPermission(getPrincipals(authToken), permissions);
return null;
}
});
}
public void checkPermission(final String authToken, final Permission permission)
throws TException {
withThriftExceptions(new Callable<Void>() {
@Override
public Void call() throws Exception {
securityManager.checkPermission(getPrincipals(authToken), permission);
return null;
}
});
}
private Subject getSubject(String authToken) {
return new Subject.Builder(securityManager).sessionId(authToken).buildSubject();
}
private PrincipalCollection getPrincipals(String authToken) {
return getSubject(authToken).getPrincipals();
}
private void withThriftExceptions(Callable<Void> callable) throws TException {
try {
callable.call();
}
catch(SessionException e) {
throw Exceptions.security(SecurityExceptions.SESSION_EXCEPTION);
}
catch(UnauthenticatedException e) {
throw Exceptions.security(SecurityExceptions.UNAUTHENTICATED_EXCEPTION);
}
catch(AuthorizationException e) {
throw Exceptions.security(SecurityExceptions.AUTHORIZATION_EXCEPTION);
}
catch(ShiroException e) {
throw Exceptions.security(SecurityExceptions.SECURITY_EXCEPTION);
}
catch(Throwable t) {
log.error("An unexpected error occurred during authorization.", t);
throw new TException("Unexpected error during authorization.", t);
}
}
}
My Thrift services use the above two classes for authentication and authorization. For example:
@Singleton
public class EchoServiceImpl implements EchoService.Iface {
private final Logger log = LoggerFactory.getLogger(EchoServiceImpl.class);
private final ThriftAuthorizationManager authorizor;
@Inject
public EchoServiceImpl(ThriftAuthorizationManager authorizor) {
this.authorizor = authorizor;
}
@Override
public Echo echo(String authToken, Echo echo) throws TException {
authorizor.checkPermissions(authToken, "echo");
return echo;
}
}
So, I guess I actually have a few questsions.
Is the error I quoted actually an error or just an overzealous log message?
Do I need to worry about Shiro relying on anything in a
ThreadContext
if I never useShiroUtils
?Is there any harm in using
SecurityUtils#setSecurityManager
if I can't guarantee a one-thread-per-request environment?I haven't tried using Shiro's advanced permissions (
org.apache.shiro.authz.Permission
) yet. Do they rely on anything in aThreadContext
or do anything weird that I should look into sooner than later?Have I done anything else that could cause me problems or could I improve anything?