Edited at

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