Конфигурация Spring WebFlux OAuth2 JWT - PullRequest
0 голосов
/ 07 апреля 2020

Я использую эту среду:

  • пружинная загрузка 2.2.6-RELEASE
  • spring webflux
  • spring-security-oauth2 с токеном JWT

Моим приложением будет сервер ресурсов. Сервер авторизации - WSO2 IS.

Я понял это SpringSecurityConfig:

@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
    http
    .authorizeExchange(exchanges ->
        exchanges
        .pathMatchers("/**").hasAuthority("SCOPE_openid")

        )
        .csrf().disable()
        .cors()
        .configurationSource(corsConfigurationSource())
        .and()
        .oauth2ResourceServer(  oauth2ResourceServer ->
        oauth2ResourceServer.jwt(jwt -> jwt.jwtDecoder(jwtDecoder()))
        );
        return http.build();
    }
private ReactiveJwtDecoder jwtDecoder() {

        JwkSetUriReactiveJwtDecoderBuilder builder =NimbusReactiveJwtDecoder.withJwkSetUri(env.getProperty("spring.security.oauth2.resourceserver.jwt.jwks-uri"));
        try {
            builder.webClient(sigecroWebClient());
        }catch (Exception e) {
            new IllegalStateException("Impossibile proseguire. Avvenuto errore nella creazione del webclient", e);
        }
        return builder.build();
    }

Теперь, прочитав конфигурацию Spring Security здесь https://docs.spring.io/spring-security/site/docs/current/reference/html5/#webflux -oauth2-resource-server I обратите внимание, что после того, как я предоставлю эмитент-uri, платформа будет искать правильную конфигурацию oauth2, пытаясь подключиться к конечной точке конфигурации провайдера.

Пока все хорошо. Я предоставил Issue-URI и JWKS-URI, но у меня возникло несколько проблем.

Отладкой я пришел к этому классу org.springframework.security.oauth2.jwt.JwtDecoderProviderConfigurationUtils, а точнее к этому методу

private static Map<String, Object> getConfiguration(String issuer, URI... uris) {
    String errorMessage = "Unable to resolve the Configuration with the provided Issuer of " +
            "\"" + issuer + "\"";
    for (URI uri : uris) {
        try {
            RequestEntity<Void> request = RequestEntity.get(uri).build();
            ResponseEntity<Map<String, Object>> response = rest.exchange(request, typeReference);
            Map<String, Object> configuration = response.getBody();

            if (configuration.get("jwks_uri") == null) {
                throw new IllegalArgumentException("The public JWK set URI must not be null");
            }

            return configuration;
        } catch (IllegalArgumentException e) {
            throw e;
        } catch (RuntimeException e) {
            if (!(e instanceof HttpClientErrorException &&
                    ((HttpClientErrorException) e).getStatusCode().is4xxClientError())) {
                throw new IllegalArgumentException(errorMessage, e);
            }
            // else try another endpoint
        }
    }
    throw new IllegalArgumentException(errorMessage);
}

Как вы можете см. RestTemplate используется для получения правильной конфигурации. Но в WSO2 конечной точке конфигурации поставщика требуется базовая аутентификация для получения конфигурации. Я не смог найти способ настроить весну oportunely. Так как я тороплюсь, я изменил org.springframework.security.oauth2.jwt.JwtDecoderProviderConfigurationUtils следующим образом:

class JwtDecoderProviderConfigurationUtils {
    private static final String OIDC_METADATA_PATH = "/.well-known/openid-configuration";
    private static final String OAUTH_METADATA_PATH = "/.well-known/oauth-authorization-server";
    private static final Logger LOGGER = LoggerFactory.getLogger(JwtDecoderProviderConfigurationUtils.class.getName());
    private static final RestTemplate rest = new RestTemplate();
    private static Properties props;
    private static final ParameterizedTypeReference<Map<String, Object>> typeReference =
            new ParameterizedTypeReference<Map<String, Object>>() {};
            static {
                Resource res = new ClassPathResource("configuration.properties");
                if( res.exists() ) {
                    props = new Properties();
                    try {
                        props.load(res.getInputStream());
                    } catch (IOException e) {
                        LOGGER.error("Errore nel caricamento delle properties");
                    }
                }
            }
            static Map<String, Object> getConfigurationForOidcIssuerLocation(String oidcIssuerLocation) {
                return getConfiguration(oidcIssuerLocation, oidc(URI.create(oidcIssuerLocation)));
            }

            static Map<String, Object> getConfigurationForIssuerLocation(String issuer) {
                URI uri = URI.create(issuer);
                return getConfiguration(issuer, oidc(uri), oidcRfc8414(uri), oauth(uri));
            }

            static void validateIssuer(Map<String, Object> configuration, String issuer) {
                String metadataIssuer = "(unavailable)";
                if (configuration.containsKey("issuer")) {
                    metadataIssuer = configuration.get("issuer").toString();
                }
                if (!issuer.equals(metadataIssuer)) {
                    throw new IllegalStateException("The Issuer \"" + metadataIssuer + "\" provided in the configuration did not "
                            + "match the requested issuer \"" + issuer + "\"");
                }
            }

            private static Map<String, Object> getConfiguration(String issuer, URI... uris) {
                String errorMessage = "Unable to resolve the Configuration with the provided Issuer of " +
                        "\"" + issuer + "\"";
                for (URI uri : uris) {
                    try {
                        RequestEntity<Void> request = null;
                        if( props != null && 
                            !props.isEmpty() && 
                            StringUtils.hasText(props.getProperty("oauth2.resourceserver.wel.known.auth.username")) &&
                            StringUtils.hasText(props.getProperty("oauth2.resourceserver.wel.known.auth.password"))) {
                            String username = props.getProperty("oauth2.resourceserver.wel.known.auth.username");
                            String password = props.getProperty("oauth2.resourceserver.wel.known.auth.password");
                            String basic = Base64.encodeBase64String((username+":"+password).getBytes());
                            request = RequestEntity.get(uri).header("Authorization", "Basic "+basic).build();
                        }else {
                            request = RequestEntity.get(uri).build();
                        }


                        ResponseEntity<Map<String, Object>> response = rest.exchange(request, typeReference);
                        Map<String, Object> configuration = response.getBody();

                        if (configuration.get("jwks_uri") == null) {
                            throw new IllegalArgumentException("The public JWK set URI must not be null");
                        }

                        return configuration;
                    } catch (IllegalArgumentException e) {
                        throw e;
                    } catch (RuntimeException e) {
                        if (!(e instanceof HttpClientErrorException &&
                                ((HttpClientErrorException) e).getStatusCode().is4xxClientError())) {
                            throw new IllegalArgumentException(errorMessage, e);
                        }
                        // else try another endpoint
                    }
                }
                throw new IllegalArgumentException(errorMessage);
            }

            private static URI oidc(URI issuer) {
                return UriComponentsBuilder.fromUri(issuer)
                        .replacePath(issuer.getPath() + OIDC_METADATA_PATH)
                        .build(Collections.emptyMap());
            }

            private static URI oidcRfc8414(URI issuer) {
                return UriComponentsBuilder.fromUri(issuer)
                        .replacePath(OIDC_METADATA_PATH + issuer.getPath())
                        .build(Collections.emptyMap());
            }

            private static URI oauth(URI issuer) {
                return UriComponentsBuilder.fromUri(issuer)
                        .replacePath(OAUTH_METADATA_PATH + issuer.getPath())
                        .build(Collections.emptyMap());
            }
}

Мне интересно, если это правильный путь, или есть лучшее решение, чем расширение / изменить исходный класс Spring.

Любые предложения приветствуются.

Спасибо, Анджело

...