guice - 在 Shiro 中,未绑定(bind)的 SecurityManager 真的是无效的应用程序配置吗?

标签 guice thrift shiro

我正在将 Apache Shiro 添加到我的应用程序中,我想知道以下错误消息是否真的准确:

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.



我浏览了一下源代码,我得到的印象是,只要我不使用 SecurityUtils我愿意通过 SecurityManager对于需要它的组件,我实际上不需要分配 SecurityManagerSecurityUtils 使用的静态单例.

我要避免的具体事情是让 Shiro 将任何内容放入 ThreadLocal。或者让 Shiro 使用它的 ThreadContext支持类。我正在使用 Apache Thrift,并且不想将自己提交给每个请求一个线程的网络设计。我对 Shiro 的要求非常低,所以我将在下面展示我在做什么。

我在我的应用程序中使用 Guice,但我是 不是 使用 shiro-guice因为 Shiro AOP 的东西取决于有一个 SubjectThreadContext 相关联.相反,我从一个极其简单的 Guice 模块开始。

public class ShiroIniModule extends AbstractModule {
    @Override
    protected void configure() {}

    @Provides
    @Singleton
    public SecurityManager provideSecurityManager() {
        return new DefaultSecurityManager(new IniRealm("classpath:shiro.ini"));
    }
}

这不完全是生产质量领域/安全管理器设置,但它足以让我进行测试。接下来,我创建自己的管理器类,其范围非常有限,供我的应用程序的组件使用。我有两个;一个 ThriftAuthenticationManagerThriftAuthorizationManager .这是前者:

@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);
        }

    }
}

而后者:

@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);
        }
    }
}

我的 Thrift 服务使用上述两个类进行身份验证和授权。例如:

@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;
    }
}

所以,我想我实际上有几个问题。
  • 我引用的错误实际上是一个错误还是只是一个过分热心的日志消息?
  • 我需要担心 Shiro 依赖 ThreadContext 中的任何内容吗?如果我从不使用 ShiroUtils ?
  • 使用SecurityUtils#setSecurityManager有什么危害吗?如果我不能保证每个请求一个线程的环境?
  • 我还没有尝试过使用 Shiro 的高级权限( org.apache.shiro.authz.Permission )。他们是否依赖 ThreadContext 中的任何内容?或者做一些我应该早点研究的奇怪事情?
  • 我是否做过任何其他可能给我带来问题的事情,或者我可以改进什么?
  • 最佳答案

  • 仅当您想调用 SecurityUtils.getSecurityManager() 时,引用的错误才是错误。 .非常欢迎您手动传递它或将其作为 SecurityUtils 使用之外的依赖项注入(inject)。
  • SecurityUtils 主要是为那些使用 Shiro 的人提供便利,如果他们想使用它的话。 Shiro 中只有少数东西真正明确地调用它:一些 Shiro 的 AspectJ 集成调用它,一些 Shiro 的 Web 支持调用它(Servlet 过滤器、JSP 和 JSF 标签库)。然而,在 Shiro 中使用它的这些情况下,它(我认为)总是由模板方法调用,允许您覆盖该方法以在需要时从其他地方获取主题。
  • 调用 SecurityUtils.setSecurityManager 无害只要您对整个 JVM 的单个 SecurityManager 实例感到满意。如果您在同一个 JVM 中有多个使用该方法的 Shiro 应用程序,则可能会导致问题。然而即便如此,因为静态内存调用和全局状态是 evil ,如果你能找到另一种引用 SecurityManager 的方法,那就更好了(例如依赖注入(inject))
  • Shiro 权限不依赖任何东西ThreadContext有关的。权限检查被委托(delegate)给一个或多个 Realm s,并且他们对允许或不允许的内容拥有最终决定权。大多数领域反过来使用授权 Cache以确保权限查找保持良好且响应迅速。但这不是线程状态 - 它是(非静态)应用程序单例状态。
  • 你的代码看起来很不错。对 ThriftAuthorizationManager 的一项建议授权检查是直接委托(delegate)给 Subject(Subject 反过来委托(delegate)给 SecurityManager)。我认为这比目前的方法和更好的自我记录 IMO 快一点:
    return getSubject(authToken).checkPermission(permission);
    

  • 感谢您分享您的问题。看到框架的非 Web 使用总是很高兴,因为它是为所有工作负载设计的。

    关于guice - 在 Shiro 中,未绑定(bind)的 SecurityManager 真的是无效的应用程序配置吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16352940/

    相关文章:

    java - 从 Apache Shiro ServletContainerSessionManager 列出经过身份验证的用户

    java - 自定义 Guice 作用域应如何与 TestNG 集成?

    java - Guice 中的空 Multibinder/MapBinder

    c++ - Thrift 在 SSL_accept 上随机崩溃

    web-services - 在 Delphi Win32 中使用 Thrift

    java - JSESSION/HTTPSession 与应用程序制作的 session ID

    shiro - 使用 JDBCRealm 通过 Shiro 对用户进行身份验证

    java - 从 Java 构造函数调用实例方法是好是坏?

    guice - 使用 guice servlet 设置 web.xml 监听器和上下文参数

    java - Parquet:递归 Thrift 数据结构