프로젝트/chaelog

@PreAuthorize

챛채 2024. 9. 25. 12:28

게시글을 삭제할 수 있는 권한을 주기위해 spring security의 커스텀 권한 평가기를 만들어 @PreAuthorize를 사용하려고 했다.

[ChaelogPermissionEvaluator]

@Slf4j
@RequiredArgsConstructor
public class ChaelogPermissionEvaluator implements PermissionEvaluator {

    private final PostRepository postRepository;

    @Override
    public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
        return false;
    }

    @Override
    public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
        log.info("hasPermission called with targetId: {}, targetType: {}, permission: {}", targetId, targetType, permission);

        if (targetId == null) {
            log.error("targetId가 null입니다.");
            return false; //권한 없음으로 처리
        }

        var memberPrincipal = (MemberPrincipal) authentication.getPrincipal();

        var post = postRepository.findById((Long) targetId)
                .orElseThrow(PostNotFound::new);

        if (!post.getMemberId().equals(memberPrincipal.getMemberId())) {
            log.error("[인가실패] 해당 사용자가 작성한 글이 아닙니다. targetId={}", targetId);
            return false;
        }
        return true;
    }
}

 

 

[PostController-delete]

    @PreAuthorize("hasRole('ROLE_ADMIN') && hasPermission(#postId, 'POST', 'DELETE')")
    @DeleteMapping("/posts/{postId}")
    public void delete(@PathVariable("postId") Long postId) {
        postService.delete(postId);
    }
}

 

실행을 해보면 글 작성후 삭제를 하려고 하면 처음에 500에러가 뜨길래 

 log.info("hasPermission called with targetId: {}, targetType: {}, permission: {}", targetId, targetType, permission);

        if (targetId == null) {
            log.error("targetId가 null입니다.");
            return false; //권한 없음으로 처리
        }

로그와 함께 확인을 다시 해보니 targetId가 null뜨면서 기존 만들었던 403 Handler로 반환이 되는 것이 아니겠는가,,, 

처음엔 spring boot 3.xx이후부터는 PathVariable의 이름을 반드시 명시해야하기 때문에 철자 오류인가 싶어 계속 다시 확인해봐도 target은 여전히 null 뜨고 있었다. 

다음 찾아본 해결 방안으로는 SpEL에서 인자 위치로 참조하는 방법이 있어서 #root.args[0]을 통해 다시 해보았음에도 실패,, targetType이나 permission은 정확하게 전달되는데 targetId는 여전히 null로 전달되고 있었다..

 

[해결방안1]

결국 고민고민하다 PostController에 ChaelogPermissionEvaluator를 주입받은 후

@PreAuthorize 없이 메서드 내부에서 권한 검사 로직을 직접 호출하는 방식으로 변경하기로 했다.

 @DeleteMapping("/posts/{postId}")
    public void delete(@PathVariable("postId") Long postId) {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

        // 권한 평가기를 직접 호출
        if (!permissionEvaluator.hasPermission(authentication, postId, "POST", "DELETE")) {
            throw new AccessDeniedException("삭제 권한이 없습니다.");
        }
        log.info("삭제할 게시글 ID: {}", postId);
        postService.delete(postId);

    }

 

 

[해결방안2]

만든 권한 평가기를 어떻게든 쓰고싶어서 root.args[0]도 써보고했는데 안 돼서 포기하려던 찰나에 #root.args[0]대신

#p0으로 바꿔서 사용해보니 정상적으로 작동된다.... 완전 럭키비키,,