【问题标题】:Shiro throws an UnavailableSecurityManagerException in custom JSON serialiserShiro 在自定义 JSON 序列化程序中抛出 Unavailable SecurityManager 异常
【发布时间】:2019-03-23 07:28:48
【问题描述】:

我正在开发基于 TomEE 7 的网络应用程序的 REST API,该应用程序使用 Shiro 1.3.2 来保证安全性。当一个 API 请求进来时,会创建一个SecurityManager 和一个Subject,后者绑定一个SubjectThreadState。我可以在端点代码中的任何位置调用SecurityUtils.getSubject(),并且主题始终可用。

但是,当我尝试在我的自定义 JSON 序列化器中执行相同操作时会出现问题。它只序列化某些类中的特定字段,因此我使用此注释在每个字段的基础上注册它:

@JsonSerialize(using = MySerialiser.class)
Long myRelatedItemId;

我基于the example code on this page under "2.7. @JsonSerialize" 编写了我的序列化程序。序列化器需要执行缓存查找,为此它必须有一个 Shiro 主题。没有,因为多亏了上面的注释,我不手动调用序列化器;相反,泽西称之为。抛出此异常(澄清:当我尝试从序列化程序代码运行 SecurityUtils.getSubject() 时):

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.
    at org.apache.shiro.SecurityUtils.getSecurityManager(SecurityUtils.java:123)
    at org.apache.shiro.subject.Subject$Builder.<init>(Subject.java:627)
    at org.apache.shiro.SecurityUtils.getSubject(SecurityUtils.java:56)

我已经确认,如果我从 API 端点代码手动调用 ObjectMapper().writeValueAsString() 之类的东西,一切正常。但是,这绝对不是正确的方法,因为这样端点将有效地发送和接收字符串,而不是它们要处理的对象。

我不太了解 Shiro 或 Jackson 的内部工作原理,但似乎序列化是在另一个线程中执行的,而 Shiro 的 SubjectThreadState 不存在。虽然如果线程确实是原因,那么我看不出为什么Thread.currentThread().getName() 在序列化器内部和外部返回相同的值,Thread.currentThread().getId() 也是如此。

我尝试了很多方法都无济于事,包括:

  • 升级到 Shiro 1.4.0。
  • 将 Jackson 从 2.7.5 升级到 2.9.7。
  • 将在 API 调用开始时创建的 SecurityManager 实例保存在序列化程序类的静态 ThreadLocal 变量中。
  • 编写我自己的MessageBodyWriter 实现,毫不奇怪,它的调用方式完全相同。
  • 在我的web.xmlShiroFilter 配置中将staticSecurityManagerEnabled 参数设置为true

谁能建议我如何使SecurityManager(或Subject)对序列化程序可见,当它在我的代码未启动的线程中运行时(澄清:或者,否则运行并行并由泽西岛开始,据我所知)?提前致谢。

更新:

此堆栈跟踪是在序列化程序中获取的:

<mypackage>.MySerializer.serialize()
com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField()
com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields()
com.fasterxml.jackson.databind.ser.BeanSerializer.serialize()
com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue()
com.fasterxml.jackson.databind.ObjectWriter$Prefetch.serialize()
com.fasterxml.jackson.databind.ObjectWriter.writeValue()
com.fasterxml.jackson.jaxrs.base.ProviderBase.writeTo()
org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor.invokeWriteTo()
org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor.aroundWriteTo()
org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed()
org.glassfish.jersey.server.internal.JsonWithPaddingInterceptor.aroundWriteTo()
org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed()
org.glassfish.jersey.server.internal.MappableExceptionWrapperInterceptor.aroundWriteTo()
org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed()
org.glassfish.jersey.message.internal.MessageBodyFactory.writeTo()
org.glassfish.jersey.server.ServerRuntime$Responder.writeResponse()
org.glassfish.jersey.server.ServerRuntime$Responder.processResponse()
org.glassfish.jersey.server.ServerRuntime$Responder.process()
org.glassfish.jersey.server.ServerRuntime$2.run()

这个是在我们的拦截器类中创建和绑定的Subject

<mypackage>.MySecurityInterceptor.createSession()
sun.reflect.NativeMethodAccessorImpl.invoke0()
sun.reflect.NativeMethodAccessorImpl.invoke()
sun.reflect.DelegatingMethodAccessorImpl.invoke()
java.lang.reflect.Method.invoke()
org.apache.openejb.core.interceptor.ReflectionInvocationContext$Invocation.invoke()
org.apache.openejb.core.interceptor.ReflectionInvocationContext.proceed()
org.apache.openejb.monitoring.StatsInterceptor.record()
org.apache.openejb.monitoring.StatsInterceptor.invoke()
sun.reflect.GeneratedMethodAccessor111.invoke()
sun.reflect.DelegatingMethodAccessorImpl.invoke()
java.lang.reflect.Method.invoke()
org.apache.openejb.core.interceptor.ReflectionInvocationContext$Invocation.invoke()
org.apache.openejb.core.interceptor.ReflectionInvocationContext.proceed()
org.apache.openejb.core.interceptor.InterceptorStack.invoke()
org.apache.openejb.core.stateless.StatelessContainer._invoke()
org.apache.openejb.core.stateless.StatelessContainer.invoke()
org.apache.openejb.core.ivm.EjbObjectProxyHandler.synchronizedBusinessMethod()
org.apache.openejb.core.ivm.EjbObjectProxyHandler.businessMethod()
org.apache.openejb.core.ivm.EjbObjectProxyHandler._invoke()
org.apache.openejb.core.ivm.BaseEjbProxyHandler.invoke()
com.sun.proxy.$Proxy279.getEntity()
org.openapitools.api.impl.MyApiServiceImpl.getEntity()
org.openapitools.api.MyApi.getEntity()
sun.reflect.NativeMethodAccessorImpl.invoke0()
sun.reflect.NativeMethodAccessorImpl.invoke()
sun.reflect.DelegatingMethodAccessorImpl.invoke()
java.lang.reflect.Method.invoke()
org.glassfish.jersey.server.model.internal.ResourceMethodInvocationHandlerFactory$1.invoke()
org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher$1.run()
org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.invoke()
org.glassfish.jersey.server.model.internal.JavaResourceMethodDispatcherProvider$ResponseOutInvoker.doDispatch()
org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.dispatch()
org.glassfish.jersey.server.model.ResourceMethodInvoker.invoke()
org.glassfish.jersey.server.model.ResourceMethodInvoker.apply()
org.glassfish.jersey.server.model.ResourceMethodInvoker.apply()
org.glassfish.jersey.server.ServerRuntime$2.run()

在最后一行之后的两条跟踪中还有 46 个相同的调用,因此我将它们排除在外。它们包含一堆org.apache.catalina.coreorg.glassfish.jersey

【问题讨论】:

    标签: java jakarta-ee jackson jersey-2.0 shiro


    【解决方案1】:

    【讨论】:

    • 感谢您的回答。我已经读过那一页了。您能否提供有关如何使用此信息的更多详细信息?我在序列化器中没有Subject,当它运行时,它不是我创建的CallableRunnable。值得一提的是,“手动关联”下的示例与我们的代码非常相似。
    • 我不确定它是否是一个不同的线程,我不知道它从哪里开始。我现在只有两个堆栈跟踪:一个在序列化程序中获取,另一个在创建和绑定Subject 的拦截器类中获取。我会将这些堆栈跟踪添加到我的问题中。
    • 我想我开始看到部分问题了。你能解释一下你的杰克逊映射器需要什么/如何需要一个主题吗?您是在会话中编组对象还是什么?
    • 上下文是我上面称为myRelatedItemId的字段必须序列化为不是Long而是String,即相关项的UUID。为了能够读取该项目的 UUID,我需要从缓存中获取该项目。每个客户都有自己的专用缓存,为了使用缓存,我必须知道哪个客户有问题。我看不到将这些信息传递给序列化程序的方法,但如果我可以调用SecurityUtils.getSubject(),那将提供我需要的一切。
    • 让我改写最后一点:缓存查找例程本身尝试调用SecurityUtils.getSubject() 来获取客户和用户,但这会抛出UnavailableSecurityManagerException
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-08
    • 1970-01-01
    • 2019-08-08
    • 2021-10-19
    • 1970-01-01
    相关资源
    最近更新 更多