Skip to content

Commit

Permalink
actuator, security, openapi, web validation, zalando
Browse files Browse the repository at this point in the history
  • Loading branch information
vondacho committed Apr 5, 2023
1 parent ed65fe4 commit 42babd2
Show file tree
Hide file tree
Showing 12 changed files with 182 additions and 31 deletions.
2 changes: 2 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.zalando:problem-spring-web-starter:${property("problem.spring.web.version")}")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("org.liquibase:liquibase-core:${property("liquibase.version")}")
implementation("com.atlassian.oai:swagger-request-validator-springmvc:${property("atlassian.validator.version")}")
compileOnly("org.projectlombok:lombok")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package edu.obya.blueprint.customer;
package edu.obya.blueprint;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration;

@EnableAutoConfiguration(exclude = ErrorMvcAutoConfiguration.class)
@SpringBootApplication
public class Application {
public static void main(String[] args) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package edu.obya.blueprint.customer.web;
package edu.obya.blueprint;

import edu.obya.blueprint.customer.web.CustomerAdviceTrait;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.zalando.problem.spring.web.advice.ProblemHandling;
import org.zalando.problem.spring.web.advice.security.SecurityAdviceTrait;
import org.zalando.problem.spring.web.advice.validation.OpenApiValidationAdviceTrait;

@ControllerAdvice
public class CustomerExceptionHandling implements
public class ExceptionHandling implements
ProblemHandling,
OpenApiValidationAdviceTrait,
CustomerAdviceTrait {
CustomerAdviceTrait,
SecurityAdviceTrait {
}
65 changes: 65 additions & 0 deletions src/main/java/edu/obya/blueprint/WebSecurityConfiguration.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package edu.obya.blueprint;

import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.configuration.WebSecurityCustomizer;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;

import static org.springframework.security.config.Customizer.withDefaults;

@EnableGlobalMethodSecurity(prePostEnabled=true)
@EnableWebSecurity
@Configuration
public class WebSecurityConfiguration {

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authz) -> authz
// Matches /customers, /customers/, /customers/123
.mvcMatchers("/customers/**").hasAnyRole("USER","ADMIN")
.requestMatchers(EndpointRequest.to("health","info","metrics","loggers")).permitAll()
.requestMatchers(EndpointRequest.toAnyEndpoint()).hasAnyRole("ADMIN")
.anyRequest().authenticated()
)
.httpBasic(withDefaults())
.csrf().disable();
return http.build();
}

@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
// By-passing Security
return (web) -> web.ignoring().antMatchers("/resources/**");
}

@Profile("dev")
@Bean
public UserDetailsService userDetailsService() {
// Uses BCrypt as a default "best practice" encoding scheme for now
PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();

return new InMemoryUserDetailsManager(
User.builder()
.username("test")
.password(passwordEncoder.encode("test"))
.roles("USER")
.build(),
User.builder()
.username("admin")
.password(passwordEncoder.encode("admin"))
.roles("ADMIN")
.build()
);
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
package edu.obya.blueprint.customer.web;
package edu.obya.blueprint;

import com.atlassian.oai.validator.OpenApiInteractionValidator;
import com.atlassian.oai.validator.springmvc.OpenApiValidationFilter;
import com.atlassian.oai.validator.springmvc.OpenApiValidationInterceptor;
import com.atlassian.oai.validator.springmvc.SpringMVCLevelResolverFactory;
import lombok.NonNull;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
Expand All @@ -16,14 +14,13 @@
import javax.servlet.Filter;
import java.io.IOException;

@EnableAutoConfiguration(exclude = ErrorMvcAutoConfiguration.class)
@Configuration
public class CustomerWebConfiguration {
public class WebValidationConfiguration {

@Bean
public Filter validationFilter() {
return new OpenApiValidationFilter(
true, // enable request validation
false, // enable request validation
false // enable response validation
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package edu.obya.blueprint.customer.web;
package edu.obya.blueprint.customer.appl;

import edu.obya.blueprint.customer.domain.Customer;
import lombok.Builder;
Expand All @@ -8,7 +8,6 @@
@Data
public class CustomerDto {
String id;
String fullName;
String firstName;
String lastName;

Expand All @@ -17,7 +16,6 @@ public static CustomerDto from(Customer customer) {
.id(customer.getId().getId().toString())
.firstName(customer.getFirstName())
.lastName(customer.getLastName())
.fullName(String.format("%s %s", customer.getFirstName(), customer.getLastName()))
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@

import edu.obya.blueprint.common.util.search.FindCriteria;
import edu.obya.blueprint.customer.domain.*;
import edu.obya.blueprint.customer.web.CustomerDto;
import lombok.RequiredArgsConstructor;
import lombok.val;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.UUID;
import java.util.function.Supplier;

@PreAuthorize("hasRole('USER')")
@Transactional
@RequiredArgsConstructor
public class CustomerService {
Expand All @@ -19,19 +20,17 @@ public class CustomerService {
private final Supplier<CustomerId> idSupplier = () -> CustomerId.of(UUID.randomUUID());

@Transactional(readOnly = true)
public CustomerDto get(CustomerId customerId) {
public Customer get(CustomerId customerId) {
return repository
.findById(customerId)
.map(CustomerDto::from)
.orElseThrow(() -> new CustomerNotFoundException(customerId));
}

@Transactional(readOnly = true)
public List<CustomerDto> findByCriteria(List<FindCriteria> criteria) {
public List<Customer> findByCriteria(List<FindCriteria> criteria) {
return repository
.findByCriteria(criteria)
.stream()
.map(CustomerDto::from)
.toList();
}

Expand All @@ -55,6 +54,7 @@ public void update(CustomerDto customerDto, CustomerId customerId) {
}
}

@PreAuthorize("hasRole('ADMIN')")
public void remove(CustomerId customerId) {
if (!repository.remove(customerId)) {
throw new CustomerNotFoundException(customerId);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package edu.obya.blueprint.customer.web;

import edu.obya.blueprint.common.util.search.FindCriteria;
import edu.obya.blueprint.customer.appl.CustomerDto;
import edu.obya.blueprint.customer.appl.CustomerService;
import edu.obya.blueprint.customer.domain.CustomerId;
import io.micrometer.core.annotation.Timed;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
Expand All @@ -11,38 +15,46 @@

import java.util.List;

@RequiredArgsConstructor
@Timed("customers.summary")
@RequestMapping("/customers")
@RestController
public class CustomerController {

private final CustomerService customerService;

@GetMapping("/customers/{id}")
public CustomerController(CustomerService customerService, MeterRegistry registry) {
this.customerService = customerService;
registry.timer("customers.summary");
}

@GetMapping
@ResponseStatus(code = HttpStatus.OK)
public CustomerDto findById(@PathVariable String id) {
return customerService.get(CustomerId.from(id));
public List<CustomerSummary> findByCriteria(@RequestParam(required = false) String filter, Pageable pageable) {
return customerService
.findByCriteria(StringUtils.hasText(filter) ? FindCriteria.from(filter) : FindCriteria.empty())
.stream().map(CustomerSummary::from)
.toList();
}

@GetMapping("/customers")
@GetMapping("/{id}")
@ResponseStatus(code = HttpStatus.OK)
public List<CustomerDto> findByCriteria(@RequestParam(required = false) String filter, Pageable pageable) {
return customerService.findByCriteria(
StringUtils.hasText(filter) ? FindCriteria.from(filter) : FindCriteria.empty());
public CustomerSummary findById(@PathVariable String id) {
return CustomerSummary.from(customerService.get(CustomerId.from(id)));
}

@PostMapping("/customers")
@PostMapping
@ResponseStatus(code = HttpStatus.CREATED)
public CustomerId create(@RequestBody CustomerDto customerDto) {
return customerService.create(customerDto);
}

@PutMapping("/customers/{id}")
@PutMapping("/{id}")
@ResponseStatus(code = HttpStatus.NO_CONTENT)
public void update(@PathVariable String id, @RequestBody CustomerDto customerDto) {
customerService.update(customerDto, CustomerId.from(id));
}

@DeleteMapping("/customers/{id}")
@DeleteMapping("/{id}")
@ResponseStatus(code = HttpStatus.NO_CONTENT)
public void remove(@PathVariable String id) {
customerService.remove(CustomerId.from(id));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package edu.obya.blueprint.customer.web;

import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;

@Component
public class CustomerHealthCheck implements HealthIndicator {
@Override
public Health health() {
return Health.up().withDetail("heartRate", "50").build();
}
}
23 changes: 23 additions & 0 deletions src/main/java/edu/obya/blueprint/customer/web/CustomerSummary.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package edu.obya.blueprint.customer.web;

import edu.obya.blueprint.customer.domain.Customer;
import lombok.Builder;
import lombok.Value;

@Builder
@Value
public class CustomerSummary {
String id;
String fullName;
String firstName;
String lastName;

public static CustomerSummary from(Customer customer) {
return CustomerSummary.builder()
.id(customer.getId().getId().toString())
.firstName(customer.getFirstName())
.lastName(customer.getLastName())
.fullName(String.format("%s %s", customer.getFirstName(), customer.getLastName()))
.build();
}
}
File renamed without changes.
39 changes: 37 additions & 2 deletions src/main/resources/config/application.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
spring:
profiles:
active: jpa
active: dev,jpa
datasource:
driver-class-name: org.h2.Driver
url: jdbc:h2:mem:test
Expand All @@ -20,4 +20,39 @@ spring:

openapi:
spec:
url: openapi.yaml
url: api/openapi.yaml


# all actuators (JMX, HTTP) are ENABLED by default
## management.endpoints.enabled-by-default: true
## management.endpoints.info.enabled: true
## management.endpoints.health.enabled: true

# all JMX actuators are EXPOSED by default
## management.endpoints.jmx.exposure.exclude:
## management.endpoints.jmx.exposure.include: "*"
## JMX actuators must be secured in production

# For security reasons, only info and health HTTP actuators are EXPOSED by default
## health and info disabled unless listed

management:
info:
java:
enabled: true
env:
enabled: true
build:
enabled: true
endpoint:
shutdown:
enabled: true
health:
show-details: always
endpoints:
web:
exposure:
include: health,info,metrics,shutdown
base-path: /admin

info.blueprint.api.url: https://api.obya.edu/blueprint

0 comments on commit 42babd2

Please sign in to comment.