Вопрос или проблема
Я использую сервер авторизации Spring, у меня есть два контроллера: /user/** и /client/**. Теперь я хочу, чтобы /user/** был доступен публично, а /client/** мог быть доступен только аутентифицированным пользователям. Я использовал две цепочки фильтров: одна для сервера авторизации, которая перенаправляет все запросы, начинающиеся с /client/**, на /login, а другая обрабатывает перенаправление на страницу /login. Вот моя конфигурация:
@Configuration
public class WebSecurityConfig {
@Order(1)
@Bean
SecurityFilterChain authServerFilterChain(HttpSecurity http, JwtAuthenticationConverter jwtAuthenticationConverter) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
.oidc(Customizer.withDefaults());
http.exceptionHandling(ex -> ex.defaultAuthenticationEntryPointFor(
new LoginUrlAuthenticationEntryPoint("/login"),
// перенаправить все неаутентифицированные запросы /client/** на login
new AntPathRequestMatcher("/client/**")
))
.oauth2ResourceServer(rs -> rs.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter)));
return http.build();
}
@Order(2)
@Bean
SecurityFilterChain defaultFilterChain(HttpSecurity http, CorsConfigurationSource corsConfigurationSource) throws Exception {
http
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.csrf(csrf -> csrf.disable())
.cors(cors -> cors.configurationSource(corsConfigurationSource))
.authorizeHttpRequests(
req -> req.requestMatchers("/user/**").permitAll()
.anyRequest().authenticated())
.formLogin(Customizer.withDefaults());
return http.build();
}
@Bean
AuthorizationServerSettings authorizationServerSettings() {
return AuthorizationServerSettings.builder()
.build();
}
}
Вот мой конечный пункт пользователя, к которому я пытаюсь получить доступ
@Controller
@ResponseBody
@RequestMapping("/user/")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping()
public String hello() {
return "Hello from User Controller";
}
}
Я ожидал получить вывод, но меня перенаправляет на страницу /login.
Я пробовал установить logging.level.org.springframework.security=debug
, и это дало:
2024-09-22T14:23:35.456+05:30 INFO 131685 --- [spring-oauth2-auth-server] [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Инициализация Spring DispatcherServlet 'dispatcherServlet'
2024-09-22T14:23:35.457+05:30 INFO 131685 --- [spring-oauth2-auth-server] [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Инициализация сервлета 'dispatcherServlet'
2024-09-22T14:23:35.459+05:30 INFO 131685 --- [spring-oauth2-auth-server] [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Завершена инициализация за 2 мс
2024-09-22T14:23:35.492+05:30 DEBUG 131685 --- [spring-oauth2-auth-server] [nio-8080-exec-1] o.s.security.web.FilterChainProxy : Защита GET /user
2024-09-22T14:23:35.508+05:30 DEBUG 131685 --- [spring-oauth2-auth-server] [nio-8080-exec-1] o.s.s.w.a.AnonymousAuthenticationFilter : Установлен SecurityContextHolder в анонимный SecurityContext
2024-09-22T14:23:35.512+05:30 DEBUG 131685 --- [spring-oauth2-auth-server] [nio-8080-exec-1] o.s.security.web.FilterChainProxy : Защищен GET /user
2024-09-22T14:23:35.548+05:30 DEBUG 131685 --- [spring-oauth2-auth-server] [nio-8080-exec-1] o.s.security.web.FilterChainProxy : Защита GET /error
2024-09-22T14:23:35.549+05:30 DEBUG 131685 --- [spring-oauth2-auth-server] [nio-8080-exec-1] o.s.s.w.a.AnonymousAuthenticationFilter : Установлен SecurityContextHolder в анонимный SecurityContext
2024-09-22T14:23:35.553+05:30 DEBUG 131685 --- [spring-oauth2-auth-server] [nio-8080-exec-1] o.s.s.web.DefaultRedirectStrategy : Перенаправление на http://localhost:8080/login
2024-09-22T14:23:35.580+05:30 DEBUG 131685 --- [spring-oauth2-auth-server] [nio-8080-exec-2] o.s.security.web.FilterChainProxy : Защита GET /login
2024-09-22T14:23:35.810+05:30 DEBUG 131685 --- [spring-oauth2-auth-server] [nio-8080-exec-3] o.s.security.web.FilterChainProxy : Защита GET /favicon.ico
2024-09-22T14:23:35.811+05:30 DEBUG 131685 --- [spring-oauth2-auth-server] [nio-8080-exec-3] o.s.s.w.a.AnonymousAuthenticationFilter : Установлен SecurityContextHolder в анонимный SecurityContext
2024-09-22T14:23:35.812+05:30 DEBUG 131685 --- [spring-oauth2-auth-server] [nio-8080-exec-3] o.s.s.web.DefaultRedirectStrategy : Перенаправление на http://localhost:8080/login
2024-09-22T14:23:35.825+05:30 DEBUG 131685 --- [spring-oauth2-auth-server] [nio-8080-exec-4] o.s.security.web.FilterChainProxy : Защита GET /login
Ответ или решение
Проблема, с которой вы столкнулись, связана с конфигурацией фильтров безопасности в Spring Security. У вас два контроллера, где /user/**
должен быть общедоступным, а /client/**
доступен только для аутентифицированных пользователей. Однако, несмотря на указание permitAll()
для /user/**
, вы все равно перенаправляетесь на страницу входа.
Давайте рассмотрим несколько возможных причин и решений.
1. Порядок фильтров
Основной момент, на который стоит обратить внимание, — это порядок ваших фильтров. Вы используете аннотацию @Order
, чтобы указать порядок приложения фильтров. Фильтры обрабатываются в порядке их объявления: префикс 1
имеет более высокий приоритет, чем 2
. Убедитесь, что фильтр для аутентификации не обрабатывает запросы к /user/**
перед тем, как ваш фильтр с permitAll()
сможет их обработать.
2. Проверка конфигурации авторизации
Убедитесь, что ваш основной фильтр, определенный под @Order(1)
, не перехватывает запросы к /user/**
. Проверьте, что в вашем фильтре аутентификации вы не добавляете никаких глобальных настроек авторизации, которые могли бы переопределить разрешения из второго фильтра.
3. Пример конфигурации
Вот как вы можете модифицировать вашу конфигурацию, чтобы убедиться, что все работает как ожидается:
@Configuration
public class WebSecurityConfig {
@Order(1)
@Bean
SecurityFilterChain authServerFilterChain(HttpSecurity http, JwtAuthenticationConverter jwtAuthenticationConverter) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
.oidc(Customizer.withDefaults());
// Настройте обработку исключений
http.exceptionHandling(ex -> ex.defaultAuthenticationEntryPointFor(
new LoginUrlAuthenticationEntryPoint("/login"),
new AntPathRequestMatcher("/client/**")
))
.oauth2ResourceServer(rs -> rs.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter)));
return http.build();
}
@Order(2)
@Bean
SecurityFilterChain defaultFilterChain(HttpSecurity http, CorsConfigurationSource corsConfigurationSource) throws Exception {
http
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.csrf(csrf -> csrf.disable())
.cors(cors -> cors.configurationSource(corsConfigurationSource))
.authorizeHttpRequests(req -> req
.requestMatchers("/user/**").permitAll()
.requestMatchers("/client/**").authenticated() // Добавляем эту строку для ясности
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults());
return http.build();
}
@Bean
AuthorizationServerSettings authorizationServerSettings() {
return AuthorizationServerSettings.builder().build();
}
}
4. Подключение логирования
Как вы уже сделали, включите уровень логирования DEBUG
для org.springframework.security
, чтобы исследовать, какие именно фильтры принимают решение по вашему запросу. Это поможет выявить, на каком этапе происходят перенаправления.
5. Проверка пути контроллера
Убедитесь, что в вашем контроллере /user/
вы используете правильный маппинг:
@Controller
@ResponseBody
@RequestMapping("/user")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping
public String hello() {
return "Hello from User Controller";
}
}
Заключение
Убедитесь, что у вас нет других конфигураций безопасности, которые могут затмевать эти настройки. Проверьте их порядок и убедитесь, что правила ваши проверки висят именно в том месте, которое вы ожидаете. Если всё настроено правильно, то /user/**
должен быть доступен без аутентификации.
Если после этих изменений проблема не будет решена, стоит проверить другие аспекты вашего проекта, такие как конфигурации Carter и наличие других фильтров, которые могут перекрывать ваши настройки.