从Android客户端登录到appengine
我正在尝试登录到应用引擎的appengine并访问应用引擎中的用户服务API。基本上我希望能够看到谁登录到我的servlets。我正在使用从android获取authtoken的身份验证流程,然后从app引擎获取ASID(或SACID)cookie。然后将cookie与http请求一起发送到appengine servlet。这似乎工作很好,但是当我试图让用户使用此代码时:从Android客户端登录到appengine
UserService userService = UserServiceFactory.getUserService();
User user= userService.getCurrentUser();
用户始终为空。我的问题是我在这里错过了什么?为什么用户服务返回空用户?下面是我的appengine和android代码。任何帮助将不胜感激!
应用引擎:
public class MyServlet extends HttpServlet {
public void process(HttpServletRequest req, HttpServletResponse resp)
throws IOException, ServletException {
resp.setContentType("text/plain");
UserService userService = UserServiceFactory.getUserService();
User user= userService.getCurrentUser();
}
public void doPost(HttpServletRequest req, HttpServletResponse resp)
throws IOException, ServletException {
process(req, resp);
}
public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException, ServletException {
process(req, resp);
}
}
的Android代码:
public class AppEngineClient {
static final String BASE_URL = Util.getBaseUrl(this);
private static final String AUTH_URL = BASE_URL + "/_ah/login";
private static final String AUTH_TOKEN_TYPE = "ah";
private final Context mContext;
private final String mAccountName;
private static final String TAG = "AppEngineClient";
public AppEngineClient(Context context, String accountName) {
this.mContext = context;
this.mAccountName = accountName;
}
public HttpResponse makeRequest(String urlPath, List<NameValuePair> params) throws Exception {
HttpResponse res = makeRequestNoRetry(urlPath, params, false);
if (res.getStatusLine().getStatusCode() == 500) {
res = makeRequestNoRetry(urlPath, params, true);
}
return res;
}
private HttpResponse makeRequestNoRetry(String urlPath, List<NameValuePair> params, boolean newToken)
throws Exception {
// Get auth token for account
Account account = new Account(mAccountName, "com.google");
String authToken = getAuthToken(mContext, account);
if (newToken) { // invalidate the cached token
AccountManager accountManager = AccountManager.get(mContext);
accountManager.invalidateAuthToken(account.type, authToken);
authToken = getAuthToken(mContext, account);
}
// Get SACSID cookie
DefaultHttpClient client = new DefaultHttpClient();
String continueURL = BASE_URL;
URI uri = new URI(AUTH_URL + "?continue=" +
URLEncoder.encode(continueURL, "UTF-8") +
"&auth=" + authToken);
HttpGet method = new HttpGet(uri);
final HttpParams getParams = new BasicHttpParams();
HttpClientParams.setRedirecting(getParams, false); // continue is not used
method.setParams(getParams);
HttpResponse res = client.execute(method);
Header[] headers = res.getHeaders("Set-Cookie");
if (res.getStatusLine().getStatusCode() != 302 ||
headers.length == 0) {
return res;
}
String sascidCookie = null;
for (Header header: headers) {
if (header.getValue().indexOf("SACSID=") >=0) {
// let's parse it
String value = header.getValue();
String[] pairs = value.split(";");
ascidCookie = pairs[0];
}
}
// Make POST request
uri = new URI(BASE_URL + urlPath);
HttpPost post = new HttpPost(uri);
UrlEncodedFormEntity entity =
new UrlEncodedFormEntity(params, "UTF-8");
post.setEntity(entity);
post.setHeader("Cookie", ascidCookie);
post.setHeader("X-Same-Domain", "1"); // XSRF
res = client.execute(post);
return res;
}
private String getAuthToken(Context context, Account account) throws PendingAuthException {
String authToken = null;
AccountManager accountManager = AccountManager.get(context);
try {
AccountManagerFuture<Bundle> future =
accountManager.getAuthToken (account, AUTH_TOKEN_TYPE, false, null, null);
Bundle bundle = future.getResult();
authToken = bundle.getString(AccountManager.KEY_AUTHTOKEN);
if (authToken == null) {
throw new PendingAuthException(bundle);
}
} catch (OperationCanceledException e) {
Log.w(TAG, e.getMessage());
} catch (AuthenticatorException e) {
Log.w(TAG, e.getMessage());
} catch (IOException e) {
Log.w(TAG, e.getMessage());
}
return authToken;
}
public class PendingAuthException extends Exception {
private static final long serialVersionUID = 1L;
private final Bundle mAccountManagerBundle;
public PendingAuthException(Bundle accountManagerBundle) {
super();
mAccountManagerBundle = accountManagerBundle;
}
public Bundle getAccountManagerBundle() {
return mAccountManagerBundle;
}
}
}
Android的代码上面越来越从谷歌帐户API一个ClientLogin的令牌。要通过UserService
登录并获取当前用户,GAE应用程序也必须使用Google Accounts API进行身份验证(“应用程序设置” - >“身份验证选项”)。
我注意到,这是很容易验证添加到现有的AppEngine终点 - 你只需要com.google.appengine.api.users.User参数添加到您的方法。最后我发现了底层的问题,以及如何以同样的方式验证任意的servlet。 所以在Android上侧进行身份验证,您需要: 1)选择帐户:
private void signIn()
{
startActivityForResult(GoogleAccountCredential.usingAudience(this, "server:client_id:{id}.apps.googleusercontent.com").newChooseAccountIntent(), REQUEST_ACCOUNT_PICKER);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode)
{
case REQUEST_ACCOUNT_PICKER:
if (data != null && data.getExtras() != null)
{
String accountName = data.getExtras().getString(AccountManager.KEY_ACCOUNT_NAME);
if (accountName != null)
{
// TODO save accountName
}
}
break;
}
}
2)获得Credential对象:
GoogleAccountCredential credential = GoogleAccountCredential.usingAudience(this, "server:client_id:{id}.apps.googleusercontent.com");
credential.setSelectedAccountName(accountName);
3)创建谷歌HttpRequest对象,并要求:
HttpTransport transport = new NetHttpTransport();
HttpRequestFactory requestFactory = transport.createRequestFactory(credential);
GenericUrl url = new GenericUrl(UPLOAD_SERVICE_URL);
HttpRequest request = requestFactory.buildGetRequest(url);
HttpResponse resp = request.execute();
// TODO check response
为了验证AppEngine上侧的要求,您可以使用“的AppEngine-endpoints.jar”库中声明的内部类WebApisUserService。这只是AppEngine在端点内部使用的类。不幸的是,这个类的构造函数和其他必需的方法受到外部使用的保护,所以我们需要使用反射来访问它。完整的辅助类如下:
public class WebApisUserServiceHelper
{
public static WebApisUserService createInstance(boolean isClientIdWhitelistEnabled)
throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException
{
Constructor<WebApisUserService> constructor;
constructor = WebApisUserService.class.getDeclaredConstructor(Boolean.TYPE);
constructor.setAccessible(true);
WebApisUserService ret = constructor.newInstance(isClientIdWhitelistEnabled);
return ret;
}
public static User getCurrentUser(WebApisUserService service, HttpServletRequest req, String appName, List<String> audiences, List<String> clientIds)
throws NoSuchMethodException, SecurityException, ClassNotFoundException, IllegalAccessException, IllegalArgumentException, InvocationTargetException
{
String token = getAuthToken(service, req);
if (token != null)
{
List<String> allowedScopes = new ArrayList<String>();
allowedScopes.add("https://www.googleapis.com/auth/userinfo.email");
return getCurrentUser(service, token, allowedScopes, audiences, clientIds);
}
return null;
}
private static User getCurrentUser(WebApisUserService service, String token, List<String> allowedScopes, List<String> allowedAudiences, List<String> allowedClientIds)
throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException
{
Method method = WebApisUserService.class.getDeclaredMethod("getCurrentUser", String.class, List.class, List.class, List.class);
method.setAccessible(true);
Object ret = method.invoke(service, token, allowedScopes, allowedAudiences, allowedClientIds);
if (ret instanceof User) return (User) ret;
return null;
}
private static String getAuthToken(WebApisUserService service, HttpServletRequest request)
throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException
{
Method method = WebApisUserService.class.getDeclaredMethod("getAuthToken", HttpServletRequest.class);
method.setAccessible(true);
Object ret = method.invoke(service, request);
if (ret instanceof String) return (String) ret;
return null;
}
}
,这里是如何使用这个帮手:
public class MyServlet extends HttpServlet
{
private final WebApisUserService auth = createAuthService();
private static WebApisUserService createAuthService()
{
try
{
return WebApisUserServiceHelper.createInstance(false);
}
catch (Exception e)
{
log.log(Level.WARNING, "Failed to create WebApisUserServiceFactory instance. Exception: %s", e.toString());
}
return null;
}
@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException
{
try
{
User user = authenticateUserSafe(req);
if (user == null)
{
resp.sendError(401, "auth required");
return;
}
String str = String.format("User id: %s, nick: %s, email: %s", user.getUserId(), user.getNickname(), user.getEmail());
resp.getWriter().write(str);
}
catch (Throwable e)
{
resp.getWriter().write("Exception: " + e);
}
}
private User authenticateUserSafe(HttpServletRequest req)
{
try
{
return authenticateUser(req);
}
catch (Exception e)
{
log.log(Level.WARNING, "Failed to authenticate user. Exception: %s", e.toString());
}
return null;
}
private User authenticateUser(HttpServletRequest req)
throws NoSuchMethodException, SecurityException, ClassNotFoundException, IllegalAccessException, IllegalArgumentException, InvocationTargetException
{
List<String> audiences = new ArrayList<String>();
audiences.add(Ids.ANDROID_AUDIENCE);
List<String> clientIds = new ArrayList<String>();
clientIds.add(Ids.WEB_CLIENT_ID);
clientIds.add(Ids.ANDROID_CLIENT_ID);
return WebApisUserServiceHelper.getCurrentUser(auth, req, "{id}", audiences, clientIds);
}
}
这种做法与AppEngine上1.8.6检查。我希望Google能公开打开WebApisUserService类,因此不需要反思。
会话已启用。我尝试从浏览器中得到同样的错误。我将不得不做更多的挖掘。谢谢 – Patrick 2012-03-22 11:27:59
您是否要求对web.xml中的资源进行身份验证? – 2012-03-22 14:37:20
我不是那么刚刚添加它,并测试。仍然没有运气。我现在被重定向到登录页面。 – Patrick 2012-03-22 17:16:44