Is an unbound SecurityManager really an invalid application configuration in Shiro?
guice, shiro, thrift
Solution
The error quoted is an error only if you want to call `SecurityUtils.getSecurityManager()`. You're more than welcome to pass it around manually or inject it as a dependency outside of SecurityUtils usage.
SecurityUtils is mostly a convenience for those using Shiro if they want to use it. Only a few things within Shiro actually call it explicitly: some of Shiro's AspectJ integration calls it, some of Shiro's web support calls it (Servlet Filters, JSP and JSF taglibs). However, in these cases where it is used within Shiro, it is (I think) always called by a template method, allowing you to override that method to acquire the Subject from somewhere else if desired.
No harm in calling `SecurityUtils.setSecurityManager` as long as you're comfortable with a single SecurityManager instance for your entire JVM. If you have more than one Shiro app that uses that method in the same JVM, it would likely cause problems. Even so however, because static memory calls and global state are evil, if you can find even another way of referencing the SecurityManager, that would be even better (e.g. Dependency Injection)
Shiro permissions do not rely on anything `ThreadContext` related. Permission checks are delegated to one or more `Realm`s, and they have the final say as to what is permitted or not. Most realms in turn use an Authorization `Cache` to ensure permission lookups stay nice and responsive. But this is not thread state - it's (non static) application singleton state.
Your code looks pretty good. One recommendation for the `ThriftAuthorizationManager` authorization checks is to delegate to the Subject directly (the Subject in turn delegates to the SecurityManager). This I think is a tad faster than the current approach and better self-documenting IMO:
return getSubject(authToken).checkPermission(permission);
Thanks for sharing your questions. It is always nice to see non-web usages of the framework, since it was designed for all workloads.
Problem
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 use `ShiroUtils`? 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 a `ThreadContext` 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?