0
votes

I have a request filter that sits in front of a controller. This filter retrieves a user profile and sets the properties on a userProfile Component with Request Scope and then passes on to the next filter.

When trying to access the userProfile from inside the filter, the property has not been successfully autowired.

I see the following exception when trying to autowire the userProfile from inside the filter:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.userProfile': 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: In this case, use RequestContextListener or RequestContextFilter to expose the current request.

When trying to access the userProfile from inside the controller however, the property has been successfully autowired.

How can I successfully autowire the userProfile Component inside the filter?

Request filter:

@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());
    }
}

Controller:

@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());
    }
}

User Profile:

@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;
}

Security Config:

@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
Works for me (spring boot 2.1.6.RELEASE), are you sure you don't have any other important parts that could cause the error? Possibly starting a new thread manually somewhere? Or maybe hystrix commands?Shadov
@Shadov I am on 2.1.4.RELEASE. I'm not messing with threads and have no hystrix commands. I am looking around to see if there is something that may be affecting the DI but nothing jumps out.Andy Jenkins
@Shadov I have found a security config, I will add it to my question.Andy Jenkins
Lot of things that could potentially go wrong, hard to say without seeing everything. Comment out two annotations on SecurityConfig and see if your filter works, it will be clear if this class is at fault. And search your project for new JwtAuthenticationFilter() and @Async, make sure no one does that.Shadov
@Shadov I know :( but definitely no new JwtAuthenticationFilter() anywhere. When I remove those annotations, my Authentication Filter doesn't get hit at all. Don't know if that helps?Andy Jenkins

2 Answers

1
votes

I believe the issue may be that you are attempting to inject a request-scoped bean (smaller scope) into a singleton-scoped bean (larger scope). There are a couple of reasons why this won't work:

  • No request scope is active when the singleton is instantiated
  • For the second request, the singleton would be working with the same stale bean that was injected for the first request.

You can get around this using a javax.inject.Provider to lazily inject the request-scoped bean on demand.

@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 has a similar interface org.springframework.beans.factory.ObjectFactory you can use if you are having trouble getting Provider's dependencies set up.

@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());
    }
}
0
votes

When Spring Boot detects a Filter in the ApplicationContext it will automatically register it in the chain of filters for the servlet container. However you don't want this to happen in this case as the filter is part of the Spring Security filter chain.

To fix do the following:

  1. Remove @Component from your filter
  2. Don't @Autowire the JwtAuthenticationFilter
  3. Create an @Bean method for the JwtAuthenticationFilter
  4. Create an @Bean method for a FilterRegistrationBean to disable the registration process.

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

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

Then in your code instead of the getFilter just reference the jwtAuthenticationFilter() method.