大部分的Android应用都有某种的服务器后台用来保存和分享数据。即使是最简单的游戏应用也需要保存玩家的高分纪录。在设计后台的时候,必须解决的一个问题是:后台程序如何知道哪个应用在和它对话,以及哪个用户在使用它。
你可能有和客户端应用对话的HTTP端点,但是服务器端程序如何确定是谁在发送消息给服务器?毕竟,任何人都可以从任何地方发送HTTP POST请求,如果他们能猜到用户的身份,他们可以伪装成你的用户吗?
在移动设备上要求用户输入用户名和密码是很差的用户体验。特别是,如果某个用户安装你的应用,允许它使用Internet并且知道了你的身份,他就不该再被纠缠。事实上,现在在所有兼容Android版本2.2或更高版本的设备上,Google Play服务提供了一个基于Google账号的解决方案。
主题
我 将全面概述这个多步骤的过程,但是下面是一个简短的版本:你可以使用Google Play服务中GoogleAuthUtil类来获取一个叫做“身份令牌(ID Token)”的字符串。发送该令牌到你的后台,然后你的后台可以使用它来快速方便的验证哪个应用发送了该令牌以及那个用户在使用该应用。
验证Android应用的后台请求这个功能已经整合Google的设施中,例如App Engine的新的云端点功能,这个功能把应用程序/后端身份变成一个简单编程模型。
现在让我们开始讲细节吧。
应用注册
在应用注册过程中,你将不得不经常用到Google API控制台。为此,你需要创建一个新项目。虽然你可以给这个项目一个响亮的且有可读性的名字以及一个图形的标识,但实际上在此过程中,我们并不需要这些东西。
你也可以授权大量的Google APIs权限给这个项目,但是事实依然是,你不需要这些东西。
你需要认真考虑你要批准谁成为这个项目的成员,因为这些成员是很重要的管理角色。
创建用户ID
你 将需要为你的项目创建两个不同的OAuth 2.0 “用户ID”。第一个是“Web应用用户ID”。同样的,你可以忽略所有的标签和品牌推广的东西,你只需要一个看起来像 “9414861317621.apps.googleusercontent.com”这样的用户ID字符串。
现在你需要为你的 Android应用创建另一个用户ID。为此,你需要提供两条信息:你的应用的程序包名称和证书签名。程序包名称就是在你的Android.xml中顶层 “package”属性中的Java风格的反向DNS,例如com.example.identity。
使用下面的shell命令来获取你的证书签名:
$ keytool -exportcert -alias <密钥名> -keystore <密钥库文件> -v -list
复制名为”SHA1″的八位字节,粘贴进“Developer Console”的字段,然后创建你应用的用户ID。同样,你真正需要的只是用户ID字符串。
在你的Anroid应用中
你 需要调用Google Play服务中的GoogleAuthUtil类以获取一个ID令牌;这个过程会在“获取一个访问令牌”部分中讲解。你需要格外注意 getToken(email, scope)方法中的scope参数的值。它必须是audience:server:client_id:X,X指的是上面介绍Web应用的用户ID。如 果我们的Web应用的用户ID是是上面例子中的值,那么scope参数值应该是 audience:server:client_id:9414861317621.apps.googleusercontent.com.
奇迹发生
通 常情况下,当你请求OAuth令牌的时候,正在使用设备的用户会看到一个询问用户是否允许使用用户的身份来获取某些资源的提示。但是这个情形中,系统会在 你的scope参数中查询服务器端用户ID,发现这个项目和你的Android应用是在同一个项目中,然后在不需要用户干预的情况下给你令牌。他们已经认 同你作为控制该项目的开发者的关系。
发送令牌
当你准备开始和你的服务器后台对话时,你需要发送你之前获得的令牌给服务器后台。最好的办法就是在POST消息中发送令牌;你也可以把令牌放在一个URL参数中,但是,他们会被系统记录。为了防止“中间人”窥视你的令牌,你必须使用HTTPS连接。
没有必要进行多余的数据往返。假如你要发送高分数据到后台,只要把身份令牌放在一个额外的参数中就好了。
使用令牌
当你的服务器收到从你的Android应用发来的令牌后,验证令牌是很重要的。这个过程需要两步:
验证该令牌令牌是Google签名的。
验证该令牌对你有什么用。
验证签名
令牌会被一个Google的公共/私有密钥对签名。而且Google会在 www.googleapis.com/oauth2/v1/certs发布公开密钥(经常会改变)。去看看吧。
你必须验证你获得的ID令牌,实际上身份令牌就是一个JSON格式的Web令牌,该令牌是由这些上面网址提到的证书之一签名的。幸运的是,有一些很不错的类库来做这些;在本篇博客中,我将介绍Java, Ruby, 以及PHP.
这些类库可以缓存Google证书,而且只在需要的时候刷新证书,因此验证过程(大部分情况下)是一个快速静态的调用。
验证令牌字段
ID令牌有一个JSON格式的有效负载,大多数的验证签名的类库也会把它以哈希或者数据字典或者其他的格式返回给你。因此,你可以读取命名的字段比如aud,azp,email
。
首先,你要查看名为aud的字段,验证是否你的用户ID(你在Android应用中scope参数值)一致。请不要忽略这一步,如果你不验证ID令牌,那么其他的开发者可以伪造请求来戏弄你。
另外你还可以根据需要查看名为azp(意为“授权方(authorized party)”)的字段,验证该字段是否和你Android应用的用户ID一样。此外,你可以在顶层项目中有不同Android客户端应用,各自有各自的用户身份。
假设你已经完成了这三件事,那么,你就应该知道
1,该令牌是Google发布的。
2,该令牌被发送到一个设备上,并且使用该设备的用户ID为有效负载中email字段值。
你也会对下面的事很有信心:
3,该令牌是从Android应用中获得的,并且该应用的ID为有效负载中azp字段值。
用户身份只能有“高度信心”而不是完全信任,因为不兼容的或者已经ROOT的Anroid设备可以篡改该信息。但是他们不可能伪造Google签名或者向Google认证设备的用户。
接下来?
那就看你的了。 你知道你在和谁以及哪个应用对话,接下来要对该信息做什么就全看你的了。
代码片段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
import
java.io.IOException;
import
java.security.GeneralSecurityException;
import
com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
import
com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier;
import
com.google.api.client.http.javanet.NetHttpTransport;
import
com.google.api.client.json.JsonFactory;
import
com.google.api.client.json.gson.GsonFactory;
public
class
Checker {
private
final
List mClientIDs;
private
final
String mAudience;
private
final
GoogleIdTokenVerifier mVerifier;
private
final
JsonFactory mJFactory;
private
String mProblem =
"Verification failed. (Time-out?)"
;
public
Checker(String[] clientIDs, String audience) {
mClientIDs = Arrays.asList(clientIDs);
mAudience = audience;
NetHttpTransport transport =
new
NetHttpTransport();
mJFactory =
new
GsonFactory();
mVerifier =
new
GoogleIdTokenVerifier(transport, mJFactory);
}
public
GoogleIdToken.Payload check(String tokenString) {
GoogleIdToken.Payload payload =
null
;
try
{
GoogleIdToken token = GoogleIdToken.parse(mJFactory, tokenString);
if
(mVerifier.verify(token)) {
GoogleIdToken.Payload tempPayload = token.getPayload();
if
(!tempPayload.getAudience().equals(mAudience))
mProblem =
"Audience mismatch"
;
else
if
(!mClientIDs.contains(tempPayload.getIssuee()))
mProblem =
"Client ID mismatch"
;
else
payload = tempPayload;
}
}
catch
(GeneralSecurityException e) {
mProblem =
"Security issue: "
+ e.getLocalizedMessage();
}
catch
(IOException e) {
mProblem =
"Network problem: "
+ e.getLocalizedMessage();
}
return
payload;
}
public
String problem() {
return
mProblem;
}
}
|
下面是一个使用Google Java类库实现了身份-令牌验证的Java类:
1
2
3
4
5
6
7
8
|
require
'google-id-token'
validator = GoogleIDToken::Validator.
new
jwt = validator.check(token, required_audience, required_client_id)
if
jwt
email = jwt[
'email'
]
else
report
"Cannot validate: #{validator.problem}"
end
|
如果你想用Ruby来实现,你需要安装 google-id-token Ruby gem,并且做些像这样的东西:
1
2
3
4
5
6
7
8
|
require
'google-id-token'
validator = GoogleIDToken::Validator.
new
jwt = validator.check(token, required_audience, required_client_id)
if
jwt
email = jwt[
'email'
]
else
report
"Cannot validate: #{validator.problem}"
end
|
对PHP程序员,参照Google APIs Client Library for PHP,特别是在apiOAuth2.php中的verifyIdToken方法。
英文原文:Android Official Blog,编译:ImportNew - 魏铮
译文链接:http://www.importnew.com/3115.html