Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BE] feat: 리소스 접근 권한 검증 aop 구현 #1089

Open
wants to merge 25 commits into
base: develop
Choose a base branch
from

Conversation

nayonsoso
Copy link
Contributor

@nayonsoso nayonsoso commented Feb 12, 2025


🚀 어떤 기능을 구현했나요 ?

  • 리소스 접근 권한 검증 aop를 구현했습니다.

🔥 어떻게 해결했나요 ?

  • 시행착오가 조금 있었는데요😅
  • 처음에는 '권한'이라는 것이 비지니스 로직이라고 생각해서 서비스 코드에 aop 를 두었어요.
    그런데 테드가 구현한 resolver 를 보고, 권한 검증도 컨트롤러에서 하는게 좋겠다 싶어서 추가 커밋으로 코드를 바꿨습니다.

📝 어떤 부분에 집중해서 리뷰해야 할까요?

  • 네이밍 고민되는게 있는데, RequireReviewAccess vs RequireReviewAuthorization 중에 뭐가 더 좋나요?

📚 참고 자료, 할 말

Copy link

github-actions bot commented Feb 12, 2025

Test Results

196 tests  +23   193 ✅ +23   5s ⏱️ ±0s
 73 suites +10     3 💤 ± 0 
 73 files   +10     0 ❌ ± 0 

Results for commit de1ff1c. ± Comparison against base commit 19c7845.

♻️ This comment has been updated with latest results.

@nayonsoso nayonsoso changed the title feat: 리소스 접근 권한 검증 aop 구현 [BE] feat: 리소스 접근 권한 검증 aop 구현 Feb 12, 2025
Copy link
Contributor

@Kimprodp Kimprodp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아직 꼼꼼하게 다 확인은 못했는데요,

리졸버 구현과 중복되는 로직이 존재하는 것 같아서 우선 RC 먼저 남길게요!

리졸버에서 세션을 확인하고, dto를 반환해서 컨트롤러 메서드 인자로 넘겨주고 있습니다.
근데 aop에서도 세션을 확인하는 동일한 로직을 하는 것으로 보여요. (아마 리졸버 구현 전이라 이렇게 구현했을 거라 생각해요.)

따라서 리졸버 구현이 단순하니 먼저 머지하고, JoinPoint의 LoginMember 또는 GuestReviewGroup을 직접 사용하도록 변경하면 어떨까요?

  1. 존재하는 세션을 확인한다. (회원 세션 or 비회원 세션)
  2. 존재하는 세션과 요청된 자원 id의 관계를 확인하여 검증

위 내용에 동의하면 작업하는 동안 더 세부적으로 보고있겠습니다~

@nayonsoso
Copy link
Contributor Author

nayonsoso commented Feb 13, 2025

저는 “세션에 저장된 정보를 가져오기 위한 resolver”와
“세션에 저장된 정보로부터 권한을 체크하기 위한 aop”가 분명히 다른 목적을 가지고 있다고 생각해요.
그래서 단일 책임 원칙을 더 중요시하고 싶어요..!

resolver가 만들어준 LoginMember 또는 GuestReviewGroup를 직접 사용하는 aop라면, 아래와 같이 구현될거예요.

@PostMapping("/v2/highlight")
@RequireReviewGroupAccess(target = "#request.reviewGroupId()”)
public ResponseEntity<Void> highlight(
        @Valid @RequestBody HighlightsRequest request,
        @LoginMemberSession(required = false) LoginMember loginMember,
        @GuestReviewGroupSession(required = false) GuestReviewGroup guestReviewGroup
) {
    highlightService.editHighlight(request);
    return ResponseEntity.ok().build();
}

aop 가 resolver에 종석적이라서, aop 에게 값을 전달하기 위한 용도의 인자가 생겼어요. 🙀
실제로 로직에 사용되는게 아니라 그저 “aop 에 값을 전달하기 위한” 코드가 존재하는건 좋지 않다 생각해요.

이들이 둘 다 세션에 접근하긴 하지만, aop와 resolver 를 같이 쓰는 곳이 없어요.
연산이 중복해서 일어나는 것도 아니라, 비효율적이지 않다고 생각합니다.


@PostMapping("/v2/highlight")
@RequireReviewGroupAccess(target = "#request.reviewGroupId()”)
public ResponseEntity<Void> highlight(
        @Valid @RequestBody HighlightsRequest request
) {
    highlightService.editHighlight(request);
    return ResponseEntity.ok().build();
}

테드는 첫번째 코드가 더 읽기 좋나요, 두번째 코드가 읽기 좋나요?
저는 두번째가 명확해서 보기 좋아보입니다... 🥹

@Kimprodp
Copy link
Contributor

사전에 작업 이해한 내용이 조금 달랐던 거 같네요..
저는 리졸버를 통해 받아온 걸로 인증하자고 이야기한걸로 기억해서 그런 방식으로 구현하는 줄 알았어요.

그럼 산초가 말한 것처럼 리졸버는 기존처럼 변환만 하고 aop에서 모든 인증을 다 하는 게 깔끔하네요~
코드 다시 확인하고 리뷰 남길게요!

Copy link
Contributor

@Kimprodp Kimprodp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

산초우 aop 구현 잘했네요 👍👍
다만 몇 가지 포인트에서 문제 될 수 있는 부분이나 더 좋은 의견과 함께 리뷰 남깁니다!

private final ReviewRepository reviewRepository;
private final ReviewGroupRepository reviewGroupRepository;

@Transactional
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aspect 내부에서 트랜잭션을 사용하는 이유가 무엇인가요?

이 안에서 트랜잭션을 시작하면, 비즈니스 로직이 수행되는 서비스 계층의 트랜잭션 경계와 중첩 되어서 예상치 못한 작동이 일어날 수 있다고 생각하는데요. 또한 컨트롤러 메서드가 트랜잭션 안에서 실행되어야 하는 이유도 모르겠어요.

Copy link
Contributor Author

@nayonsoso nayonsoso Feb 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DB조회하는 동작이라 습관적으로 붙였던 것 같습니다😞
없애는게 맞겠네요!

3ab68dc

Comment on lines 50 to 60
private boolean isMemberAuthorized(Review review, HttpSession session) {
GitHubMember gitHubMember = sessionManager.getGitHubMember(session);
if (gitHubMember == null) {
return false;
}

boolean isReviewGroupCreator = reviewGroupRepository.findAllByMemberId(gitHubMember.getMemberId())
.stream()
.map(ReviewGroup::getId)
.anyMatch(id -> id == review.getReviewGroupId());
boolean isReviewAuthor = review.getMemberId() != null && review.getMemberId() == gitHubMember.getMemberId();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

회원이 등록한 모든 그룹을 찾고 반복하면서 리뷰의 그룹 아이디와 일치하는 그룹을 찾고 있어서 비효율적으로 보여요.
리뷰에 있는 그룹 아이디로 해당 그룹만 찾고 그룹에 있는 회원 아이디와 gitHubMember.getMemberId()만 비교하면 간단할 거 같다는 생각인데, 혹시 이렇게 한 이유가 있을까요?

or reviewGroupRepository.existsByMemberIdAndReviewGroupId

Copy link
Contributor Author

@nayonsoso nayonsoso Feb 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

혹시 이렇게 한 이유가 있을까요?

특별한 이유는 없고, 위 박식만 떠올라서 그렇게 했습니다😅
테드가 제안한 방식이 더 효율적이겠네요.

bb63961

}

ReviewGroup reviewGroup = reviewGroupRepository.findByReviewRequestCode(reviewRequestCode)
.orElseThrow(() -> new ReviewGroupNotFoundByReviewRequestCodeException(reviewRequestCode));
Copy link
Contributor

@Kimprodp Kimprodp Feb 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 예외를 여기서 던지는 것이 맞을지 생각이 듭니다요..

예외 메세지가 클라이언트에게 전달이 될텐데, 클라이언트 입장에서는 세션을 통해 던져진 ReviewRequestCode라서, 사실 세션을 사용한지도 모를텐데 뜬금없는 메세지를 받게 될 것 같아요. 그리고 세션에 저장된 코드로 도메인 객체를 찾을 수 없다는 건 NotFound가 아닌 서버 에러로 봐야 한다고 생각합니다!

Copy link
Contributor Author

@nayonsoso nayonsoso Feb 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

그렇네요, ReviewRequestCode 때문이라는 메세지를 담은
"ReviewGroupNotFoundByReviewRequestCodeException"는 변경해야겠네요😅

9412d32

private final SessionManager sessionManager;
private final ReviewGroupRepository reviewGroupRepository;

@Transactional
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기 트랜잭션도 위에서 했던 의견과 동일합니다!

@nayonsoso nayonsoso force-pushed the be/feat/1085-resource-authorization-aop branch from 58ed576 to d8f2ca4 Compare February 15, 2025 11:41
Copy link

cloudflare-workers-and-pages bot commented Feb 15, 2025

Deploying 2024-review-me-release with  Cloudflare Pages  Cloudflare Pages

Latest commit: de1ff1c
Status: ✅  Deploy successful!
Preview URL: https://c6652008.2024-review-me-release.pages.dev
Branch Preview URL: https://be-feat-1085-resource-author.2024-review-me-release.pages.dev

View logs

Copy link

cloudflare-workers-and-pages bot commented Feb 15, 2025

Deploying 2024-review-me-develop with  Cloudflare Pages  Cloudflare Pages

Latest commit: de1ff1c
Status: ✅  Deploy successful!
Preview URL: https://1c986b6f.2024-review-me-develop.pages.dev
Branch Preview URL: https://be-feat-1085-resource-author.2024-review-me-develop.pages.dev

View logs

@nayonsoso
Copy link
Contributor Author

✍️ 추가된 부분

  • 리뷰 남겨준 부분 반영했습니다.
  • 권한 관련 예외를 Unauthorized → Forbidden 로 변경했습니다.
  • 권한 관련 클래스들 security 패키지 하위로 이동했습니다.

@nayonsoso nayonsoso force-pushed the be/feat/1085-resource-authorization-aop branch from 281a992 to de1ff1c Compare February 15, 2025 14:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[BE] 리소스에 대한 권한 체크를 aop로 진행한다.
2 participants