【问题标题】:Unable to Autowire Component with Request Scope in Servlet Filter无法在 Servlet 过滤器中使用请求范围自动装配组件
【发布时间】:2019-06-26 20:33:26
【问题描述】:

我有一个位于控制器前面的请求过滤器。此过滤器检索用户配置文件并在具有请求范围的 userProfile 组件上设置属性,然后传递到下一个过滤器。

当尝试从过滤器内部访问userProfile 时,该属性尚未成功自动装配。

我在尝试从过滤器内部自动连接 userProfile 时看到以下异常:

org.springframework.beans.factory.BeanCreationException:创建名为“scopedTarget.userProfile”的bean时出错:当前线程的范围“请求”不活动;如果您打算从单例中引用它,请考虑为该 bean 定义一个作用域代理;嵌套异常是 java.lang.IllegalStateException:未找到线程绑定请求:您是指实际 Web 请求之外的请求属性,还是在原始接收线程之外处理请求?如果您实际上是在 Web 请求中操作并且仍然收到此消息,则您的代码可能在 DispatcherServlet 之外运行:在这种情况下,请使用 RequestContextListener 或 RequestContextFilter 来公开当前请求。

但是,当尝试从控制器内部访问 userProfile 时,该属性已成功自动装配。

如何在过滤器中成功自动装配userProfile 组件?

请求过滤器:

@Component
public class JwtAuthenticationFilter extends GenericFilterBean implements Filter {

    @Autowired
    public UserProfile userProfile;

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain next) throws IOException, ServletException {

        ....

        userProfile
            .username(authorizedUser.username())
            .email(authorizedUser.email())
            .firstName(authorizedUser.firstName())
            .lastName(authorizedUser.lastName());
    }
}

控制器:

@CrossOrigin
@RestController
@RequestMapping("/users")
public class UsersController {

    @Autowired
    public UserProfile userProfile;

    @GetMapping(
        path = "/current",
        produces = MediaType.APPLICATION_JSON_VALUE
    )
    @ResponseStatus(HttpStatus.OK)
    public String currentUser() throws ResponseFormatterException {

        System.out.println(userProfile.email());
    }
}

用户个人资料:

@Component
@RequestScope
public class UserProfile {

    @Getter @Setter
    @Accessors(fluent = true)
    @JsonProperty("username")
    private String username;

    @Getter @Setter
    @Accessors(fluent = true)
    @JsonProperty("email")
    private String email;

    @Getter @Setter
    @Accessors(fluent = true)
    @JsonProperty("firstName")
    private String firstName;

    @Getter @Setter
    @Accessors(fluent = true)
    @JsonProperty("lastName")
    private String lastName;
}

安全配置:

@Configuration
@EnableWebSecurity
public class SecurityConfigurator extends WebSecurityConfigurerAdapter {

    @Autowired
    private JwtAuthenticatingFilter jwtAuthenticatingFilter;

    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(getAuthenticator());
    }

    public void configure(WebSecurity web) throws Exception {
      web
        .ignoring()
           .antMatchers("/actuator/**")
           .antMatchers("/favicon.ico");
    }

    protected void configure(HttpSecurity http) throws Exception { 
      http
        .csrf()
          .disable()
        .sessionManagement()
          .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
          .and()
        .authorizeRequests()
          .antMatchers("/actuator/**").permitAll()
          .antMatchers("/favicon.ico").permitAll()
          .and()
        .authorizeRequests()
          .anyRequest()
            .authenticated()
            .and()
          .addFilterBefore(getFilter(), SessionManagementFilter.class)
            .authenticationProvider(getAuthenticator())
            .exceptionHandling()
            .authenticationEntryPoint(new HttpAuthenticationEntryPoint());
    }

    protected AbstractAuthenticator getAuthenticator() {
        return new JwtAuthenticator();
    }

    protected AuthenticatingFilter getFilter() {
        return jwtAuthenticatingFilter;
    }
}

【问题讨论】:

  • 为我工作(spring boot 2.1.6.RELEASE),你确定你没有其他可能导致错误的重要部分吗?可能在某处手动启动一个新线程?或者也许是 hystrix 命令?
  • @Shadov 我在2.1.4.RELEASE。我没有搞乱线程,也没有hystrix 命令。我正在环顾四周,看看是否有什么东西可能会影响 DI,但没有任何反应。
  • @Shadov 我找到了一个安全配置,我会将它添加到我的问题中。
  • 很多事情都可能出错,如果没有看到一切就很难说。注释掉SecurityConfig上的两个注解,看看你的过滤器是否有效,这个类是否有问题就清楚了。并在您的项目中搜索new JwtAuthenticationFilter()@Async,确保没有人这样做。
  • @Shadov 我知道 :( 但绝对没有new JwtAuthenticationFilter() 任何地方。当我删除这些注释时,我的身份验证过滤器根本不会受到影响。不知道这是否有帮助?

标签: java spring spring-boot dependency-injection autowired


【解决方案1】:

我认为问题可能在于您试图将请求范围的 bean(较小的范围)注入单例范围的 bean(较大的范围)。这不起作用有几个原因:

  • 实例化单例时,没有处于活动状态的请求范围
  • 对于第二个请求,单例将使用为第一个请求注入的同一个陈旧 bean。

您可以使用 javax.inject.Provider 来解决这个问题,以便按需延迟注入请求范围的 bean。

@Component
public class JwtAuthenticationFilter extends GenericFilterBean implements Filter {

    @Autowired
    public Provider<UserProfile> userProfileProvider;

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain next) throws IOException, ServletException {

        ....

        userProfileProvider.get()
            .username(authorizedUser.username())
            .email(authorizedUser.email())
            .firstName(authorizedUser.firstName())
            .lastName(authorizedUser.lastName());
    }
}

Spring 有一个类似的接口org.springframework.beans.factory.ObjectFactory,如果您在设置 Provider 的依赖项时遇到问题,可以使用。

@Component
public class JwtAuthenticationFilter extends GenericFilterBean implements Filter {

    @Autowired
    public ObjectFactory<UserProfile> userProfileFactory;

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain next) throws IOException, ServletException {

        ....

        userProfileFactory.getObject()
            .username(authorizedUser.username())
            .email(authorizedUser.email())
            .firstName(authorizedUser.firstName())
            .lastName(authorizedUser.lastName());
    }
}

【讨论】:

  • 感谢您的建议,当我尝试此操作时,我收到以下错误:NoClassDefFoundError: javax/inject/Provider 即使我有此依赖项。
  • 编辑了我的回复,因此您可以在不使用 Provider 界面的情况下尝试此操作。
【解决方案2】:

当 Spring Boot 在 ApplicationContext 中检测到 Filter 时,它会自动将其注册到 servlet 容器的过滤器链中。但是,您不希望在这种情况下发生这种情况,因为过滤器是 Spring Security 过滤器链的一部分。

要修复,请执行以下操作:

  1. 从过滤器中删除 @Component
  2. 不要@AutowireJwtAuthenticationFilter
  3. JwtAuthenticationFilter 创建一个@Bean 方法
  4. FilterRegistrationBean 创建一个@Bean 方法以禁用注册过程。

@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() {
  return new JwtAuthenticationFilter();
}

@Bean
public FilterRegistrationBean<JwtAuthenticationFilter> jwtAuthenticationFilterRegistrationBean() {
  FilterRegistrationBean<JwtAuthenticationFilter> frb = new JwtAuthenticationFilter(jwtAuthenticationFilter());
  frb.setEnabled(false);
  return frb;
}

然后在您的代码中而不是 getFilter 只需引用 jwtAuthenticationFilter() 方法。

【讨论】:

  • 当我在JwtAuthenticationFilter 中有Autowired 属性时,这如何工作?
  • 它仍然是一个 bean,就像 @Component 一样,但现在你有了更多的控制权。
  • 我不明白,你为什么认为他的过滤器是弹簧安全过滤器链的一部分?
  • 我不认为...那是因为addFilterBefore 正是这样做的,将过滤器添加到弹簧安全过滤器链中。
  • @M.Deinum 抱歉延迟尝试您的建议。不幸的是,在尝试自动连接 UserProfile 时,我仍然遇到同样的错误。
猜你喜欢
  • 1970-01-01
  • 2011-12-17
  • 2014-09-13
  • 2012-11-13
  • 1970-01-01
  • 2015-09-12
  • 2012-02-02
  • 2019-11-02
  • 2021-11-27
相关资源
最近更新 更多