我正在将 Apache Shiro 添加到我的应用程序中,我想知道以下错误消息是否真的准确:
org.apache.shiro.UnavailableSecurityManagerException:调用代码无法访问 SecurityManager,绑定到 org.apache.shiro.util.ThreadContext 或作为 vm 静态单例。这是无效的应用程序配置。
我已经浏览了一下源代码,我得到的印象是,只要我不使用SecurityUtils
并且我愿意将 a 传递SecurityManager
给需要它的组件,我实际上不需要SecurityManager
将使用的静态单例SecurityUtils
。
我要避免的具体事情是让 Shiro 放入任何东西ThreadLocal
或让 Shiro 使用它的ThreadContext
支持类。我正在使用 Apache Thrift,并且不想将自己提交给每个请求一个线程的网络设计。我对 Shiro 的要求非常低,所以我将在下面展示我在做什么。
我在我的应用程序中使用 Guice,但我没有使用shiro-guice
,因为 Shiro AOP 的东西依赖于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"));
}
}
这不完全是生产质量领域/安全管理器设置,但它足以让我进行测试。接下来,我创建自己的管理器类,其范围非常有限,供我的应用程序的组件使用。我有两个;一个ThriftAuthenticationManager
和一个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);
}
}
}
而后者:
@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;
}
}
所以,我想我实际上有几个问题。
我引用的错误实际上是一个错误还是只是一个过分热心的日志消息?
ThreadContext
如果我从不使用,我是否需要担心 Shiro 依赖于 a 中的任何东西ShiroUtils
?SecurityUtils#setSecurityManager
如果我不能保证每个请求一个线程的环境,使用会有什么危害吗?我还没有尝试过使用 Shiro 的高级权限 (
org.apache.shiro.authz.Permission
)。他们是否依赖 a 中的任何东西ThreadContext
或做任何我应该早晚研究的奇怪事情?我是否做过任何其他可能给我带来问题的事情,或者我可以改进什么?