Googleでログインお勉強メモ1:Google Identity Services
はじめに
Googleでログインする方法はいくつかあります。これは Google Identity Services を使って実装をお試ししたメモです
✅ Google Identity Services
- 新しい Googleログイン
- g_id_signin
以下のことは書いていません
❌Google Sign-In(g-signin2)
❌ Spring Security OAuth2/OpenID Connect Client を使った実装
❌ macOS以外の実装
❌ Google One Tapについて
❌ JavaScriptでの実装
❌ Kotlin(Java)以外の実装
参考
環境
フロントはJavaScriptを使わずHTMLで構成する
バックエンドはSpring Boot & Kotlin で実装する
- macOS Big Sur
- Kotlin
- Spring Boot
準備
Spring Bootプロジェクト作成
spring initializr で以下の設定でプロジェクトを作成する
- Project
- Gradle Project
- Language
- Kotlin
- Dependencies
- Spring Web
- Thymeleaf
クライアントID取得
Google Cloud Platform で クライアント ID を取得する
以下の説明を参考にする
以下の設定をする
-
承認済みの JavaScript 生成元(Authorized JavaScript origins) には
http://localhost
とhttp://localhost:8080
の2つを設定する
実装
1. Sign-In With Googleボタンを配置する
Front - HTML
resources/templates/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Google Identity Services Demo</title>
</head>
<body>
<script src="https://accounts.google.com/gsi/client" async defer></script>
<div id="g_id_onload"
data-client_id="CLIENT ID"
data-login_uri="http://localhost:8080/signin"
data-ux_mode="popup"
data-auto_prompt="false">
</div>
<div class="g_id_signin"
data-type="standard"
data-size="large"
data-theme="outline"
data-text="sign_in_with"
data-shape="rectangular"
data-logo_alignment="left">
</div>
</body>
</html>
g_id_onload
-
data-client_id
には取得したクライアントID(xxxx.apps.googleusercontent.com)を設定する -
data-login_uri
はログイン完了のコールバックURLを設定する -
data-ux_mode
はpopup
かredirect
を設定する
g_id_signin
- 上記リンクの設定でお好みに調整する
Backend - Kotlin
上記 index.html を表示するコントローラを作成する
RootController.kt
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.GetMapping
@Controller
class RootController {
@GetMapping("/")
fun index(): String {
return "index"
}
}
動作確認
この時点でGoogleでログインは動作します。(ログイン完了後のエンドポイントが無いのでエラーになります)
Googleにログインしていないとき
Googleにログイン済みのとき
2. ログイン完了コールバックを実装する
Googleでログイン完了した時のコールバックを作成します。
Gradle
build.gradle.kts
- 依存関係に
com.google.api-client:google-api-client
を追加する
dependencies {
implementation("com.google.api-client:google-api-client:1.31.5")
implementation("org.springframework.boot:spring-boot-starter-thymeleaf")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
testImplementation("org.springframework.boot:spring-boot-starter-test")
}
Backend - Kotlin
-
ログイン後のコールバックには
credential
パラメータでIDトークン(JWT)がついてくる- IDトークンについてはOpenID Connectのドキュメントを参照のこと
-
このIDトークンをDecode(Verify)して真正性を確認する必要がある
-
IDトークンのVerifyは GoogleIdTokenVerifier を使う
- Verify the Google ID token on your server side
-
setAudience
にクライアントIDを設定する(自分宛てのものであることを確認する)
-
IDトークンのVerifyに成功すると中のユーザー情報が取り出せる
-
subject
がユーザーを特定するID
-
-
IDトークンから取り出したユーザー情報をセッション保存してuserinfoページにリダイレクトする
SignInController.kt
import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier
import com.google.api.client.http.javanet.NetHttpTransport
import com.google.api.client.json.gson.GsonFactory
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestParam
import javax.servlet.http.HttpServletRequest
@Controller
class SignInController {
@PostMapping("signin")
fun signIn(
@RequestParam("credential") credential: String,
request: HttpServletRequest,
): String {
// verify ID Token
val verifier = GoogleIdTokenVerifier.Builder(
NetHttpTransport(), GsonFactory.getDefaultInstance()
)
.setAudience(listOf("CLIENT ID"))
.build()
val idToken = verifier.verify(credential) ?: throw Exception()
val payload = idToken.payload
// Get profile information from payload
val session = request.getSession(true)
session.setAttribute("subject", payload.subject)
session.setAttribute("email", payload.email)
session.setAttribute("emailVerified", payload.emailVerified)
session.setAttribute("name", payload["name"])
session.setAttribute("picture", payload["picture"])
session.setAttribute("family_name", payload["family_name"])
session.setAttribute("given_name", payload["given_name"])
return "redirect:userinfo"
}
}
3. ログイン後ページを実装する
ログインしたユーザー情報を表示するページを実装します
Front - HTML
resources/templates/userinfo.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<html lang="en">
<head>
<meta charset="UTF-8">
<title>UserInfo</title>
</head>
<body>
<div>subject(User ID):<span th:text="${subject}"></span></div>
<div>email:<span th:text="${email}"></span></div>
<div>emailVerified:<span th:text="${emailVerified}"></span></div>
<div>name:<span th:text="${name}"></span></div>
<div>picture:<span th:text="${picture}"></span></div>
<div>family_name:<span th:text="${family_name}"></span></div>
<div>given_name:<span th:text="${given_name}"></span></div>
<a href="/">Back</a>
</body>
</html>
Backend - Kotlin
セッションからユーザー情報を取り出してフロントに引き渡します
UserInfoController.kt
import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.web.bind.annotation.GetMapping
import javax.servlet.http.HttpServletRequest
@Controller
class UserInfoController {
@GetMapping("userinfo")
fun userInfo(
request: HttpServletRequest,
model: Model
): String {
// Get User Info from session
val session = request.getSession(false) ?: throw Exception()
model.addAttribute("subject", session.getAttribute("subject"))
model.addAttribute("email", session.getAttribute("email"))
model.addAttribute("emailVerified", session.getAttribute("emailVerified"))
model.addAttribute("name", session.getAttribute("name"))
model.addAttribute("picture", session.getAttribute("picture"))
model.addAttribute("locale", session.getAttribute("locale"))
model.addAttribute("family_name", session.getAttribute("family_name"))
model.addAttribute("given_name", session.getAttribute("given_name"))
return "userinfo"
}
}
動作確認
Googleでログインすると Backend の SignInController.signin()が実行された後 userinfo.html に遷移する
4. 実際の実装は
実際の実装は以下のようになる
①Googleでログインをクリック
②Googleの画面でログインする
- 2要素認証などで
③ログイン後のコールバックでIDトークンをデコードする
④IDトークンからsubject(ユーザーID)などのユーザー情報を取得し、自アプリに登録済みのユーザーかチェックする
- 自アプリに登録済みであればそのユーザーでログインし、ログイン後の画面に遷移する
- 未登録であれば登録画面に遷移する(登録画面ではユーザー情報を入力補完する)
おつかれさまでした
- 実装は非常に簡単
- バックエンドのIDトークン検証のところだけOpenID Connectの知識があるといいかも
- レガシーなGoogleログインはIDトークンをVerifyしなくてもユーザー情報を取り出せたが今回は必ずVerifyが必要がなのでよりセキュアになっている印象
Discussion