Skip to content

Commit 7e6091f

Browse files
Lukasz Gadomskielowski
authored andcommitted
feat: implement Security
1 parent b025f8d commit 7e6091f

File tree

8 files changed

+140
-1
lines changed

8 files changed

+140
-1
lines changed

pom.xml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@
4343
<groupId>org.springframework.boot</groupId>
4444
<artifactId>spring-boot-starter-web</artifactId>
4545
</dependency>
46+
<dependency>
47+
<groupId>org.springframework.boot</groupId>
48+
<artifactId>spring-boot-starter-security</artifactId>
49+
</dependency>
4650
<dependency>
4751
<groupId>org.flywaydb</groupId>
4852
<artifactId>flyway-core</artifactId>
@@ -214,7 +218,7 @@
214218
<generateApiTests>false</generateApiTests>
215219
<generateModelTests>false</generateModelTests>
216220
<configOptions>
217-
<!-- <delegatePattern>true</delegatePattern>-->
221+
<!-- <delegatePattern>true</delegatePattern>-->
218222
<useTags>true</useTags>
219223
<library>spring-boot</library>
220224
<interfaceOnly>true</interfaceOnly>

src/main/java/com/capgemini/training/appointmentbooking/logic/FindAppointmentUc.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
import com.capgemini.training.appointmentbooking.common.to.AppointmentCto;
44
import com.capgemini.training.appointmentbooking.dataaccess.repository.criteria.AppointmentCriteria;
5+
import com.capgemini.training.appointmentbooking.security.AccessControl;
6+
import com.capgemini.training.appointmentbooking.security.access.HasRole;
7+
58
import jakarta.validation.constraints.NotNull;
69

710
import java.time.Instant;
@@ -10,12 +13,16 @@
1013

1114
public interface FindAppointmentUc {
1215

16+
@HasRole(AccessControl.ROLE_RECEPTIONIST)
1317
Optional<AppointmentCto> findById(@NotNull Long id);
1418

19+
@HasRole(AccessControl.ROLE_RECEPTIONIST)
1520
List<AppointmentCto> findByCriteria(AppointmentCriteria criteria);
1621

22+
@HasRole(AccessControl.ROLE_RECEPTIONIST)
1723
List<AppointmentCto> findAll();
1824

25+
@HasRole(AccessControl.ROLE_RECEPTIONIST)
1926
boolean hasConflictingAppointment(@NotNull Long specialistId, @NotNull Instant dateTime);
2027

2128
}

src/main/java/com/capgemini/training/appointmentbooking/logic/ManageAppointmentUc.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,18 @@
44
import com.capgemini.training.appointmentbooking.common.to.AppointmentBookingEto;
55
import com.capgemini.training.appointmentbooking.common.to.AppointmentCto;
66
import com.capgemini.training.appointmentbooking.common.to.AppointmentEto;
7+
import com.capgemini.training.appointmentbooking.security.AccessControl;
8+
import com.capgemini.training.appointmentbooking.security.access.HasRole;
9+
710
import jakarta.validation.Valid;
811
import jakarta.validation.constraints.NotNull;
912

1013
public interface ManageAppointmentUc {
1114

15+
@HasRole(AccessControl.ROLE_RECEPTIONIST)
1216
AppointmentCto bookAppointment(@Valid AppointmentBookingEto appointmentBookingEto);
1317

18+
@HasRole(AccessControl.ROLE_SPECIALIST)
1419
AppointmentEto updateAppointmentStatus(@NotNull Long appointmentId, @NotNull AppointmentStatus appointmentStatus);
1520

1621
}

src/main/java/com/capgemini/training/appointmentbooking/logic/ManageTreatmentUc.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@
22

33
import com.capgemini.training.appointmentbooking.common.to.TreatmentCreationTo;
44
import com.capgemini.training.appointmentbooking.common.to.TreatmentCto;
5+
import com.capgemini.training.appointmentbooking.security.AccessControl;
6+
import com.capgemini.training.appointmentbooking.security.access.HasRole;
7+
58
import jakarta.validation.Valid;
69

710
public interface ManageTreatmentUc {
811

12+
@HasRole(AccessControl.ROLE_ADMIN)
913
TreatmentCto createTreatment(@Valid TreatmentCreationTo treatmentCreationTo);
1014

1115
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.capgemini.training.appointmentbooking.security;
2+
3+
import org.springframework.stereotype.Component;
4+
5+
@Component
6+
public class AccessControl {
7+
public static final String ROLE_ADMIN = "ADMIN";
8+
public static final String ROLE_SPECIALIST = "SPECIALIST";
9+
public static final String ROLE_RECEPTIONIST = "RECEPTIONIST";
10+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.capgemini.training.appointmentbooking.security;
2+
3+
import java.util.List;
4+
5+
import org.springframework.http.HttpStatus;
6+
import org.springframework.http.ResponseEntity;
7+
import org.springframework.security.core.GrantedAuthority;
8+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
9+
import org.springframework.security.core.userdetails.UserDetails;
10+
import org.springframework.web.bind.annotation.GetMapping;
11+
import org.springframework.web.bind.annotation.RequestMapping;
12+
import org.springframework.web.bind.annotation.RestController;
13+
14+
import lombok.Data;
15+
16+
@RestController()
17+
@RequestMapping("auth")
18+
public class SecurityController {
19+
20+
@GetMapping("/me")
21+
public ResponseEntity<UserDetailsResponse> me(@AuthenticationPrincipal UserDetails userDetails) {
22+
if (userDetails == null) {
23+
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
24+
}
25+
26+
List<String> roles = userDetails.getAuthorities().stream().map(GrantedAuthority::getAuthority).toList();
27+
return ResponseEntity.ok(new UserDetailsResponse(userDetails.getUsername(), roles));
28+
}
29+
30+
@Data
31+
public static class UserDetailsResponse {
32+
private final String username;
33+
private final List<String> roles;
34+
}
35+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.capgemini.training.appointmentbooking.security.access;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
import org.springframework.security.access.prepost.PreAuthorize;
9+
10+
@Target({ElementType.METHOD, ElementType.TYPE})
11+
@Retention(RetentionPolicy.RUNTIME)
12+
@PreAuthorize("hasRole('{value}')")
13+
public @interface HasRole {
14+
String value();
15+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package com.capgemini.training.appointmentbooking.security.config;
2+
3+
import static org.springframework.security.config.Customizer.withDefaults;
4+
5+
import org.springframework.context.annotation.Bean;
6+
import org.springframework.context.annotation.Configuration;
7+
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
8+
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
9+
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
10+
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
11+
import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults;
12+
import org.springframework.security.core.userdetails.User;
13+
import org.springframework.security.core.userdetails.UserDetails;
14+
import org.springframework.security.core.userdetails.UserDetailsService;
15+
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
16+
import org.springframework.security.web.SecurityFilterChain;
17+
18+
import com.capgemini.training.appointmentbooking.security.AccessControl;
19+
20+
@Configuration
21+
@EnableMethodSecurity
22+
public class SecurityConfiguration {
23+
24+
private static final String[] UNAUTHORIZED_ENDPOINTS = {"/", "/appointments", "/treatments", "/auth/me"};
25+
26+
@Bean
27+
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
28+
http.authorizeHttpRequests(
29+
auth -> auth.requestMatchers(UNAUTHORIZED_ENDPOINTS).permitAll().anyRequest().authenticated())
30+
.formLogin(withDefaults()).httpBasic(withDefaults()).csrf(AbstractHttpConfigurer::disable)
31+
.headers(customizer -> customizer.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin));
32+
33+
return http.build();
34+
}
35+
36+
@Bean
37+
public UserDetailsService userDetailsService() {
38+
UserDetails client = createTestUser("client");
39+
UserDetails receptionist = createTestUser("receptionist", AccessControl.ROLE_RECEPTIONIST);
40+
UserDetails specialist = createTestUser("specialist", AccessControl.ROLE_SPECIALIST, AccessControl.ROLE_RECEPTIONIST);
41+
UserDetails admin = createTestUser("admin", AccessControl.ROLE_ADMIN, AccessControl.ROLE_SPECIALIST,
42+
AccessControl.ROLE_RECEPTIONIST);
43+
44+
return new InMemoryUserDetailsManager(client, receptionist, specialist, admin);
45+
}
46+
47+
@Bean
48+
static AnnotationTemplateExpressionDefaults templateExpressionDefaults() {
49+
return new AnnotationTemplateExpressionDefaults();
50+
}
51+
52+
@SuppressWarnings("deprecation")
53+
private UserDetails createTestUser(String login, String... roles) {
54+
// As stated in documentation.
55+
// Using this method is not considered safe for production, but is acceptable
56+
// for demos and getting started.
57+
return User.withDefaultPasswordEncoder().username(login).password("password").roles(roles).build();
58+
}
59+
}

0 commit comments

Comments
 (0)