diff --git a/.env.example b/.env.example
index f4ff381..ec2f963 100644
--- a/.env.example
+++ b/.env.example
@@ -1,3 +1,5 @@
SPRING_DATASOURCE_USERNAME=root
SPRING_DATASOURCE_PASSWORD=changeme
APP_PORT=80
+ADMIN_USER_NAME=admin
+ADMIN_USER_HASHED_PASSWORD=changeme
\ No newline at end of file
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c88c7dc..783dc82 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -66,4 +66,6 @@ jobs:
SPRING_DATASOURCE_USERNAME: root
SPRING_DATASOURCE_PASSWORD: rootpassword
SERVER_PORT: 8080
+ ADMIN_USER_NAME: admin
+ ADMIN_USER_HASHED_PASSWORD: admin
run: ./mvnw test -B
diff --git a/docker-compose.yml b/docker-compose.yml
index 0fb70f1..e5ec01a 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -10,6 +10,8 @@ services:
SPRING_DATASOURCE_URL: jdbc:mysql://db:3306/healthiermo?useSSL=true&useLegacyDatetimeCode=false&serverTimezone=UTC
SPRING_DATASOURCE_USERNAME: ${SPRING_DATASOURCE_USERNAME}
SPRING_DATASOURCE_PASSWORD: ${SPRING_DATASOURCE_PASSWORD}
+ ADMIN_USER_NAME: ${ADMIN_USER_NAME}
+ ADMIN_USER_HASHED_PASSWORD: ${ADMIN_USER_HASHED_PASSWORD}
depends_on:
db:
condition: service_healthy
diff --git a/pom.xml b/pom.xml
index a21bdc9..dd1685a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -53,6 +53,10 @@
mysql-connector-j
runtime
+
+ org.springframework.boot
+ spring-boot-starter-security
+
org.springframework.boot
spring-boot-starter-webmvc-test
diff --git a/src/main/java/org/healthiermo/homepage/config/SecurityConfig.java b/src/main/java/org/healthiermo/homepage/config/SecurityConfig.java
new file mode 100644
index 0000000..1316ebd
--- /dev/null
+++ b/src/main/java/org/healthiermo/homepage/config/SecurityConfig.java
@@ -0,0 +1,57 @@
+package org.healthiermo.homepage.config;
+
+import org.springframework.beans.factory.annotation.Value;
+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.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+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;
+
+@Configuration
+@EnableWebSecurity
+public class SecurityConfig {
+
+ @Value("${spring.security.user.name}")
+ private String adminUsername;
+
+ @Value("${spring.security.user.password}")
+ private String adminPassword ;
+
+ @Bean
+ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+ http
+ .authorizeHttpRequests(auth -> auth
+ .requestMatchers("/", "/index", "/*.png", "/*.js", "/tingle-master/**").permitAll()
+ .anyRequest().authenticated()
+ )
+ .httpBasic(withDefaults())
+ .csrf(AbstractHttpConfigurer::disable);
+
+ return http.build();
+ }
+
+ @Bean
+ public UserDetailsService x() {
+ UserDetails user = User.builder()
+ .username(this.adminUsername)
+ .password(this.adminPassword)
+ .roles("USER")
+ .build();
+
+ return new InMemoryUserDetailsManager(user);
+ }
+
+ @Bean
+ public PasswordEncoder passwordEncoder() {
+ return new BCryptPasswordEncoder();
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml
index 0ee201b..73b4c84 100644
--- a/src/main/resources/application.yaml
+++ b/src/main/resources/application.yaml
@@ -12,6 +12,10 @@ spring:
url: jdbc:mysql://localhost:3306/healthiermo?useSSL=true&useLegacyDatetimeCode=false&serverTimezone=UTC
username: ${SPRING_DATASOURCE_USERNAME}
password: ${SPRING_DATASOURCE_PASSWORD}
+ security:
+ user:
+ name: ${ADMIN_USER_NAME}
+ password: ${ADMIN_USER_HASHED_PASSWORD}
server:
port: 80
tomcat:
diff --git a/src/test/java/org/healthiermo/homepage/FileUploadIntegrationTest.java b/src/test/java/org/healthiermo/homepage/FileUploadIntegrationTest.java
index f6b7575..fea185e 100644
--- a/src/test/java/org/healthiermo/homepage/FileUploadIntegrationTest.java
+++ b/src/test/java/org/healthiermo/homepage/FileUploadIntegrationTest.java
@@ -9,6 +9,7 @@
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.springframework.util.LinkedMultiValueMap;
@@ -32,9 +33,17 @@ class FileUploadIntegrationTest {
@LocalServerPort
private int port;
+ @Value("${spring.security.user.name}")
+ private String username;
+
+ @Value("${spring.security.user.password}")
+ private String password;
+
@DynamicPropertySource
static void overrideProperties(DynamicPropertyRegistry registry) {
registry.add("app.public-data-root", () -> tempDir.toString());
+ registry.add("spring.security.user.name", () -> "testuser");
+ registry.add("spring.security.user.password", () -> "testpass");
}
@Test
@@ -57,6 +66,7 @@ public String getFilename() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
+ headers.setBasicAuth(username, password);
HttpEntity> request = new HttpEntity<>(body, headers);
RestTemplate restTemplate = new RestTemplate();
diff --git a/src/test/java/org/healthiermo/homepage/config/SecurityConfigTest.java b/src/test/java/org/healthiermo/homepage/config/SecurityConfigTest.java
new file mode 100644
index 0000000..cb8fb03
--- /dev/null
+++ b/src/test/java/org/healthiermo/homepage/config/SecurityConfigTest.java
@@ -0,0 +1,71 @@
+package org.healthiermo.homepage.config;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.test.context.DynamicPropertyRegistry;
+import org.springframework.test.context.DynamicPropertySource;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+
+@SpringBootTest
+class SecurityConfigTest {
+
+ @Autowired
+ private SecurityConfig config;
+
+ @Autowired
+ private PasswordEncoder passwordEncoder;
+
+ @Autowired
+ private UserDetailsService userDetailsService;
+
+ @DynamicPropertySource
+ static void overrideProperties(DynamicPropertyRegistry registry) {
+ registry.add("spring.security.user.name", () -> "admin");
+ registry.add("spring.security.user.password", () -> "password");
+ }
+
+ @Test
+ void passwordEncoderShouldNotBeNull() {
+ assertNotNull(passwordEncoder);
+ }
+
+ @Test
+ void passwordEncoderShouldEncodePasswords() {
+ String rawPassword = "password";
+ String encodedPassword = passwordEncoder.encode(rawPassword);
+
+ assertNotNull(encodedPassword);
+ assertNotEquals(rawPassword, encodedPassword);
+ assertTrue(passwordEncoder.matches(rawPassword, encodedPassword));
+ }
+
+ @Test
+ void userDetailsServiceShouldNotBeNull() {
+ assertNotNull(userDetailsService);
+ }
+
+ @Test
+ void userDetailsServiceShouldLoadUserByUsername() {
+ UserDetails user = userDetailsService.loadUserByUsername("admin");
+
+ assertNotNull(user);
+ assertEquals("admin", user.getUsername());
+ assertTrue(user.getAuthorities().stream()
+ .anyMatch(a -> a.getAuthority().equals("ROLE_USER")));
+ }
+
+ @Test
+ void userDetailsServiceShouldEncodePassword() {
+ UserDetails user = userDetailsService.loadUserByUsername("admin");
+
+ assertNotNull(user.getPassword());
+ assertNotEquals("password", user.getPassword());
+ assertTrue(passwordEncoder.matches("password", user.getPassword()));
+ }
+}