Closed ulk200 closed 1 month ago
Hi @ulk200 and thanks for reaching out.
allowed-origins
is a private claim. If other authorization servers were providing with equivalents, it would probably in other claims. In such a case, it could be worth to implement something similar to how authorities mapping is done (expose some configuration property accepting a JSON path to allowed origins claim). But I don't know any other provider exposing the origins it allows in claims, reason why I'm not quite inclined to add such a feature to spring-addons-starter-oidc
.
However, your use-case is interesting and I'll try to put together the Java configuration to add (might take a few days to find the time for that).
Yes a code sample or a tutorial would be enough since this is not standard. Your project always comes along when searching for a replacement for the adapters that the Keycloak team used to maintain, so i think it's an ideal place to find everything that can permit to fully replace it. Thank you
@ulk200 I finally have a few holidays and had time to get a closer look. You could try to register a CorsFilter
like that:
@Bean
CorsFilter corsFilter(JwtDecoder jwtDecoder) {
return new CorsFilter(new CustomCorsConfigurationSource(jwtDecoder));
}
public static class CustomCorsConfigurationSource implements CorsConfigurationSource {
private static final String ALLOWED_ORIGINS_CLAIM = "allowed-origins";
private final JwtDecoder jwtDecoder;
private final UrlBasedCorsConfigurationSource defaultConfigSource;
public CustomCorsConfigurationSource(JwtDecoder jwtDecoder) {
this.jwtDecoder = jwtDecoder;
this.defaultConfigSource = defaultConfigSource();
}
@Override
@Nullable
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
final var delegate = getAllowedOrigins(request).map(allowedOrigins -> {
final var configSource = defaultConfigSource();
final var configs = configSource.getCorsConfigurations();
configs.forEach((k, v) -> {
v.setAllowedOrigins(allowedOrigins);
});
configSource.setCorsConfigurations(configs);
return configSource;
}).orElse(this.defaultConfigSource);
return delegate.getCorsConfiguration(request);
}
private Optional<String> getBearer(HttpServletRequest request) {
return Optional
.ofNullable(request.getHeader(HttpHeaders.AUTHORIZATION))
.filter(h -> h.toLowerCase().startsWith("bearer "))
.map(h -> h.substring(7))
.filter(StringUtils::hasText);
}
private Optional<Jwt> getJwt(HttpServletRequest request) {
final var bearer = getBearer(request);
return bearer.map(jwtDecoder::decode);
}
private Optional<List<String>> getAllowedOrigins(HttpServletRequest request) {
final var jwt = getJwt(request);
return jwt.map(token -> token.getClaimAsStringList(ALLOWED_ORIGINS_CLAIM));
}
private static UrlBasedCorsConfigurationSource defaultConfigSource() {
final var configSource = new UrlBasedCorsConfigurationSource();
final var configuration = new CorsConfiguration();
configuration.setAllowedMethods(List.of("*"));
// insert here a default value for allowed origins
// Also update the path matcher below if you don't want the config to apply to all your resources
configSource.registerCorsConfiguration("/**", configuration);
return configSource;
}
}
For now, spring-addons
CORS configuration backs off only if cors
properties are left empty (it should soon be improved to check that no CorsFilter
bean is registered). So check that your application.properties or application.yaml does not contain cors
configuration for spring-addons.
I would like to mimic the behaviour of the adapter that was maintained by the keycloak team, but that is now deprecated. In class
org.keycloak.adapters.AuthenticatedActionsHandler
they used to read theallowed-origins
claim and validate theOrigin
header of the HTTP request.This claim is populated with the urls that are configured on the client, in the Admin Console : Clients -> #the client# -> Settings -> Web Origins
A nice feature is that when we set this form field with a plus sign + every valid redirect URI is also a valid web-origin that is copied in the token.
It seems that configuring cors with spring's
CorsConfigurationSource
is not dynamic enough to read from the token with each request. What would be the cleanest way of doing it ?Thanks