Я использую эту среду:
- пружинная загрузка 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.
Любые предложения приветствуются.
Спасибо, Анджело