기록일자
24.9.11
문제상황
- 현재 회원가입한 사용자는 2가지 종류의 권한을 가진다.
- 회원여부: UserRole.USER
- 회원가입시 자동으로 부여되는 권한이다. 로그인이 필요한 API에 접근하기 위한 권한
- 동아리 내 권한(3가지): ClubRole.PRESIDENT, ClubRole.MEMBER, 미지정 회원
- 특정 동아리의 권한 신청 후 수락되는 경우 부여 받는 권한
- 특정 회원이 여러 동아리에서, 여러 권한을 가질 수 있기 때문에, user_club_role 테이블에서 별도로 관리한다.
- 예: A회원은 기타동아리 회장임과 동시에, 코딩동아리 회원일 수 있다.
🙋 2종류의 권한으로 분리한 이유
- 내 서비스 로직을 설명하면, 먼저 회원가입을 하면, spiring secruity필터에서 기본적으로 USER라는 ROLE을 부여해, 해당 USER role을 가진 사람은 로그인 된 회원이고, 이때는 미지정 회원이야. 그리고 추후에 특정 동아리의 회장 혹은 동아리원 권한을 요청할 수 있어! 해당 요청에 의해 권한을 부여받는 방식이야. 그래서 user A는 K동아리의 회장이면서, S동아리의 동아리원일 수 있어. 이 동아리 내 부여받은 권한은, User_Role 테이블에서 관리가 되는데 해당 테이블의 컬럼은, user_id와 club_id 그리고 해당 동아리 내에서 역할로 총 3가지 컬럼으로 구성되어 잇어. 정리하자면, 결국 처음에 회원가입할 때 부여받는 USER역할과 별도로 각 동아리 내에서의 권한도 있는 서비스지. 내가 설계한 API는 동아리 회장인지, 동아리원인지에 먼저 확인하고, 해당 api들에게 접근가능한지 판단하는 메소드를 반드시 수반해.
그래서, 모든 api를 호출하면, clubID를 함께 보내주고, 현재 로그인된 user의 정보를 Authenication 객체에서 꺼내서 userId와 함께 User_Role 테이블을 조회하고, 해당 동아리 내 권한이 동아리원인지 회장인지 먼저 검사하고, 적절한 요청이면 해당 api의 service로직을 수행하고 있단 말이야. 근데 여기서 문제점이 모든 API에 대해서 해당 로직을 작성을 해주다 보니까 service 메소드 내에 해당코드가 전부 들어가 있어. 그리고 굳이 clubId가 필요없는 api 메소드에 대해서도, 사용자 검증을 위해 clubID를 보내줘야하는 불필요한 상황이 발생하는거 같아 그래서 내가 궁금한거는 테이블 조회를 통해 검증을 하는 해당 로직, 이를 처리할 수 있는 spring security설정이 있는지, 아니면 이 상황에서 사용할 수 있는 최적의 방안은 무엇인지 궁금해. 그리고 해당 답변에 대한 기술블로그나 별도의 참조 래퍼런스도 함께 알려줘
현재 구현
- JWT 토큰으로, 로그인 여부를 먼저 security filter에서 검증하고, 추후에 api 호출시 service 메소드에서 해당 사용자가, 해당 동아리 내에서 어떤 권한을 가지고 있는지, user_club_role 테이블을 조회한다.
- 이때, user_club_role 테이블 조회를 위해, 사용자(User)와 현재 API를 요청한 동아리(Club)에 대한 정보가 필요하다.
- User: Authentication에서 현재 로그인한 사용자의 정보(user_id)를 가져온다.
- Club: api요청시 파라미터 혹은 request body에 요청하는 clubId 담아 요청한다.
//모든 API의 서비스 메소드에서, 사용자의 동아리내 권한을 확인하는 검증 로직(아래 코드)을 일일이 포함하고 있다.
//TODO: AOP를 활용하여 권한 검증 로직과 비즈니스 로직을 분리하기
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
CustomUserDetail userDetails = (CustomUserDetail) authentication.getPrincipal();
User user = userRepository.findById(userDetails.getId())
.orElseThrow(() -> new NoSuchElementException("User not found"));
Club club = stepRepository.findById(stepId)
.orElseThrow(() -> new NoSuchElementException("Step not found"))
.getRecruitment().getClub();
//User와 Club을 이용하여, 사용자의 동아리 내 권한을 조회한다.
UserClubRole userClubRole = userClubRoleRepository.findByClubAndUser(club, user)
.orElseThrow(() -> new NoSuchElementException("User is not in Club"));
문제 상황
- 각 API 호출 시, 사용자의 동아리 내 역할(동아리장 또는 동아리원)을 검사해야 하는 로직이 서비스 메소드에 중복되어 작성되고 있음.
- 또한, 해당 API의 주요 비즈니스 로직이 clubId가 필요하지 않은 상황에서도 사용자 검증을 위해 clubId를 함께 보내야 하는 상황이 발생.
문제 해결
- 목표
- AOP를 활용하여, User와 Club을 이용한 권한 검증 로직을 서비스 메소드에서 분리
- API 내부에서는 비즈니스 로직에만 집중하고, clubId와 권한 검증 로직은 자동으로 처리
- API가 호출되면, AOP가 자동으로 실행되어 clubId와 userId를 기반으로 사용자 권한을 검증
AOP 기초