この記事は認証認可あたりを聞いたことはあるけど、実装はしたことないって人向けです。
具体的にどのようにJwtを発行したり、デコードするかは「Jwt 仕組み」とかでググってもらえるとわかるとおもいます。
正しいリクエストなのかどうやって認証するか?
認証の仕組みとしては、
こんな風にRequestのHeaderに格納されているJsonWebTokenをAppサーバー側で、正しくデコードできればそのユーザーは正規のユーザーであると認証する感じです。
RequestHeaderからアクセストークン(String)を取得!
最初はRequestHeaderに入っている
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の中身を見てたら便利そうなのを発見
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をうまく扱えそう^^
val extractCreds: Directive1[Option[OAuth2BearerToken]] =
extractCredentials.flatMap {
case Some(c: OAuth2BearerToken) ⇒ provide(Some(c))
case _ ⇒ extractAccessTokenParameterAsBearerToken
}
ここの部分でHeaderのAuthorizationの部分をStringからOAuth2BearerTokenにしてSome(c)に格納してるみたいですね。
さらに下の
authenticator(Credentials(cred)).fast.map {
case Some(t) ⇒ AuthenticationResult.success(t)
case None ⇒ AuthenticationResult.failWithChallenge(HttpChallenges.oAuth2(realm))
}
の部分で引数のauthenticatorにCredentials(cred)の形で渡しているので、ここを追ってみると
最終的に
override def authenticate(credentials: Credentials): Future[Option[AuthContext]] = {
~~~~ここでJwt認証の実装をする ~~~~~
}
って形で、authenticatorを実装すれば認証機能が作れそうです。
つまり
a: Future[Option[AuthContext]] match {
Some(t) => 認証成功
None => 認証失敗
}
となるわけですね
Authenticatorを実装してみる
authenticator(Credentials(cred))とあるので、HeaderのAuthorization部分の取得を1から書く必要はなさそう。
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