Невозможно автоматически связать компонент с областью запроса в фильтре сервлетов - PullRequest
0 голосов
/ 26 июня 2019

У меня есть фильтр запросов, который находится перед контроллером.Этот фильтр извлекает профиль пользователя и устанавливает свойства компонента userProfile с областью запроса, а затем переходит к следующему фильтру.

При попытке доступа к userProfile из фильтра свойство имеетНе удалось автоматически подключить.

При попытке автоматического подключения userProfile из фильтра я вижу следующее исключение:

org.springframework.beans.factory.BeanCreationException: Ошибка при созданииbean-компонент с именем scopedTarget.userProfile: Scope 'request' не активен для текущего потока;рассмотрите возможность определения прокси-объекта с заданной областью действия для этого компонента, если вы собираетесь ссылаться на него из одноэлементного объекта;Вложенное исключение - java.lang.IllegalStateException: не найден привязанный к потоку запрос: Вы ссылаетесь на атрибуты запроса вне фактического веб-запроса или обрабатываете запрос вне первоначально получающего потока?Если вы действительно работаете в веб-запросе и по-прежнему получаете это сообщение, ваш код, вероятно, выполняется за пределами 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 голосов
/ 27 июня 2019

Я полагаю, что проблема может заключаться в том, что вы пытаетесь внедрить bean-объект с областью запроса (меньшая область действия) в bean-объект с одной областью действия (большая область действия).Есть несколько причин, по которым это не сработает:

  • При создании экземпляра singleton область действия не активна
  • Для второго запроса singleton будет работать стот же устаревший 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, который вы можете использовать, если у вас возникают проблемы с настройкой зависимостей провайдера.

@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 голосов
/ 27 июня 2019

Когда Spring Boot обнаруживает Filter в ApplicationContext, он автоматически регистрирует его в цепочке фильтров для контейнера сервлета. Однако вы не хотите, чтобы это происходило в этом случае, так как фильтр является частью цепочки фильтров Spring Security.

Чтобы исправить, сделайте следующее:

  1. Удалите @Component из вашего фильтра
  2. Не @Autowire JwtAuthenticationFilter
  3. Создать метод @Bean для JwtAuthenticationFilter
  4. Создайте метод @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 просто укажите метод jwtAuthenticationFilter().

...