Spring 7기 프로젝트/뉴스피드 팀 프로젝트
Spring Security에서 @PreAuthorize와 별도 권한 컴포넌트를 활용한 동적 인가 구현법
JuNo_12
2025. 5. 28. 16:19
1. 서론
Spring Security는 인증(Authentication)과 인가(Authorization)를 강력하게 지원하는 프레임워크입니다.
특히, 복잡한 권한 요구사항을 깔끔하고 유지보수하기 쉽게 처리하기 위해 @PreAuthorize 애노테이션과 별도의 권한 검증 컴포넌트를 함께 사용하는 동적 인가 기법이 많이 활용됩니다.
2. 동적 인가란?
- 동적 인가(Dynamic Authorization) 란, 요청에 포함된 사용자 정보와 요청 파라미터, 리소스 상태 등을 실시간으로 판단해 권한 여부를 결정하는 방식입니다.
- 예:
- 댓글 수정 요청 시, 해당 댓글의 작성자인지 확인
- 특정 문서에 접근 시, 문서 상태 및 사용자 역할에 따라 접근 권한 부여 등
3. @PreAuthorize + 별도 권한 컴포넌트
1. @PreAuthorize 애노테이션
- 메서드 호출 전, SpEL(Spring Expression Language)로 권한 조건을 평가합니다.
- 선언적으로 권한 정책을 분리할 수 있습니다.
2. 별도 권한 검증 컴포넌트
- 권한 검증을 위한 로직을 별도 컴포넌트에 모아 재사용성 및 유지보수를 용이하게 만듭니다.
- 예시: CommentSecurity 클래스
4. 구현 예시
1. 권한 검증 컴포넌트 (CommentSecurity.java)
@Component
@RequiredArgsConstructor
public class CommentSecurity {
private final CommentRepository commentRepository;
public boolean isCommentOwner(Long commentId) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || !authentication.isAuthenticated()) return false;
CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal();
Long userId = userDetails.getUserId();
return commentRepository.existsByIdAndUserId(commentId, userId);
}
}
2. 컨트롤러에서 @PreAuthorize 사용
@PreAuthorize("@commentSecurity.isCommentOwner(#commentId)")
@PatchMapping("/comments/{commentId}")
public ResponseEntity<Void> updateComment(
// 비지니스 로직만 집중
}
@Component
public class PostService {
public boolean isOwner(Long postId, String username) {
Post post = postRepository.findById(postId)
.orElseThrow(() -> new PostNotFoundException());
return post.getAuthor().getUsername().equals(username);
}
}
5. 장점
- 선언적 보안: 메서드 레벨에서 보안 규칙을 명시적으로 선언
- AOP 기반: 비즈니스 로직과 보안 로직의 완전한 분리
- 코드 가독성: 어떤 권한이 필요한지 메서드 시그니처에서 바로 확인 가능
- 일관성: 다른 권한 검증과 동일한 패턴 사용
- 테스트 용이성: 보안 설정을 모킹하여 단위 테스트 가능
- 확장성: 나중에 관리자 권한, 그룹 권한 등이 추가되어도 표현식만 수정하면 됨\
- 보안 표준: Spring Security의 표준 접근 방식
- 재사용성: 다른 엔티티(댓글, 파일 등)에도 동일한 패턴 적용 가능
더보기
선언적(Declarative) 권한 정책이란?
- 선언적이란, 코드 안에서 무엇을 할 것인지를 명확하게 '선언'하는 방식을 말합니다.
- 반대로, 명령적(Imperative)은 어떻게 할 것인지 절차를 직접 작성하는 방식입니다.
예를 들어,
- 명령적 코드:
서비스 메서드 안에서 직접 if문으로 권한 체크를 하고, 조건문과 예외 처리를 상세히 작성하는 방식 - 선언적 코드:
메서드 위에 @PreAuthorize("조건") 애노테이션만 붙여서 "이 메서드는 이런 조건을 만족하는 사용자만 실행 가능하다"라고 선언하는 방식
그래서 "선언적으로 분리"되면?
- 권한 체크가 서비스 비즈니스 로직과 분리되어
- 권한 검증 로직을 메서드 선언부(애노테이션 등)에서 한눈에 볼 수 있어서
- 코드를 읽을 때 "이 메서드는 어떤 권한이 필요한지"를 직관적으로 파악할 수 있다는 뜻입니다.
가독성이 높아진다?
- 권한 검사 코드가 메서드 내부에 숨어있지 않고,
- 메서드 선언부 위에 명확하게 적혀 있어서
- 코드가 더 깔끔하고 이해하기 쉬워진다는 의미입니다.