Spring boot 集成Spring Security过程中的出现的关于Session scope的异常排查及解决方案

阅读: 评论:0

Spring boot 集成Spring Security过程中的出现的关于Session scope的异常排查及解决方案

Spring boot 集成Spring Security过程中的出现的关于Session scope的异常排查及解决方案

背景介绍

最近做的一个项目,其一需要用到Spring 的oauth认证功能, 其二需要对spring 的ContextRefreshedEvent 这个事件进行监听,实现一部分自定义注解的功能(具体功能不作赘述),本来以为毫不相关的两个功能,却出现了一些意料之外的异常。下面是一些具体的异常排查过程以及最终的解决方案,若有部分理解错误或描述错误,欢迎指正(自创文章,如需转载请说明出处)。


场景

  1. 引入Spring Security Oauth2,通过 @EnableOAuthClient 注解激活所需oauth认证功能
  2. 监听ContextRefreshedEvent事件,从ApplicationContext中获取所有的bean names并根据相应的bean name获取到bean,具体代码如下:
/*** @author Lanny Yao* @date 8/30/2018 9:58 AM*/
@Component
public class Listener implements ApplicationListener<ContextRefreshedEvent> {@Overridepublic void onApplicationEvent(ContextRefreshedEvent event) {ApplicationContext context = ApplicationContext();String[] beanNames = BeanNamesForType(Object.class);for (String beanName : beanNames){Object bean = Bean(beanName);...}}
}

启动程序,抛出异常

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.oauth2ClientContext': Scope 'session' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:362) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]at org.springframework.beans.factory.Bean(AbstractBeanFactory.java:199) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]at t.Bean(AbstractApplicationContext.java:1089) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]at com.lanny.blog.demo.ApplicationEvent(Listener.java:21) ~[classes/:na]at com.lanny.blog.demo.ApplicationEvent(Listener.java:12) ~[classes/:na]at t.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:172) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]at t.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:165) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]at t.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:139) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]at t.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:400) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]at t.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:354) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]at t.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:888) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]at org.springframework.boot.t.ServletWebServerApplicationContext.finishRefresh(ServletWebServerApplicationContext.java:161) ~[spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]at t.fresh(AbstractApplicationContext.java:553) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]at org.springframework.boot.fresh(ServletWebServerApplicationContext.java:140) ~[spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]at org.springframework.fresh(SpringApplication.java:762) [spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]at org.springframework.freshContext(SpringApplication.java:398) [spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]at org.springframework.boot.SpringApplication.run(SpringApplication.java:330) [spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]at org.springframework.boot.SpringApplication.run(SpringApplication.java:1258) [spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]at org.springframework.boot.SpringApplication.run(SpringApplication.java:1246) [spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]at com.lanny.blog.demo.seesionexception.SeesionexceptionApplication.main(SeesionexceptionApplication.java:14) [classes/:na]
Caused by: java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.at org.t.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131) ~[spring-web-5.0.8.RELEASE.jar:5.0.8.RELEASE]at org.t.(SessionScope.java:55) ~[spring-web-5.0.8.RELEASE.jar:5.0.8.RELEASE]at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:350) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]... 19 common frames omitted

排查过程

起初一看,完全不知所云啊, 不能创建bean的异常倒是经常看到,但是明明是在getBean(),怎么还影响到了oauth2ClientContext 这个bean的创建了呢?看下面spring 的源代码(位于AbstractBeanFactory 中的doGetBean()方法中),发现在根据scope和beanName获取相应bean的时候会有一个create Bean的操作,所以也就印证了上面说的问题。

try {Object scopedInstance = (beanName, () -> {beforePrototypeCreation(beanName);try {return createBean(beanName, mbd, args);}finally {afterPrototypeCreation(beanName);}});bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);}catch (IllegalStateException ex) {throw new BeanCreationException(beanName,"Scope '" + scopeName + "' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton", ex);}
}

既然是oauth相关的bean,罪魁祸首肯定是@EnableOAuth2Client注解了,果不其然,注释掉该注解之后程序可以正常启动,而且试了几种其他的监听方式,发现只要用到了ApplicationContext 和这个注解,就会报错。你俩到底谁的锅,我来找找。
先看看这个oauth2ClientContext bean是在哪里定义的,全局搜索一下,发现一个代码片段,哟嗬,确实是被标记为“session” scope的,那么问题来了,是不是拥有“session”这个scope的bean都会出现这个异常呢

@Bean
@Scope(value = "session", proxyMode = ScopedProxyMode.INTERFACES)
public OAuth2ClientContext oauth2ClientContext() {return new DefaultOAuth2ClientContext(accessTokenRequest);
}

try catch 一下:

Error bean -> [scopedTarget.oauth2ClientContext], caused by:Error creating bean with name 'scopedTarget.oauth2ClientContext': Scope 'session' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
Error bean -> [scopedTarget.accessTokenRequest], caused by:Error creating bean with name 'scopedTarget.accessTokenRequest': Scope 'request' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.

发现还有另外一个bean accessTokenRequest也出现问题了,这么看来不只是session scope, request scope的bean也出了问题,如此一来弄清楚scope的意义就变成首要任务了:

spring中bean的scope属性,有如下5种类型:singleton 表示在spring容器中的单例,通过spring容器获得该bean时总是返回唯一的实例
prototype表示每次获得bean都会生成一个新的对象
request表示在一次http请求内有效(只适用于web应用)
session表示在一个用户会话内有效(只适用于web应用)
globalSession表示在全局会话内有效(只适用于web应用)
在多数情况,我们只会使用singleton和prototype两种scope,如果在spring配置文件内未指定scope属性,默认为singleton。(摘自.html)

可以清楚的看到session 和request 的scope只适用于web应用,生命周期取决于http请求和session过期时间,所以通过Spring 上下文也就是ApplicationContext获取bean时,当beanName对应的bean的scope是“session”或者“request”之类时,其实是不允许直接创建的.所以到这里,异常出现的根本原因已经找到,所以代码里面需要做的就是: 过滤scope !

很庆幸,ApplicationContext本身就提供了方法判断scope,但是只能判断“singleton” 和“prototype”类型的:

if (context.isPrototype(beanName) || context.isSingleton(beanName))

ok,过滤完成,运行一下,WTF!加个判断条件,又给我出新的异常,让不让人活了!!!

2018-08-30 13:54:04.396  INFO 18784 --- [           main] ConditionEvaluationReportLoggingListener : Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2018-08-30 13:54:04.437 ERROR 18784 --- [           main] o.s.b.d.LoggingFailureAnalysisReporter   : ***************************
APPLICATION FAILED TO START
***************************Description:A component required a bean named 'autoConfigurationReport' that could not be found.Action:Consider defining a bean named 'autoConfigurationReport' in your configuration.

这个更懵逼,感觉更加的毫不相关,研究源码之下发现scope 是singleton的bean是不能去调isPrototype()方法的,调用后会出现这个异常,直接导致JVM挂掉,比之前的异常更加暴力。关于这一点没有去深究,不知道是出于什么策略会有这样的设计,还是说是因为其他的一些原因。其实没有特殊需求的情况下,工程项目下的自定义的所有的bean都默认scope是singleton的,所以,只需要找出singleton的bean 就能满足需求了

解决方案

  1. context.isSingleton(beanName) 直接通过这个方法做判断找出所有的singleton的bean,但是如上所述这个方法存在风险
  2. 可以看到代码里是通过 String[] beanNames = BeanNamesForType(Object.class);获取到的所有的bean name,对于这个方法,其实有两个参数可以使用,这是解决这个问题的关键,把第二个参数设置成false,就可以只取scope为singleton的bean了,第三个参数根据实际情况设置,我这里直接设为true。
String[] beanNames = BeanNamesForType(Object.class,false,true);
* @param type the class or interface to match, or {@code null} for all bean names* @param includeNonSingletons whether to include prototype or scoped beans too* or just singletons (also applies to FactoryBeans)* @param allowEagerInit whether to initialize <i>lazy-init singletons</i> and* <i>objects created by FactoryBeans</i> (or by factory methods with a* "factory-bean" reference) for the type check. Note that FactoryBeans need to be* eagerly initialized to determine their type: So be aware that passing in "true"* for this flag will initialize FactoryBeans and "factory-bean" references.* @return the names of beans (or objects created by FactoryBeans) matching* the given object type (including subclasses), or an empty array if none* @see FactoryBean#getObjectType* @see BeanFactoryUtils#beanNamesForTypeIncludingAncestors(ListableBeanFactory, Class, boolean, boolean)*/String[] getBeanNamesForType(@Nullable Class<?> type, boolean includeNonSingletons, boolean allowEagerInit);

至此,问题得以解决。

本文发布于:2024-02-02 13:52:14,感谢您对本站的认可!

本文链接:https://www.4u4v.net/it/170685313244231.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:过程中   异常   解决方案   boot   Spring
留言与评论(共有 0 条评论)
   
验证码:

Copyright ©2019-2022 Comsenz Inc.Powered by ©

网站地图1 网站地图2 网站地图3 网站地图4 网站地图5 网站地图6 网站地图7 网站地图8 网站地图9 网站地图10 网站地图11 网站地图12 网站地图13 网站地图14 网站地图15 网站地图16 网站地图17 网站地图18 网站地图19 网站地图20 网站地图21 网站地图22/a> 网站地图23