diff --git a/jpo-conflictvisualizer-api/pom.xml b/jpo-conflictvisualizer-api/pom.xml
index 03739c3b7..c536b1df7 100644
--- a/jpo-conflictvisualizer-api/pom.xml
+++ b/jpo-conflictvisualizer-api/pom.xml
@@ -141,10 +141,6 @@
org.springframework.boot
spring-boot-starter-security
-
- org.springframework.boot
- spring-boot-starter-oauth2-client
-
org.springframework.boot
spring-boot-starter-oauth2-resource-server
diff --git a/jpo-conflictvisualizer-api/src/main/java/us/dot/its/jpo/ode/api/ConflictApiApplication.java b/jpo-conflictvisualizer-api/src/main/java/us/dot/its/jpo/ode/api/ConflictApiApplication.java
index 1399ff231..cd8e82440 100644
--- a/jpo-conflictvisualizer-api/src/main/java/us/dot/its/jpo/ode/api/ConflictApiApplication.java
+++ b/jpo-conflictvisualizer-api/src/main/java/us/dot/its/jpo/ode/api/ConflictApiApplication.java
@@ -30,17 +30,17 @@ public static void main(String[] args) {
}
- @Bean
- public WebMvcConfigurer corsConfigurer() {
- return new WebMvcConfigurer() {
- @Override
- public void addCorsMappings(CorsRegistry registry) {
- ConflictMonitorApiProperties props = new ConflictMonitorApiProperties();
- registry.addMapping("/**").allowedOrigins(props.getCors());
- // registry.addMapping("/**").allowedMethods("*");
- }
- };
- }
+// @Bean
+// public WebMvcConfigurer corsConfigurer() {
+// return new WebMvcConfigurer() {
+// @Override
+// public void addCorsMappings(CorsRegistry registry) {
+// ConflictMonitorApiProperties props = new ConflictMonitorApiProperties();
+// registry.addMapping("/**").allowedOrigins(props.getCors());
+// // registry.addMapping("/**").allowedMethods("*");
+// }
+// };
+// }
diff --git a/jpo-conflictvisualizer-api/src/main/java/us/dot/its/jpo/ode/api/CorsFilter.java b/jpo-conflictvisualizer-api/src/main/java/us/dot/its/jpo/ode/api/CorsFilter.java
index 7782d272e..8a62f2cab 100644
--- a/jpo-conflictvisualizer-api/src/main/java/us/dot/its/jpo/ode/api/CorsFilter.java
+++ b/jpo-conflictvisualizer-api/src/main/java/us/dot/its/jpo/ode/api/CorsFilter.java
@@ -1,33 +1,33 @@
-package us.dot.its.jpo.ode.api;
-
-import jakarta.servlet.*;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
-
-import java.io.IOException;
-
-/**
- * Custom servlet filter to add CORS header
- */
-public class CorsFilter implements Filter {
- @Override
- public void init(FilterConfig filterConfig) throws ServletException {
- // Nothing to initialize
- }
-
- @Override
- public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
- var response = (HttpServletResponse)servletResponse;
- var request = (HttpServletRequest)servletRequest;
- response.setHeader("Access-Control-Allow-Origin", "*");
- response.setHeader("Access-Control-Allow-Methods", "OPTIONS,GET,POST,DELETE");
- response.setHeader("Access-Control-Allow-Headers", "authorization");
- response.setIntHeader("Access-Control-Max-Age", 1800);
- filterChain.doFilter(servletRequest, servletResponse);
- }
-
- @Override
- public void destroy() {
- // Nothing to destroy
- }
-}
+//package us.dot.its.jpo.ode.api;
+//
+//import jakarta.servlet.*;
+//import jakarta.servlet.http.HttpServletRequest;
+//import jakarta.servlet.http.HttpServletResponse;
+//
+//import java.io.IOException;
+//
+///**
+// * Custom servlet filter to add CORS header
+// */
+//public class CorsFilter implements Filter {
+// @Override
+// public void init(FilterConfig filterConfig) throws ServletException {
+// // Nothing to initialize
+// }
+//
+// @Override
+// public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
+// var response = (HttpServletResponse)servletResponse;
+// var request = (HttpServletRequest)servletRequest;
+// response.setHeader("Access-Control-Allow-Origin", "*");
+// response.setHeader("Access-Control-Allow-Methods", "OPTIONS,GET,POST,DELETE");
+// response.setHeader("Access-Control-Allow-Headers", "authorization");
+// response.setIntHeader("Access-Control-Max-Age", 1800);
+// filterChain.doFilter(servletRequest, servletResponse);
+// }
+//
+// @Override
+// public void destroy() {
+// // Nothing to destroy
+// }
+//}
diff --git a/jpo-conflictvisualizer-api/src/main/java/us/dot/its/jpo/ode/api/KeycloakConfig.java b/jpo-conflictvisualizer-api/src/main/java/us/dot/its/jpo/ode/api/KeycloakConfig.java
index ab7e3532b..f9e1c466c 100644
--- a/jpo-conflictvisualizer-api/src/main/java/us/dot/its/jpo/ode/api/KeycloakConfig.java
+++ b/jpo-conflictvisualizer-api/src/main/java/us/dot/its/jpo/ode/api/KeycloakConfig.java
@@ -2,6 +2,7 @@
import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.KeycloakBuilder;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
@@ -12,20 +13,35 @@
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
+import org.springframework.security.config.annotation.web.configurers.CorsConfigurer;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
import org.springframework.security.oauth2.core.AuthenticationMethod;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
+import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;
+import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.session.SessionManagementFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
+
+import java.util.*;
+import java.util.stream.Collectors;
import static org.springframework.security.config.Customizer.withDefaults;
-// provides keycloak based spring security configuration
-// annotation covers 2 annotations - @Configuration and @EnableWebSecurity
-//@KeycloakConfiguration
+/**
+ * provides keycloak based spring security configuration
+ * annotation covers 2 annotations - @Configuration and @EnableWebSecurity
+ *
+ * @see Keycloak Spring Boot 3 API Example App
+ */
@Configuration
@EnableWebSecurity
public class KeycloakConfig {
@@ -54,32 +70,30 @@ public class KeycloakConfig {
@Value("${keycloak.client-secret}")
private String clientSecret;
- // This condition allows for disabling security
-// @ConditionalOnProperty(prefix = "security",
-// name = "enabled",
-// havingValue = "true")
-// @EnableMethodSecurity(prePostEnabled = true, jsr250Enabled = true) // Allow @PreAuthorize and @RoleAllowed annotations
-// static class Dummy {
-// public Dummy(){
-// System.out.println("Initializing Security");
-// }
-//
-// }
+ private ConflictMonitorApiProperties properties;
- @Bean
- CorsFilter corsFilter() {
- return new CorsFilter();
+ @Autowired
+ public KeycloakConfig(ConflictMonitorApiProperties properties) {
+ this.properties = properties;
}
+// @Bean
+// CorsFilter corsFilter() {
+// return new CorsFilter();
+// }
+
+
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
if(securityEnabled){
System.out.println("Running with KeyCloak Authentication");
return httpSecurity
- .addFilterBefore(corsFilter(), SessionManagementFilter.class)
+ //.addFilterBefore(corsFilter(), SessionManagementFilter.class)
+ .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
+ .cors(this::configureCors)
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(request -> request
.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() // Allow CORS preflight
@@ -92,7 +106,9 @@ public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws
}else{
System.out.println("Running without KeyCloak Authentication");
return httpSecurity
- .addFilterBefore(corsFilter(), SessionManagementFilter.class)
+ //.addFilterBefore(corsFilter(), SessionManagementFilter.class)
+ .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
+ .cors(this::configureCors)
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(
request -> request.anyRequest().permitAll()
@@ -120,5 +136,97 @@ public Keycloak keyCloakBuilder() {
.build();
}
+ /**
+ * Configures CORS
+ *
+ * @param cors mutable cors configuration
+ *
+ *
+ */
+ protected void configureCors(CorsConfigurer cors) {
+
+ UrlBasedCorsConfigurationSource defaultUrlBasedCorsConfigSource = new UrlBasedCorsConfigurationSource();
+ CorsConfiguration corsConfiguration = new CorsConfiguration().applyPermitDefaultValues();
+ corsConfiguration.addAllowedOrigin(properties.getCors());
+ List.of("GET", "POST", "PUT", "DELETE", "OPTIONS").forEach(corsConfiguration::addAllowedMethod);
+ defaultUrlBasedCorsConfigSource.registerCorsConfiguration("/**", corsConfiguration);
+
+ cors.configurationSource(req -> {
+
+ CorsConfiguration config = new CorsConfiguration();
+
+ config = config.combine(defaultUrlBasedCorsConfigSource.getCorsConfiguration(req));
+
+ // check if request Header "origin" is in white-list -> dynamically generate cors config
+
+ return config;
+ });
+ }
+
+// // This condition allows for disabling security
+// @ConditionalOnProperty(prefix = "security",
+// name = "enabled",
+// havingValue = "true")
+// @EnableMethodSecurity(prePostEnabled = true, jsr250Enabled = true) // Allow @PreAuthorize and @RoleAllowed annotations
+// static class Dummy {
+// public Dummy(){
+// System.out.println("Initializing Security");
+// }
+//
+// }
+
+// private static final String GROUPS = "groups";
+// private static final String REALM_ACCESS_CLAIM = "realm_access";
+// private static final String ROLES_CLAIM = "roles";
+//
+// /**
+// * Needed to get role-based authorization to work.
+// * @see https://www.baeldung.com/spring-boot-keycloak
+// */
+// @Bean
+// public GrantedAuthoritiesMapper userAuthoritiesMapperForKeycloak() {
+// return authorities -> {
+// System.out.printf("Authorities: %s%n", authorities);
+// Set mappedAuthorities = new HashSet<>();
+// var authority = authorities.iterator().next();
+// boolean isOidc = authority instanceof OidcUserAuthority;
+//
+// if (isOidc) {
+// var oidcUserAuthority = (OidcUserAuthority) authority;
+// var userInfo = oidcUserAuthority.getUserInfo();
+//
+// // Tokens can be configured to return roles under
+// // Groups or REALM ACCESS hence have to check both
+// if (userInfo.hasClaim(REALM_ACCESS_CLAIM)) {
+// var realmAccess = userInfo.getClaimAsMap(REALM_ACCESS_CLAIM);
+// var roles = (Collection) realmAccess.get(ROLES_CLAIM);
+// mappedAuthorities.addAll(generateAuthoritiesFromClaim(roles));
+// } else if (userInfo.hasClaim(GROUPS)) {
+// Collection roles = (Collection) userInfo.getClaim(
+// GROUPS);
+// mappedAuthorities.addAll(generateAuthoritiesFromClaim(roles));
+// }
+// } else {
+// var oauth2UserAuthority = (OAuth2UserAuthority) authority;
+// Map userAttributes = oauth2UserAuthority.getAttributes();
+//
+// if (userAttributes.containsKey(REALM_ACCESS_CLAIM)) {
+// Map realmAccess = (Map) userAttributes.get(
+// REALM_ACCESS_CLAIM);
+// Collection roles = (Collection) realmAccess.get(ROLES_CLAIM);
+// mappedAuthorities.addAll(generateAuthoritiesFromClaim(roles));
+// }
+// }
+// System.out.printf("Mapped Authorities: %s%n", mappedAuthorities);
+// return mappedAuthorities;
+// };
+// }
+//
+// Collection generateAuthoritiesFromClaim(Collection roles) {
+// return roles.stream().map(role -> new SimpleGrantedAuthority("ROLE_" + role)).collect(
+// Collectors.toList());
+// }
+
+
}
\ No newline at end of file