From cefddb61e182e89b5ee6f4718e192c742fdeb59b Mon Sep 17 00:00:00 2001 From: Edward Cheng Date: Tue, 10 Sep 2024 17:21:09 +1000 Subject: [PATCH] update security module --- .../feign/FeignBadRequestException.java | 12 ++++ .../exceptions/feign/FeignErrorException.java | 18 ++++++ .../handlers/AuthExceptionHandler.java | 22 +++++++ .../handlers/FeignExceptionHandler.java | 26 ++++++++ .../handlers/GenericExceptionHandler.java | 21 +++++++ pom.xml | 3 +- security/pom.xml | 44 ++++++++++++++ .../commons/security/config/FeignConfig.java | 14 +++++ .../security/config/SecurityConfig.java | 60 +++++++++++++++++++ .../security/utils/FeignErrorDecoder.java | 41 +++++++++++++ 10 files changed, 260 insertions(+), 1 deletion(-) create mode 100644 exception/src/main/java/sydney/cheng/microservice/commons/exceptions/feign/FeignBadRequestException.java create mode 100644 exception/src/main/java/sydney/cheng/microservice/commons/exceptions/feign/FeignErrorException.java create mode 100644 exception/src/main/java/sydney/cheng/microservice/commons/exceptions/handlers/AuthExceptionHandler.java create mode 100644 exception/src/main/java/sydney/cheng/microservice/commons/exceptions/handlers/FeignExceptionHandler.java create mode 100644 exception/src/main/java/sydney/cheng/microservice/commons/exceptions/handlers/GenericExceptionHandler.java create mode 100644 security/pom.xml create mode 100644 security/src/main/java/sydney/cheng/microservice/commons/security/config/FeignConfig.java create mode 100644 security/src/main/java/sydney/cheng/microservice/commons/security/config/SecurityConfig.java create mode 100644 security/src/main/java/sydney/cheng/microservice/commons/security/utils/FeignErrorDecoder.java diff --git a/exception/src/main/java/sydney/cheng/microservice/commons/exceptions/feign/FeignBadRequestException.java b/exception/src/main/java/sydney/cheng/microservice/commons/exceptions/feign/FeignBadRequestException.java new file mode 100644 index 0000000..75cc1e0 --- /dev/null +++ b/exception/src/main/java/sydney/cheng/microservice/commons/exceptions/feign/FeignBadRequestException.java @@ -0,0 +1,12 @@ +package sydney.cheng.microservice.commons.exceptions.feign; + +import lombok.Builder; +import lombok.Getter; + +import java.util.Map; + +@Builder +@Getter +public class FeignBadRequestException extends RuntimeException { + private Map errors; +} diff --git a/exception/src/main/java/sydney/cheng/microservice/commons/exceptions/feign/FeignErrorException.java b/exception/src/main/java/sydney/cheng/microservice/commons/exceptions/feign/FeignErrorException.java new file mode 100644 index 0000000..87c3a47 --- /dev/null +++ b/exception/src/main/java/sydney/cheng/microservice/commons/exceptions/feign/FeignErrorException.java @@ -0,0 +1,18 @@ +package sydney.cheng.microservice.commons.exceptions.feign; + +import lombok.Builder; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Builder +@Getter +public class FeignErrorException extends RuntimeException { + private final String message; + private final HttpStatus httpStatus; + + public FeignErrorException(String message, HttpStatus httpStatus) { + super(message); + this.message = message; + this.httpStatus = httpStatus; + } +} diff --git a/exception/src/main/java/sydney/cheng/microservice/commons/exceptions/handlers/AuthExceptionHandler.java b/exception/src/main/java/sydney/cheng/microservice/commons/exceptions/handlers/AuthExceptionHandler.java new file mode 100644 index 0000000..ade7ec9 --- /dev/null +++ b/exception/src/main/java/sydney/cheng/microservice/commons/exceptions/handlers/AuthExceptionHandler.java @@ -0,0 +1,22 @@ +package sydney.cheng.microservice.commons.exceptions.handlers; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; +import sydney.cheng.microservice.commons.exceptions.auth.WrongCredentialsException; + +import java.util.HashMap; +import java.util.Map; + +@RestControllerAdvice +public class AuthExceptionHandler extends ResponseEntityExceptionHandler { + + @ExceptionHandler(WrongCredentialsException.class) + public ResponseEntity usernameOrPasswordInvalidException(WrongCredentialsException exception) { + Map errors = new HashMap<>(); + errors.put("error", exception.getMessage()); + return new ResponseEntity<>(errors, HttpStatus.UNAUTHORIZED); + } +} diff --git a/exception/src/main/java/sydney/cheng/microservice/commons/exceptions/handlers/FeignExceptionHandler.java b/exception/src/main/java/sydney/cheng/microservice/commons/exceptions/handlers/FeignExceptionHandler.java new file mode 100644 index 0000000..bdbc303 --- /dev/null +++ b/exception/src/main/java/sydney/cheng/microservice/commons/exceptions/handlers/FeignExceptionHandler.java @@ -0,0 +1,26 @@ +package sydney.cheng.microservice.commons.exceptions.handlers; + +import sydney.cheng.microservice.commons.exceptions.feign.*; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; + +import java.util.HashMap; +import java.util.Map; + +@RestControllerAdvice +public class FeignExceptionHandler extends ResponseEntityExceptionHandler { + + @ExceptionHandler(FeignErrorException.class) + public ResponseEntity genericError(FeignErrorException exception) { + Map errors = new HashMap<>(); + errors.put("error", exception.getMessage()); + return new ResponseEntity<>(errors, exception.getHttpStatus()); + } + + @ExceptionHandler(FeignBadRequestException.class) + public ResponseEntity validationException(FeignBadRequestException exception) { + return ResponseEntity.badRequest().body(exception.getErrors()); + } +} diff --git a/exception/src/main/java/sydney/cheng/microservice/commons/exceptions/handlers/GenericExceptionHandler.java b/exception/src/main/java/sydney/cheng/microservice/commons/exceptions/handlers/GenericExceptionHandler.java new file mode 100644 index 0000000..36377ca --- /dev/null +++ b/exception/src/main/java/sydney/cheng/microservice/commons/exceptions/handlers/GenericExceptionHandler.java @@ -0,0 +1,21 @@ +package sydney.cheng.microservice.commons.exceptions.handlers; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; + +import java.util.HashMap; +import java.util.Map; + +@RestControllerAdvice +public class GenericExceptionHandler extends ResponseEntityExceptionHandler { + + @ExceptionHandler(Exception.class) + public final ResponseEntity handleAllException(Exception ex) { + Map errors = new HashMap<>(); + errors.put("error", ex.getMessage()); + return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST); + } +} diff --git a/pom.xml b/pom.xml index 292b23d..1f5c081 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ sydney.cheng ec-super-pom - 1.0.4 + 1.0.5 ec-microservice-commons @@ -44,6 +44,7 @@ configuration database exception + security diff --git a/security/pom.xml b/security/pom.xml new file mode 100644 index 0000000..961eaca --- /dev/null +++ b/security/pom.xml @@ -0,0 +1,44 @@ + + + 4.0.0 + + sydney.cheng + ec-microservice-commons + 1.0.1-SNAPSHOT + + + ec-microservice-commons-security + + + 17 + 17 + UTF-8 + + + + + sydney.cheng + ec-microservice-commons-configuration + ${project.version} + + + sydney.cheng + ec-microservice-commons-exceptions + ${project.version} + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + com.fasterxml.jackson.core + jackson-databind + + + \ No newline at end of file diff --git a/security/src/main/java/sydney/cheng/microservice/commons/security/config/FeignConfig.java b/security/src/main/java/sydney/cheng/microservice/commons/security/config/FeignConfig.java new file mode 100644 index 0000000..209e014 --- /dev/null +++ b/security/src/main/java/sydney/cheng/microservice/commons/security/config/FeignConfig.java @@ -0,0 +1,14 @@ +package sydney.cheng.microservice.commons.security.config; + +import feign.codec.ErrorDecoder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import sydney.cheng.microservice.commons.security.utils.FeignErrorDecoder; + +@Configuration +public class FeignConfig { + @Bean + public ErrorDecoder errorDecoder() { + return new FeignErrorDecoder(); + } +} diff --git a/security/src/main/java/sydney/cheng/microservice/commons/security/config/SecurityConfig.java b/security/src/main/java/sydney/cheng/microservice/commons/security/config/SecurityConfig.java new file mode 100644 index 0000000..ff714ae --- /dev/null +++ b/security/src/main/java/sydney/cheng/microservice/commons/security/config/SecurityConfig.java @@ -0,0 +1,60 @@ +package sydney.cheng.microservice.commons.security.config; + +import lombok.AllArgsConstructor; +import org.springframework.http.HttpMethod; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.access.AccessDeniedHandlerImpl; +import org.springframework.security.web.csrf.CookieCsrfTokenRepository; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import sydney.cheng.microservice.commons.configuration.properties.auth.CorsProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +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.web.SecurityFilterChain; + +@Configuration +@EnableWebSecurity +@AllArgsConstructor +public class SecurityConfig { + private final CorsProperties corsProperties; + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + return http + .formLogin(AbstractHttpConfigurer::disable) + .cors(cors -> cors.configurationSource(corsConfigurationSource())) + .csrf(csrf -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())) + .authorizeHttpRequests(authorize -> + authorize + .requestMatchers(HttpMethod.OPTIONS, "*").permitAll() + .requestMatchers("/actuator/**", + "/swagger-ui/**", "/swagger-resources/**", "/api-docs/**", + "/config/**" + ).permitAll() + .anyRequest().authenticated() + ) + .exceptionHandling(exceptionHandling -> exceptionHandling.accessDeniedHandler(new AccessDeniedHandlerImpl())) + .sessionManagement(sess -> sess.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .httpBasic(Customizer.withDefaults()) + + .build(); + } + + @Bean + public CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration corsConfiguration = new CorsConfiguration(); + corsConfiguration.setAllowCredentials(corsProperties.isAllowCredentials()); + corsConfiguration.setAllowedOrigins(corsProperties.getAllowedUrlList()); + corsConfiguration.setAllowedHeaders(corsProperties.getAllowedHeaders()); + corsConfiguration.setAllowedMethods(corsProperties.getAllowedMethods()); + corsConfiguration.setMaxAge(corsProperties.getAllowedMaxAge()); + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", corsConfiguration); + return source; + } +} diff --git a/security/src/main/java/sydney/cheng/microservice/commons/security/utils/FeignErrorDecoder.java b/security/src/main/java/sydney/cheng/microservice/commons/security/utils/FeignErrorDecoder.java new file mode 100644 index 0000000..d81baea --- /dev/null +++ b/security/src/main/java/sydney/cheng/microservice/commons/security/utils/FeignErrorDecoder.java @@ -0,0 +1,41 @@ +package sydney.cheng.microservice.commons.security.utils; + +import com.fasterxml.jackson.databind.ObjectMapper; +import feign.Response; +import feign.codec.ErrorDecoder; +import org.apache.commons.io.IOUtils; +import org.springframework.http.HttpStatus; +import sydney.cheng.microservice.commons.exceptions.feign.FeignBadRequestException; +import sydney.cheng.microservice.commons.exceptions.feign.FeignErrorException; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Map; + +public class FeignErrorDecoder implements ErrorDecoder { + private final ObjectMapper mapper = new ObjectMapper(); + + @Override + public Exception decode(String methodKey, Response response) { + try (InputStream body = response.body().asInputStream()) { + Map errors = + mapper.readValue(IOUtils.toString(body, StandardCharsets.UTF_8), Map.class); + if (response.status() == 400) { + return FeignBadRequestException.builder() + .errors(errors).build(); + } else + return FeignErrorException + .builder() + .httpStatus(HttpStatus.valueOf(response.status())) + .message(errors.get("error")) + .build(); + + } catch (IOException exception) { + throw FeignErrorException.builder() + .httpStatus(HttpStatus.valueOf(response.status())) + .message(exception.getMessage()) + .build(); + } + } +}