我一直在研究如何使用Cloud Endpoints 验证您的客户端(Android,iOS,网络应用),而要求您的用户使用他们的Google帐户登录{{3告诉你。
这样做的原因是我希望保护我的API或者#34;将其锁定"仅限我指定的客户。有时我会有一个没有用户登录的应用程序。我讨厌纠缠我的用户现在登录,因此我的API是安全的。或者其他时候,我只想在网站上管理自己的用户,而不是使用Google+,Facebook或其他任何登录身份验证。
首先,让我首先展示使用您的Cloud Endpoints API使用documentation中指定的Google帐户登录信息验证Android应用的方式。在那之后,我将向您展示我的发现以及我需要帮助的解决方案的潜在领域。
(1)指定授权向API后端发出请求的应用程序的客户端ID(clientIds);(2)将User参数添加到要通过授权保护的所有公开方法。
public class Constants {
public static final String WEB_CLIENT_ID = "1-web-apps.apps.googleusercontent.com";
public static final String ANDROID_CLIENT_ID = "2-android-apps.googleusercontent.com";
public static final String IOS_CLIENT_ID = "3-ios-apps.googleusercontent.com";
public static final String ANDROID_AUDIENCE = WEB_CLIENT_ID;
public static final String EMAIL_SCOPE = "https://www.googleapis.com/auth/userinfo.email";
}
import com.google.api.server.spi.auth.common.User; //import for the User object
@Api(name = "myApi", version = "v1",
namespace = @ApiNamespace(ownerDomain = "${endpointOwnerDomain}",
ownerName = "${endpointOwnerDomain}",
packagePath="${endpointPackagePath}"),
scopes = {Constants.EMAIL_SCOPE},
clientIds = {Constants.WEB_CLIENT_ID, Constants.ANDROID_CLIENT_ID,
Constants.IOS_CLIENT_ID,
Constants.API_EXPLORER_CLIENT_ID},
audiences = {Constants.ANDROID_AUDIENCE})
public class MyEndpoint {
/** A simple endpoint method that takes a name and says Hi back */
@ApiMethod(name = "sayHi")
public MyBean sayHi(@Named("name") String name, User user) throws UnauthorizedException {
if (user == null) throw new UnauthorizedException("User is Not Valid");
MyBean response = new MyBean();
response.setData("Hi, " + name);
return response;
}
}
(3)在Android中调用Asynctask中的API方法,确保传递credential
中的Builder
变量:
class EndpointsAsyncTask extends AsyncTask<Pair<Context, String>, Void, String> {
private static MyApi myApiService = null;
private Context context;
@Override
protected String doInBackground(Pair<Context, String>... params) {
credential = GoogleAccountCredential.usingAudience(this,
"server:client_id:1-web-app.apps.googleusercontent.com");
credential.setSelectedAccountName(settings.getString(PREF_ACCOUNT_NAME, null));
if(myApiService == null) { // Only do this once
MyApi.Builder builder = new MyApi.Builder(AndroidHttp.newCompatibleTransport(),
new AndroidJsonFactory(), credential)
// options for running against local devappserver
// - 10.0.2.2 is localhost's IP address in Android emulator
// - turn off compression when running against local devappserver
.setRootUrl("http://<your-app-engine-project-id-here>/_ah/api/")
.setGoogleClientRequestInitializer(new GoogleClientRequestInitializer() {
@Override
public void initialize(AbstractGoogleClientRequest<?> abstractGoogleClientRequest) throws IOException {
abstractGoogleClientRequest.setDisableGZipContent(true);
}
});
// end options for devappserver
myApiService = builder.build();
}
context = params[0].first;
String name = params[0].second;
try {
return myApiService.sayHi(name).execute().getData();
} catch (IOException e) {
return e.getMessage();
}
}
@Override
protected void onPostExecute(String result) {
Toast.makeText(context, result, Toast.LENGTH_LONG).show();
}
}
在您的Android应用中,您首先要显示Google帐户选择器,将Google帐户电子邮件存储在您的共享首选项中,然后将其设置为GoogleAccountCredential
对象的一部分(更多信息,请参阅怎么做documentation)。
Google App Engine服务器会收到您的请求并进行检查。如果Android客户端是您在@Api
表示法中指定的客户端之一,则服务器会将com.google.api.server.spi.auth.common.User
对象注入您的API方法。您现在有责任在API方法中检查User
对象是否为null
。如果User
对象为null
,则应在方法中引发异常以防止其运行。如果您不进行此项检查,则会执行您的API方法(如果您尝试限制对其的访问权限,则禁止执行)。
您可以转到Google Developers Console来获取ANDROID_CLIENT_ID
。在那里,您提供Android应用程序的包名称和SHA1,它为您生成一个Android客户端ID,供您在@Api
注释中使用(或将其放在上面指定的类Constants
中可用性)。
我已经对上述所有内容进行了一些广泛的测试,这是我发现的:
如果您在@Api
注释中指定了虚假或无效的Android clientId,则{API}方法中的User
对象将为null
。如果您正在检查if (user == null) throw new UnauthorizedException("User is Not Valid");
,那么您的API方法将无法运行。
这是令人惊讶的,因为看起来在Cloud Endpoints中有一些幕后验证会检查Android ClientId是否有效。如果它无效,它将无法返回User
对象 - 即使最终用户登录到其Google帐户且GoogleAccountCredential
有效。
我的问题是,是否有人知道如何在我的Cloud Endpoints方法中自行检查该类型的ClientId验证?例如,可以在HttpHeader
中传递这些信息吗?
Cloud Endpoints中的另一个注入类型是javax.servlet.http.HttpServletRequest
。您可以在API方法中获得这样的请求:
@ApiMethod(name = "sayHi")
public MyBean sayHi(@Named("name") String name, HttpServletRequest req) throws UnauthorizedException {
String Auth = req.getHeader("Authorization");//always null based on my tests
MyBean response = new MyBean();
response.setData("Hi, " + name);
return response;
}
}
但我不确定是否有必要的信息或如何获得它。
当然某处必须有一些数据告诉我们客户是@Api
clientIds
中的授权和指定客户。
这样,您可以将API锁定到Android应用程序(以及可能的其他客户端),而无需纠缠最终用户登录(或只创建自己的简单用户名+密码登录)。
尽管如此,你必须在null
的第三个参数中传递Builder
,如下所示:
MyApi.Builder builder = new MyApi.Builder(AndroidHttp.newCompatibleTransport(), 新的AndroidJsonFactory(),null)
然后在您的API方法中提取调用是否来自经过身份验证的客户端,并抛出异常或运行您想要的任何代码。
我知道这是可能的,因为在GoogleAccountCredential
中使用Builder
时,云端点会以某种方式知道调用是否来自经过身份验证的客户端,然后注入其User
对象基于此进入API方法与否。
这些信息可能会以某种方式出现在标题或正文中吗?如果是这样,我怎样才能把它拿出去检查我的API方法中是否存在?
注意:我阅读了有关此主题的其他帖子。它们提供了传递您自己的身份验证令牌的方法 - 这很好 - 但如果有人反编译您的.apk仍然不安全。我认为如果我的假设有效,您将能够在没有任何登录的情况下将您的Cloud Endpoints API锁定到客户端。
Custom Authentication for Google Cloud Endpoints (instead of OAuth2)
Authenticate my "app" to Google Cloud Endpoints not a "user"
修改 我们使用了Google云平台的金牌支持,并与他们的支持团队进行了数周的讨论。这是他们的最终答案:
&#34;不幸的是,我对此没有任何好运。我问过我的问题 团队,并检查了所有文档。它看起来像使用OAuth2 是你唯一的选择。原因是端点服务器处理 验证到达您的应用程序之前。这意味着你不会 能够开发自己的身份验证流程,并获得结果 就像你用令牌看到的一样。
我很乐意为您提交功能请求。如果你可以 提供有关OAuth2流无法解释的原因的更多信息 为您的客户工作,我可以把剩下的信息 一起并将其提交给产品经理。&#34;
(皱眉的脸) - 然而,也许它还有可能吗?
答案 0 :(得分:1)
我已经使用自定义标头实现了Endpoint Auth&#34;授权&#34;它工作得很好。在我的情况下,此令牌在登录后设置,但应该与您的应用程序完全相同。检查您的测试,因为值应该在那里。 确实检索该标头的方法是:
String Auth = req.getHeader("Authorization");
您可以更进一步,定义自己的Authenticator实现,并将其应用于您的安全API调用。
答案 1 :(得分:1)
遇到同样的问题,找到一个解决方案,从我的终端安全地调用我的API,不使用Google帐户。我们无法反编译IOS App(Bundle),但反编译Android App非常简单..
我找到的解决方案并不完美,但做得很好:
<强> 实施例 强>
@ApiMethod(name = "sayHi")
public void sayHi(@Named("name") String name, @Named("Token") String token) {
if (token == tokenStoreOnAPIServer) {
//Allow it
} else {
//Refuse it and print error
}
}
不是完美的安全解决方案,但它确实有用,并且真正(真的)很难找到真正的API密钥,以便任何试图在反编译后尝试读取代码的人。
答案 2 :(得分:0)
因此,您没有任何特定于用户的信息,但只是想确保只有您的应用能够与您的后端通信... 这就是我的想法,
变化
@Api(name = "myApi", version = "v1",
namespace = @ApiNamespace(ownerDomain = "${endpointOwnerDomain}",
ownerName = "${endpointOwnerDomain}",
packagePath="${endpointPackagePath}"),
scopes = {Constants.EMAIL_SCOPE},
clientIds = {Constants.WEB_CLIENT_ID, Constants.ANDROID_CLIENT_ID,
Constants.IOS_CLIENT_ID,
Constants.API_EXPLORER_CLIENT_ID},
audiences = {Constants.ANDROID_AUDIENCE})
{
...
}
到
@Api(name = "myApi", version = "v1",
namespace = @ApiNamespace(ownerDomain = "${endpointOwnerDomain}",
ownerName = "${endpointOwnerDomain}",
packagePath="${endpointPackagePath}"),
scopes = {Constants.EMAIL_SCOPE},
clientIds = {Constants.ANDROID_CLIENT_ID},
audiences = {Constants.ANDROID_AUDIENCE})
{
...
}
客户端ID是根据您应用的签名生成的。它无法复制。如果您只允许端点接受来自Android应用程序的请求,那么您的问题就会得到解决。
告诉我这是否有效。