使用后端服务器进行身份验证

如果您将Google登录用于与后端服务器通信的应用或网站,则可能需要识别服务器上当前登录的用户。为了安全地执行此操作,在用户成功登录后,请使用HTTPS将用户的ID令牌发送到您的服务器。然后,在服务器上,验证ID令牌的完整性,并使用令牌中包含的用户信息建立会话或创建新帐户。

将ID令牌发送到您的服务器

首先,当用户登录时,获取其ID令牌:

  1. 配置Google登录后,请调用requestIdToken方法并将其传递给 服务器的Web客户端ID

    // Request only the user's ID token, which can be used to identify the
    // user securely to your backend. This will contain the user's basic
    // profile (name, profile picture URL, etc) so you should not need to
    // make an additional call to personalize your application.
    GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
            .requestIdToken(getString(R.string.server_client_id))
            .requestEmail()
            .build();
  2. 当您的应用启动时,请通过调用silentSignIn来检查用户是否已经在此设备或其他设备上使用Google登录了您的应用:

    GoogleSignIn.silentSignIn()
        .addOnCompleteListener(
            this,
            new OnCompleteListener<GoogleSignInAccount>() {
              @Override
              public void onComplete(@NonNull Task<GoogleSignInAccount> task) {
                handleSignInResult(task);
              }
            });
  3. 如果用户无法静默登录,请提供正常的退出体验,并为用户提供登录选项。当用户登录后GoogleSignInAccount在登录意图的活动结果中获取用户的GoogleSignInAccount

    // This task is always completed immediately, there is no need to attach an
    // asynchronous listener.
    Task<GoogleSignInAccount> task = GoogleSignIn.getSignedInAccountFromIntent(data);
    handleSignInResult(task);
  4. 用户静默或明确GoogleSignInAccountGoogleSignInAccount对象获取ID令牌:

    private void handleSignInResult(@NonNull Task<GoogleSignInAccount> completedTask) {
        try {
            GoogleSignInAccount account = completedTask.getResult(ApiException.class);
            String idToken = account.getIdToken();
    
            // TODO(developer): send ID Token to server and validate
    
            updateUI(account);
        } catch (ApiException e) {
            Log.w(TAG, "handleSignInResult:error", e);
            updateUI(null);
        }
    }

然后,使用HTTPS POST请求将ID令牌发送到您的服务器:

HttpClient httpClient = new DefaultHttpClient();
HttpPost httpPost = new HttpPost("https://yourbackend.example.com/tokensignin");

try {
  List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(1);
  nameValuePairs.add(new BasicNameValuePair("idToken", idToken));
  httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs));

  HttpResponse response = httpClient.execute(httpPost);
  int statusCode = response.getStatusLine().getStatusCode();
  final String responseBody = EntityUtils.toString(response.getEntity());
  Log.i(TAG, "Signed in as: " + responseBody);
} catch (ClientProtocolException e) {
  Log.e(TAG, "Error sending ID token to backend.", e);
} catch (IOException e) {
  Log.e(TAG, "Error sending ID token to backend.", e);
}

验证ID令牌的完整性

通过HTTPS POST收到ID令牌后,必须验证令牌的完整性。要验证令牌是否有效,请确保满足以下条件:

  • 该ID令牌已由Google正确签名。使用Google的公共密钥(以JWKPEM格式提供)来验证令牌的签名。这些键会定期旋转。检查响应中的Cache-Control标头,以确定何时应再次检索它们。
  • ID令牌中aud的值等于您应用的客户端ID之一。必须进行此检查,以防止发布给恶意应用程序的ID令牌被用来访问有关您应用程序后端服务器上同一用户的数据。
  • 的值iss在ID令牌等于accounts.google.comhttps://accounts.google.com
  • ID令牌的到期时间( exp )尚未过去。
  • 如果您只想限制您的G Suite域成员访问,请验证ID令牌是否具有与您的G Suite域名匹配的hd声明。

强烈建议您使用适合平台的Google API客户端库或通用JWT库,而不是编写自己的代码来执行这些验证步骤。为了进行开发和调试,您可以调用我们的tokeninfo验证端点。

使用Google API客户端库

建议使用Google API客户端库之一(例如JavaNode.jsPHPPython )在生产环境中验证Google ID令牌。

爪哇

要在Java中验证ID令牌,请使用GoogleIdTokenVerifier对象。例如:

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;

...

GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(transport, jsonFactory)
    // Specify the CLIENT_ID of the app that accesses the backend:
    .setAudience(Collections.singletonList(CLIENT_ID))
    // Or, if multiple clients access the backend:
    //.setAudience(Arrays.asList(CLIENT_ID_1, CLIENT_ID_2, CLIENT_ID_3))
    .build();

// (Receive idTokenString by HTTPS POST)

GoogleIdToken idToken = verifier.verify(idTokenString);
if (idToken != null) {
  Payload payload = idToken.getPayload();

  // Print user identifier
  String userId = payload.getSubject();
  System.out.println("User ID: " + userId);

  // Get profile information from payload
  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");

  // Use or store profile information
  // ...

} else {
  System.out.println("Invalid ID token.");
}

GoogleIdTokenVerifier.verify()方法验证JWT签名, aud声明, iss声明和exp声明。

如果您想仅限制对G Suite域成员的访问,还可以通过检查Payload.getHostedDomain()方法返回的域名来验证hd声明。

Node.js

要在Node.js中验证ID令牌,请使用Node.js的Google身份验证库。安装库:

npm install google-auth-library --save
然后,调用verifyIdToken()函数。例如:

const {OAuth2Client} = require('google-auth-library');
const client = new OAuth2Client(CLIENT_ID);
async function verify() {
  const ticket = await client.verifyIdToken({
      idToken: token,
      audience: CLIENT_ID,  // Specify the CLIENT_ID of the app that accesses the backend
      // Or, if multiple clients access the backend:
      //[CLIENT_ID_1, CLIENT_ID_2, CLIENT_ID_3]
  });
  const payload = ticket.getPayload();
  const userid = payload['sub'];
  // If request specified a G Suite domain:
  // const domain = payload['hd'];
}
verify().catch(console.error);

verifyIdToken函数验证JWT签名, aud声明, exp声明和iss声明。

如果您只想限制您的G Suite网域成员访问,还请确认hd声明与您的G Suite网域名称相符。

的PHP

要在PHP中验证ID令牌,请使用适用于PHP的Google API客户端库。安装该库(例如,使用Composer):

composer require google/apiclient
然后,调用verifyIdToken()函数。例如:

require_once 'vendor/autoload.php';

// Get $id_token via HTTPS POST.

$client = new Google_Client(['client_id' => $CLIENT_ID]);  // Specify the CLIENT_ID of the app that accesses the backend
$payload = $client->verifyIdToken($id_token);
if ($payload) {
  $userid = $payload['sub'];
  // If request specified a G Suite domain:
  //$domain = $payload['hd'];
} else {
  // Invalid ID token
}

verifyIdToken函数验证JWT签名, aud声明, exp声明和iss声明。

如果您只想限制您的G Suite网域成员访问,还请确认hd声明与您的G Suite网域名称相符。

Python

要在Python中验证ID令牌,请使用verify_oauth2_token函数。例如:

from google.oauth2 import id_token
from google.auth.transport import requests

# (Receive token by HTTPS POST)
# ...

try:
    # Specify the CLIENT_ID of the app that accesses the backend:
    idinfo = id_token.verify_oauth2_token(token, requests.Request(), CLIENT_ID)

    # Or, if multiple clients access the backend server:
    # idinfo = id_token.verify_oauth2_token(token, requests.Request())
    # if idinfo['aud'] not in [CLIENT_ID_1, CLIENT_ID_2, CLIENT_ID_3]:
    #     raise ValueError('Could not verify audience.')

    # If auth request is from a G Suite domain:
    # if idinfo['hd'] != GSUITE_DOMAIN_NAME:
    #     raise ValueError('Wrong hosted domain.')

    # ID token is valid. Get the user's Google Account ID from the decoded token.
    userid = idinfo['sub']
except ValueError:
    # Invalid token
    pass

verify_oauth2_token函数验证JWT签名, aud声明和exp声明。您还必须通过检查verify_oauth2_token返回的对象来验证hd声明(如果适用)。如果多个客户端访问后端服务器,请手动验证aud声明。

调用tokeninfo端点

验证ID令牌签名以进行调试的一种简单方法是使用tokeninfo端点。调用此终结点涉及一个额外的网络请求,该请求会在您用自己的代码测试正确的验证和有效负载提取时为您执行大部分验证。它不适合在生产代码中使用,因为请求可能受到限制,否则可能会出现间歇性错误。

要使用tokeninfo端点验证ID令牌,请tokeninfo端点发出HTTPS POST或GET请求,然后在id_token参数中传递您的ID令牌。例如,要验证令牌“ XYZ123”,请发出以下GET请求:

https://oauth2.googleapis.com/tokeninfo?id_token=XYZ123

如果令牌已正确签名,并且issexp声明具有期望的值,您将获得HTTP 200响应,其中正文包含JSON格式的ID令牌声明。这是一个示例响应:

{
 // These six fields are included in all Google ID Tokens.
 "iss": "https://accounts.google.com",
 "sub": "110169484474386276334",
 "azp": "1008719970978-hb24n2dstb40o45d4feuo2ukqmcc6381.apps.googleusercontent.com",
 "aud": "1008719970978-hb24n2dstb40o45d4feuo2ukqmcc6381.apps.googleusercontent.com",
 "iat": "1433978353",
 "exp": "1433981953",

 // These seven fields are only included when the user has granted the "profile" and
 // "email" OAuth scopes to the application.
 "email": "testuser@gmail.com",
 "email_verified": "true",
 "name" : "Test User",
 "picture": "https://lh4.googleusercontent.com/-kYgzyAWpZzJ/ABCDEFGHI/AAAJKLMNOP/tIXL9Ir44LE/s99-c/photo.jpg",
 "given_name": "Test",
 "family_name": "User",
 "locale": "en"
}

如果您是G Suite客户,则可能对hd声明感兴趣,该声明表明了用户的托管域。这可用于将对资源的访问限制为仅某些域的成员。缺少此声明表明该用户不属于G Suite托管域。

创建一个帐户或会话

验证令牌后,请检查用户是否已在用户数据库中。如果是这样,请为用户建立一个经过身份验证的会话。如果该用户尚未在您的用户数据库中,请根据ID令牌有效负载中的信息创建新的用户记录,并为该用户建立会话。当您在应用中检测到新创建的用户时,可以提示用户提供所需的其他任何个人资料信息。

使用跨帐户保护保护用户的帐户安全

当您依靠Google登录用户时,您将自动受益于Google为保护用户数据而构建的所有安全功能和基础架构。但是,万一用户的Google帐户遭到入侵或发生其他重大安全事件(这种情况不太可能发生),您的应用也很容易受到攻击。为了更好地保护您的帐户免受任何重大安全事件的影响,请使用“跨帐户保护”接收来自Google的安全警报。收到这些事件后,您可以查看用户的Google帐户安全性的重要变化,然后可以对服务采取措施以保护您的帐户。