Spring Reactive Security API Gateway с OpenID Connect с использованием private_key_jwt (login.gov) - PullRequest
0 голосов
/ 20 апреля 2020

Я следую этому учебному пособию, состоящему из двух частей.

Описанная цель :

Шлюз API будет служить примером выполнения входа на основе кода авторизации OAuth 2 с управлением сеансом. Кроме того, в нем будет показано, как дополнить HTTP-запросы соответствующим токеном-носителем OAuth, как того требует сервер ресурсов. Главное, что токен доступа / refre sh, полученный от Keycloak, никогда не будет открыт браузеру.

Разница в том, что я не использую Keycloak, я использую Login .gov, и - из моего опыта - интеграция с ними может быть довольно болезненной, несмотря на их приличную документацию

Проблема :

Учебник думает, что я могу обойтись без таких вещей, как basi c, например:

...
    .oauth2Login()

Раньше, когда я НЕ использовал парадигму шлюза или модель реактивного программирования, я много раз настраивал oauth на моя конфигурация безопасности для интеграции с Login.gov, например:

...
    .oauth2Login()
        .loginPage(DefaultLoginPageGeneratingFilter.DEFAULT_LOGIN_PAGE_URL)
        .authorizationEndpoint()
        .authorizationRequestResolver(new LoginGovAuthorizationRequestResolver(clientRegistrationRepository))
        .authorizationRequestRepository(authorizationRequestRepository())
        .and()
        .tokenEndpoint()
        .accessTokenResponseClient(accessTokenResponseClient())
        .and()
        .failureHandler(new LoginGovAuthenticationFailureHandler())
        .successHandler(new LoginGovAuthenticationSuccessHandler())

Здесь, очевидно, много чего происходит, но суть в том, что все, кто прыгал через обручи, в основном делали пару вещей (которые я можно запомнить):

  • добавить 2 параметра в распознаватель запросов
    • acr_values (константа: может быть добавлена ​​в конфигурации authorization_uri)
    • nonce (Минимум 22 символа и, по крайней мере, esolver)
additionalParameters.put("acr_values", LoginGovConstants.LOGIN_GOV_LOA1)
additionalParameters.put("nonce", nonce)

Кроме того, конвертер, очевидно, потребовал, чтобы я подписал свой собственный JWT и предоставил его client_assertion и постоянные client_assertion_type параметры:

    @Override
    RequestEntity<?> convert(OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest) {
        RequestEntity<?> originalRequestEntity = super.convert(authorizationCodeGrantRequest)
        String registrationId = resolveRegistrationId(authorizationCodeGrantRequest)
        if(registrationId == LoginGovConstants.LOGIN_GOV_REGISTRATION_ID) {
            ClientRegistration clientRegistration = clientRegistrationRepository.findByRegistrationId(registrationId)
            String clientId = clientRegistration.clientId
            String clientSecret = clientRegistration.clientSecret
            String tokenUri = clientRegistration.providerDetails.tokenUri
            Long expirationTime = LoginGovConstants.LOGIN_GOV_TOKEN_EXPIRATION_TIME

            String jwt = JWT.create()
                .withSubject(clientId)
                .withIssuer(clientId)
                .withAudience(tokenUri)
                // Should be an un-guessable, random string generated by the client
                .withJWTId(UUID.randomUUID().toString())
                .withExpiresAt(new Date(System.currentTimeMillis() + expirationTime))
                .sign(Algorithm.RSA256(keystoreUtil.rsaPublicKey(), keystoreUtil.rsaPrivateKey()))

            HttpHeaders headers = originalRequestEntity.headers
            MultiValueMap<String, String> formParameters = originalRequestEntity.body as MultiValueMap<String, String>
            URI uri = originalRequestEntity.url

            formParameters.add("client_assertion", jwt)
            formParameters.add("client_assertion_type", LoginGovConstants.LOGIN_GOV_CLIENT_ASSERTION_TYPE)
            return new RequestEntity<?>(formParameters, headers, HttpMethod.POST, uri)
        }
    }

Достаточно сказать, что в то время, когда я реализовывал свое предыдущее решение, казалось, что было выполнено множество обратных откатов для выполнения sh некоторых дополнительных параметров URL, которые, по моему мнению, должен был предоставить Spring Security по умолчанию. Поэтому, если мы сможем улучшить мою предыдущую оценку, я был бы признателен за некоторые рекомендации.

Тем не менее, с этим новым подходом API-шлюза я еще больше запутался, рассматривая Webflux и реактивную парадигму.

Например:

  • OAuth2AuthorizationCodeGrantRequestEntityConverter против ServerOAuth2AuthorizationCodeAuthenticationTokenConverter
  • OAuth2AuthorizationRequestResolver против ServerOAuth2AuthorizationRequestResolver

Какое совпадение между этими классами , если таковые имеются?

Есть ли более простой способ выполнить sh то, что я ранее делал с этими различными Server* классами?

Текущее поведение :

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

spring:
  application:
    name: gateway
  security:
    oauth2:
      client:
        registration:
          login-gov:
            client-id: \${LOGIN_GOV_CLIENT_ID}
            authorization-grant-type: authorization_code
            redirect-uri: "{baseUrl}/{action}/oauth2/code/{registrationId}"
            scope:
              - openid
              - email
        provider:
          login-gov:
            authorization-uri: https://idp.int.identitysandbox.gov/openid_connect/authorize
            token-uri: https://idp.int.identitysandbox.gov/api/openid_connect/token?client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer
            user-info-uri: https://idp.int.identitysandbox.gov/api/openid_connect/userinfo
            jwk-set-uri: https://idp.int.identitysandbox.gov/api/openid_connect/certs
            user-name-attribute: sub

Login.gov использует метод аутентификации private_key_jwt, и это кажется, означает, что у меня нет «секрета клиента». Вот почему мне было необходимо создать и подписать свой собственный JWT в моей предыдущей реализации?

Это вывод шлюза, когда я достиг конечной точки шлюза (/ api / user / api / v1 / savesearches /), который должен в конечном итоге перенаправить на реальный сервис.

Поток входа в систему инициируется, как и ожидалось, и я ввожу свои учетные данные на странице интеграции Login.gov. После отправки я в конечном итоге перенаправляюсь обратно на страницу с ошибкой /login?error

2020-04-20 14:10:30.934 DEBUG 15352 --- [ctor-http-nio-5] o.s.w.s.adapter.HttpWebHandlerAdapter    : [eae7892d-10] HTTP GET "/api/user/api/v1/savesearches/"
2020-04-20 14:10:30.983 DEBUG 15352 --- [oundedElastic-2] o.s.w.s.s.DefaultWebSessionManager       : Created new WebSession.
2020-04-20 14:10:30.995 DEBUG 15352 --- [oundedElastic-2] .s.u.m.MediaTypeServerWebExchangeMatcher : httpRequestMediaTypes=[text/html, application/xhtml+xml, image/webp, image/apng, application/xml;q=0.9, application/signed-exchange;v=b3;q=0.9, */*;q=0.8]
2020-04-20 14:10:30.996 DEBUG 15352 --- [oundedElastic-2] .s.u.m.MediaTypeServerWebExchangeMatcher : Processing text/html
2020-04-20 14:10:30.996 DEBUG 15352 --- [oundedElastic-2] .s.u.m.MediaTypeServerWebExchangeMatcher : text/html .isCompatibleWith text/html = true
2020-04-20 14:10:30.997 DEBUG 15352 --- [oundedElastic-2] o.s.w.s.adapter.HttpWebHandlerAdapter    : [eae7892d-10] Completed 302 FOUND
2020-04-20 14:10:31.006 DEBUG 15352 --- [ctor-http-nio-5] o.s.w.s.adapter.HttpWebHandlerAdapter    : [eae7892d-11] HTTP GET "/oauth2/authorization/login-gov"
2020-04-20 14:10:31.017 DEBUG 15352 --- [ctor-http-nio-5] o.s.w.s.adapter.HttpWebHandlerAdapter    : [eae7892d-11] Completed 302 FOUND
2020-04-20 14:10:31.853 DEBUG 15352 --- [ctor-http-nio-5] o.s.w.s.adapter.HttpWebHandlerAdapter    : [eae7892d-12] HTTP GET "/api/user/api/v1/savesearches/"
2020-04-20 14:10:31.861 DEBUG 15352 --- [ctor-http-nio-5] .s.u.m.MediaTypeServerWebExchangeMatcher : httpRequestMediaTypes=[text/html, application/xhtml+xml, image/webp, image/apng, application/xml;q=0.9, application/signed-exchange;v=b3;q=0.9, */*;q=0.8]
2020-04-20 14:10:31.861 DEBUG 15352 --- [ctor-http-nio-5] .s.u.m.MediaTypeServerWebExchangeMatcher : Processing text/html
2020-04-20 14:10:31.861 DEBUG 15352 --- [ctor-http-nio-5] .s.u.m.MediaTypeServerWebExchangeMatcher : text/html .isCompatibleWith text/html = true
2020-04-20 14:10:31.861 DEBUG 15352 --- [ctor-http-nio-5] o.s.w.s.adapter.HttpWebHandlerAdapter    : [eae7892d-12] Completed 302 FOUND
2020-04-20 14:10:31.868 DEBUG 15352 --- [ctor-http-nio-5] o.s.w.s.adapter.HttpWebHandlerAdapter    : [eae7892d-13] HTTP GET "/oauth2/authorization/login-gov"
2020-04-20 14:10:31.874 DEBUG 15352 --- [ctor-http-nio-5] o.s.w.s.adapter.HttpWebHandlerAdapter    : [eae7892d-13] Completed 302 FOUND
2020-04-20 14:10:44.361 DEBUG 15352 --- [ctor-http-nio-5] o.s.w.s.adapter.HttpWebHandlerAdapter    : [eae7892d-14] HTTP GET "/login/oauth2/code/login-gov?code=d2236ca8-1458-4631-b067-057f461d2a71&state=pp0V-IpV75MSgq995i5IUoQvuaFGhBXBJyRiHsAhQvM%3D"
2020-04-20 14:10:44.382 DEBUG 15352 --- [ctor-http-nio-5] o.s.w.r.f.client.ExchangeFunctions       : [312e7ca5] HTTP POST https://idp.int.identitysandbox.gov/api/openid_connect/token?client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer
2020-04-20 14:10:44.950 DEBUG 15352 --- [ctor-http-nio-5] org.springframework.web.HttpLogging      : [312e7ca5] Writing form fields [grant_type, code, redirect_uri, client_id, code_verifier] (content masked)
2020-04-20 14:10:45.080 DEBUG 15352 --- [ctor-http-nio-5] o.s.w.r.f.client.ExchangeFunctions       : [312e7ca5] Response 400 BAD_REQUEST
2020-04-20 14:10:45.094 DEBUG 15352 --- [ctor-http-nio-5] org.springframework.web.HttpLogging      : [312e7ca5] Decoded [{error=Client assertion Nil JSON web token}]
2020-04-20 14:10:45.095 DEBUG 15352 --- [ctor-http-nio-5] o.s.w.s.adapter.HttpWebHandlerAdapter    : [eae7892d-14] Completed 302 FOUND
2020-04-20 14:10:45.099 DEBUG 15352 --- [ctor-http-nio-5] o.s.w.s.adapter.HttpWebHandlerAdapter    : [eae7892d-15] HTTP GET "/login?error"
2020-04-20 14:10:45.101 DEBUG 15352 --- [ctor-http-nio-5] o.s.w.s.adapter.HttpWebHandlerAdapter    : [eae7892d-15] Completed 200 OK

То, что выделяется для меня, очень ясно здесь. Я вижу, что этап авторизации выполнен успешно, о чем свидетельствует перенаправление на /login/oauth2/code/login-gov?code=d2236ca8-1458-4631-b067-057f461d2a71&state=pp0V-IpV75MSgq995i5IUoQvuaFGhBXBJyRiHsAhQvM%3D с существующими параметрами code и state.

Что сбивает с толку, так это параметры, предоставляемые конечной точке токена: это только имеет client_assertion_type, который я указал в конфиге выше, но он также должен иметь client_assertion (JWT). Я предполагаю, что это все прыжки h oop, которые я делал прежде, чтобы создать JWT в пользовательском конвертере, но я так и не понял, почему Spring не сделал этого для меня?

Я что-то упустил Basi c шаг настройки? Если нет, я мог бы использовать некоторую помощь для повторной реализации конвертера реактивным (ServerWebExchange) способом.

Спасибо за ваше терпение.

...