배경
현재 프로젝트에서는 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 설정이 복잡해질 수 있음
- 특정 경로에 대해 권한 검사가 필요 없는 경우 예외 처리 관리 필요
결론
위 개선 방안 중 선택은 프로젝트의 규모와 요구사항에 따라 달라질 수 있습니다.
- 메서드 추출은 간단하고 빠르게 적용 가능한 방법으로, 작은 프로젝트에 적합합니다.
- AOP는 권한 확인 로직을 컨트롤러에서 완전히 분리하여, 재사용성과 유지보수성을 극대화할 수 있습니다.
- Interceptor는 전역적인 권한 확인이 필요한 경우 가장 적합하며, 설정이 한 번 이루어지면 관리가 용이합니다.
현재 프로젝트는 권한 확인 로직이 다양한 경로에서 반복적으로 사용되므로, AOP 또는 Interceptor 방식을 도입하는 것을 권장합니다. 이를 통해 코드의 일관성을 유지하고 유지보수성을 높일 수 있습니다.
'PORTFOLIO > TECHNICAL DECISION-MAKING' 카테고리의 다른 글
[library - 기술적 의사 결정] 프리 티어 한계를 넘어서: 메모리 부족과의 전쟁 (0) | 2025.01.08 |
---|---|
[CodeChef - 기술적 의사 결정] Redis Sentinel 도입이 필요한 이유 (0) | 2025.01.06 |
[CodeChef - 기술적 의사 결정] Redis 도입이 필요한 이유 (0) | 2025.01.06 |
[CodeChef - 기술적 의사 결정] Docker Hub? AWS ECR? (0) | 2025.01.06 |
[CodeChef - 기술적 의사 결정] CI/CD 툴, Jenkins? Github Actions? (0) | 2025.01.06 |