Scala
Akka
JWT
Akka-HTTP

Scala、AkkaHttp、JsonWebToken(Jwt)を使用して認証を実装したい!パート1

この記事は認証認可あたりを聞いたことはあるけど、実装はしたことないって人向けです。

具体的にどのようにJwtを発行したり、デコードするかは「Jwt 仕組み」とかでググってもらえるとわかるとおもいます。

正しいリクエストなのかどうやって認証するか?

認証の仕組みとしては、
スクリーンショット 2018-10-25 15.49.32.png
こんな風にRequestのHeaderに格納されているJsonWebTokenをAppサーバー側で、正しくデコードできればそのユーザーは正規のユーザーであると認証する感じです。

RequestHeaderからアクセストークン(String)を取得!

最初はRequestHeaderに入っている

スクリーンショット 2018-10-25 15.55.12.png
の部分を

qiita.scala
val validateCustomHeader: Directive1[String] = {
    val value = headerValueByName("Authorization")
    value.filter(_ == "Bearer [Jwtトークン部分]" ,MalformedHeaderRejection("Authorization", "value was wrong"))
}

と、AkkaのRouteを使ってリクエストの認証を行おうと思ったんですが、
valueのfilterの_ == "Bearer ~~~~~~~~~~"部分をどうもうまく拡張できない。

value.filter(_ => (jwtの有効性をチェックするBoolean))とかで書けると思ったんですが、型的にoutでした。(かなしい)

なので、別のアプローチから実装することに。

SecurityDirectivesで取得してみる

SecurityDirectivesの中身を見てたら便利そうなのを発見

qiita.scala
  def authenticateOAuth2Async[T](realm: String, authenticator: AsyncAuthenticator[T]): AuthenticationDirective[T] =
    extractExecutionContext.flatMap { implicit ec 
      def extractAccessTokenParameterAsBearerToken = {
        import akka.http.scaladsl.server.Directives._
        parameter('access_token.?).map(_.map(OAuth2BearerToken))
      }
      val extractCreds: Directive1[Option[OAuth2BearerToken]] =
        extractCredentials.flatMap {
          case Some(c: OAuth2BearerToken)  provide(Some(c))
          case _                           extractAccessTokenParameterAsBearerToken
        }
      extractCredentialsAndAuthenticateOrRejectWithChallenge[OAuth2BearerToken, T](extractCreds, { cred 
        authenticator(Credentials(cred)).fast.map {
          case Some(t)  AuthenticationResult.success(t)
          case None     AuthenticationResult.failWithChallenge(HttpChallenges.oAuth2(realm))
        }
      })
    }

なんかOAuth2BearerTokenをうまく扱えそう^^

qiita.scala
val extractCreds: Directive1[Option[OAuth2BearerToken]] =
  extractCredentials.flatMap {
    case Some(c: OAuth2BearerToken)  provide(Some(c))
    case _                           extractAccessTokenParameterAsBearerToken
  }

ここの部分でHeaderのAuthorizationの部分をStringからOAuth2BearerTokenにしてSome(c)に格納してるみたいですね。

さらに下の

qiita.scala
        authenticator(Credentials(cred)).fast.map {
          case Some(t)  AuthenticationResult.success(t)
          case None     AuthenticationResult.failWithChallenge(HttpChallenges.oAuth2(realm))
        }

の部分で引数のauthenticatorにCredentials(cred)の形で渡しているので、ここを追ってみると
最終的に

qiita.scala
  override def authenticate(credentials: Credentials): Future[Option[AuthContext]] = {
       ~~~~ここでJwt認証の実装をする ~~~~~
  }

って形で、authenticatorを実装すれば認証機能が作れそうです。

つまり

qiita.scala
a: Future[Option[AuthContext]] match {
  Some(t) => 認証成功
  None => 認証失敗
}

となるわけですね

Authenticatorを実装してみる

authenticator(Credentials(cred))とあるので、HeaderのAuthorization部分の取得を1から書く必要はなさそう。

qiita.scala
  override def authenticate(credentials: Credentials): Future[Option[AuthContext]] = {
    credentials match {
      case Credentials.Provided(jwt) => validateJwt(jwt)
      case _ => Future { None }
    }
  }

と簡単に書いちゃいましょう。これでjwtがString形式で受け取れるようになりました。

というわけで次回は、Jwtのデコードをしていこうと思います。

パート2 -> https://qiita.com/katokonn1020/items/44ff9a6bec7967358b1d