Spring Cloud Gateway с Spring Security для регистрации/входа с использованием аутентификации JWT. 403 Запрещенная ошибка

Я изучаю SpringBoot для проекта приложения микросервиса. Я использую маршрут аутентификации клиента Keycloak с ролью учетных записей служб и добавил конфигурацию keycloak в приложение application.properties службы шлюза API. Вот конфигурация безопасности шлюза API:


@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {


    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity serverHttpSecurity){

        serverHttpSecurity
                .authorizeExchange(exchange -> exchange
                        .pathMatchers(
                                "/api/**",
                                "/eureka/**"
                        )
                        .permitAll()
                        .anyExchange()
                        .authenticated())
                .csrf()
                .disable()

                .oauth2ResourceServer(ServerHttpSecurity.OAuth2ResourceServerSpec::jwt);
        return serverHttpSecurity.build();
    }

}

Теперь я создаю приложение службы идентификации, которое обрабатывает регистрацию и вход пользователей отдельно от службы пользователей для управления пользователями. Эта служба использует аутентификацию JWT с Spring Security AuthenticationProvider, JwtFilter и UserNamePasswordAuthenticationFilter. Ниже приведена конфигурация безопасности службы идентификации.


@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
@EnableMethodSecurity(securedEnabled = true)
public class SecurityConfig {


    private final JwtFilter jwtAuthFilter;
    private final AuthenticationProvider authenticationProvider;


    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception{
        httpSecurity
                .cors(withDefaults())
                .csrf(AbstractHttpConfigurer::disable)
                .authorizeRequests(request -> request.regexMatchers(
                        "/api/auth/*",
                        "/v2/api-docs",
                        "/v3/api-docs",
                        "/v3/api-docs/*",
                                "/swagger-resources/",
                                "/swagger-resources/*",
                                "/configuration/ui",
                                "/configuration/security",
                                "/swagger-ui/",
                                "/webjars/*",
                                "/swagger-ui.html"
                        )
                        .permitAll()
                        .anyRequest()
                        .authenticated()
                )
                .sessionManagement(
                        session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                )
                .authenticationProvider(authenticationProvider)
                .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);

        return httpSecurity.build();
    }
}

Зарегистрироваться / Localhost Это конечная точка: при выполнении POST-запроса почтальона с использованием OAuth2.0 с токеном JWT я получаю в ответ 403: запрещено. Ниже приведен журнал службы шлюза API:

2024-04-12 20:15:02.924 TRACE [xpense-gateway,,] 35685 --- [     parallel-8] o.s.c.g.h.p.PathRoutePredicateFactory    : Pattern "[/eureka/web]" does not match against value "/api/auth/register"
2024-04-12 20:15:02.924 TRACE [xpense-gateway,,] 35685 --- [     parallel-8] o.s.c.g.h.p.PathRoutePredicateFactory    : Pattern "/api/auth/**" matches against value "/api/auth/register"
2024-04-12 20:15:02.924 DEBUG [xpense-gateway,,] 35685 --- [     parallel-8] o.s.c.g.h.RoutePredicateHandlerMapping   : Route matched: identity-service
2024-04-12 20:15:02.924 DEBUG [xpense-gateway,,] 35685 --- [     parallel-8] o.s.c.g.h.RoutePredicateHandlerMapping   : Mapping [Exchange: POST http://localhost:8080/api/auth/register] to Route{id='identity-service', uri=lb://identity-service, order=0, predicate=Paths: [/api/auth/**], match trailing slash: true, gatewayFilters=[[[SpringCloudCircuitBreakerResilience4JFilterFactory name = 'resilience', fallback = /identity-fallback], order = 0]], metadata = {}}
2024-04-12 20:15:02.924 DEBUG [xpense-gateway,,] 35685 --- [     parallel-8] o.s.c.g.h.RoutePredicateHandlerMapping   : [ade0fee6-4] Mapped to org.springframework.cloud.gateway.handler.FilteringWebHandler@56568494
2024-04-12 20:15:02.924 DEBUG [xpense-gateway,,] 35685 --- [     parallel-8] o.s.c.g.handler.FilteringWebHandler      : Sorted gatewayFilterFactories: [[GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.RemoveCachedBodyFilter@7a3a49e5}, order = -2147483648], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.AdaptCachedBodyGlobalFilter@305881b8}, order = -2147482648], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.NettyWriteResponseFilter@6dbb3d7d}, order = -1], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.ForwardPathFilter@3ea9a091}, order = 0], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.GatewayMetricsFilter@26495639}, order = 0], [[SpringCloudCircuitBreakerResilience4JFilterFactory name = 'resilience', fallback = /identity-fallback], order = 0], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter@6c1b82cd}, order = 10000], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter@54687fd0}, order = 10150], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.LoadBalancerServiceInstanceCookieFilter@6eaf030c}, order = 10151], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.WebsocketRoutingFilter@16f4a3c0}, order = 2147483646], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.NettyRoutingFilter@b2da3a5}, order = 2147483647], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.ForwardRoutingFilter@acd3460}, order = 2147483647]]
2024-04-12 20:15:02.924 TRACE [xpense-gateway,ffa6cb10b03275d4,5984f380554abaa0] 35685 --- [     parallel-8] o.s.c.g.filter.RouteToRequestUrlFilter   : RouteToRequestUrlFilter start
2024-04-12 20:15:02.924 TRACE [xpense-gateway,ffa6cb10b03275d4,5984f380554abaa0] 35685 --- [     parallel-8] s.c.g.f.ReactiveLoadBalancerClientFilter : ReactiveLoadBalancerClientFilter url before: lb://identity-service/api/auth/register
2024-04-12 20:15:02.925 TRACE [xpense-gateway,ffa6cb10b03275d4,5984f380554abaa0] 35685 --- [     parallel-8] s.c.g.f.ReactiveLoadBalancerClientFilter : LoadBalancerClientFilter url chosen: http://VFIEVOX3.Router:35185/api/auth/register
2024-04-12 20:15:02.945 TRACE [xpense-gateway,,] 35685 --- [r-http-epoll-11] o.s.c.gateway.filter.NettyRoutingFilter  : outbound route: dc99dbca, inbound: [ade0fee6-4] 
2024-04-12 20:15:02.951 TRACE [xpense-gateway,,] 35685 --- [r-http-epoll-11] o.s.c.g.filter.NettyWriteResponseFilter  : NettyWriteResponseFilter start inbound: dc99dbca, outbound: [ade0fee6-4] 
2024-04-12 20:15:02.952 TRACE [xpense-gateway,,] 35685 --- [r-http-epoll-11] o.s.c.g.filter.GatewayMetricsFilter      : spring.cloud.gateway.requests tags: [tag(httpMethod=POST),tag(httpStatusCode=403),tag(outcome=CLIENT_ERROR),tag(routeId=identity-service),tag(routeUri=lb://identity-service),tag(status=FORBIDDEN)]

Я знаю, что механизм KeyCloak может обрабатывать аутентификацию пользователей, но я пытаюсь создать в службе с нуля собственный механизм для аутентификации и авторизации пользователей.

Я пытался просмотреть документацию по Spring Security, но теперь я запутался, так как не понимаю, как именно SecurityFilterChain и SecurityWebFilterChain. SecurityWebFilterChain предоставляет Oauth2ResourceServer с ServerHttpSecurity, но не обрабатывает SessionManagement и sessionCreation, которые я с нетерпением жду реализации.

Заранее спасибо.

Почему вы создали собственный JWTFilter? Почему вы не используете фильтр Spring oauth2resourceserver? Является ли ваш самодельный фильтр более безопасным, чем тот, который предусмотрен каркасом?

Toerktumlare 12.04.2024 22:22

Я почти уверен, что мой собственный фильтр не лучше, чем oauth2resourceserver. Я пытаюсь создать собственную реализацию, которая включает в себя генерацию токена наряду с другими механизмами, такими как активация учетной записи электронной почты с использованием кода активации и так далее.

Aniket Angwalkar 13.04.2024 23:37

Тогда я бы предложил вместо этого использовать перехватчик onAuthentication, ваш поток очень неясен, вы используете аутентификацию учетных данных клиента, то есть аутентификацию службы для службы (микросервис для микросервиса, а не браузер), именно для этого создан oauth2ResourceServer. Но тогда вы говорите об управлении сеансами и создании сеансов. Здесь вам следует прочитать о шаблоне BFF, и чтобы не показаться грубым, я бы посоветовал вам прочитать больше об oauth и особенно об open id Connect, если вы хотите учиться. И не пишите собственную безопасность, используйте инструменты, встроенные в Spring Security.

Toerktumlare 13.04.2024 23:43

например, есть отличные ресурсы, которые бесплатны Spring.academy

Toerktumlare 13.04.2024 23:48

Хорошо, я посмотрю на это. Я только начал изучать Springboot, и меня просто ошеломляет его необъятность. Причина, по которой я внедрил Keycloak, заключалась в том, чтобы обрабатывать аутентификацию на уровне приложения. С другой стороны, JWT с пружинной безопасностью для механизма аутентификации на уровне пользователя. Я могу отправить вам репозиторий на git, если вы хотите, возможно, тем временем взглянуть на кодовую базу.

Aniket Angwalkar 15.04.2024 00:29

Нет, я не буду просматривать какой-либо код на GitHub и т. д. У меня, как и у многих других, есть работа, и я трачу немного свободного времени, отвечая на четко определенные вопросы, которые требуют надлежащего исследования и отдельных ответов. Stack Overflow — это сайт вопросов и ответов, а не форум, если вы ищете обзоры, обсуждения и т. д. Я бы посоветовал вам посетить другие сайты или серверы разногласий и т. д. Безопасность — это сложная тема, и вам действительно следует подружиться с официальные документы. Прочтите главы об архитектуре. И не используйте JWT, начните с простого FormLogin и узнайте, как он работает. Удачи

Toerktumlare 15.04.2024 02:15

Вы пытаетесь построить предприятие, не зная, как работают такие вещи, как BASIC-безопасность, FormLogin (аутентификация на основе сеанса с использованием файлов cookie), аутентификация на основе сертификатов или такие вещи, как SAML и т. д. Начните с простого (базового), затем реализуйте промежуточный (FormLogin), затем изучите oauth.

Toerktumlare 15.04.2024 02:20

Я понял проблему и почему она зависает. Спасибо за вашу помощь и время,

Aniket Angwalkar 15.04.2024 04:03
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Версия Java на основе версии загрузки
Версия Java на основе версии загрузки
Если вы зайдете на официальный сайт Spring Boot , там представлен start.spring.io , который упрощает создание проектов Spring Boot, как показано ниже.
Документирование API с помощью Swagger на Springboot
Документирование API с помощью Swagger на Springboot
В предыдущей статье мы уже узнали, как создать Rest API с помощью Springboot и MySql .
2
8
296
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Проблема в конфигурации кода заключалась в порядке механизма цепочки веб-фильтров.

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
  httpSecurity
    .authorizeRequests(request -> request.regexMatchers(
      "/api/auth/*",
      "/v2/api-docs",
      "/v3/api-docs",
      "/v3/api-docs/*",
      "/swagger-resources/",
      "/swagger-resources/*",
      "/configuration/ui",
      "/configuration/security",
      "/swagger-ui/",
      "/webjars/*",
      "/swagger-ui.html"
    )
    .permitAll()
    .anyRequest()
    .authenticated()
  )
  .cors(withDefaults())
  .csrf(AbstractHttpConfigurer::disable)
  .sessionManagement(
    session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
  )
  .authenticationProvider(authenticationProvider)
  .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);

  return httpSecurity.build();
}

Поскольку authorizeRequests не был настроен должным образом и вместо этого настройщик cors(withDefaults()) инициализировался в исходной конфигурации с помощью csrf().
Это привело к разрешению аутентификации и разрешениям для запросов, соответствующих шаблону.

Другие вопросы по теме