Я настроил Spring-безопасность с помощью keycloak, используя функцию клиента Oauth2 в BFF, keycloak будет играть роль idp-брокера, и он успешно настроен с помощью Microsoft (позже мы добавим поддержку для Google, Facebook и т. д.) каждый раз. вещь работает как ожидалось!
в нашем приложении есть требование, чтобы пользователи не попадали на страницу входа в систему keycloak, вместо этого мы добавим специальную кнопку «войти с помощью Micorosoft» (или с помощью его/ее предпочтительного провайдера входа) в наше приложение пользовательского интерфейса, и пользователь будет перенаправлен на страницу входа Microsoft для подключения.
Моя настройка OAuth2 (и она работает):
Вопрос в том, как обойти страницу входа в систему keycloak и перенаправить пользователя непосредственно к выбранному им провайдеру, которого мы уже настроили и поддерживаем? Как мы можем сделать это из системы безопасности srping, чтобы мы могли указать keycloak перенаправить на определенную страницу входа в систему idp?
Что я нашел на данный момент:
Любая помощь, пожалуйста?
Вы уже нашли ответ на этот вопрос: укажите параметр kc_idp_hint
в запросе на авторизацию.
kc_idp_hint
для Keycloak, audience
для Auth0 и т. д.)?Это делается с помощью кастома (Server)OAuth2AuthorizationRequestResolver
.
Я представлю решения, основанные на регистрации клиента OAuth2 для каждого варианта входа, с примером:
default-registration-id
в качестве идентификатора)google-registration-id
при условии, что вы указали google
IDP в Keycloak)facebook-registration-id
при условии, что вы назвали facebook
IDP в Keycloak)Это подразумевает, что свойства конфигурации выглядят примерно так:
oauth2-issuer: change-me
oauth2-client-id: change-me
oauth2-client-secret: change-me
scope: "openid profile email offline_access"
spring:
security:
oauth2:
client:
provider:
keycloak:
issuer-uri: ${oauth2-issuer}
user-name-attribute: preferred_username
registration:
# registration for direct access to Google login page
google-registration-id:
provider: keycloak
client-id: ${oauth2-client-id}
client-secret: ${oauth2-client-secret}
authorization-grant-type: authorization_code
scope: $scope
# registration for direct access to Facebook login page
facebook-registration-id:
provider: keycloak
client-id: ${oauth2-client-id}
client-secret: ${oauth2-client-secret}
authorization-grant-type: authorization_code
scope: $scope
# registration for accessing Keycloak login page
default-registration-id:
provider: keycloak
client-id: ${oauth2-client-id}
client-secret: ${oauth2-client-secret}
authorization-grant-type: authorization_code
scope: $scope
В зависимости от того, какую регистрацию клиента OAuth2 активирует интерфейс (путем перенаправления пользователя на конечную точку BFF /oauth2/authorization/{registration-id}
), пользователь увидит другой экран входа в систему.
Поскольку я предполагаю, что ваш Spring BFF, настроенный как конфиденциальный клиент OAuth2, является реактивным экземпляром Spring Cloud Gateway, я сосредоточусь на реактивном преобразователе запросов авторизации: ServerOAuth2AuthorizationRequestResolver
.
Два варианта в зависимости от того, используете ли вы только spring-boot-starter-oauth2-client
или spring-addons-starter-oidc
(моя закваска) в дополнение к нему.
spring-addons-starter-oidc
Начнем с простого. Все, что вам нужно в этом случае, это добавить kc_idp_hint
для каждого идентификатора регистрации в свойствах приложения:
com:
c4-soft:
springaddons:
oidc:
client:
authorization-params:
google-registration-id:
kc_idp_hint: google
facebook-registration-id:
kc_idp_hint: facebook
spring-boot-starter-oauth2-client
В этом случае вам придется самостоятельно написать собственный преобразователь запросов авторизации, а затем настроить с его помощью цепочку фильтров безопасности.
Вот возможный вариант:
@Component
public class KcIdpHintServerOAuth2AuthorizationRequestResolver implements ServerOAuth2AuthorizationRequestResolver {
private final DefaultServerOAuth2AuthorizationRequestResolver defaultDelegate;
private final Map<String, ServerOAuth2AuthorizationRequestResolver> authorizationRequestResolversByRegistrationId;
public KcIdpHintServerOAuth2AuthorizationRequestResolver(ReactiveClientRegistrationRepository clientRegistrationRepository) {
this.defaultDelegate = new DefaultServerOAuth2AuthorizationRequestResolver(clientRegistrationRepository);
// Rather than a static conf like that, you might parse a map from custom configuration properties
this.authorizationRequestResolversByRegistrationId = Map.of(
"google-registration-id", authorizationRequestResolverWithHintFor(clientRegistrationRepository, "google"),
"facebook-registration-id", authorizationRequestResolverWithHintFor(clientRegistrationRepository, "facebook"));
}
@Override
public Mono<OAuth2AuthorizationRequest> resolve(ServerWebExchange exchange) {
return defaultDelegate.resolve(exchange);
}
@Override
public Mono<OAuth2AuthorizationRequest> resolve(ServerWebExchange exchange, String clientRegistrationId) {
final var delegate = authorizationRequestResolversByRegistrationId.getOrDefault(clientRegistrationId, defaultDelegate);
return delegate.resolve(exchange, clientRegistrationId);
}
private
ServerOAuth2AuthorizationRequestResolver
authorizationRequestResolverWithHintFor(ReactiveClientRegistrationRepository clientRegistrationRepository, String hint) {
final var authorizationRequestResolver = new DefaultServerOAuth2AuthorizationRequestResolver(clientRegistrationRepository);
authorizationRequestResolver.setAuthorizationRequestCustomizer(
oauth2AuthorizationRequestBuilder -> oauth2AuthorizationRequestBuilder.additionalParameters(Map.of("kc_idp_hint", hint)));
return authorizationRequestResolver;
}
}
@Bean
SecurityWebFilterChain clientSecurityFilterChain(
ServerHttpSecurity http,
ServerOAuth2AuthorizationRequestResolver authorizationRequestResolver) {
http.oauth2Login(oauth2 -> {
oauth2.authorizationRequestResolver(authorizationRequestResolver);
});
...
return http.build();
}
Если вы еще не используете «мой» стартер в качестве лучшего друга, вам, вероятно, стоит попробовать. Он решает гораздо больше, чем просто дополнительные параметры запроса авторизации: URI перенаправления входа в систему / выхода из системы, дает выбор статуса HTTP для ответов BFF во время потока кода авторизации, помогает с настройкой защиты CSRF, ... Пример здесь
Часть безопасности Spring - это та часть, в которой я не уверен, еще не пробовал ваш ответ, но, похоже, она решает мой вариант использования. Спасибо, что нашли время ответить @ch4mp.