Skip to content

Commit

Permalink
Do CORS more like the right way
Browse files Browse the repository at this point in the history
  • Loading branch information
iyourshaw committed Feb 14, 2024
1 parent d613518 commit 3b10f30
Show file tree
Hide file tree
Showing 4 changed files with 171 additions and 67 deletions.
4 changes: 0 additions & 4 deletions jpo-conflictvisualizer-api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -141,10 +141,6 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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("*");
// }
// };
// }



Expand Down
Original file line number Diff line number Diff line change
@@ -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
// }
//}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 <a href="https://github.com/thomasdarimont/keycloak-project-example/tree/main/apps/backend-api-springboot3">Keycloak Spring Boot 3 API Example App</a>
*/
@Configuration
@EnableWebSecurity
public class KeycloakConfig {
Expand Down Expand Up @@ -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
Expand All @@ -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()
Expand Down Expand Up @@ -120,5 +136,97 @@ public Keycloak keyCloakBuilder() {
.build();
}

/**
* Configures CORS
*
* @param cors mutable cors configuration
*
*
*/
protected void configureCors(CorsConfigurer<HttpSecurity> 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 <a href="https://www.baeldung.com/spring-boot-keycloak">https://www.baeldung.com/spring-boot-keycloak</a>
// */
// @Bean
// public GrantedAuthoritiesMapper userAuthoritiesMapperForKeycloak() {
// return authorities -> {
// System.out.printf("Authorities: %s%n", authorities);
// Set<GrantedAuthority> 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<String>) realmAccess.get(ROLES_CLAIM);
// mappedAuthorities.addAll(generateAuthoritiesFromClaim(roles));
// } else if (userInfo.hasClaim(GROUPS)) {
// Collection<String> roles = (Collection<String>) userInfo.getClaim(
// GROUPS);
// mappedAuthorities.addAll(generateAuthoritiesFromClaim(roles));
// }
// } else {
// var oauth2UserAuthority = (OAuth2UserAuthority) authority;
// Map<String, Object> userAttributes = oauth2UserAuthority.getAttributes();
//
// if (userAttributes.containsKey(REALM_ACCESS_CLAIM)) {
// Map<String, Object> realmAccess = (Map<String, Object>) userAttributes.get(
// REALM_ACCESS_CLAIM);
// Collection<String> roles = (Collection<String>) realmAccess.get(ROLES_CLAIM);
// mappedAuthorities.addAll(generateAuthoritiesFromClaim(roles));
// }
// }
// System.out.printf("Mapped Authorities: %s%n", mappedAuthorities);
// return mappedAuthorities;
// };
// }
//
// Collection<GrantedAuthority> generateAuthoritiesFromClaim(Collection<String> roles) {
// return roles.stream().map(role -> new SimpleGrantedAuthority("ROLE_" + role)).collect(
// Collectors.toList());
// }



}

0 comments on commit 3b10f30

Please sign in to comment.