我正在将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的内容取决于Subject
与{{1}相关联}}。相反,我从一个非常简单的Guice模块开始。
ThreadContext
这不是一个生产质量领域/安全管理器设置,但它足以让我进行测试。接下来,我创建自己的管理器类,其范围非常有限,我的应用程序的组件使用它们。我有两个;一个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);
}
}
}
My Thrift服务使用上述两个类进行身份验证和授权。例如:
@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);
}
}
}
所以,我想我实际上有几个任务。
我引用的错误实际上是错误还是过于热心的日志消息?
如果我从不使用@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
会有什么危害吗?
我还没有尝试过使用Shiro的高级权限(SecurityUtils#setSecurityManager
)。他们是否依赖于org.apache.shiro.authz.Permission
中的任何事情或者做一些我应该早点研究的奇怪事情?
我是否做过任何可能导致我出现问题的事情,或者我可以改进任何事情吗?
答案 0 :(得分:8)
仅当您要致电SecurityUtils.getSecurityManager()
时,引用的错误才会出错。非常欢迎您手动传递它或将其作为SecurityUtils使用之外的依赖项注入。
对于那些使用Shiro的用户来说,SecurityUtils主要是方便的。 Shiro中只有少数事情实际上明确地称之为:Shiro的一些AspectJ集成称之为,Shiro的一些Web支持称之为(Servlet过滤器,JSP和JSF标签库)。但是,在Shiro中使用它的情况下,它(我认为)总是通过模板方法调用,允许您覆盖该方法以从其他地方获取主题(如果需要)。
只要您对整个JVM的单个SecurityManager实例感到满意,就可以在调用SecurityUtils.setSecurityManager
时受到伤害。如果您有多个在同一JVM中使用该方法的Shiro应用程序,则可能会导致问题。但即便如此,因为静态内存调用和全局状态是evil,如果你能找到另一种引用SecurityManager的方法,那就更好了(例如依赖注入)
Shiro权限不依赖于任何ThreadContext
相关的内容。权限检查被委托给一个或多个Realm
,并且他们对允许或不允许的内容有最终决定权。大多数领域反过来使用授权Cache
来确保权限查找保持良好和响应。但这不是线程状态 - 它是(非静态)应用程序单例状态。
您的代码看起来很不错。 ThriftAuthorizationManager
授权检查的一个建议是直接委托给Subject(Subject依次委托给SecurityManager)。我认为这比目前的方法和更好的自我记录IMO要快一点:
return getSubject(authToken).checkPermission(permission);
感谢您分享您的问题。看到框架的非Web用法总是很好,因为它是为所有工作负载而设计的。
答案 1 :(得分:5)
这是我在遇到同样问题时发现的:我将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>
然后我继续写下面的声明:
Subject subject = SecurityUtils.getSubject();