HwangHub

[리팩토링] AuthenticatedUserUtils 사용하여 로그인 유저 Id를 추출 본문

무엇을 합니다/프로젝트

[리팩토링] AuthenticatedUserUtils 사용하여 로그인 유저 Id를 추출

HwangJerry 2023. 10. 13. 14:21

기존에는 유저 ID를 클라이언트에서 입력하도록 API가 설계되어 있었다. 즉, Controller 레벨에서 pathVariable 또는 requestParam으로 userId를 받고 있었다. 아래는 api 중 유사한 api의 예시이다.

@GetMapping("/mypage/author/{userId}")
public SuccessResponse<?> findAuthorPosts(@PathVariable Long userId, @RequestParam Integer page, @RequestParam Integer size) {
	...
    return SuccessResponse.of(response);
}

문제가 무엇인가?

  • 클라이언트는 엑세스 토큰, 리프레시 토큰을 가지고 있음에 불과하고, 로그인 유저 정보를 관리하고 있지 않다.
  • 엑세스 토큰을 request 헤더에 포함함으로써 이미 authorization을 수행하는데, 서버에서는 이를 이용하여 이미 로그인 유저 정보를 알 수 있으므로 클라이언트로부터 별도로 userId를 받는 것이 불필요하다.
  • 서버는 어차피 pathvariable로 넘어오는 userId를 무작정 신뢰할 수 없는 상황이다. 클라이언트에서 실수하면 추가적인 point of failure가 발생하는 것이므로, 이러한 포인트를 최소화하는 것이 더욱 안정적인 구조일 것이다.

따라서 클라이언트에서 request header에 담아서 보내는 엑세스 토큰을 활용하여 서버에서 자체적으로 userId를 추출할 수 있는 객체를 구성하고, 이를 활용하여 로그인 유저의 id값을 파싱하도록 리팩토링하였다.

 

솔루션은 어떻게 구성되는가?

스프링 시큐리티에서 제공하는 SecurityContextHolder는 Principal이라는 로그인 계정 정보를 담고 있는 객체를 제공해주는데, 이를 이용하여 로그인 유저의 id값을 추출할 수 있다. Security관련 작업을 수행하는 객체를 SecurityUtils라는 클래스로 선언하고, 그 안에 엑세스 토큰을 활용하여 유저 id를 추출하는 메서드를 getCurrentUserId()라고 선언하여 구현해 주었다.

public class SecurityUtils {
    private SecurityUtils(){};
    public static Long getCurrentUserId() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

        if (authentication != null && authentication.isAuthenticated() && authentication.getPrincipal() instanceof Long) {
            ...
            return (Long) authentication.getPrincipal();
        } else {
            throw new NotAuthentiatedException();
        }
    }
}

재사용성을 위해 이를 한 단계 더 추상화하여 필요한 작업만을 위해 직관적으로 사용할 수 있도록 바로 로그인 유저 객체를 추출하는 메서드와 단순 로그인 유저 Id를 추출하는 메서드로 사용할 수 있는 AuthenticatedUserUtils 객체를 구성하였다.

@Component
@RequiredArgsConstructor
public class AuthentiatedUserUtils {
    private final UserAdaptor userAdaptor;

    public Long getCurrentUserId() {
        return SecurityUtils.getCurrentUserId();
    }

    public User getCurrentUser() {
        return userAdaptor.findById(getCurrentUserId());
    }
}

 이를 활용하여 앞으로 로그인 유저 Id가 필요한 모든 메서드의 로직에 추가해주어 적용해 주었다. 해당 로직은 service layer인 도메인서비스에서 활용되는 것이 가장 일반적인 케이스라고 생각했고, 조회의 경우에는 service layer를 추가하는 것이 구조의 복잡성만 더한다고 판단하였기 때문에 필요한 데이터를 바로 가져다가 데이터를 조회할 수 있도록 persistence layer에서 사용하게 하였다.

 

참조 : https://velog.io/@yoho98/Spring-Security-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%ED%9B%84-%EC%82%AC%EC%9A%A9%EC%9E%90-%EC%A0%95%EB%B3%B4%EC%96%BB%EA%B8%B0

참조 : https://www.inflearn.com/questions/964440/session-%EC%82%AC%EC%9A%A9-%EC%8B%9C-restful-api-users-userid-%EC%97%90%EC%84%9C-userid-%EA%B0%80%EC%A0%B8%EC%98%A4%EA%B8%B0%EC%97%90%EC%84%9C

Comments