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: ลงชื่อเข้าใช้ผู้ใช้

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

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

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

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

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

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

<!-- 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 API ในนามของผู้ใช้และ (ไม่บังคับ) จัดเก็บโทเค็นการรีเฟรชเพื่อรับโทเค็นเพื่อการเข้าถึงใหม่เมื่อโทเค็นเพื่อการเข้าถึงหมดอายุ

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

เช่น

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']