Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -38,6 +39,7 @@ public ResponseEntity<ApplicationSubmissionResponse> apply(
.body(applicationSubmissionResponse);
}

@RequireAdminAccess
@GetMapping
public ResponseEntity<ApplicationsResponse> getApplicants(
@AuthorizedUser SiteUser siteUser,
Expand Down
Original file line number Diff line number Diff line change
@@ -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 {
}
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
}