PORTFOLIO/TECHNICAL DECISION-MAKING

[ACE Hand Wash - 기술적 의사 결정]코드 정리 대작전: 권한 확인 로직, 어디까지 분리해봤니?

jki09871 2025. 1. 8. 00:20

배경

현재 프로젝트에서는 UserVO를 세션에서 가져와 권한을 확인하는 로직이 컨트롤러 메서드마다 반복되고 있습니다. 이러한 중복은 작동에는 문제가 없지만, 유지보수성과 코드의 가독성을 저하시킬 수 있습니다. 특히, 반복된 로직을 수정해야 하는 경우 모든 메서드를 업데이트해야 하며, 이는 일관성 유지에 어려움을 초래할 수 있습니다.

따라서, 권한 확인 로직의 중복을 제거하고, 재사용성과 유지보수성을 높이는 방향으로 개선을 진행하려 합니다.


개선 방향

1. 메서드 추출 방식

권한 확인 로직을 별도 메서드로 분리하여 각 컨트롤러 메서드에서 이를 호출하도록 단순화할 수 있습니다.

구현

private boolean isAdmin(HttpServletRequest request) {
    UserVO userVO = (UserVO) request.getSession().getAttribute("login");
    return userVO != null && userVO.getRole() == UserRole.ADMIN;
}

@GetMapping("/write.do")
public String notificationWrite(HttpServletRequest request) {
    if (!isAdmin(request)) {
        return authorityCheck(request);
    }
    return "/notification/write";
}

장점

  • 간단하고 빠르게 적용 가능
  • 코드 중복 제거로 가독성 향상

단점

  • 여전히 컨트롤러 내에 권한 확인 로직이 포함되어 있음
  • 로직이 분산될 가능성 존재

2. AOP(Aspect-Oriented Programming) 도입

Spring AOP를 활용하여 권한 확인 로직을 별도로 분리함으로써, 컨트롤러 코드에서 완전히 제거할 수 있습니다.

구현

1.권한 확인 어노테이션 생성

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AdminOnly {}

 

2. Aspect 정의

@Aspect
@Component
public class AuthorizationAspect {

    @Around("@annotation(com.example.annotation.AdminOnly)")
    public Object checkAdminAccess(ProceedingJoinPoint joinPoint) throws Throwable {
        HttpServletRequest request =
            ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        UserVO userVO = (UserVO) request.getSession().getAttribute("login");

        if (userVO == null || userVO.getRole() != UserRole.ADMIN) {
            request.setAttribute("msg", "권한이 없습니다.");
            request.setAttribute("returnUrl", "/api/v1/home.do");
            request.setAttribute("method", "get");
            return "forward:/api/v1/notifications/msg.do";
        }

        return joinPoint.proceed();
    }
}

 

3. 컨트롤러에서 어노테이션 적용

@AdminOnly
@GetMapping("/write.do")
public String notificationWrite() {
    return "/notification/write";
}

장점

  • 권한 확인 로직이 완전히 분리되어 컨트롤러 코드가 간결해짐
  • 재사용성이 높아지고, 로직 관리가 중앙화됨

단점

  • 초기 설정 및 학습 곡선이 존재
  • 어노테이션 기반 로직에 익숙하지 않은 팀원에게 설명 필요

3. Interceptor 사용

Spring의 HandlerInterceptor를 활용하여 전역적으로 권한 확인 로직을 처리할 수 있습니다.

구현

1. Interceptor 구현

public class AuthorizationInterceptor extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 세션에서 UserVO 가져오기
        UserVO userVO = (UserVO) request.getSession().getAttribute("login");

        // 권한 확인: ADMIN이 아니면 메시지와 함께 msg.do로 포워딩
        if (userVO == null || userVO.getRole() != UserRole.ADMIN) {
            request.setAttribute("msg", "권한이 없습니다.");
            request.setAttribute("returnUrl", "/api/v1/home.do");
            request.setAttribute("method", "get");
            request.getRequestDispatcher("/api/v1/notifications/msg.do").forward(request, response);
            return false; // 요청을 처리하지 않고 종료
        }

        return true; // 요청 처리 계속 진행
    }
}

 

2. Interceptor 등록

Spring 설정 파일(spring-servlet.xml 등)에서 인터셉터를 등록합니다.
<mvc:interceptors> 태그를 사용하여 특정 경로에 대해 인터셉터를 적용합니다

<mvc:interceptors>
    <mvc:interceptor>
        <!-- 인터셉터가 적용될 경로 설정 -->
        <mvc:mapping path="/api/v1/notifications/modified.do" />
        <mvc:mapping path="/api/v1/notifications/delete.do" />
        <mvc:mapping path="/api/v1/notifications/write.do" />

        <!-- 인터셉터 클래스 등록 -->
        <bean class="com.interceptor.AuthorizationInterceptor" />
    </mvc:interceptor>
</mvc:interceptors>

 

3. 인터셉터 제외 경로 설정

공지사항 목록 조회(/list.do)와 같은 공개 접근 경로는 인터셉터에서 제외할 수 있습니다.

<mvc:interceptor>
    <!-- 인터셉터 적용 경로 -->
    <mvc:mapping path="/api/v1/notifications/**" />
    <!-- 제외할 경로 -->
    <mvc:exclude-mapping path="/api/v1/notifications/list.do" />
    
    <bean class="com.carwash.interceptor.AuthorizationInterceptor" />
</mvc:interceptor>

장점

  • 권한 확인 로직을 전역적으로 처리 가능
  • 컨트롤러 코드에서 로직이 완전히 제거됨

단점

  • Interceptor 설정이 복잡해질 수 있음
  • 특정 경로에 대해 권한 검사가 필요 없는 경우 예외 처리 관리 필요

결론

위 개선 방안 중 선택은 프로젝트의 규모와 요구사항에 따라 달라질 수 있습니다.

  1. 메서드 추출은 간단하고 빠르게 적용 가능한 방법으로, 작은 프로젝트에 적합합니다.
  2. AOP는 권한 확인 로직을 컨트롤러에서 완전히 분리하여, 재사용성과 유지보수성을 극대화할 수 있습니다.
  3. Interceptor는 전역적인 권한 확인이 필요한 경우 가장 적합하며, 설정이 한 번 이루어지면 관리가 용이합니다.

현재 프로젝트는 권한 확인 로직이 다양한 경로에서 반복적으로 사용되므로, AOP 또는 Interceptor 방식을 도입하는 것을 권장합니다. 이를 통해 코드의 일관성을 유지하고 유지보수성을 높일 수 있습니다.