구글 로그인 노트 2: Google Sign-In 남기기

개시하다


Google로 로그인하는 몇 가지 방법이 있습니다.구형 Google Sign-In을 사용하여 구현을 시도한 노트입니다.
✅Google Sign-In
  • 기존 Google 로그인
  • g-signin2
  • 다음 내용을 쓰지 않았습니다
    ❌ Google Identity Services(g_id_signin)

  • 새 Google 로그인 여기에 →Google 로그인 노트 1: Google Identity Services
  • ❌ Spring Security OAuth2/OpenID Connect Client 를 사용하여 설치
  • 여기 참조→로그인 노트 3: Spring Security OAuth2/OpenID Connect Client
  • ❌ macOS 이외의 설치
    ❌ Google One Tap 정보
    ❌ Kotlin(Java) 이외의 설치
    주의
    구글의 지원은 2023년 3월 31일에 끝난다
  • 웹용 Google Sign-In JavaScript 플랫폼 라이브러리 제공 종료 정보
  • 앞으로 Google Identity Services 사용하세요.
    참고 자료
  • Integrating Google Sign-In into your web app
  • Google Sign-In JavaScript client reference
  • 컨디션


    HTML+JavaScript로 구성된 프런트엔드는 Spring Boot & Kottlin으로 설치
  • macOS Big Sur
  • Kotlin
  • Spring Boot
  • 차리다


    Spring Boot 프로젝트 제작


    spring initializr에서 다음 설정에 따라 프로젝트를 만듭니다.
  • Project
  • Gradle Project
  • Language
  • Kotlin
  • Dependencies
  • Spring Web
  • Thymeleaf
  • 클라이언트 ID 가져오기


    Google Cloud Platform에서 클라이언트 ID 가져오기
    아래 설명을 참고하시오.
    Get your Google API client ID
    다음 내용 설정

  • 승인된 JavaScript 생성 소스 설정http://localhosthttp://localhost:8080 두 개
  • 이루어지다


    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_idcontent에서 획득한 클라이언트 ID를 설정합니다.
  • g-signin2data-onsuccess 로그인이 완료된 호출 함수 설정 (JavaScritp)

  • 버튼 설계 조정 등 자세한 내용은 Building a custom Google Sign-In button 참조
  • Backend - Kotlin


    상기 index."}"을 표시하는 컨트롤러 만들기
    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.> 에 호출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 설명서를 참조하십시오.
  • OpenID Connect Core 1.0 일본어 번역
  • OpenID Connect Basic Center Implementer's Guide1.0 일본어 번역
  • OpenID Connect Discovery 1.0

  • ID 토큰의 신뢰성을 확인하려면 Decode(Verify)가 필요합니다.

  • ID 토큰 Verify 구글 IdToken Verifier 사용
  • Verify the Google ID token on your server side
  • setAudience에 클라이언트 ID 설정

  • ID 토큰의 Verify가 성공하면 사용자 정보를 얻을 수 있습니다.
  • subject 사용자를 식별하는 ID

  • ID 토큰에서 추출한 사용자 정보 세그먼트를 사용자 info 페이지로 저장 및 리디렉션
  • 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의 Sign InController가 표시됩니다.signin 실행 후 사용자 info.건너뛰다
    UserInfo 화면

    수고하셨습니다.

  • Google Identity Services와 대체로 같지만 popup이 아니라 Redirect의 방법이 불분명하다
  • 체불 버튼이 귀찮은 것 같아
  • 좋은 웹페이지 즐겨찾기