Перенос Spring Cloud с OAuth 2.0 на OAuth 2.1

У меня есть старый шлюз Spring Cloud, работающий с сервером Keyclock. У меня нет веб-интерфейса для входа в систему, потому что проект представляет собой Rest API. OAuth 2.0 используется с паролем типа Grant.

Я хочу перейти на OAuth 2.1, но пароль типа Grant устарел.

Можете ли вы посоветовать в моем случае, как лучше всего перенести проект, чтобы снова иметь имя пользователя и пароль для выдачи токена, чтобы аутентифицировать пользователей и делать запросы к API?

Глядя на это руководство https://connect2id.com/learn/oauth-2-1 Я думаю, что тип гранта носителя JWT является хорошим кандидатом?

Что, если я создам свой собственный тип гранта, аналогичный типу гранта пароля?

Просто интересно, почему этот вопрос помечен как spring-authorization-server? Кажется, это вообще не относится к Spring или SAS.

Steve Riesenberg 02.11.2022 22:23
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
1
168
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

API REST, защищенные с помощью OAuth2, являются серверами ресурсов. Настройте свои приложения Spring как таковые.

Поток, который клиенты используют для получения токена доступа, не имеет отношения к серверам ресурсов. Не создавайте свои собственные. Клиенты используют:

  • authorization-code действовать от имени пользователя (физического лица, вошедшего в систему)
  • client-credentials если это программа, которой вы разрешаете выполнять запросы, не связанные с пользователем (пакетный процесс или любой другой доверенный сервис)

Настройка сервера ресурсов для Keycloak с помощью начальных библиотек spring-boot, указанных выше, может быть такой простой, как:

<dependency>
    <groupId>com.c4-soft.springaddons</groupId>
    <artifactId>spring-addons-webmvc-jwt-resource-server</artifactId>
    <version>6.0.4</version>
</dependency>
@EnableMethodSecurity
public static class WebSecurityConfig { }
com.c4-soft.springaddons.security.issuers[0].location=https://localhost:8443/realms/master
com.c4-soft.springaddons.security.issuers[0].authorities.claims=realm_access.roles,ressource_access.some-client.roles

com.c4-soft.springaddons.security.cors[0].path=/some-api

Настройка другого сервера авторизации OIDC, отличного от Keycloak, — это просто вопрос редактирования местоположения эмитента и утверждений властей.

Вы также можете использовать spring-boot-starter-oauth2-resource-server напрямую, как описано в первом уроке. Стартеры Spring-addons - это просто тонкие обертки вокруг него, которые сохраняют довольно много java conf. Вот что вам нужно написать, чтобы добиться того же, что и выше:

@EnableWebSecurity
@EnableMethodSecurity
@Configuration
public class SecurityConfig {

    interface Jwt2AuthoritiesConverter extends Converter<Jwt, Collection<? extends GrantedAuthority>> {
    }

    @SuppressWarnings("unchecked")
    @Bean
    Jwt2AuthoritiesConverter authoritiesConverter() {
        // This is a converter for roles as embedded in the JWT by a Keycloak server
        // Roles are taken from both realm_access.roles & resource_access.{client}.roles
        return jwt -> {
            final var realmAccess = (Map<String, Object>) jwt.getClaims().getOrDefault("realm_access", Map.of());
            final var realmRoles = (Collection<String>) realmAccess.getOrDefault("roles", List.of());

            final var resourceAccess = (Map<String, Object>) jwt.getClaims().getOrDefault("resource_access", Map.of());
            // We assume here you have "spring-addons-confidential" and
            // "spring-addons-public" clients configured with "client roles" mapper in
            // Keycloak
            final var confidentialClientAccess = (Map<String, Object>) resourceAccess
                    .getOrDefault("spring-addons-confidential", Map.of());
            final var confidentialClientRoles = (Collection<String>) confidentialClientAccess.getOrDefault("roles",
                    List.of());
            final var publicClientAccess = (Map<String, Object>) resourceAccess.getOrDefault("spring-addons-public",
                    Map.of());
            final var publicClientRoles = (Collection<String>) publicClientAccess.getOrDefault("roles", List.of());

            return Stream
                    .concat(realmRoles.stream(),
                            Stream.concat(confidentialClientRoles.stream(), publicClientRoles.stream()))
                    .map(SimpleGrantedAuthority::new).toList();
        };
    }

    interface Jwt2AuthenticationConverter extends Converter<Jwt, AbstractAuthenticationToken> {
    }

    @Bean
    Jwt2AuthenticationConverter authenticationConverter(
            Converter<Jwt, Collection<? extends GrantedAuthority>> authoritiesConverter) {
        return jwt -> new JwtAuthenticationToken(jwt, authoritiesConverter.convert(jwt));
    }

    @Bean
    SecurityFilterChain filterChain(
            HttpSecurity http,
            Converter<Jwt, AbstractAuthenticationToken> authenticationConverter,
            ServerProperties serverProperties)
            throws Exception {

        // Enable OAuth2 with custom authorities mapping
        http.oauth2ResourceServer().jwt().jwtAuthenticationConverter(authenticationConverter);

        // Enable anonymous
        http.anonymous();

        // Enable and configure CORS
        http.cors().configurationSource(corsConfigurationSource());

        // State-less session (state in access-token only)
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

        // Disable CSRF because of state-less session-management
        http.csrf().disable();

        // Return 401 (unauthorized) instead of 403 (redirect to login) when
        // authorization is missing or invalid
        http.exceptionHandling().authenticationEntryPoint((request, response, authException) -> {
            response.addHeader(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"Restricted Content\"");
            response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
        });

        // If SSL enabled, disable http (https only)
        if (serverProperties.getSsl() != null && serverProperties.getSsl().isEnabled()) {
            http.requiresChannel().anyRequest().requiresSecure();
        } else {
            http.requiresChannel().anyRequest().requiresInsecure();
        }

        // Route security: authenticated to all routes but actuator and Swagger-UI
        // @formatter:off
        http.authorizeHttpRequests()
            .requestMatchers("/actuator/health/readiness", "/actuator/health/liveness", "/v3/api-docs", "/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html").permitAll()
            .anyRequest().authenticated();
        // @formatter:on

        return http.build();
    }

    CorsConfigurationSource corsConfigurationSource() {
        // Very permissive CORS config...
        final var configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("*"));
        configuration.setAllowedMethods(Arrays.asList("*"));
        configuration.setAllowedHeaders(Arrays.asList("*"));
        configuration.setExposedHeaders(Arrays.asList("*"));

        // Limited to API routes (neither actuator nor Swagger-UI)
        final var source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/greet/**", configuration);

        return source;
    }
}

Не могли бы вы показать мне расширенный пример использования spring-boot-starter-oauth2-resource-server с client-credentials, пожалуйста?

Peter Penzov 03.11.2022 00:05

Прочитайте мой ответ еще раз: ресурс-сервер не заботится о потоке, который клиент использовал для получения токена доступа. Запрос, авторизованный с помощью токена доступа Bearer, — это все, что имеет значение для сервера ресурсов. Обратитесь к документу вашего сервера авторизации, чтобы узнать, как объявить клиента с учетными данными клиента. Перейдите по ссылке в моем ответе на образец с spring-boot-starter-oauth2-resource-server.

ch4mp 03.11.2022 00:41

У меня есть еще один вопрос, есть ли подходящее решение для этого варианта использования: клиент использует токен, созданный вручную каким-то администратором и вручную отправленный пользователю. Этот токен используется для отправки запросов в заголовок запросов API с использованием «Basic ...», и токен используется без истечения срока действия по умолчанию. Если возможно, токен каждый раз аутентифицируется на сервере авторизации.

Peter Penzov 03.11.2022 02:07

Это зависит от вашего сервера авторизации. Обратитесь к его документу. Насколько мне известно, Keycloak не позволяет этого, и, учитывая, насколько легко настроить клиент с учетными данными клиента для автоматического получения токена доступа с сервера авторизации, я не вижу допустимого варианта использования.

ch4mp 03.11.2022 02:31

Краткое изложение соответствующих типов грантов может быть следующим:

Если у вас нет более конкретных требований, предоставление учетных данных клиента может показаться лучшим вариантом.

Поскольку вы упомянули пользователей, но у вас нет пользовательского интерфейса, я предполагаю, что они не являются пользователями веб-клиента. Если это так, клиент (например, приложение javascript/SPA) должен использовать код авторизации. Ваше приложение не является клиентом, если оно принимает токены доступа, оно является сервером ресурсов.

Спасибо за ответ. Я тоже думаю, что Client credentials пока это вариант решения. У меня есть еще один вопрос, есть ли подходящее решение для этого варианта использования: клиент использует токен, созданный вручную каким-то администратором и вручную отправленный пользователю. Этот токен используется для отправки запросов в заголовок запросов API с использованием «Basic ...», и токен используется без истечения срока действия по умолчанию. Если возможно, токен каждый раз аутентифицируется на сервере авторизации.

Peter Penzov 03.11.2022 00:03

По вашему описанию трудно сказать, описываете ли вы «ключ API» или что-то еще. Если он использует «Basic», это просто базовый HTTP, что означает, что имя пользователя/пароль закодированы в токен. Было бы небезопасно, если бы вы хотели сохранить пароль в секрете от пользователя. Вы всегда можете попросить оператора/администратора использовать свои собственные учетные данные клиента для создания токена, но обычно не рекомендуется делиться ими с кем-то еще. Я считаю, что у каждого клиента должен быть свой секрет.

Steve Riesenberg 03.11.2022 16:01

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