더 좋은 방법이 있다면 댓글로 알려주세요.
리소스 소유권 기반 접근 제어
를 어떻게 구현하는 것이 좋을지, 고민한 내용을 작성한다.
더 좋은 방법이 있는 경우, 댓글로 제안해주면 고맙겠다.
다음과 같은 상황을 가정해보자
너무 기본적이고 자주 발생하는 상황이다.
하지만 이 게시글(Domain)의 소유권은 저 뒤쪽의 Database 레이어까지 내려가야 체크할 수 있다.
반면 (Spring Security)기준, 유저 정보는 Filter를 통해 부여되어 처음 Controller에 도착한다.
그렇다면, 소유권 체크는 어디에서 하는 것이 좋고, 그 계층까지는 어떤 식으로 정보를 전달하는 것이 좋을까?
Spring Security Authorization Filter의 경우, Authorization을 수행한다. 하지만, 어디까지나 ROLE기반과 같은 다소 정적인 판단만 가능하다.
우리가 원하는 정보는 맨 뒤에 데이터 베이스까지 접근해야한다.
하지만, Filter단 에서 이를 판단하는건 다소 부적합 하다고 생각했다.
둘다 살짝은 애매한 느낌이었다.
이렇게 된다면, Controller 쪽에서 매번 인자로 Servic쪽에 유저 정보를 넘겨줘야한다.
물론 Thread Local을 이용한 SpringSecurityHolder을 사용할 수 있지만, 이렇게 되면, 모든 Service 자체가 Spring Security에 전적으로 의존하게 된다.
만약 소유권 체크를 하지 않게된다면 어떻게 될까? 모두 다 바꿔야한다. 그리고 대부분의 메서드에 소유권 체크로직을 넣어야하는 것도 문제였다. 소유권 체크 방법이 바뀐 경우, 어떻게 되는가?.. 문제였다.
이렇게 되면, 컨트롤러 쪽에 로직이 일부 분산되는 문제가 생겼다.
또한 RestAPI 뿐만 아니라 Graphql, Grpc 같이 api exposure를 여러개 둘 경우, 중복해서 처리해야한다는 문제도 있을 것 같았다.
아무래도 내 생각은, 컨트롤러 쪽에서 처리를 하되, 접근 권한 체크를 제공하는 컴포넌트 인터페이스를 별도로 빼는게 좋겠다고 결론을 내렸다.
다음 StackOverflow 글에서 많은 힌트를 얻었다.(물론 정답이라는 것은 없다.)
@Autowired
private CreatorCheck creatorCheck;
@PutMapping
@PreAuthorize("@creatorChecker.check(#post,authentication)")
public void update(@RequestBody Post post, Authentication authentication) {
service.updatePost(post);
}
또는
try {
// check access beforehand
resourceAccessHelper.checkAccess(principalObject.getEmail(), givenCourseId);
//proceed with the deletion of course.
courseService.deleteCourse(givenCourseId)
} catch (AccessDeniedException ignored) {
//handle your exception here by sending a 404 response error or something similar
}
저렇게 접근 권한을 체크하는 Interface를 정의하고, 이것을 이용하여 접근 권한을 체크하는 것은 어떨까 싶다.
이렇게 되면, 컨트롤러에서 도메인에 직접 접근하는 일도 없고, 권한 체크 관심사와 책임이 하나의 객체에 위임되게 된다.
만약 Spring Security를 사용하지 않게 되거나, 도메인 측에 변경사항(소유자와 협력자 관계가 생긴다던지, 읽기, 쓰기 권한을 따로 체크한다던지)도 구현체만 변경해서 대응할 수 있을 것 같다.
저 소유권 체크하는 객체의 경우, 레이어로 따지자면, Service 레이어에 해당하게된다.
물론, Rest api 말고 다른 api도 함께 쓰는 경우, 중복으로 코드가 작성되는 위험성이 있긴하지만, 그쪽에서도 저렇게 명세된 interface를 사용하면 유지보수나 구현이 간편해질 수 있다고 생각한다.
나의 경우, read 권한, 수정 권한, 삭제 권한 같은 것을 enum으로 만들어서, 이 현재 사용자가 이 리소스에 접근할 권한이 있는지 체크하는 메서드를 구현할 예정이다.