LoginSignup
8
3

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-10-25

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

具体的にどのように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

8
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
3