From 85b8c749513675259b681faf54df16cf051a6f66 Mon Sep 17 00:00:00 2001 From: sdikyarts Date: Sun, 26 May 2024 11:03:42 +0700 Subject: [PATCH] Modify JWT --- .github/workflows/ci.yml | 3 +- build.gradle.kts | 16 +++---- .../config/JWTAuthFilter.java | 1 + .../config/SecurityConfig.java | 8 +++- .../controller/AdminController.java | 28 +++++++++--- .../resources/application-prod.properties | 2 +- .../controller/AdminControllerTest.java | 44 +++++++++++-------- 7 files changed, 67 insertions(+), 35 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c7b3736..7ef8fd3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,6 +34,7 @@ jobs: sed -i "s|JDBC_DATABASE_USERNAME=.*|JDBC_DATABASE_USERNAME=${{ secrets.JDBC_DATABASE_USERNAME }}|g" src/main/resources/application.properties sed -i "s|JDBC_DATABASE_PASSWORD=.*|JDBC_DATABASE_PASSWORD=${{ secrets.JDBC_DATABASE_PASSWORD }}|g" src/main/resources/application.properties sed -i "s|PRODUCTION=.*|PRODUCTION=${{ secrets.PRODUCTION }}|g" src/main/resources/application.properties + sed -i 's|${JWT_SECRET}|'"${{ secrets.JWT_SECRET }}"'|g' src/main/resources/application-prod.properties - name: Make gradlew executable run: chmod +x ./gradlew - name: Build with Gradle @@ -107,7 +108,7 @@ jobs: run: export DOCKER_BUILDKIT=1 - name: Build Docker Image run: | - docker build --build-arg PRODUCTION=$PRODUCTION --build-arg JDBC_DATABASE_PASSWORD=$JDBC_DATABASE_PASSWORD --build-arg JDBC_DATABASE_URL=$JDBC_DATABASE_URL --build-arg JDBC_DATABASE_USERNAME=$JDBC_DATABASE_USERNAME -t ${{ secrets.REGISTRY_USER }}/${{ secrets.IMAGE_NAME }}:${{ secrets.IMAGE_TAG }} . + docker build --build-arg PRODUCTION=$PRODUCTION --build-arg JDBC_DATABASE_PASSWORD=$JDBC_DATABASE_PASSWORD --build-arg JDBC_DATABASE_URL=$JDBC_DATABASE_URL --build-arg JDBC_DATABASE_USERNAME=$JDBC_DATABASE_USERNAME --build-arg JWT_SECRET=${{ secrets.JWT_SECRET }} -t ${{ secrets.REGISTRY_USER }}/${{ secrets.IMAGE_NAME }}:${{ secrets.IMAGE_TAG }} . docker push ${{ secrets.REGISTRY_USER }}/${{ secrets.IMAGE_NAME }}:${{ secrets.IMAGE_TAG }} deploy: diff --git a/build.gradle.kts b/build.gradle.kts index 5bd8d78..3373987 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -52,6 +52,14 @@ dependencies { runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.12.5") } +sonar { + properties { + property("sonar.projectKey", "ADPRO-C11_snackscription-subscription-admin") + property("sonar.organization", "adpro-c11") + property("sonar.host.url", "https://sonarcloud.io") + } +} + tasks.register("unitTest") { description = "Runs unit tests." group = "verification" @@ -70,14 +78,6 @@ tasks.register("functionalTest") { } } -sonar { - properties { - property("sonar.projectKey", "ADPRO-C11_snackscription-subscription-admin") - property("sonar.organization", "adpro-c11") - property("sonar.host.url", "https://sonarcloud.io") - } -} - tasks.withType().configureEach() { useJUnitPlatform() } diff --git a/src/main/java/snackscription/subscriptionadmin/config/JWTAuthFilter.java b/src/main/java/snackscription/subscriptionadmin/config/JWTAuthFilter.java index 87e1be0..6c00693 100644 --- a/src/main/java/snackscription/subscriptionadmin/config/JWTAuthFilter.java +++ b/src/main/java/snackscription/subscriptionadmin/config/JWTAuthFilter.java @@ -4,6 +4,7 @@ import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; diff --git a/src/main/java/snackscription/subscriptionadmin/config/SecurityConfig.java b/src/main/java/snackscription/subscriptionadmin/config/SecurityConfig.java index c4675d0..d021585 100644 --- a/src/main/java/snackscription/subscriptionadmin/config/SecurityConfig.java +++ b/src/main/java/snackscription/subscriptionadmin/config/SecurityConfig.java @@ -1,13 +1,18 @@ package snackscription.subscriptionadmin.config; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.Customizer; 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.config.http.SessionCreationPolicy; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import snackscription.subscriptionadmin.utils.JWTUtils; +@Configuration +@EnableWebSecurity public class SecurityConfig { private final JWTUtils jwtUtils; @@ -18,8 +23,9 @@ public SecurityConfig(JWTUtils jwtUtils) { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception { httpSecurity.csrf(AbstractHttpConfigurer::disable) + .cors(Customizer.withDefaults()) .authorizeHttpRequests(authorizeRequests -> - authorizeRequests.requestMatchers("/adminSubscription/**", "/public/**").permitAll() + authorizeRequests.requestMatchers("/admin/**", "/public/**").permitAll() .anyRequest().authenticated()) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .addFilterBefore(new JWTAuthFilter(jwtUtils), UsernamePasswordAuthenticationFilter.class); diff --git a/src/main/java/snackscription/subscriptionadmin/controller/AdminController.java b/src/main/java/snackscription/subscriptionadmin/controller/AdminController.java index 52b8aa3..f17dff7 100644 --- a/src/main/java/snackscription/subscriptionadmin/controller/AdminController.java +++ b/src/main/java/snackscription/subscriptionadmin/controller/AdminController.java @@ -5,6 +5,7 @@ import snackscription.subscriptionadmin.dto.AdminDTO; import snackscription.subscriptionadmin.model.AdminSubscription; import snackscription.subscriptionadmin.service.AdminService; +import snackscription.subscriptionadmin.utils.JWTUtils; import java.util.List; import java.util.Optional; @@ -16,9 +17,18 @@ @CrossOrigin(origins = "http://localhost:3000") public class AdminController { private final AdminService adminService; + private final JWTUtils jwtUtils; - public AdminController(AdminService adminService) { + public AdminController(AdminService adminService, JWTUtils jwtUtils) { this.adminService = adminService; + this.jwtUtils = jwtUtils; + } + + private void validateToken(String token) throws IllegalAccessException { + String jwt = token.replace("Bearer ", ""); + if (!jwtUtils.isTokenValid(jwt)) { + throw new IllegalAccessException("You have no permission."); + } } @GetMapping("") @@ -27,19 +37,23 @@ public ResponseEntity home() { } @PostMapping("/create") - public CompletableFuture> create(@RequestBody AdminDTO adminDTO) { + public CompletableFuture> create(@RequestHeader(value="Authorization") String token, @RequestBody AdminDTO adminDTO) + throws IllegalAccessException { + validateToken(token); return adminService.create(adminDTO).thenApply(ResponseEntity::ok) .exceptionally(ex -> ResponseEntity.badRequest().build()); } @GetMapping("/list") - public CompletableFuture>> findAll() { + public CompletableFuture>> findAll(@RequestHeader(value="Authorization") String token) throws IllegalAccessException { + validateToken(token); return adminService.findAll().thenApply(ResponseEntity::ok); } @GetMapping("/{subscriptionId}") - public CompletableFuture> findById(@PathVariable String subscriptionId) { + public CompletableFuture> findById(@RequestHeader(value="Authorization") String token, @PathVariable String subscriptionId) throws IllegalAccessException { + validateToken(token); try { UUID.fromString(subscriptionId); } catch (IllegalArgumentException e) { @@ -50,7 +64,8 @@ public CompletableFuture> findById(@PathVariable String } @PutMapping("/update") - public CompletableFuture> update(@RequestBody AdminDTO adminDTO) { + public CompletableFuture> update(@RequestHeader(value="Authorization") String token, @RequestBody AdminDTO adminDTO) throws IllegalAccessException { + validateToken(token); if (adminDTO.getSubscriptionId() == null) { return CompletableFuture.completedFuture(ResponseEntity.badRequest().build()); } @@ -65,7 +80,8 @@ public CompletableFuture> update(@RequestBody } @DeleteMapping("/{subscriptionId}") - public CompletableFuture> delete(@PathVariable String subscriptionId) { + public CompletableFuture> delete(@RequestHeader(value="Authorization") String token, @PathVariable String subscriptionId) throws IllegalAccessException{ + validateToken(token); try { UUID.fromString(subscriptionId); } catch (IllegalArgumentException e) { diff --git a/src/main/resources/application-prod.properties b/src/main/resources/application-prod.properties index 209fd70..2abb0ab 100644 --- a/src/main/resources/application-prod.properties +++ b/src/main/resources/application-prod.properties @@ -4,4 +4,4 @@ spring.datasource.password=YouMakeStrayKidsStay spring.jpa.hibernate.ddl-auto=update spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect spring.jpa.properties.hibernate.format_sql=true - +JWT_SECRET=${JWT_SECRET} \ No newline at end of file diff --git a/src/test/java/snackscription/subscriptionadmin/controller/AdminControllerTest.java b/src/test/java/snackscription/subscriptionadmin/controller/AdminControllerTest.java index a0c2885..8758e76 100644 --- a/src/test/java/snackscription/subscriptionadmin/controller/AdminControllerTest.java +++ b/src/test/java/snackscription/subscriptionadmin/controller/AdminControllerTest.java @@ -8,6 +8,7 @@ import snackscription.subscriptionadmin.dto.AdminDTO; import snackscription.subscriptionadmin.model.AdminSubscription; import snackscription.subscriptionadmin.service.AdminService; +import snackscription.subscriptionadmin.utils.JWTUtils; import java.util.Collections; import java.util.List; @@ -22,11 +23,16 @@ public class AdminControllerTest { @Mock private AdminService adminService; + @Mock + private JWTUtils jwtUtils; + @InjectMocks private AdminController adminController; private AdminDTO adminDTO; private AdminSubscription adminSubscription; + private final String validToken = + "eyJhbGciOiJIUzI1NiJ9.eyJyb2xlIjoiQURNSU4iLCJzdWIiOiJhZG1pbkBnbWFpbC5jb20iLCJpYXQiOjE3MTY0NTUyMzgsImV4cCI6MTcxNjU0MTYzOH0.D5PKsxm3jr7SybMfsBylQqd2lT8S_cuX3jQWGolOD78"; @BeforeEach void setUp(){ @@ -47,6 +53,8 @@ void setUp(){ adminSubscription.setSubscriberId("0325"); adminSubscription.setSubscriptionBoxId("143ily"); adminSubscription.setSubscriptionStatus("PENDING"); + + when(jwtUtils.isTokenValid(validToken)).thenReturn(true); } @Test @@ -57,32 +65,32 @@ void testHome(){ } @Test - void testCreate(){ + void testCreate() throws IllegalAccessException{ when(adminService.create(adminDTO)).thenReturn(CompletableFuture.completedFuture(adminSubscription)); - CompletableFuture> response = adminController.create(adminDTO); + CompletableFuture> response = adminController.create(validToken, adminDTO); assertNotNull(response); assertEquals(ResponseEntity.ok(adminSubscription), response.join()); } @Test - void testFindAll(){ + void testFindAll() throws IllegalAccessException{ List adminDTOList = Collections.singletonList(adminDTO); when(adminService.findAll()).thenReturn(CompletableFuture.completedFuture(adminDTOList)); - CompletableFuture>> response = adminController.findAll(); + CompletableFuture>> response = adminController.findAll(validToken); assertNotNull(response); assertEquals(ResponseEntity.ok(adminDTOList), response.join()); } @Test - void testFindById(){ + void testFindById() throws IllegalAccessException{ String validUUID = "8a56e04b-d0c8-4e43-b2e0-fdf43e304d9e"; adminDTO.setSubscriptionId(validUUID); adminSubscription.setSubscriptionId(validUUID); when(adminService.findById(validUUID)).thenReturn(CompletableFuture.completedFuture(adminDTO)); - CompletableFuture> result = adminController.findById(validUUID); + CompletableFuture> result = adminController.findById(validToken, validUUID); assertNotNull(result); assertTrue(result.isDone()); @@ -90,66 +98,66 @@ void testFindById(){ } @Test - void testUpdate(){ + void testUpdate() throws IllegalAccessException{ when(adminService.update(adminDTO)).thenReturn(CompletableFuture.completedFuture(adminSubscription)); - CompletableFuture> response = adminController.update(adminDTO); + CompletableFuture> response = adminController.update(validToken, adminDTO); assertNotNull(response); assertEquals(ResponseEntity.ok(adminSubscription), response.join()); } @Test - void testDelete(){ + void testDelete() throws IllegalAccessException{ String validUUID = "8a56e04b-d0c8-4e43-b2e0-fdf43e304d9e"; when(adminService.delete(validUUID)).thenReturn(CompletableFuture.completedFuture(null)); - CompletableFuture> response = adminController.delete(validUUID); + CompletableFuture> response = adminController.delete(validToken, validUUID); assertNotNull(response); assertEquals(ResponseEntity.ok("DELETE SUCCESS"), response.join()); } @Test - void testUpdateInvalidSubscriptionId(){ + void testUpdateInvalidSubscriptionId() throws IllegalAccessException{ AdminDTO adminDTO = new AdminDTO(); CompletableFuture> expectedResponse = CompletableFuture.completedFuture(ResponseEntity.badRequest().build()); - CompletableFuture> response = adminController.update(adminDTO); + CompletableFuture> response = adminController.update(validToken, adminDTO); assertTrue(response.isDone()); assertEquals(expectedResponse.join(), response.join()); } @Test - void testUpdateNonexistentSubscription(){ + void testUpdateNonexistentSubscription() throws IllegalAccessException{ AdminDTO adminDTO = new AdminDTO(); adminDTO.setSubscriptionId(UUID.randomUUID().toString()); CompletableFuture> expectedResponse = CompletableFuture.completedFuture(ResponseEntity.notFound().build()); when(adminService.findById(adminDTO.getSubscriptionId())).thenReturn(CompletableFuture.completedFuture(null)); - CompletableFuture> response = adminController.update(adminDTO); + CompletableFuture> response = adminController.update(validToken, adminDTO); assertTrue(response.isDone()); assertEquals(expectedResponse.join(), response.join()); } @Test - void testDeleteInvalidSubscriptionId(){ + void testDeleteInvalidSubscriptionId() throws IllegalAccessException{ CompletableFuture> expectedResponse = CompletableFuture.completedFuture(ResponseEntity.badRequest().build()); - CompletableFuture> response = adminController.delete("invalid-id"); + CompletableFuture> response = adminController.delete(validToken, "invalid-id"); assertTrue(response.isDone()); assertEquals(expectedResponse.join(), response.join()); } @Test - void testDeleteNonexistentSubscription(){ + void testDeleteNonexistentSubscription() throws IllegalAccessException{ String validUUID = UUID.randomUUID().toString(); CompletableFuture> expectedResponse = CompletableFuture.completedFuture(ResponseEntity.notFound().build()); when(adminService.delete(validUUID)).thenThrow(new IllegalArgumentException("Subscription not found")); - CompletableFuture> response = adminController.delete(validUUID); + CompletableFuture> response = adminController.delete(validToken, validUUID); assertTrue(response.isDone()); assertEquals(expectedResponse.join(), response.join());