未绑定的SecurityManager在Shiro中是否真的是无效的应用程序配置?

Rya*_*n J 10 thrift guice shiro

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

org.apache.shiro.UnavailableSecurityManagerException:调用代码无法访问的SecurityManager,无论是绑定到org.apache.shiro.util.ThreadContext还是作为vm静态单例.这是一个无效的应用程序配置.

我查看了一下源代码,我得到的印象是,只要我没有使用SecurityUtils并且我愿意传递SecurityManager给需要它的组件,我实际上不需要分配SecurityManager给它使用的静态单例SecurityUtils.

我想避免的具体事情是让Shiro把任何东西放入ThreadLocal或让Shiro使用它的ThreadContext支持类.我正在使用Apache Thrift,并且不想让自己致力于每个请求的单线程网络设计.我对Shiro的要求非常小,所以我将在下面展示我正在做的事情.

我在我的应用程序中使用Guice,但我没有使用,shiro-guice因为Shiro AOP的东西取决于与a Subject相关联ThreadContext.相反,我从一个非常简单的Guice模块开始.

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

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

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

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

    }
}
Run Code Online (Sandbox Code Playgroud)

而后者:

@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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我的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;
    }
}
Run Code Online (Sandbox Code Playgroud)

所以,我想我实际上有几个任务.

  1. 我引用的错误实际上是错误还是只是过度热心的日志消息?

  2. ThreadContext如果我从不使用过,我是否需要担心Shiro依赖于任何东西ShiroUtils

  3. SecurityUtils#setSecurityManager如果我不能保证每个请求的单线程环境,是否有任何损害?

  4. 我还没有尝试过使用Shiro的高级权限(org.apache.shiro.authz.Permission).他们是否依赖于任何事情,ThreadContext或做任何奇怪的事情,我应该早点研究?

  5. 我是否做过任何可能导致我出现问题的事情或者我可以改进任何事情?

Les*_*ood 8

  1. 仅当您要呼叫时,引用的错误才是错误SecurityUtils.getSecurityManager().非常欢迎您手动传递它或将其作为SecurityUtils使用之外的依赖项注入.

  2. 对于那些使用Shiro的用户来说,SecurityUtils主要是一种便利.Shiro中只有少数事情实际上明确地称之为:Shiro的一些AspectJ集成称之为,Shiro的一些Web支持称之为(Servlet过滤器,JSP和JSF标签库).但是,在Shiro中使用它的这些情况下,它(我认为)总是通过模板方法调用,允许您覆盖该方法以从其他地方获取主题(如果需要).

  3. SecurityUtils.setSecurityManager只要您对整个JVM的单个SecurityManager实例感到满意,就不会对调用造成任何伤害.如果您有多个在同一JVM中使用该方法的Shiro应用程序,则可能会导致问题.然而,即便如此,因为静态内存调用和全局状态是邪恶的,如果你甚至可以找到另一种引用SecurityManager的方法,那就更好了(例如依赖注入)

  4. Shiro权限不依赖于任何ThreadContext相关的内容.权限检查被委托给一个或多个Realms,并且他们对于允许与否的最终决定权.大多数领域反过来使用授权Cache来确保权限查找保持良好和响应.但这不是线程状态 - 它是(非静态)应用程序单例状态.

  5. 你的代码看起来很不错.ThriftAuthorizationManager授权检查的一个建议是直接委托给主题(主题依次委托给SecurityManager).我认为这比目前的方法和更好的自我记录IMO要快一点:

    return getSubject(authToken).checkPermission(permission);
    
    Run Code Online (Sandbox Code Playgroud)

感谢您分享您的问题.看到框架的非Web用法总是很好,因为它是为所有工作负载而设计的.


Tad*_*she 6

这是我在遇到同样问题时发现的:我将shiro过滤器语句添加到我的web.xml文件中,然后在我的bean中直接调用了Subject.我补充说:

<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>
Run Code Online (Sandbox Code Playgroud)

然后我继续写下面的声明:

Subject subject = SecurityUtils.getSubject();
Run Code Online (Sandbox Code Playgroud)