无法在Servlet筛选器中自动连接具有请求范围的组件

时间:2019-06-26 20:33:26

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

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

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

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

  

org.springframework.beans.factory.BeanCreationException:创建名称为'scopedTarget.userProfile'的bean时出错:当前线程的作用域'request'未激活;如果您打算从单例中引用它,请考虑为此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;
    }
}

2 个答案:

答案 0 :(得分:0)

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

要解决此问题,请执行以下操作:

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

然后在您的代码中代替 @Bean public JwtAuthenticationFilter jwtAuthenticationFilter() { return new JwtAuthenticationFilter(); } @Bean public FilterRegistrationBean<JwtAuthenticationFilter> jwtAuthenticationFilterRegistrationBean() { FilterRegistrationBean<JwtAuthenticationFilter> frb = new JwtAuthenticationFilter(jwtAuthenticationFilter()); frb.setEnabled(false); return frb; } 而不是getFilter方法。

答案 1 :(得分:0)

我认为问题可能在于您试图将请求范围的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());
    }
}