2012-03-21 144 views
4

我正在嘗試登錄到應用引擎的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; 
    } 
} 

}

回答

3

Android的代碼上面越來越從谷歌帳戶API一個ClientLogin的令牌。要通過UserService登錄並獲取當前用戶,GAE應用程序也必須使用Google Accounts API進行身份驗證(「應用程序設置」 - >「身份驗證選項」)。

+0

會話已啓用。我嘗試從瀏覽器中得到同樣的錯誤。我將不得不做更多的挖掘。謝謝 – Patrick 2012-03-22 11:27:59

+0

您是否要求對web.xml中的資源進行身份驗證? – 2012-03-22 14:37:20

+0

我不是那麼剛剛添加它,並測試。仍然沒有運氣。我現在被重定向到登錄頁面。 – Patrick 2012-03-22 17:16:44

0

我注意到,這是很容易驗證添加到現有的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類,因此不需要反思。