Googleでログインお勉強メモ2:レガシーGoogle Sign-In
はじめに
Googleでログインする方法はいくつかあります。これは レガシーGoogle Sign-In を使って実装をお試ししたメモです
✅Google Sign-In
- レガシーなGoogleログイン
- g-signin2
以下のことは書いていません
❌ Google Identity Services(g_id_signin)
❌ Spring Security OAuth2/OpenID Connect Client を使った実装
❌ macOS以外の実装
❌ Google One Tapについて
❌ Kotlin(Java)以外の実装
注意
Googleのサポートは2023 年 3 月 31 日で終了とのこと
今後は Google Identity Services を使いましょう
参考
環境
フロントはHTML+JavaScriptで構成する
バックエンドは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 生成元 には
http://localhost
とhttp://localhost:8080
の2つを設定する
実装
1. Sign-In With Googleボタンを配置する
Front - HTML
resources/templates/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Google Sign-In Demo</title>
</head>
<body>
<script src="https://apis.google.com/js/platform.js" async defer></script>
<meta name="google-signin-client_id" content="CLIENT ID">
<div class="g-signin2" data-onsuccess="onSignIn"></div>
</body>
</html>
-
google-signin-client_id
のcontent
に取得したクライアントID(xxxx.apps.googleusercontent.com)を設定する -
g-signin2
のdata-onsuccess
はログイン完了のコールバック関数(JavaScritp)を設定する -
ボタンのデザイン調整など細かい情報は Building a custom Google Sign-In buttonを参照のこと
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でログインは動作します。(ログイン完了後のコールバック関数が無いのでエラーになります)
ログイン画面
2. ログイン完了コールバックを実装する
Googleでログイン完了した時のコールバックを作成します。
フロントのJavaScritpでコールバックを受けてバックエンドに処理を移します。
Front - HTML
- index.htmlにコールバック
onSignIn
を実装する -
auth2.disconnect()
でログイン状態を保持しないようにする -
googleUser.getAuthResponse()
でIDトークンを取得しバックエンドhttp://localhost:8080/signin
に送る- →
SignInController
- →
- バックエンドから応答が来たら
http://localhost:8080/userinfo
にリダイレクトする- →
UserInfoController
- →
resources/templates/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Google Sign-In Demo</title>
</head>
<body>
<script src="https://apis.google.com/js/platform.js" async defer></script>
<meta name="google-signin-client_id" content="CLIENT ID">
<div class="g-signin2" data-onsuccess="onSignIn"></div>
<script>
function onSignIn(googleUser) {
console.log('onSignIn.');
var auth2 = gapi.auth2.getAuthInstance();
auth2.disconnect();
// Get ID Token
var id_token = googleUser.getAuthResponse().id_token;
// Send ID Token to Backend
var xhr = new XMLHttpRequest();
xhr.open('POST', 'http://localhost:8080/signin');
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onload = function() {
if(xhr.readyState == 4 && xhr.status == 200){
// Redirect
window.location.href = 'http://localhost:8080/userinfo';
}else{
console.log('Error');
}
};
xhr.send('credential=' + id_token);
}
</script>
</body>
</html>
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
-
フロントから送られたIDトークンを受け取って検証する
-
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.http.HttpStatus
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.ResponseStatus
import org.springframework.web.bind.annotation.RestController
import javax.servlet.http.HttpServletRequest
@RestController
class SignInController {
@PostMapping("signin")
@ResponseStatus(HttpStatus.OK)
fun signIn(
@RequestParam("credential") credential: String,
request: HttpServletRequest,
) {
// 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("locale", payload["locale"])
session.setAttribute("family_name", payload["family_name"])
session.setAttribute("given_name", payload["given_name"])
return
}
}
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>pictureUrl:<span th:text="${picture}"></span></div>
<div>locale:<span th:text="${locale}"></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 に遷移する
UserInfo画面
おつかれさまでした
- Google Identity Servicesと大体一緒だけどpopupではなく、redirectにする方法が不明
- ボタンをカスマイズするのが果てしなく面倒そう
Discussion