diff --git a/src/main/java/com/example/solidconnection/application/controller/ApplicationController.java b/src/main/java/com/example/solidconnection/application/controller/ApplicationController.java index 801cbf10c..36c7d6af2 100644 --- a/src/main/java/com/example/solidconnection/application/controller/ApplicationController.java +++ b/src/main/java/com/example/solidconnection/application/controller/ApplicationController.java @@ -6,6 +6,7 @@ import com.example.solidconnection.application.service.ApplicationQueryService; import com.example.solidconnection.application.service.ApplicationSubmissionService; import com.example.solidconnection.custom.resolver.AuthorizedUser; +import com.example.solidconnection.custom.security.annotation.RequireAdminAccess; import com.example.solidconnection.siteuser.domain.SiteUser; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -38,6 +39,7 @@ public ResponseEntity apply( .body(applicationSubmissionResponse); } + @RequireAdminAccess @GetMapping public ResponseEntity getApplicants( @AuthorizedUser SiteUser siteUser, diff --git a/src/main/java/com/example/solidconnection/custom/security/annotation/RequireAdminAccess.java b/src/main/java/com/example/solidconnection/custom/security/annotation/RequireAdminAccess.java new file mode 100644 index 000000000..559664e25 --- /dev/null +++ b/src/main/java/com/example/solidconnection/custom/security/annotation/RequireAdminAccess.java @@ -0,0 +1,11 @@ +package com.example.solidconnection.custom.security.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface RequireAdminAccess { +} diff --git a/src/main/java/com/example/solidconnection/custom/security/aspect/AdminAuthorizationAspect.java b/src/main/java/com/example/solidconnection/custom/security/aspect/AdminAuthorizationAspect.java new file mode 100644 index 000000000..20e8c27c8 --- /dev/null +++ b/src/main/java/com/example/solidconnection/custom/security/aspect/AdminAuthorizationAspect.java @@ -0,0 +1,35 @@ +package com.example.solidconnection.custom.security.aspect; + +import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.custom.security.annotation.RequireAdminAccess; +import com.example.solidconnection.siteuser.domain.SiteUser; +import lombok.RequiredArgsConstructor; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.stereotype.Component; + +import static com.example.solidconnection.custom.exception.ErrorCode.ACCESS_DENIED; +import static com.example.solidconnection.type.Role.ADMIN; + +@Aspect +@Component +@RequiredArgsConstructor +public class AdminAuthorizationAspect { + + @Around("@annotation(requireAdminAccess)") + public Object checkAdminAccess(ProceedingJoinPoint joinPoint, + RequireAdminAccess requireAdminAccess) throws Throwable { + SiteUser siteUser = null; + for (Object arg : joinPoint.getArgs()) { + if (arg instanceof SiteUser) { + siteUser = (SiteUser) arg; + break; + } + } + if (siteUser == null || !ADMIN.equals(siteUser.getRole())) { + throw new CustomException(ACCESS_DENIED); + } + return joinPoint.proceed(); + } +} diff --git a/src/test/java/com/example/solidconnection/custom/security/aspect/AdminAuthorizationAspectTest.java b/src/test/java/com/example/solidconnection/custom/security/aspect/AdminAuthorizationAspectTest.java new file mode 100644 index 000000000..2996eb5b6 --- /dev/null +++ b/src/test/java/com/example/solidconnection/custom/security/aspect/AdminAuthorizationAspectTest.java @@ -0,0 +1,99 @@ +package com.example.solidconnection.custom.security.aspect; + +import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.custom.security.annotation.RequireAdminAccess; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.support.TestContainerSpringBootTest; +import com.example.solidconnection.type.Gender; +import com.example.solidconnection.type.PreparationStatus; +import com.example.solidconnection.type.Role; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.stereotype.Component; + +import static com.example.solidconnection.custom.exception.ErrorCode.ACCESS_DENIED; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; + +@TestContainerSpringBootTest +@DisplayName("어드민 권한 검사 Aspect 테스트") +class AdminAuthorizationAspectTest { + + @Autowired + private TestService testService; + + @Test + void 어드민_사용자는_어드민_전용_메소드에_접근할_수_있다() { + // given + SiteUser adminUser = createSiteUser(Role.ADMIN); + + // when + boolean response = testService.adminOnlyMethod(adminUser); + + // then + assertThat(response).isTrue(); + } + + @Test + void 일반_사용자가_어드민_전용_메소드에_접근하면_예외_응답을_반환한다() { + // given + SiteUser mentorUser = createSiteUser(Role.MENTOR); + + // when & then + assertThatCode(() -> testService.adminOnlyMethod(mentorUser)) + .isInstanceOf(CustomException.class) + .hasMessage(ACCESS_DENIED.getMessage()); + } + + @Test + void 어드민_어노테이션이_없는_메소드는_모두_접근_가능하다() { + // given + SiteUser menteeUser = createSiteUser(Role.MENTEE); + SiteUser adminUser = createSiteUser(Role.ADMIN); + + // when + boolean menteeResponse = testService.publicMethod(menteeUser); + boolean adminResponse = testService.publicMethod(adminUser); + + // then + assertThat(menteeResponse).isTrue(); + assertThat(adminResponse).isTrue(); + } + + private SiteUser createSiteUser(Role role) { + return new SiteUser( + "test@example.com", + "nickname", + "profileImageUrl", + "1999-01-01", + PreparationStatus.CONSIDERING, + role, + Gender.MALE + ); + } + + @TestConfiguration + static class TestConfig { + + @Bean + public TestService testService() { + return new TestService(); + } + } + + @Component + static class TestService { + + @RequireAdminAccess + public boolean adminOnlyMethod(SiteUser siteUser) { + return true; + } + + public boolean publicMethod(SiteUser siteUser) { + return true; + } + } +}