Google ドライブに接続するプレイ Web アプリを構築しています。Google OAuth 2.0 プロセスを経て、ユーザーがログインすると、access_token をキャッシュに保存し、refresh_token を (他のユーザー データと共に) データベースとキャッシュに保存します。Google OAuth accessTokens は1 時間しか持続せず、同様にキャッシュ内の accessToken は 1 時間で期限切れになります。
したがって、次のように、認証済みアクションを作成する別の方法の行に沿って認証済み関数を作成しましたが、ユーザーに加えて、accessToken も保存します。
ただし、accessToken は 1 時間後に有効期限が切れます。有効期限が切れている場合は、別の access_token を取得するために、refresh_token を使用して Google に Web サービス GET リクエストを実行する必要があります。
少し醜いように見えますが、機能する同期バージョンを作成することができました。同期するように再加工する方法があるのではないかと思っていましたか?
def Authenticated[A](p: BodyParser[A])(f: AuthenticatedRequest[A] => Result) = {
Action(p) { request =>
val result1 = for {
userId <- request.session.get(username)
user <- Cache.getAs[User](s"user$userId")
token <- Cache.getAs[String](accessTokenKey(userId))
} yield f(AuthenticatedRequest(user, token, request))
import scala.concurrent.duration._
lazy val result2 = for {
userId <- request.session.get(username)
user <- Cache.getAs[User](s"user$userId")
token <- persistAccessToken(Await.result(requestNewAccessToken(user.refreshToken)(userId), 10.seconds))(userId)
} yield f(AuthenticatedRequest(user, token, request))
result1.getOrElse(result2.getOrElse(Results.Redirect(routes.Application.index())))
}
}
requestNewAccessToken
Google に WS ポスト リクエストを送信し、refreshToken を他のものと一緒に送信します。その見返りとして、Google は新しいアクセス トークンを返します。メソッドは次のとおりです。
def refreshTokenBody(refreshToken: String) = Map(
"refresh_token" -> Seq(refreshToken),
"client_id" -> Seq(clientId),
"client_secret" -> Seq(clientSecret),
"grant_type" -> Seq(tokenGrantType)
)
def requestNewAccessToken(refreshToken: String)(implicit userId: String): Future[Response] =
WS.url(tokenUri).withHeaders(tokenHeader).post(refreshTokenBody(refreshToken))
Future[ws.Response] を ws.Response に変換する他の唯一の方法は onComplete を使用することですが、それは戻り値の型が Unit のコールバック関数であり、提供された例とうまく適合していないようです。 Playframework docs (上記) で、2 番目のルーターにリダイレクトせずに AsyncResult を応答に変換する方法がわかりません。私が考えたもう 1 つの可能性は、リクエストをインターセプトするフィルターです。キャッシュ内の accessToken の有効期限が切れている場合は、アクション メソッドが開始する前に、Google から別のトークンを取得してキャッシュに保存します (そうすれば、accessToken は常に最新であること)。
私が言ったように、同期バージョンは機能します。それがこの手順を実装する唯一の方法である場合は、それもそうですが、非同期でこれを行う方法があるかもしれないことを望んでいました.
どうもありがとうございます!
Play 2.2.0 のアップデート
async {}
は Play 2.2.0 で非推奨となり、Play 2.3 で削除されます。そのため、現在のバージョンの Play を使用している場合は、上記のソリューションを修正する必要があります。
ユーザーが正常にログインすると、Google の access_token がキャッシュに永続化されます。access_token は 1 時間しか持続しないため、1 時間後にキャッシュから access_token を削除します。
したがって、のロジックはAuthenticated
、リクエストに userId Cookie があるかどうかを確認することです。次に、その userId を使用して、一致するUser
ものをキャッシュからフェッチします。現在の有効期限が切れた場合に備えて、 にUser
は が含まれます。キャッシュに Cookieがない場合、またはキャッシュから一致する Cookie を取得できない場合は、新しいセッションを開始し、アプリケーションのランディング ページにリダイレクトします。refresh_token
access_token
userId
user
ユーザーがキャッシュから正常に取得された場合、キャッシュからの取得を試みaccess_token
ます。そこにある場合は、 、、およびWrappedRequest
を含むオブジェクトを作成します。キャッシュにない場合は、Google への Web サービス呼び出しを行い、新しいを取得します。これはキャッシュに保持されてから、request
user
access_token
access_token
WrappedRequest
を使用して非同期リクエストを作成するには、次のように( の場合と同じ)Authenticated
を追加するだけです。.apply
Action
def testing123 = Authenticated.async {
Future.successful { Ok("testing 123") }
}
Play 2.2.0 で動作する更新されたトレイトは次のとおりです。
import controllers.routes
import models.User
import play.api.cache.Cache
import play.api.libs.concurrent.Execution.Implicits.defaultContext
import play.api.mvc._
import play.api.Play.current
import scala.concurrent.Future
trait Authenticate extends GoogleOAuth {
case class AuthenticatedRequest[A](user: User, accessToken: String, request: Request[A])
extends WrappedRequest[A](request)
val startOver: Future[SimpleResult] = Future {
Results.Redirect(routes.Application.index()).withNewSession
}
object Authenticated extends ActionBuilder[AuthenticatedRequest] {
def invokeBlock[A](request: Request[A],
block: (AuthenticatedRequest[A] => Future[SimpleResult])) = {
request.session.get(userName).map { implicit userId =>
Cache.getAs[User](userKey).map { user =>
Cache.getAs[String](accessTokenKey).map { accessToken =>
block(AuthenticatedRequest(user, accessToken, request))
}.getOrElse { // user's accessToken has expired, so do WS call to Google for another one
requestNewAccessToken(user.token).flatMap { response =>
persistAccessToken(response).map { accessToken =>
block(AuthenticatedRequest(user, accessToken, request))
}.getOrElse(startOver)
}
}
}.getOrElse(startOver) // user not found in Cache
}.getOrElse(startOver) // userName not found in session
}
}
}