Andy Jenkins
Andy Jenkins

Reputation: 647

Unable to Autowire Component with Request Scope in Servlet Filter

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

Upvotes: 1

Views: 3998

Answers (5)

Amir Pashazadeh
Amir Pashazadeh

Reputation: 7322

What about having something like:

@Component
public class UserProfile {

    public String getUsername() {
       return getUserDetails().map(UserDetails::getUsername).orElse(null);
    }

    public String getEmail() {
       return getUserDetails().map(UserDetails::getEmail).orElse(null);          
    }

    ...

    private Optional<UserDetails> getUserDetails() {
        return Optional.ofNullable(SecurityContextHolder.getContext())
          .map(SecurityContext::getAuthentication)
          .map(Authentication::getPrincipal);
    }

    private UserDetails getUserDetails(Object principal) {
      // do logic of extracting data... and principal is probably String (username).
    }
 }

So your UserProfile won't be request scope anymore, and all you must do is extracting UserDetails object according to principal. And you can inject the UserProfile anywhere you want, and you won't need the filter anymore.

Upvotes: 0

Amir Pashazadeh
Amir Pashazadeh

Reputation: 7322

I am not sure, but I believe without custom configuration it is DispatcherServlet which binds RequestContext to active thread, so when having a filter before that expecting accessing RequestContext won't work (and I believe RequestScope proxies depend on thread-bound RequestContext).

So you must activate org.springframework.web.context.request.RequestContextListener or org.springframework.web.filter.RequestContextFilter before your own filter, otherwise RequestScope bean (which is in fact a proxy) should not work.

Upvotes: 0

Rory G
Rory G

Reputation: 172

Try this:

UserProfile userService = (UserProfile) req.getAttribute("scopedTarget.userProfile");

Upvotes: 0

user506069
user506069

Reputation: 781

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

Upvotes: 3

M. Deinum
M. Deinum

Reputation: 124898

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.

Upvotes: 1

Related Questions