山崎屋の技術メモ

IT業界で働く中でテクノロジーを愛するSIerのシステムエンジニア👨‍💻 | AndroidとWebアプリの二刀流🧙‍♂️ | コードの裏にあるストーリーを綴るブログ執筆者✍️ | 日々進化するデジタル世界で学び続ける探究者🚀 | #TechLover #CodeArtisan、気になること、メモしておきたいことを書いていきます。

SNS 認証を実装する。Google 編。Java & Tymeleaf。

Web サービスを利用する際、いちいちアカウントを作っていたのでは管理が面倒です。

なので最近は Google・AppleID・Line・Yahoo・Facebook 等のアカウントでログインできるようにしているサイトが多くみられます。




今回は Google アカウントでの認証を Web サービスに実装したいと思います。

OAuth 同意画面を作成する

ユーザに表示するための Google 同意画面に表示する内容を決めます。

Google Cloud コンソールで左上のナビゲーションメニューから「API と サービス」→「OAuth同意画面」を選択します。

アプリに関する情報を色々聞かれます。
今回はアプリ名・ユーザサポートメール・メールアドレスの必須項目のみ入力しました。


全 3 ページの入力を終わらせ、ダッシュボードに戻ります。

次にすべての Google ユーザが利用できるようにアプリを公開しておきます。

以上で OAuth 同意画面の設定が完了しました。

アプリのクライアントIDを取得

Google に Web サービス固有のクライアントIDを発行してもらいます。
 
Google Cloud コンソールで左上のナビゲーションメニューから「API と サービス」→「認証情報」を選択します。

画面上の「認証情報を作成」→「OAuth クライアント ID」を選択します。

アプリケーションの種類に「ウェブアプリケーション」を選択し、任意の「名前」を入力します。

次に「承認済みの JavaScript 生成元」を登録します。
今回はテスト用の「http://localhost」と「http://localhost:8080」を追加します。
テストで使うのは「http://localhost:8080」だけなのですが、なぜか「http://localhost」も必要になるようです。

ソース:
設定  |  Authentication  |  Google for Developers

「承認済みのリダイレクト URI」は指定なしとしました。

作成ボタンをクリックすると完了画面が表示されます。

これで Google Cloud Platform 側での設定は以上です。
あとはソースを準備します。

アプリのソースを準備する

まず、Spring Initializr を使って次のような雛形を作成しました。
https://start.spring.io/

作成された ZIP(雛形)を適当なフォルダに展開します。

雛形から次の修正を行いました。

  • pom.xml に依存ライブラリを追加する。
  • index.html を作成する。(Thymeleafのテンプレート)
  • DemoApplication.java を修正する。()

最終的なフォルダ・ファイル構成はこうなりました。

追加・修正したモジュールの全量を貼っておきます。

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.7.14</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>demo</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>11</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>com.google.api-client</groupId>
			<artifactId>google-api-client</artifactId>
			<version>2.0.0</version>
		</dependency>
		<dependency>
			<groupId>com.google.http-client</groupId>
			<artifactId>google-http-client-gson</artifactId>
			<version>1.43.3</version>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>



index.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">

<head>
	<meta charset="UTF-8">
	<script>
		// ユーザがログイン操作した時のイベントハンドラ
		window.handleCredentialResponse = (response) => {
			// hidden に response の credential をセットして、フォームを submit する。
			document.getElementById("credential").value = response.credential;
			document.getElementById("credentialForm").submit();
		}
	</script>
	<title>SNS Login Sample</title>
</head>

<body>
	<script src="https://accounts.google.com/gsi/client" async defer></script>
	<div id="g_id_onload" data-client_id="(Googleから発行されたクライアントID)"
		data-callback="handleCredentialResponse"  data-ux_mode="popup">
	</div>
	<div class="g_id_signin" data-type="standard" data-size="large" data-theme="filled_blue" data-text="sign_in_with"
		data-shape="rectangular" data-logo_alignment="left">
	</div>
	<form id="credentialForm" action="http://localhost:8080/next" method="post">
		<input type="hidden" id="credential" name="credential" required>
	</form>
</body>

</html>


DemoApplication.java

package com.example.demo;

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Collections;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken.Payload;
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;

@SpringBootApplication
@Controller
public class DemoApplication {

	public static void main(String[] args) {
		SpringApplication.run(DemoApplication.class, args);
	}

	@GetMapping("/")
	public String hello(Model model) {
		return "index";
	}

	@PostMapping("/next")
	@ResponseBody
	public String next(@RequestParam("credential") String credential, Model model) {
		GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(new NetHttpTransport(),
				GsonFactory.getDefaultInstance())
				.setAudience(Collections
						.singletonList("(Googleから発行されたクライアントID)"))
				.build();

		GoogleIdToken idToken = null;
		try {
			// credential をデコードして検証する
			idToken = verifier.verify(credential);
		} catch (GeneralSecurityException | IOException e) {
			e.printStackTrace();
		}
		String result = null;
		if (idToken != null) {
			Payload payload = idToken.getPayload();

			String userId = payload.getSubject();
			String email = payload.getEmail();
			boolean emailVerified = Boolean.valueOf(payload.getEmailVerified());
			String name = (String) payload.get("name");
			String pictureUrl = (String) payload.get("picture");
			String locale = (String) payload.get("locale");
			String familyName = (String) payload.get("family_name");
			String givenName = (String) payload.get("given_name");
			
			result = "userId = " +  userId + "<br>" + 
			         "email = " + email + "<br>" +
			         "emailVerified = " + emailVerified + "<br>" +
			         "name = " + name + "<br>" +
			         "pictureUrl = " + pictureUrl + "<br>" +
			         "locale = " + locale + "<br>" +
			         "familyName = " + familyName + "<br>" +
			         "givenName = " + givenName;
		} else {
			System.out.println("Invalid ID token.");
		}
		return result;
	}
}

実行してみる

ローカルで埋め込み Tomcat を起動します。
ここでは詳しく説明しませんが、コマンドプロンプトで「mvn spring-boot:run」を実行します。

http://localhost:8080」にアクセスします。「Google でログイン」のボタンが表示されていることがわかります。


ボタンを押すと Google ログインのポップアップが表示されます。


ログイン処理を完了すると Google に登録されたユーザ情報が表示されました。

 

まとめ

アカウント管理の煩雑さを避けるため、Googleアカウントや他のSNSアカウントを使用してログインできるようにすることが一般的になっています。
この記事では、Googleアカウントを利用した認証を実装する手順を紹介しました。

それでは!!