在 objective-c 中刷新 Gmail API 访问令牌的正确方法
Right way to refresh Gmail API access token in objective-c
我通过该方法获得了访问令牌:
- (void)viewController:(GTMOAuth2ViewControllerTouch *)viewController
finishedWithAuth:(GTMOAuth2Authentication *)authResult
error:(NSError *)error {
if (error != nil) {
[self showAlert:@"Authentication Error" message:error.localizedDescription];
self.service.authorizer = nil;
}
else {
self.service.authorizer = authResult;
NSLog(@"Token: %@ id: %@", authResult.accessToken, authResult.userID);
[self makeGmailLabelVisibleWithToken:authResult.accessToken]; //make an authorized request to gmailAPI with the access token
[self dismissViewControllerAnimated:YES completion:nil];
}
}
因此,经过身份验证后它工作正常,但过了一会儿它停止工作(我猜是因为令牌已过期)。另外,如果我使用
[authResult refreshToken]
而不是
authResult.accessToken
不行。
那么刷新 Gmail 访问令牌的正确方法是什么?我应该使用哪种方法?
P.S: documentation 表示
- (void) refreshTokensWithHandler:(GIDAuthenticationHandler)handler
应该有帮助,但我还没有找到任何样本。
要获取刷新令牌,您必须为您的应用enable server-side APIA access。
"To obtain an access token and refresh token for your server, you can request a one-time authorization code that your server exchanges for these two tokens. You request the one-time code by specifying your server's client ID along with your other GIDSignIn parameters. After you successfully connect the user, you will find the one-time code as the auth parameter server_code accessible via the finishedWithAuth:error handler."
- 按照开始集成中的说明配置 iOS 应用项目。
- 定义您的应用委托的 application:didFinishLaunchingWithOptions: 方法,如上文启用登录中所述,但对于此实现,您将设置 serverClientID 属性,如下所示。
-(BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[GIDSignIn sharedInstance].clientID = @"APP_CLIENT_ID";
[GIDSignIn sharedInstance].serverClientID = @"SERVER_CLIENT_ID";
// Additional scopes, if any
// [GIDSignIn sharedInstance].scopes = @[ @"other_scope" ];
return YES;
}
- 用户登录后,获取一次性授权码:
-(void)signIn:(GIDSignIn *)signIn didSignInForUser:(GIDGoogleUser *)user
withError:(NSError *)error {
// Perform any operations on signed in user here.
// user.serverAuthCode now has a server authorization code!
}
- 使用 HTTPS POST.
将 serverAuthCode 字符串安全地传递到您的服务器
- 在您应用的后端服务器上,将授权码交换为访问和刷新令牌。使用访问令牌代表用户调用 Google API,并且可以选择存储刷新令牌以在访问令牌过期时获取新的访问令牌。
您可以使用 HTTP/REST 电话。
这是 Python 中的 HTTP 调用,只需使用 Objective-C 等价物即可。
import json
import flask
import requests
app = flask.Flask(__name__)
CLIENT_ID = '123456789.apps.googleusercontent.com'
CLIENT_SECRET = 'abc123' # Read from a file or environmental variable in a real app
SCOPE = 'https://www.googleapis.com/auth/drive.metadata.readonly'
REDIRECT_URI = 'http://example.com/oauth2callback'
@app.route('/')
def index():
if 'credentials' not in flask.session:
return flask.redirect(flask.url_for('oauth2callback'))
credentials = json.loads(flask.session['credentials'])
if credentials['expires_in'] <= 0:
return flask.redirect(flask.url_for('oauth2callback'))
else:
headers = {'Authorization': 'Bearer {}'.format(credentials['access_token'])}
req_uri = 'https://www.googleapis.com/drive/v2/files'
r = requests.get(req_uri, headers=headers)
return r.text
@app.route('/oauth2callback')
def oauth2callback():
if 'code' not in flask.request.args:
auth_uri = ('https://accounts.google.com/o/oauth2/v2/auth?response_type=code'
'&client_id={}&redirect_uri={}&scope={}').format(CLIENT_ID, REDIRECT_URI, SCOPE)
return flask.redirect(auth_uri)
else:
auth_code = flask.request.args.get('code')
data = {'code': auth_code,
'client_id': CLIENT_ID,
'client_secret': CLIENT_SECRET,
'redirect_uri': REDIRECT_URI,
'grant_type': 'authorization_code'}
r = requests.post('https://www.googleapis.com/oauth2/v4/token', data=data)
flask.session['credentials'] = r.text
return flask.redirect(flask.url_for('index'))
if __name__ == '__main__':
import uuid
app.secret_key = str(uuid.uuid4())
app.debug = False
app.run()
所以,这其实很简单。要刷新令牌,您需要使用以下代码:
self.service = [[GTLServiceGmail alloc] init];
self.service.authorizer =
[GTMOAuth2ViewControllerTouch authForGoogleFromKeychainForName:kKeychainItemName
clientID:kClientID
clientSecret:nil];
[[GIDSignIn sharedInstance] setScopes:[NSArray arrayWithObject: @"https://www.googleapis.com/auth/plus.me"]];
[GIDSignIn sharedInstance].clientID = kClientID;
GTMOAuth2Authentication *auth = [GTMOAuth2ViewControllerTouch authForGoogleFromKeychainForName:kKeychainItemName
clientID:kClientID
clientSecret:nil];
NSLog(@"accessToken: %@", auth.accessToken); //If the token is expired, this will be nil
// authorizeRequest will refresh the token, even though the NSURLRequest passed is nil
[auth authorizeRequest:nil
completionHandler:^(NSError *error) {
if (error) {
NSLog(@"error: %@", error);
}
else {
[USER_CACHE setValue:auth.accessToken forKey:@"googleAccessToken"];
}
}];
例如,您可以将其粘贴到 ViewDidLoad 方法中。因此,在执行此代码后,您将在 UserDefaults 中拥有有效的访问令牌(在我的示例中为 USER_CAHCE)。
我通过该方法获得了访问令牌:
- (void)viewController:(GTMOAuth2ViewControllerTouch *)viewController
finishedWithAuth:(GTMOAuth2Authentication *)authResult
error:(NSError *)error {
if (error != nil) {
[self showAlert:@"Authentication Error" message:error.localizedDescription];
self.service.authorizer = nil;
}
else {
self.service.authorizer = authResult;
NSLog(@"Token: %@ id: %@", authResult.accessToken, authResult.userID);
[self makeGmailLabelVisibleWithToken:authResult.accessToken]; //make an authorized request to gmailAPI with the access token
[self dismissViewControllerAnimated:YES completion:nil];
}
}
因此,经过身份验证后它工作正常,但过了一会儿它停止工作(我猜是因为令牌已过期)。另外,如果我使用
[authResult refreshToken]
而不是
authResult.accessToken
不行。
那么刷新 Gmail 访问令牌的正确方法是什么?我应该使用哪种方法?
P.S: documentation 表示
- (void) refreshTokensWithHandler:(GIDAuthenticationHandler)handler
应该有帮助,但我还没有找到任何样本。
要获取刷新令牌,您必须为您的应用enable server-side APIA access。 "To obtain an access token and refresh token for your server, you can request a one-time authorization code that your server exchanges for these two tokens. You request the one-time code by specifying your server's client ID along with your other GIDSignIn parameters. After you successfully connect the user, you will find the one-time code as the auth parameter server_code accessible via the finishedWithAuth:error handler."
- 按照开始集成中的说明配置 iOS 应用项目。
- 定义您的应用委托的 application:didFinishLaunchingWithOptions: 方法,如上文启用登录中所述,但对于此实现,您将设置 serverClientID 属性,如下所示。
-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [GIDSignIn sharedInstance].clientID = @"APP_CLIENT_ID"; [GIDSignIn sharedInstance].serverClientID = @"SERVER_CLIENT_ID"; // Additional scopes, if any // [GIDSignIn sharedInstance].scopes = @[ @"other_scope" ]; return YES; }
- 用户登录后,获取一次性授权码:
-(void)signIn:(GIDSignIn *)signIn didSignInForUser:(GIDGoogleUser *)user withError:(NSError *)error { // Perform any operations on signed in user here. // user.serverAuthCode now has a server authorization code! }
- 使用 HTTPS POST. 将 serverAuthCode 字符串安全地传递到您的服务器
- 在您应用的后端服务器上,将授权码交换为访问和刷新令牌。使用访问令牌代表用户调用 Google API,并且可以选择存储刷新令牌以在访问令牌过期时获取新的访问令牌。
您可以使用 HTTP/REST 电话。
这是 Python 中的 HTTP 调用,只需使用 Objective-C 等价物即可。
import json
import flask
import requests
app = flask.Flask(__name__)
CLIENT_ID = '123456789.apps.googleusercontent.com'
CLIENT_SECRET = 'abc123' # Read from a file or environmental variable in a real app
SCOPE = 'https://www.googleapis.com/auth/drive.metadata.readonly'
REDIRECT_URI = 'http://example.com/oauth2callback'
@app.route('/')
def index():
if 'credentials' not in flask.session:
return flask.redirect(flask.url_for('oauth2callback'))
credentials = json.loads(flask.session['credentials'])
if credentials['expires_in'] <= 0:
return flask.redirect(flask.url_for('oauth2callback'))
else:
headers = {'Authorization': 'Bearer {}'.format(credentials['access_token'])}
req_uri = 'https://www.googleapis.com/drive/v2/files'
r = requests.get(req_uri, headers=headers)
return r.text
@app.route('/oauth2callback')
def oauth2callback():
if 'code' not in flask.request.args:
auth_uri = ('https://accounts.google.com/o/oauth2/v2/auth?response_type=code'
'&client_id={}&redirect_uri={}&scope={}').format(CLIENT_ID, REDIRECT_URI, SCOPE)
return flask.redirect(auth_uri)
else:
auth_code = flask.request.args.get('code')
data = {'code': auth_code,
'client_id': CLIENT_ID,
'client_secret': CLIENT_SECRET,
'redirect_uri': REDIRECT_URI,
'grant_type': 'authorization_code'}
r = requests.post('https://www.googleapis.com/oauth2/v4/token', data=data)
flask.session['credentials'] = r.text
return flask.redirect(flask.url_for('index'))
if __name__ == '__main__':
import uuid
app.secret_key = str(uuid.uuid4())
app.debug = False
app.run()
所以,这其实很简单。要刷新令牌,您需要使用以下代码:
self.service = [[GTLServiceGmail alloc] init];
self.service.authorizer =
[GTMOAuth2ViewControllerTouch authForGoogleFromKeychainForName:kKeychainItemName
clientID:kClientID
clientSecret:nil];
[[GIDSignIn sharedInstance] setScopes:[NSArray arrayWithObject: @"https://www.googleapis.com/auth/plus.me"]];
[GIDSignIn sharedInstance].clientID = kClientID;
GTMOAuth2Authentication *auth = [GTMOAuth2ViewControllerTouch authForGoogleFromKeychainForName:kKeychainItemName
clientID:kClientID
clientSecret:nil];
NSLog(@"accessToken: %@", auth.accessToken); //If the token is expired, this will be nil
// authorizeRequest will refresh the token, even though the NSURLRequest passed is nil
[auth authorizeRequest:nil
completionHandler:^(NSError *error) {
if (error) {
NSLog(@"error: %@", error);
}
else {
[USER_CACHE setValue:auth.accessToken forKey:@"googleAccessToken"];
}
}];
例如,您可以将其粘贴到 ViewDidLoad 方法中。因此,在执行此代码后,您将在 UserDefaults 中拥有有效的访问令牌(在我的示例中为 USER_CAHCE)。