Google Sign-In สำหรับแอปฝั่งเซิร์ฟเวอร์

หากต้องการใช้บริการของ Google ในนามของผู้ใช้เมื่อผู้ใช้ออฟไลน์ คุณต้องใช้ขั้นตอนฝั่งเซิร์ฟเวอร์แบบผสมที่ผู้ใช้ให้สิทธิ์แอปในฝั่งไคลเอ็นต์โดยใช้ไคลเอ็นต์ JavaScript API และส่งรหัสการให้สิทธิ์แบบครั้งเดียวพิเศษไปยังเซิร์ฟเวอร์ของคุณ เซิร์ฟเวอร์ของคุณจะแลกเปลี่ยนโค้ดแบบใช้ครั้งเดียวนี้เพื่อรับการเข้าถึงและรีเฟรชโทเค็นจาก Google เพื่อให้เซิร์ฟเวอร์เรียก API ของตัวเองได้ ซึ่งทำได้ขณะที่ผู้ใช้ออฟไลน์ ขั้นตอนของโค้ดแบบใช้ครั้งเดียวนี้มีข้อดีด้านความปลอดภัยมากกว่าขั้นตอนฝั่งเซิร์ฟเวอร์เพียงอย่างเดียวและมากกว่าการส่งโทเค็นเพื่อการเข้าถึงไปยังเซิร์ฟเวอร์

ด้านล่างนี้แสดงขั้นตอนการลงชื่อเข้าใช้เพื่อรับโทเค็นเพื่อการเข้าถึงสำหรับแอปพลิเคชันฝั่งเซิร์ฟเวอร์

รหัสแบบใช้ครั้งเดียวมีข้อดีด้านความปลอดภัยหลายประการ เมื่อใช้รหัส Google จะ มอบโทเค็นไปยังเซิร์ฟเวอร์โดยตรงโดยไม่ต้องมีตัวกลาง แม้ว่าเราจะไม่แนะนำให้ใช้โค้ดที่รั่วไหล แต่ใช้งานได้ยากมากโดยไม่ต้องใช้รหัสลับไคลเอ็นต์ เก็บรหัสลับไคลเอ็นต์ไว้!

การใช้ขั้นตอนการใช้โค้ดแบบครั้งเดียว

ปุ่ม Google Sign-In จะมีทั้งโทเค็นเพื่อการเข้าถึงและรหัสการให้สิทธิ์ โค้ดดังกล่าวเป็นรหัสแบบใช้ครั้งเดียวที่เซิร์ฟเวอร์จะแลกเปลี่ยนกับเซิร์ฟเวอร์ของ Google เป็นโทเค็นเพื่อการเข้าถึงได้

โค้ดตัวอย่างต่อไปนี้แสดงวิธีการดำเนินการขั้นตอนการใช้โค้ดแบบครั้งเดียว

คุณต้องทำดังนี้เพื่อตรวจสอบสิทธิ์ Google Sign-In ด้วยรหัสแบบครั้งเดียว

ขั้นตอนที่ 1: สร้างรหัสไคลเอ็นต์และรหัสลับไคลเอ็นต์

หากต้องการสร้างรหัสไคลเอ็นต์และรหัสลับไคลเอ็นต์ ให้สร้างโปรเจ็กต์คอนโซล Google API, ตั้งค่ารหัสไคลเอ็นต์ OAuth และลงทะเบียนต้นทางของ JavaScript ดังนี้

  1. ไปที่คอนโซล Google API

  2. จากเมนูแบบเลื่อนลงของโปรเจ็กต์ ให้เลือกโปรเจ็กต์ที่มีอยู่หรือสร้างโปรเจ็กต์ใหม่โดยเลือกสร้างโปรเจ็กต์ใหม่

  3. ในแถบด้านข้างใต้ "API และบริการ" ให้เลือกข้อมูลเข้าสู่ระบบ แล้วคลิกกำหนดค่าหน้าจอคำยินยอม

    เลือกอีเมล ระบุชื่อผลิตภัณฑ์ แล้วกดบันทึก

  4. ในแท็บข้อมูลเข้าสู่ระบบ ให้เลือกรายการแบบเลื่อนลงสร้างข้อมูลเข้าสู่ระบบ แล้วเลือกรหัสไคลเอ็นต์ OAuth

  5. ในส่วนประเภทแอปพลิเคชัน ให้เลือกเว็บแอปพลิเคชัน

    ลงทะเบียนต้นทางที่แอปของคุณได้รับอนุญาตให้เข้าถึง Google APIs ดังนี้ ต้นทางเป็นการรวมโปรโตคอล ชื่อโฮสต์ และพอร์ตที่ไม่ซ้ำกัน

    1. ในช่องต้นทาง JavaScript ที่ได้รับอนุญาต ให้ป้อนต้นทางของแอป คุณสามารถป้อนต้นทางหลายรายการเพื่อให้แอปทำงานในโปรโตคอล โดเมน หรือโดเมนย่อยที่แตกต่างกันได้ คุณไม่สามารถใช้ไวลด์การ์ด ในตัวอย่างด้านล่าง URL ที่ 2 อาจเป็น URL เวอร์ชันที่ใช้งานจริง

      http://localhost:8080
      https://myproductionurl.example.com
      
    2. ช่อง URI การเปลี่ยนเส้นทางที่ได้รับอนุญาตไม่จำเป็นต้องมีค่า URI การเปลี่ยนเส้นทางไม่ใช้กับ JavaScript API

    3. กดปุ่มสร้าง

  6. คัดลอกรหัสไคลเอ็นต์จากกล่องโต้ตอบไคลเอ็นต์ OAuth ที่ปรากฏขึ้น รหัสไคลเอ็นต์ช่วยให้แอปของคุณเข้าถึง Google API ที่เปิดใช้อยู่ได้

ขั้นตอนที่ 2: ใส่ไลบรารีแพลตฟอร์มของ Google ในหน้าเว็บ

ใส่สคริปต์ต่อไปนี้ซึ่งสาธิตฟังก์ชันที่ไม่ระบุชื่อที่แทรกสคริปต์ลงใน DOM ของหน้าเว็บ index.html นี้

<!-- The top of file index.html -->
<html itemscope itemtype="http://schema.org/Article">
<head>
  <!-- BEGIN Pre-requisites -->
  <script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js">
  </script>
  <script src="https://apis.google.com/js/client:platform.js?onload=start" async defer>
  </script>
  <!-- END Pre-requisites -->

ขั้นตอนที่ 3: เริ่มต้นออบเจ็กต์ GoogleAuth

โหลดไลบรารี auth2 และเรียกใช้ gapi.auth2.init() เพื่อเริ่มต้นออบเจ็กต์ GoogleAuth ระบุรหัสไคลเอ็นต์และขอบเขตที่ต้องการขอเมื่อเรียกใช้ init()

<!-- Continuing the <head> section -->
  <script>
    function start() {
      gapi.load('auth2', function() {
        auth2 = gapi.auth2.init({
          client_id: 'YOUR_CLIENT_ID.apps.googleusercontent.com',
          // Scopes to request in addition to 'profile' and 'email'
          //scope: 'additional_scope'
        });
      });
    }
  </script>
</head>
<body>
  <!-- ... -->
</body>
</html>

ขั้นตอนที่ 4: เพิ่มปุ่มลงชื่อเข้าใช้ลงในหน้าเว็บของคุณ

เพิ่มปุ่มลงชื่อเข้าใช้ลงในหน้าเว็บ และแนบเครื่องจัดการคลิกเพื่อเรียกใช้ grantOfflineAccess() เพื่อเริ่มขั้นตอนการใช้โค้ดแบบครั้งเดียว

<!-- Add where you want your sign-in button to render -->
<!-- Use an image that follows the branding guidelines in a real app -->
<button id="signinButton">Sign in with Google</button>
<script>
  $('#signinButton').click(function() {
    // signInCallback defined in step 6.
    auth2.grantOfflineAccess().then(signInCallback);
  });
</script>

ขั้นตอนที่ 5: ลงชื่อเข้าใช้ผู้ใช้

ผู้ใช้คลิกปุ่มลงชื่อเข้าใช้และให้สิทธิ์แอปเข้าถึงสิทธิ์ที่คุณขอ จากนั้นฟังก์ชัน Callback ที่คุณระบุในเมธอด grantOfflineAccess().then() จะส่งผ่านออบเจ็กต์ JSON ที่มีรหัสการให้สิทธิ์ เช่น

{"code":"4/yU4cQZTMnnMtetyFcIWNItG32eKxxxgXXX-Z4yyJJJo.4qHskT-UtugceFc0ZRONyF4z7U4UmAI"}

ขั้นตอนที่ 6: ส่งรหัสการให้สิทธิ์ไปยังเซิร์ฟเวอร์

code คือรหัสแบบใช้ครั้งเดียวที่เซิร์ฟเวอร์ของคุณแลกเปลี่ยนเป็นโทเค็นเพื่อการเข้าถึงและโทเค็นการรีเฟรชได้ คุณจะรับโทเค็นการรีเฟรชได้หลังจากที่ผู้ใช้เห็นกล่องโต้ตอบการให้สิทธิ์ซึ่งขอการเข้าถึงแบบออฟไลน์แล้วเท่านั้น หากระบุ prompt ของ select-account ใน OfflineAccessOptions ในขั้นตอนที่ 4 แล้ว คุณต้องจัดเก็บโทเค็นการรีเฟรชที่ดึงมาเพื่อใช้ในภายหลังเนื่องจากการแลกเปลี่ยนครั้งต่อๆ ไปจะส่งกลับ null สำหรับโทเค็นการรีเฟรช ซึ่งขั้นตอนนี้ช่วยเพิ่มความปลอดภัยให้มากกว่าขั้นตอน OAuth 2.0 มาตรฐาน

โทเค็นเพื่อการเข้าถึงจะส่งคืนพร้อมการแลกเปลี่ยนรหัสการให้สิทธิ์ที่ถูกต้องเสมอ

สคริปต์ต่อไปนี้จะกำหนดฟังก์ชัน Callback สำหรับปุ่มลงชื่อเข้าใช้ เมื่อลงชื่อเข้าใช้สำเร็จ ฟังก์ชันจะจัดเก็บโทเค็นเพื่อการเข้าถึงสำหรับการใช้งานฝั่งไคลเอ็นต์และส่งรหัสแบบใช้ครั้งเดียวไปยังเซิร์ฟเวอร์ของคุณในโดเมนเดียวกัน

<!-- Last part of BODY element in file index.html -->
<script>
function signInCallback(authResult) {
  if (authResult['code']) {

    // Hide the sign-in button now that the user is authorized, for example:
    $('#signinButton').attr('style', 'display: none');

    // Send the code to the server
    $.ajax({
      type: 'POST',
      url: 'http://example.com/storeauthcode',
      // Always include an `X-Requested-With` header in every AJAX request,
      // to protect against CSRF attacks.
      headers: {
        'X-Requested-With': 'XMLHttpRequest'
      },
      contentType: 'application/octet-stream; charset=utf-8',
      success: function(result) {
        // Handle or verify the server response.
      },
      processData: false,
      data: authResult['code']
    });
  } else {
    // There was an error.
  }
}
</script>

ขั้นตอนที่ 7: แลกเปลี่ยนรหัสการให้สิทธิ์สำหรับโทเค็นเพื่อการเข้าถึง

แลกเปลี่ยนรหัสการตรวจสอบสิทธิ์ในเซิร์ฟเวอร์เพื่อการเข้าถึงและรีเฟรชโทเค็น ใช้โทเค็นเพื่อการเข้าถึงเพื่อเรียกใช้ Google APIs ในนามของผู้ใช้ หรือจัดเก็บโทเค็นการรีเฟรชเพื่อขอโทเค็นเพื่อการเข้าถึงใหม่เมื่อโทเค็นเพื่อการเข้าถึงหมดอายุ

หากขอสิทธิ์เข้าถึงโปรไฟล์ คุณจะได้รับโทเค็นรหัสที่มีข้อมูลโปรไฟล์พื้นฐานสำหรับผู้ใช้ด้วย

เช่น

Java
// (Receive authCode via HTTPS POST)


if (request.getHeader("X-Requested-With") == null) {
  // Without the `X-Requested-With` header, this request could be forged. Aborts.
}

// Set path to the Web application client_secret_*.json file you downloaded from the
// Google API Console: https://console.cloud.google.com/apis/credentials
// You can also find your Web application client ID and client secret from the
// console and specify them directly when you create the GoogleAuthorizationCodeTokenRequest
// object.
String CLIENT_SECRET_FILE = "/path/to/client_secret.json";

// Exchange auth code for access token
GoogleClientSecrets clientSecrets =
    GoogleClientSecrets.load(
        JacksonFactory.getDefaultInstance(), new FileReader(CLIENT_SECRET_FILE));
GoogleTokenResponse tokenResponse =
          new GoogleAuthorizationCodeTokenRequest(
              new NetHttpTransport(),
              JacksonFactory.getDefaultInstance(),
              "https://oauth2.googleapis.com/token",
              clientSecrets.getDetails().getClientId(),
              clientSecrets.getDetails().getClientSecret(),
              authCode,
              REDIRECT_URI)  // Specify the same redirect URI that you use with your web
                             // app. If you don't have a web version of your app, you can
                             // specify an empty string.
              .execute();

String accessToken = tokenResponse.getAccessToken();

// Use access token to call API
GoogleCredential credential = new GoogleCredential().setAccessToken(accessToken);
Drive drive =
    new Drive.Builder(new NetHttpTransport(), JacksonFactory.getDefaultInstance(), credential)
        .setApplicationName("Auth Code Exchange Demo")
        .build();
File file = drive.files().get("appfolder").execute();

// Get profile info from ID token
GoogleIdToken idToken = tokenResponse.parseIdToken();
GoogleIdToken.Payload payload = idToken.getPayload();
String userId = payload.getSubject();  // Use this value as a key to identify a user.
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");
Python
from apiclient import discovery
import httplib2
from oauth2client import client

# (Receive auth_code by HTTPS POST)


# If this request does not have `X-Requested-With` header, this could be a CSRF
if not request.headers.get('X-Requested-With'):
    abort(403)

# Set path to the Web application client_secret_*.json file you downloaded from the
# Google API Console: https://console.cloud.google.com/apis/credentials
CLIENT_SECRET_FILE = '/path/to/client_secret.json'

# Exchange auth code for access token, refresh token, and ID token
credentials = client.credentials_from_clientsecrets_and_code(
    CLIENT_SECRET_FILE,
    ['https://www.googleapis.com/auth/drive.appdata', 'profile', 'email'],
    auth_code)

# Call Google API
http_auth = credentials.authorize(httplib2.Http())
drive_service = discovery.build('drive', 'v3', http=http_auth)
appfolder = drive_service.files().get(fileId='appfolder').execute()

# Get profile info from ID token
userid = credentials.id_token['sub']
email = credentials.id_token['email']