http4s에서 유저 인증하기

구경회·2020년 9월 13일
0
post-thumbnail

스칼라로 작성된 함수형 웹 프레임워크인 http4s에서 인증하는 방식을 따라가 보자. cats등의 함수형 라이브러리와 함수형 프로그래밍에 대한 살짝의 지식이 필요하다.

최종 목표

헤더에서 토큰을 파싱하여 유저를 인증한다. 올바른 토큰이 아닌 경우 401 invalid 응답을 보낸다.

배경지식

http4s에서 서비스는 Kleisli[OptionT[F, *], Request[F], Response[F]]이다.

이게 무슨 소리인가 싶다. Kleisli는 단순히 함수를 합성하기 쉽게 감싸 놓은 것이다. 즉, http4s에서의 서비스는 Request[F] => OptionT[F, Response[F]]이다. OptionT[F[_], A]F[Option[A]]를 사용하기 편하게 포장한 것이다.

어려운 말들로 써 있지만 결국 http4s에서 서비스는 리퀘스트를 받아 결과값을 돌려주는 함수일 뿐인 것이다.

유저 정보 추가하기

이 서비스에 인증을 추가하기 위해서는 유저에 대한 정보가 필요하다. 당연히 유저에 대한 인증 정보는 리퀘스트 안에 들어있다. 그 요청은 (User, Request[F])와 같이 표현할 수 있다. http4s는 이 과정을 돕기 위해 AuthedRequest[F, User]를 제공한다. 하지만 유저나 인증에 대한 정보를 우리가 제공해야 한다.

case class User(id: Long, name: String)

유저에 대한 정보를 제공하기 위해 위와 같은 클래스를 하나 선언하자. 이제 유저와 인증 정보를 담은 리퀘스트를 정의했으니 이 둘을 엮어 자연스럽게 서비스를 정의할 수 있다.
AuthedRequest[F, User] => OptionT[F, Response[F]]을 정의하고, 이것을 AuthedRoutes[User, F]라 하자. 적당한 표현을 찾았으니, 이제 리퀘스트에서 유저의 정보를 추출하는 과정을 다음과 같이 작성하자.

/* TODO: 추출 과정 구현 */
val authUser: Kleisli[OptionT[IO, *], Request[IO], User] =
  Kleisli(_ => OptionT.liftF(IO(???)))

실제로 추출하는 과정은 나중에 구현하자. 그래서 그 내용을 ???로 비워 두었다.

그리고 일반적인 서비스와 인증 서비스 사이의 가교 역할을 하는 미들웨어를 만들 필요성이 있다. http4s는 이 과정을 위해 AuthMiddelware를 제공한다.

val middleware: AuthMiddleware[IO, User] =
  AuthMiddleware(authUser)

이것들을 한데 엮어 다음과 같은 서비스를 만들 수 있다.

val authedRoutes: AuthedRoutes[User, IO] =
  AuthedRoutes.of {
    case GET -> Root / "welcome" as user => 
      Ok(s"Welcome, ${user.name}")
  }

val service: HttpRoutes[IO] = middleware(authedRoutes)

유저 인증 구현

이제 모든 준비는 끝났다. 남은 것은 authUser를 실제로 구현하는 것이다. 헤더 중 하나로 Token이라는 값을 주고, 그 토큰 값이 secret이라면 유저의 정보를 돌려주는 식의 예제를 만들자.

def checkToken(token: String): Option[User] = {
  if (token == "secret") User(42, "K").pure[Option]
  else None
}

대략 위와 같은 식으로 간단하게 만들 수 있다. cats.implicits 를 불러오면 F[A].pure(a)같은 형식 대신 A.pure[F] 같은 식으로 쓸 수 있다.

val authUser: Kleisli[OptionT[IO, *], Request[IO], User] = 
  Kleisli { req =>
    val hs: Option[User] = for {
      token <- req.headers.get(CaseInsensitiveString("Token"))
      res <- checkToken(token.value)
    } yield { res }
    OptionT.fromOption(hs)
  }

authUser 를 위와 같이 구현할 수 있다. 리퀘스트의 헤더들에서 token을 추출하면 Option[Header] 를 얻을 수 있다. 이 헤더의 값이 우리가 원하는 값이다. 우리가 원하는 값은 OptionT[IO, User] 기 때문에 승급시켜주면 된다.

결과 확인


올바른 토큰에 대해서는 상태코드 200과 함게 알맞은 대답을, 잘못된 토큰에 대해서는 상태코드 401을 보내는 것을 확인할 수 있다.

마치며

생각보다 http4s에 대한 정보를 찾기 힘들었다. "우리 라이브러리를 사용해!"같은 글들만 잔뜩 있다보니 자꾸 build.sbt 만 더러워지고 있더라. 특히 한글로 된 글은 전멸이었다. 이 글이 조금이나마 도움이 되었으면 좋겠다.

그리고 벨로그에서 scala 문법 강조 해줬으면 좋겠다. kotlin이나 java로 설정해놓으니 어느정도 색깔이 돌지만, 중간중간 제대로 하이라이팅 되어있지 않은 부분이 거슬린다.

참조

http4s: Authentication
Http4s Introduction
Kleisli
Monad Transformer: OptionT

profile
즐기는 거야

0개의 댓글