게스트는 저번에도 구현한 적이 있다. 그것과 완전 동일한 이유로 게스트를 추가하려고 한다.
이전 프로젝트의 글을 모르는 사람들을 위해 다시 설명한다.
## 게스트의 필요성 - 누가 쓴 병이고 누가 받은거지?
음악이 담긴 병(이하 병)을 생성한 유저를 식별할 필요성이 있다.
일단 자신이 만든 병을 자신이 받게 되는 불상사를 방지하기 위함이기도 하고,
유저 이름을 담게되면 저 인터넷 너머 누군가 정말 보냈다는 것을 알 수 있고,
내 병을 다른 사람이 듣고 평가를 받을 수도 있기 때문이다. (너 음악 센스 좋다~ 거나 으엑 씹덕 음악 저리가 등등)
그래서 유저를 식별하기 위해선 로그인 인증을 구현하면 좋은데..
문제는 사람들은 이상한 서비스에 로그인 하기를 꺼려한다. 그것도 개인정보라고 취급하기도 하고, 일단은 귀찮기 때문이다.
때문에 차라리 아예 유저 생성 자체를 뒤에서 몰래 하거나 간단히 닉네임만 입력하면 짜잔 하고 생성되는 방식이 낫다.
그래서 결론은 회원가입-로그인 절차 없이 게스트(유저) 생성을 구현한다! 가 되었다.
## 그리운 FastAPI
이전에 FastAPI를 이용해 이를 구현할때는 아주 쉽고 직관적이었다.
FastAPI에서 지원하는 Depends를 이용한 di는 아주 쉽고 내 맘대로 가공한 데이터를 주입하는 것도 가능했기 때문이었다.
(이건 스프링 빈이랑은 좀 다르다. 보통의 스프링 빈은 최초 객체를 생성한 뒤 주입하는 방식이라면 여기선 요청마다 계속 의존성 주입 함수를 호출해 주입하는 방식이였다. (물론 캐싱이 가능하긴 하다))
FastAPI에서는 라우터 함수의 파라미터로 게스트 id를 받곤했는데, 게스트 id는 쿠키를 통해서 받아온다는 사실을 의존성 주입 함수안으로 숨겼다.
그러니까 “게스트 id를 가져온다”는 사실 “쿠키에서 GUEST_ID 필트를 찾아서 가져오고~ 유저가 실제하는지 확인하고~ 건내준다”였고, 이를 공통화 필요성 & 책임분리때문에 단순화 한 것이였다.
## 이렇게 만들어 보았다!
musicinabottle.auth라는 패키지를 하나 판 뒤에 그 안에 각종 인증 관련된 것들을 넣었다.
이 안에는 5개의 친구들이 들어 있다.
- annotation UserId
- class UserIdResolver
- class AuthenticationInterceptor
- class AuthenticationContext
- class AuthenticationConfig
## @UserId
이 어노테이션은 컨트롤러의 파라미터에 들어가서 인증(유저 id를 받아와야 함)이 필요하고 유저 id(방금까지 게스트 id라고 함)를 가져올 수 있다.
## UserIdResolver
HandlerMethodArgumentResolver의 구현체로, @UserId가 붙은 파라미터에 값을 만들어준다. Fastapi의 의존성 주입기와 비슷한 느낌이다.
이 친구는 RequestMappingHandlerAdapter가 호출해주는데, 백엔드 개발자 입장에서는 두개의 메서드만 구현하면 된다.
// 첫번째는 어떤 파타미터에 적용할 것인지
@Override
public boolean supportsParameter(MethodParameter parameter) {
// @UserId이 있으면 true반환
}
// 두번째는 어떻게 주입해줄 건데?
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
// 여기서 userId가 없으면 에러 터뜨린다.
}
여기서 쿠키를 직접 까보지는 않는다. 주입만 해주는 친구.
## AuthenticationContext
userId를 저장하는 컨테이너 객체로 쓰인다.
아무것도 구현하거나 상속하지 않았지만 특이한 점은 @RequestScope를 적용해서 다른 빈들과 다르게 요청당 하나씩 생성된다.
## AuthenticationInterceptor
여기서 컨트롤러 앞단에서 userId값이 쿠키에 있으면 위 context객체에 넣어준다.
없으면 그냥 넘어간다.
사실 앞에 resolver에서도 쿠키를 읽어들일 수 있긴하다.
그럼에도 인터셉터를 만든 이유는 FastAPI와 다르게 의존성 주입기(리졸버) 끼리 주입하기 어렵기 때문이였다.
fastapi는 아래와 같이 할 수 있었다.
# 문법이 맞는지 잘 모르겠다 그냥 이렇게 중첩하는게 가능하다는 것만 보세요
def get_user_id(user_id: str | None = Cookie(“userId”, default=None)) -> str | None:
return user_id
def get_valid_user_id(user_id: str | None = Depends(get_user_id), user_service = Depends(get_user_service):
# 의존성 주입기끼리 중첩할 수 있다. 순서도 직접 주입하는 방식이라서 크게 어렵지 않다.
if user_id is None or not user_service.exists(user_id):
raise InvalidUserIdError()
return user_id
##
@router.get(“/user/unsafe”)
def get_user_unsafe(user_id: str | None = Depends(get_user_id)):
pass
@router.get(“/user”)
def get_user(user_id: str | None = Depends(get_valid_user_id)):
pass
반면에 스프링은 이렇게 리졸버끼리 값을 던져주는 건 어려워서 인터셉터에서 쿠키 값을 받고, 컨텍스트에 저장한 뒤에 리졸버가 알아서 가져가도록 만들었다.
## AuthenticationConfig
설정용 객체로, 인터셉터와 리졸버를 등록해주는 일만 한다.
'프로그래밍 > A music in a balloon' 카테고리의 다른 글
# 10 스프링 예외처리 (1) | 2024.06.06 |
---|---|
# 9 프로젝트 수정 (0) | 2024.06.06 |
# 8 깃허브 엑션 테스트 적용기 (0) | 2024.06.05 |
# 7 LazyInitializationException을 보았다! (0) | 2024.06.04 |
# 2 스프링과 재회 (1) | 2024.05.30 |