我正在将 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
对于需要它的组件,我实际上不需要分配 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
中的任何内容吗?如果我从不使用 ShiroUtils
? SecurityUtils#setSecurityManager
有什么危害吗?如果我不能保证每个请求一个线程的环境? org.apache.shiro.authz.Permission
)。他们是否依赖 ThreadContext
中的任何内容?或者做一些我应该早点研究的奇怪事情? 最佳答案
SecurityUtils.getSecurityManager()
时,引用的错误才是错误。 .非常欢迎您手动传递它或将其作为 SecurityUtils 使用之外的依赖项注入(inject)。 SecurityUtils.setSecurityManager
无害只要您对整个 JVM 的单个 SecurityManager 实例感到满意。如果您在同一个 JVM 中有多个使用该方法的 Shiro 应用程序,则可能会导致问题。然而即便如此,因为静态内存调用和全局状态是 evil ,如果你能找到另一种引用 SecurityManager 的方法,那就更好了(例如依赖注入(inject))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/