Skip to content

Commit 03ca87e

Browse files
authored
Merge pull request #3 from ftn-projects/feature/integrate-key-db
Add database setup, integrate key wrappinig, repositories, models and global auth
2 parents eb9d80d + 7cd0b24 commit 03ca87e

35 files changed

Lines changed: 804 additions & 170 deletions

MiniKms/pom.xml

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,18 +34,12 @@
3434
<groupId>org.springframework.boot</groupId>
3535
<artifactId>spring-boot-starter-security</artifactId>
3636
</dependency>
37-
3837
<dependency>
3938
<groupId>org.springframework.boot</groupId>
4039
<artifactId>spring-boot-devtools</artifactId>
4140
<scope>runtime</scope>
4241
<optional>true</optional>
4342
</dependency>
44-
<dependency>
45-
<groupId>org.projectlombok</groupId>
46-
<artifactId>lombok</artifactId>
47-
<optional>true</optional>
48-
</dependency>
4943
<dependency>
5044
<groupId>org.springframework.boot</groupId>
5145
<artifactId>spring-boot-starter-test</artifactId>
@@ -55,11 +49,48 @@
5549
<groupId>org.springframework.boot</groupId>
5650
<artifactId>spring-boot-starter-data-jpa</artifactId>
5751
</dependency>
52+
<dependency>
53+
<groupId>org.springframework.boot</groupId>
54+
<artifactId>spring-boot-starter-web</artifactId>
55+
</dependency>
5856
<dependency>
5957
<groupId>org.springframework.security</groupId>
6058
<artifactId>spring-security-test</artifactId>
6159
<scope>test</scope>
6260
</dependency>
61+
<dependency>
62+
<groupId>io.jsonwebtoken</groupId>
63+
<artifactId>jjwt-api</artifactId>
64+
<version>0.13.0</version>
65+
</dependency>
66+
<dependency>
67+
<groupId>io.jsonwebtoken</groupId>
68+
<artifactId>jjwt-impl</artifactId>
69+
<version>0.13.0</version>
70+
<scope>runtime</scope>
71+
</dependency>
72+
<dependency>
73+
<groupId>io.jsonwebtoken</groupId>
74+
<artifactId>jjwt-jackson</artifactId>
75+
<version>0.13.0</version>
76+
<scope>runtime</scope>
77+
</dependency>
78+
<dependency>
79+
<groupId>org.postgresql</groupId>
80+
<artifactId>postgresql</artifactId>
81+
<version>42.7.8</version>
82+
</dependency>
83+
<dependency>
84+
<groupId>org.projectlombok</groupId>
85+
<artifactId>lombok</artifactId>
86+
<version>1.18.42</version>
87+
<scope>provided</scope>
88+
</dependency>
89+
<dependency>
90+
<groupId>org.mapstruct</groupId>
91+
<artifactId>mapstruct</artifactId>
92+
<version>1.6.0</version>
93+
</dependency>
6394
</dependencies>
6495

6596
<build>
@@ -72,6 +103,12 @@
72103
<path>
73104
<groupId>org.projectlombok</groupId>
74105
<artifactId>lombok</artifactId>
106+
<version>1.18.40</version>
107+
</path>
108+
<path>
109+
<groupId>org.mapstruct</groupId>
110+
<artifactId>mapstruct-processor</artifactId>
111+
<version>1.6.0</version>
75112
</path>
76113
</annotationProcessorPaths>
77114
</configuration>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package ftn.security.minikms.config;
2+
3+
import com.fasterxml.jackson.databind.MapperFeature;
4+
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
5+
import org.springframework.context.annotation.Bean;
6+
import org.springframework.context.annotation.Configuration;
7+
8+
@Configuration
9+
public class JsonDeserializationConfig {
10+
@Bean
11+
public Jackson2ObjectMapperBuilderCustomizer caseInsensitiveEnums() {
12+
return builder -> builder
13+
.featuresToEnable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS);
14+
}
15+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package ftn.security.minikms.config;
2+
3+
import ftn.security.minikms.service.auth.JwtAuthenticationFilter;
4+
import ftn.security.minikms.service.auth.MiniKmsUserDetailsService;
5+
import org.springframework.beans.factory.annotation.Autowired;
6+
import org.springframework.beans.factory.annotation.Value;
7+
import org.springframework.context.annotation.Bean;
8+
import org.springframework.context.annotation.Configuration;
9+
import org.springframework.http.HttpMethod;
10+
import org.springframework.security.authentication.AuthenticationManager;
11+
import org.springframework.security.config.Customizer;
12+
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
13+
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
14+
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
15+
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
16+
import org.springframework.security.config.http.SessionCreationPolicy;
17+
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
18+
import org.springframework.security.crypto.password.PasswordEncoder;
19+
import org.springframework.security.web.SecurityFilterChain;
20+
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
21+
import org.springframework.web.cors.CorsConfiguration;
22+
import org.springframework.web.cors.CorsConfigurationSource;
23+
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
24+
25+
import java.util.List;
26+
27+
@Configuration
28+
@EnableWebSecurity
29+
public class SecurityConfig {
30+
private final String jwtSecret;
31+
private final MiniKmsUserDetailsService service;
32+
33+
@Autowired
34+
public SecurityConfig(
35+
@Value("${jwt.secret}") String jwtSecret,
36+
MiniKmsUserDetailsService service) {
37+
this.jwtSecret = jwtSecret;
38+
this.service = service;
39+
}
40+
41+
@Bean
42+
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
43+
return http
44+
// Stateless, token-only API: no cookies => CSRF not applicable
45+
.csrf(AbstractHttpConfigurer::disable)
46+
.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
47+
.cors(Customizer.withDefaults())
48+
49+
.formLogin(AbstractHttpConfigurer::disable)
50+
.httpBasic(AbstractHttpConfigurer::disable)
51+
.logout(AbstractHttpConfigurer::disable)
52+
53+
.authorizeHttpRequests(auth -> auth
54+
.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
55+
.requestMatchers("/api/v1/auth/**").permitAll()
56+
.requestMatchers(HttpMethod.GET, "/api/v1/keys/**").authenticated() // Allow all roles to GET
57+
.requestMatchers("/api/v1/keys/**").hasRole("MANAGER")
58+
.anyRequest().authenticated()
59+
)
60+
61+
.userDetailsService(service)
62+
.addFilterBefore(new JwtAuthenticationFilter(
63+
jwtSecret,
64+
service
65+
), UsernamePasswordAuthenticationFilter.class)
66+
.build();
67+
}
68+
69+
@Bean
70+
public PasswordEncoder passwordEncoder() {
71+
return new BCryptPasswordEncoder();
72+
}
73+
74+
@Bean
75+
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
76+
return configuration.getAuthenticationManager();
77+
}
78+
79+
@Bean
80+
public CorsConfigurationSource corsConfigurationSource() {
81+
var config = new CorsConfiguration();
82+
config.setAllowedOrigins(List.of("http://localhost:4200", "http://localhost"));
83+
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
84+
config.setAllowedHeaders(List.of("Authorization", "Content-Type"));
85+
config.setAllowCredentials(false);
86+
87+
var source = new UrlBasedCorsConfigurationSource();
88+
source.registerCorsConfiguration("/**", config);
89+
return source;
90+
}
91+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package ftn.security.minikms.controller;
2+
3+
import ftn.security.minikms.dto.AuthDTO;
4+
import ftn.security.minikms.dto.TokenDTO;
5+
import ftn.security.minikms.service.auth.JwtService;
6+
import org.springframework.beans.factory.annotation.Autowired;
7+
import org.springframework.http.ResponseEntity;
8+
import org.springframework.security.authentication.AuthenticationManager;
9+
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
10+
import org.springframework.security.core.AuthenticationException;
11+
import org.springframework.web.bind.annotation.PostMapping;
12+
import org.springframework.web.bind.annotation.RequestBody;
13+
import org.springframework.web.bind.annotation.RequestMapping;
14+
import org.springframework.web.bind.annotation.RestController;
15+
16+
@RestController
17+
@RequestMapping("/api/v1/auth")
18+
public class AuthController {
19+
private final AuthenticationManager authManager;
20+
private final JwtService jwtService;
21+
22+
@Autowired
23+
public AuthController(AuthenticationManager authManager, JwtService jwtService) {
24+
this.authManager = authManager;
25+
this.jwtService = jwtService;
26+
}
27+
28+
@PostMapping
29+
public ResponseEntity<?> auth(@RequestBody AuthDTO dto) {
30+
try {
31+
var auth = authManager.authenticate(
32+
new UsernamePasswordAuthenticationToken(dto.getUsername(), dto.getPassword())
33+
);
34+
var token = jwtService.generateToken(auth.getName());
35+
return ResponseEntity.ok(new TokenDTO(token));
36+
} catch (AuthenticationException e) {
37+
return ResponseEntity.status(401).body("Invalid credentials");
38+
}
39+
}
40+
}
Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,64 @@
11
package ftn.security.minikms.controller;
22

33
import ftn.security.minikms.dto.KeyDTO;
4+
import ftn.security.minikms.dto.KeyMapper;
45
import ftn.security.minikms.service.KeyService;
6+
import org.mapstruct.factory.Mappers;
57
import org.springframework.beans.factory.annotation.Autowired;
68
import org.springframework.http.HttpStatus;
79
import org.springframework.http.ResponseEntity;
810
import org.springframework.web.bind.annotation.*;
911

10-
import java.security.NoSuchAlgorithmException;
12+
import java.security.GeneralSecurityException;
13+
import java.security.InvalidParameterException;
14+
import java.security.Principal;
15+
import java.util.UUID;
1116

1217
@RestController
1318
@RequestMapping(value = "/api/v1/keys")
1419
public class KeyManagementController {
20+
private final KeyService keyService;
21+
private final KeyMapper mapper;
22+
1523
@Autowired
16-
private KeyService keyService;
24+
public KeyManagementController(KeyService keyService) {
25+
this.keyService = keyService;
26+
this.mapper = Mappers.getMapper(KeyMapper.class);
27+
}
1728

1829
@PostMapping("/create")
19-
public ResponseEntity<KeyDTO> createKey(@RequestBody KeyDTO dto) throws NoSuchAlgorithmException {
20-
String id = keyService.createKey(dto.getKeyType());
21-
dto.setId(id);
22-
return new ResponseEntity<>(dto, HttpStatus.CREATED);
30+
public ResponseEntity<?> createKey(@RequestBody KeyDTO dto, Principal principal) throws GeneralSecurityException {
31+
var username = principal.getName();
32+
33+
try {
34+
var created = keyService.createKey(dto.getAlias(), dto.getKeyType(), dto.getAllowedOperations(), username);
35+
return ResponseEntity.status(HttpStatus.CREATED).body(mapper.toDto(created));
36+
} catch (InvalidParameterException e) {
37+
return ResponseEntity.badRequest().body(e.getMessage());
38+
}
2339
}
40+
2441
@PostMapping("/rotate")
25-
public ResponseEntity<KeyDTO> rotateKey(@RequestBody KeyDTO dto){
26-
keyService.rotateKey(dto.getKeyType(), dto.getId());
27-
return new ResponseEntity<>(dto, HttpStatus.CREATED);
28-
}
29-
@PutMapping("/delete/{id}")
30-
public ResponseEntity<String> deleteKey(@PathVariable String id){
31-
keyService.deleteKey(id);
32-
return new ResponseEntity<>(id, HttpStatus.OK);
42+
public ResponseEntity<?> rotateKey(@RequestBody KeyDTO dto, Principal principal) throws GeneralSecurityException {
43+
var username = principal.getName();
44+
45+
try {
46+
var created = keyService.rotateKey(dto.getId(), username);
47+
return ResponseEntity.status(HttpStatus.CREATED).body(mapper.toDto(created));
48+
} catch (InvalidParameterException e) {
49+
return ResponseEntity.badRequest().body(e.getMessage());
50+
}
3351
}
3452

53+
@DeleteMapping("/{id}")
54+
public ResponseEntity<?> deleteKey(@PathVariable UUID id, Principal principal) {
55+
var username = principal.getName();
56+
57+
try {
58+
keyService.deleteKey(id, username);
59+
return ResponseEntity.noContent().build();
60+
} catch (InvalidParameterException e) {
61+
return ResponseEntity.badRequest().body(e.getMessage());
62+
}
63+
}
3564
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package ftn.security.minikms.dto;
2+
3+
import lombok.Data;
4+
5+
@Data
6+
public class AuthDTO {
7+
private String username;
8+
private String password;
9+
}
Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
package ftn.security.minikms.dto;
22

3-
import lombok.AllArgsConstructor;
4-
import lombok.Getter;
5-
import lombok.NoArgsConstructor;
6-
import lombok.Setter;
3+
import ftn.security.minikms.enumeration.KeyOperation;
4+
import ftn.security.minikms.enumeration.KeyType;
5+
import lombok.*;
76

8-
@Getter
9-
@Setter
7+
import java.util.List;
8+
import java.util.UUID;
9+
10+
@Data
1011
@NoArgsConstructor
11-
@AllArgsConstructor
1212
public class KeyDTO {
13-
private String id;
13+
private UUID id;
1414
private String alias;
15-
private String keyType;
15+
private KeyType keyType;
16+
private List<KeyOperation> allowedOperations;
1617
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package ftn.security.minikms.dto;
2+
3+
import ftn.security.minikms.entity.KeyMetadata;
4+
import org.mapstruct.Mapper;
5+
6+
@Mapper
7+
public interface KeyMapper {
8+
KeyMetadataDTO toDto(KeyMetadata key);
9+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package ftn.security.minikms.dto;
2+
3+
import ftn.security.minikms.enumeration.KeyOperation;
4+
import ftn.security.minikms.enumeration.KeyType;
5+
import lombok.*;
6+
7+
import java.time.Instant;
8+
import java.util.List;
9+
import java.util.UUID;
10+
11+
@Data
12+
@NoArgsConstructor
13+
public class KeyMetadataDTO {
14+
private UUID id;
15+
private String alias;
16+
private Integer primaryVersion;
17+
private KeyType keyType;
18+
private List<KeyOperation> allowedOperations;
19+
private Instant createdAt;
20+
private Instant rotatedAt;
21+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package ftn.security.minikms.dto;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Data;
5+
6+
@Data
7+
@AllArgsConstructor
8+
public class TokenDTO {
9+
private String token;
10+
}

0 commit comments

Comments
 (0)