Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is an unbound SecurityManager really an invalid application configuration in Shiro?

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.

  1. Is the error I quoted actually an error or just an overzealous log message?

  2. Do I need to worry about Shiro relying on anything in a ThreadContext if I never use ShiroUtils?

  3. Is there any harm in using SecurityUtils#setSecurityManager if I can't guarantee a one-thread-per-request environment?

  4. 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?

  5. Have I done anything else that could cause me problems or could I improve anything?

like image 669
Ryan J Avatar asked May 03 '13 06:05

Ryan J


2 Answers

  1. 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.

  2. 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.

  3. 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)

  4. Shiro permissions do not rely on anything ThreadContext related. Permission checks are delegated to one or more Realms, 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.

  5. 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.

like image 77
Les Hazlewood Avatar answered Nov 11 '22 04:11

Les Hazlewood


This is what i discovered when i came across the same issue: I added the shiro filter statement onto my web.xml file then directly called the Subject in my bean. I added:

<filter>
        <filter-name>ShiroFilter</filter-name>
        <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
</filter>
<filter-mapping>
        <filter-name>ShiroFilter</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>FORWARD</dispatcher>
        <dispatcher>INCLUDE</dispatcher>
        <dispatcher>ERROR</dispatcher>
</filter-mapping>

and then after that I went on write just the following statement:

Subject subject = SecurityUtils.getSubject();
like image 7
Tadiwanashe Avatar answered Nov 11 '22 04:11

Tadiwanashe