Reputation: 647
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
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
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
Reputation: 172
Try this:
UserProfile userService = (UserProfile) req.getAttribute("scopedTarget.userProfile");
Upvotes: 0
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:
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
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:
@Component
from your filter@Autowire
the JwtAuthenticationFilter
@Bean
method for the JwtAuthenticationFilter
@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