Сценарий предварительной аутентификации Spring Boot Security с анонимным доступом
У меня есть приложение Spring Boot (1.5.6), в котором используется сценарий аутентификации " Pre-authenticated" (SiteMinder) из Spring Security.
Мне необходимо анонимно выставить конечную точку "работоспособности" привода, что означает, что запросы к этой конечной точке не будут проходить через SiteMinder, и в результате заголовок SM_USER не будет присутствовать в заголовке HTTP-запроса.
Проблема, с которой я сталкиваюсь, заключается в том, что независимо от того, как я пытаюсь настроить конечную точку "работоспособности", инфраструктура выдает org.springframework.security.web.authentication.preauth.PreAuthenticatedCredentialsNotFoundException
потому что ожидаемый заголовок ("SM_USER") отсутствует, когда запрос не проходит через SiteMinder.
Это была моя оригинальная конфигурация безопасности:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests().antMatchers("/cars/**", "/dealers/**")
.hasAnyRole("CLIENT", "ADMIN")
.and()
.authorizeRequests().antMatchers("/health")
.permitAll()
.and()
.authorizeRequests().anyRequest().denyAll()
.and()
.addFilter(requestHeaderAuthenticationFilter())
.csrf().disable();
}
@Bean
public Filter requestHeaderAuthenticationFilter() throws Exception {
RequestHeaderAuthenticationFilter filter = new RequestHeaderAuthenticationFilter();
filter.setAuthenticationManager(authenticationManager());
return filter;
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(preAuthProvider());
}
@Bean
public AuthenticationProvider preAuthProvider() {
PreAuthenticatedAuthenticationProvider authManager = new PreAuthenticatedAuthenticationProvider();
authManager.setPreAuthenticatedUserDetailsService(preAuthUserDetailsService());
return authManager;
}
@Bean
public AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> preAuthUserDetailsService() {
return new UserDetailsByNameServiceWrapper<>(inMemoryUserDetails());
}
@Bean
public UserDetailsService inMemoryUserDetails() {
return new InMemoryUserDetailsManager(getUserSource().getUsers());
}
@Bean
public UserHolder getUserHolder() {
return new UserHolderSpringSecurityImple();
}
@Bean
@ConfigurationProperties
public UserSource getUserSource() {
return new UserSource();
}
Я пытался исключить конечную точку /health несколькими разными способами, но безрезультатно.
Вещи, которые я пробовал:
Настройте конечную точку работоспособности для анонимного доступа, а не allowAll:
http
.authorizeRequests().antMatchers("/health")
.anonymous()
Настройте WebSecurity, чтобы игнорировать конечную точку работоспособности:
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/health");
}
Отключите защиту для всех конечных точек привода (не идея, но я цеплялся за соломинку):
management.security.enabled=false
Глядя на журналы, кажется, что проблема заключается в том, что RequestHeaderAuthenticationFilter регистрируется как фильтр верхнего уровня, а не как фильтр в существующем securityFilterChain:
.s.DelegatingFilterProxyRegistrationBean : Mapping filter: 'springSecurityFilterChain' to: [/*]
o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'webRequestLoggingFilter' to: [/*]
o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'requestHeaderAuthenticationFilter' to: [/*]
Исходя из моего понимания, потому что RequestHeaderAuthenticationFilter
продолжается AbstractPreAuthenticatedProcessingFilter
фреймворк знает, куда вставить фильтр в цепочке, поэтому я не возиться с вариантами addFilterBefore или addFilterAfter. Может быть, я должен быть? Кто-нибудь знает правильное место, чтобы вставить фильтр явно? (Я думал, что необходимость явно указывать порядок фильтров была удалена в предыдущих версиях Spring Security)
Я знаю, что могу настроить RequestHeaderAuthenticationFilter
чтобы он не выдавал исключение, если заголовок отсутствует, но я бы хотел оставить его включенным, если это вообще возможно.
Я нашел этот пост, который, похоже, похож на мою проблему, но, к сожалению, там тоже нет ответа. весна-загрузки безопасности предварительной проверки подлинности-с разрешенными-ресурсами, еще Authenti
Любая помощь будет принята с благодарностью! Спасибо!
2 ответа
Проблема заключалась в том, что RequestHeaderAuthenticationFilter регистрировался как фильтр верхнего уровня (нежелательный), а также внутри Spring Security FilterChain (желательно).
Причина "двойной регистрации" заключается в том, что Spring Boot зарегистрирует любую Filter
Фасоль с контейнером сервлетов автоматически.
Чтобы предотвратить "авторегистрацию", мне просто нужно было определить FilterRegistrationBean
вот так:
@Bean
public FilterRegistrationBean registration(RequestHeaderAuthenticationFilter filter) {
FilterRegistrationBean registration = new FilterRegistrationBean(filter);
registration.setEnabled(false);
return registration;
}
Документы: https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/
Альтернативное / более простое решение: просто не отмечайте RequestHeaderAuthenticationFilter
класс как @Bean
это нормально, потому что для этого конкретного фильтра не требуется никакой DI. Не помечая фильтр @Bean
, Boot не будет пытаться автоматически зарегистрировать его, что устраняет необходимость в определении FilterRegistrationBean.
Предоставленный ответ не является полным, скорее всего, потому, что Шон Кейси сделал так много проб и ошибок, что потерял представление о том, какая конфигурация действительно устранила проблему. Я публикую свои выводы, которые, как мне кажется, имеют правильную конфигурацию:
Если вы неправильно зарегистрировали
RequestHeaderAuthenticationFilter
как
@Bean
, как говорится в исходном ответе, затем удалите его. Простое создание его как обычного экземпляра и добавление в качестве фильтра регистрирует его правильно:
@Override
protected void configure(HttpSecurity http) throws Exception {
RequestHeaderAuthenticationFilter filter = new RequestHeaderAuthenticationFilter();
// configure your filter
http
.authorizeRequests().anyRequest().authenticated()
.and()
.addFilter(filter)
.csrf().disable();
}
Волшебная конфигурация, которую попробовал Шон Кейси, но сначала не удалось (из-за двойной регистрации фильтра авторизации), это конфигурация:
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/health");
}
Обратите внимание, что добавление
HttpSecurity
antMatchers
ничего не делает в этом случае, кажется, что
WebSecurity
это тот, кто имеет приоритет и контролирует то, что происходит.
ДОПОЛНИТЕЛЬНО: согласно документации Spring Security,
WebSecurity.ignore
следует использовать для статических ресурсов, а не для динамических, которые вместо этого должны быть сопоставлены, чтобы разрешить всем пользователям. Однако в этом случае кажется, что сопоставление переопределяется фильтром PreAuthentication, который заставляет использовать более агрессивный
ignore
сценарий.