Allow anonymous requests for OPTIONS
This commit is contained in:
+8
-3
@@ -3,6 +3,8 @@ package de.twomartens.wahlrecht.configuration;
|
||||
import de.twomartens.wahlrecht.interceptor.HeaderInterceptorRest;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.web.servlet.config.annotation.CorsRegistration;
|
||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
@@ -15,14 +17,17 @@ public class WebConfiguration implements WebMvcConfigurer {
|
||||
private final HeaderInterceptorRest headerInterceptorRest;
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
public void addInterceptors(@NonNull InterceptorRegistry registry) {
|
||||
registry.addInterceptor(headerInterceptorRest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry) {
|
||||
public void addCorsMappings(@NonNull CorsRegistry registry) {
|
||||
CorsRegistration registration = registry.addMapping("/**");
|
||||
registration.allowedOrigins("*");
|
||||
registration.allowedMethods(HttpMethod.GET.name(), HttpMethod.POST.name(),
|
||||
HttpMethod.PUT.name(), HttpMethod.OPTIONS.name());
|
||||
registration.allowCredentials(true);
|
||||
registration.allowedOrigins("http://localhost:4200");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+14
-4
@@ -1,12 +1,12 @@
|
||||
package de.twomartens.wahlrecht.configuration;
|
||||
|
||||
|
||||
import de.twomartens.wahlrecht.security.SpringPolicyEnforcerFilter;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.keycloak.adapters.authorization.integration.jakarta.ServletPolicyEnforcerFilter;
|
||||
import org.keycloak.adapters.authorization.spi.ConfigurationResolver;
|
||||
import org.keycloak.adapters.authorization.spi.HttpRequest;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
|
||||
@@ -16,6 +16,7 @@ import org.keycloak.util.JsonSerialization;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
@@ -53,6 +54,10 @@ public class WebSecurityConfiguration {
|
||||
.requestMatchers(PERMITTED_PATHS.toArray(new String[0]))
|
||||
.permitAll()
|
||||
.and()
|
||||
.authorizeHttpRequests()
|
||||
.requestMatchers(HttpMethod.OPTIONS)
|
||||
.permitAll()
|
||||
.and()
|
||||
.authorizeHttpRequests()
|
||||
.anyRequest()
|
||||
.authenticated()
|
||||
@@ -67,15 +72,20 @@ public class WebSecurityConfiguration {
|
||||
return NimbusJwtDecoder.withJwkSetUri(this.jwkSetUri).build();
|
||||
}
|
||||
|
||||
private ServletPolicyEnforcerFilter createPolicyEnforcerFilter() {
|
||||
return new ServletPolicyEnforcerFilter(new ConfigurationResolver() {
|
||||
private SpringPolicyEnforcerFilter createPolicyEnforcerFilter() {
|
||||
return new SpringPolicyEnforcerFilter(new ConfigurationResolver() {
|
||||
@Override
|
||||
public PolicyEnforcerConfig resolve(HttpRequest request) {
|
||||
try {
|
||||
PolicyEnforcerConfig policyEnforcerConfig = JsonSerialization.readValue(
|
||||
getClass().getResourceAsStream("/policy-enforcer.json"), PolicyEnforcerConfig.class);
|
||||
policyEnforcerConfig.setCredentials(Map.of("secret", clientSecret));
|
||||
policyEnforcerConfig.setPaths(PATHS);
|
||||
if (request.getMethod().equals(HttpMethod.OPTIONS.name())) {
|
||||
// always allow options request
|
||||
policyEnforcerConfig.setEnforcementMode(EnforcementMode.DISABLED);
|
||||
} else {
|
||||
policyEnforcerConfig.setPaths(PATHS);
|
||||
}
|
||||
|
||||
return policyEnforcerConfig;
|
||||
} catch (IOException e) {
|
||||
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
package de.twomartens.wahlrecht.security;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.keycloak.AuthorizationContext;
|
||||
import org.keycloak.adapters.authorization.PolicyEnforcer;
|
||||
import org.keycloak.adapters.authorization.spi.HttpRequest;
|
||||
import org.keycloak.adapters.authorization.spi.HttpResponse;
|
||||
import org.keycloak.authorization.client.ClientAuthorizationContext;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.EnforcementMode;
|
||||
import org.keycloak.representations.idm.authorization.Permission;
|
||||
|
||||
/**
|
||||
* Allows any request, even from anonymous, if enforcement mode is disabled. For remaining requests,
|
||||
* it refers to the original PolicyEnforcer.
|
||||
*/
|
||||
@Slf4j
|
||||
public class SpringPolicyEnforcer {
|
||||
|
||||
private final PolicyEnforcer policyEnforcer;
|
||||
private final PolicyEnforcerConfig policyEnforcerConfig;
|
||||
|
||||
public SpringPolicyEnforcer(PolicyEnforcer policyEnforcer, PolicyEnforcerConfig policyEnforcerConfig) {
|
||||
this.policyEnforcer = policyEnforcer;
|
||||
this.policyEnforcerConfig = policyEnforcerConfig;
|
||||
}
|
||||
|
||||
public AuthorizationContext enforce(HttpRequest request, HttpResponse response) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Policy enforcement is enabled. Enforcing policy decisions for path [{}].", request.getURI());
|
||||
}
|
||||
|
||||
AuthorizationContext context = authorize(request, response);
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Policy enforcement result for path [{}] is : {}", request.getURI(), context.isGranted() ? "GRANTED" : "DENIED");
|
||||
log.debug("Returning authorization context with permissions:");
|
||||
for (Permission permission : context.getPermissions()) {
|
||||
log.debug(permission.toString());
|
||||
}
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
private AuthorizationContext authorize(HttpRequest request, HttpResponse response) {
|
||||
EnforcementMode enforcementMode = policyEnforcerConfig.getEnforcementMode();
|
||||
|
||||
if (EnforcementMode.DISABLED.equals(enforcementMode)) {
|
||||
return createAuthorizedContext();
|
||||
}
|
||||
|
||||
return policyEnforcer.enforce(request, response);
|
||||
}
|
||||
|
||||
private AuthorizationContext createAuthorizedContext() {
|
||||
return new ClientAuthorizationContext(policyEnforcer.getAuthzClient()) {
|
||||
@Override
|
||||
public boolean hasPermission(String resourceName, String scopeName) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasResourcePermission(String resourceName) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasScopePermission(String scopeName) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Permission> getPermissions() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGranted() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
package de.twomartens.wahlrecht.security;
|
||||
|
||||
import jakarta.servlet.Filter;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.keycloak.AuthorizationContext;
|
||||
import org.keycloak.adapters.authorization.PolicyEnforcer;
|
||||
import org.keycloak.adapters.authorization.integration.elytron.ServletHttpRequest;
|
||||
import org.keycloak.adapters.authorization.integration.elytron.ServletHttpResponse;
|
||||
import org.keycloak.adapters.authorization.spi.ConfigurationResolver;
|
||||
import org.keycloak.adapters.authorization.spi.HttpRequest;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
/**
|
||||
* A Jakarta Servlet {@link Filter} acting as a policy enforcer. This filter does not enforce access for anonymous subjects.</p>
|
||||
*
|
||||
* For authenticated subjects, this filter delegates the access decision to the {@link PolicyEnforcer} and decide if
|
||||
* the request should continue.</p>
|
||||
*
|
||||
* If access is not granted, this filter aborts the request and relies on the {@link PolicyEnforcer} to properly
|
||||
* respond to client.
|
||||
*
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
@Slf4j
|
||||
public class SpringPolicyEnforcerFilter implements Filter {
|
||||
private final Map<PolicyEnforcerConfig, SpringPolicyEnforcer> policyEnforcer;
|
||||
private final ConfigurationResolver configResolver;
|
||||
|
||||
public SpringPolicyEnforcerFilter(ConfigurationResolver configResolver) {
|
||||
this.configResolver = configResolver;
|
||||
this.policyEnforcer = Collections.synchronizedMap(new HashMap<>());
|
||||
}
|
||||
|
||||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
|
||||
HttpServletRequest request = (HttpServletRequest) servletRequest;
|
||||
HttpServletResponse response = (HttpServletResponse) servletResponse;
|
||||
ServletHttpRequest httpRequest = new ServletHttpRequest(request,
|
||||
() -> extractBearerToken(request));
|
||||
|
||||
SpringPolicyEnforcer policyEnforcer = getOrCreatePolicyEnforcer(request, httpRequest);
|
||||
AuthorizationContext authzContext = policyEnforcer.enforce(httpRequest, new ServletHttpResponse(response));
|
||||
|
||||
request.setAttribute(AuthorizationContext.class.getName(), authzContext);
|
||||
|
||||
if (authzContext.isGranted()) {
|
||||
log.debug("Request authorized, continuing the filter chain");
|
||||
filterChain.doFilter(servletRequest, servletResponse);
|
||||
} else {
|
||||
log.debug("Unauthorized request to path [{}], aborting the filter chain", request.getRequestURI());
|
||||
}
|
||||
}
|
||||
|
||||
protected String extractBearerToken(@NonNull HttpServletRequest request) {
|
||||
Enumeration<String> authorizationHeaderValues = request.getHeaders("Authorization");
|
||||
|
||||
while (authorizationHeaderValues.hasMoreElements()) {
|
||||
String value = authorizationHeaderValues.nextElement();
|
||||
String[] parts = value.trim().split("\\s+");
|
||||
|
||||
if (parts.length != 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String bearer = parts[0];
|
||||
|
||||
if (bearer.equalsIgnoreCase("Bearer")) {
|
||||
return parts[1];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private SpringPolicyEnforcer getOrCreatePolicyEnforcer(HttpServletRequest servletRequest, HttpRequest request) {
|
||||
return policyEnforcer.computeIfAbsent(configResolver.resolve(request),
|
||||
enforcerConfig -> createPolicyEnforcer(servletRequest, enforcerConfig));
|
||||
}
|
||||
|
||||
protected SpringPolicyEnforcer createPolicyEnforcer(HttpServletRequest servletRequest, @NonNull PolicyEnforcerConfig enforcerConfig) {
|
||||
String authServerUrl = enforcerConfig.getAuthServerUrl();
|
||||
|
||||
return new SpringPolicyEnforcer(PolicyEnforcer.builder()
|
||||
.authServerUrl(authServerUrl)
|
||||
.realm(enforcerConfig.getRealm())
|
||||
.clientId(enforcerConfig.getResource())
|
||||
.credentials(enforcerConfig.getCredentials())
|
||||
.bearerOnly(false)
|
||||
.enforcerConfig(enforcerConfig).build(), enforcerConfig);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user