2017-01-31 36 views
0

我有一個看起來像這樣一個用例:UserService.getCurrentUser()返回null在谷歌應用程序引擎

  1. 用戶進入https://mydomain.appspot.com/listen
  2. 用戶被重定向到谷歌的認證
  3. 如果成功,應用程序向Google發送http請求以啓用針對Google雲端硬盤上特定文件(工作表)的更改的推送通知
  4. 用戶輸入Google表格並編輯該文件。
  5. Google通過文件ID和其他一些數據向我的應用程序(https://mydomain.appspot.com/notifications)發送一個http文章。
  6. 我的應用程序收到http post,驗證文件ID並嘗試打開文件以查看內容。

步驟6不起作用。這樣做,當我在第二行一個NullPointerException:

final UserService userService = UserServiceFactory.getUserService(); 
    final User user = userService.getCurrentUser(); 

我真的不知道我應該怎麼解決這個問題。在步驟1-3中,用戶登錄並授予對該文件的訪問權限。 Google會觸發步驟5-6。如果它是由用戶觸發的,那麼用戶可以被重定向到登錄頁面。由於請求來自Google,因此這不是一種選擇。

有什麼辦法可以使這項工作?注意:有問題的文件屬於特定用戶。它不屬於某種服務帳戶。

我在Google提供的示例中使用了我的Sheet認證。看起來是這樣的:

public class ConcreteSheetWriter implements SheetWriter { 


    public ConcreteSheetWriter(DriveFileMaker driveFileMaker) { 
     DriveFileMaker driveFileMaker1 = driveFileMaker; 

     try { 
      httpTransport = GoogleNetHttpTransport.newTrustedTransport(); 
      dataStoreFactory = AppEngineDataStoreFactory.getDefaultInstance(); //TODO replace with appenginedatastore otherwise restart is painful 
     } catch (Throwable t) { 
      t.printStackTrace(); 
      // System.exit(1); TODO potentially fix for app engine 
      logger.warning("Could not connect to sheets"); 
      throw new RuntimeException(t); 
     } 


    } 

    private static Credential authorize(HttpTransport HTTP_TRANSPORT, DataStoreFactory dataStoreFactory) throws IOException { 
     // Load client secrets. 
     InputStream in = 
       ConcreteSheetWriter.class.getResourceAsStream(SECRET_PATH); 
     GoogleClientSecrets clientSecrets = 
       GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(in)); 
     /* THE CODE BELOW IN THIS METHOD REPRESENT STEP 6 */ 
     // Build flow and trigger user authorization request. 
     GoogleAuthorizationCodeFlow flow = 
       new GoogleAuthorizationCodeFlow.Builder(
         HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES) 
         .setDataStoreFactory(dataStoreFactory) 
         .setAccessType("offline") 
         .build(); 
     /* 

     The credentials before deploying to GAE. Problems when deploying on GAE 
     Credential credential = new AuthorizationCodeInstalledApp(
       flow, new LocalServerReceiver()).authorize("user"); 
     */ 
     final UserService userService = UserServiceFactory.getUserService(); 
     final User user = userService.getCurrentUser(); 
     logger.info("User is " + user); 
     final String userId = user.getUserId(); 
     final Credential credential = flow.loadCredential(userId); 
     return credential; 
    } 

    @Override 
    public List<List<String>> read(String changedFileId) { 
     Sheets service = null; 
     final String range = "Sheet1!A1:AF30"; 
     try { 
      service = getSheetsService(authorize(httpTransport, dataStoreFactory), httpTransport); 
      ValueRange spreadsheets = service.spreadsheets().values().get(changedFileId, range).execute(); 
      return convert(spreadsheets.getValues()); 
     } catch (IOException e) { 
      throw new CouldNotCommunicateWithGoogleSheetsException(e); 
     } 


    } 
} 

下面是登錄用戶的代碼,代表步驟1-3:

public class PlusSampleServlet extends AbstractAppEngineAuthorizationCodeServlet { 
    private final static Logger logger = Logger.getLogger(PlusSampleServlet.class.getName()); 
    private static final long serialVersionUID = 1L; 

    private final DriveUtilityService driveUtilityService; 


    public PlusSampleServlet() { 
     //omitted 
    } 

    private static void addLoginLogoutButtons(HttpServletRequest req, HttpServletResponse resp, StringBuilder resultFromWatch, UserService userService, String thisUrl, PrintWriter respWriter) throws IOException { 

     //omitted 
    } 

    private static Optional<Channel> watchFile(Drive service, String fileId, 
               String channelId, String channelType, String channelAddress) throws IOException { 
     final Channel returnValue; 
     final Channel channel = new Channel(); 
     channel.setId(channelId); 
     channel.setType(channelType); 
     channel.setAddress(channelAddress); 
     final Drive.Files tmp = service.files(); 
     returnValue = tmp.watch(fileId, channel).execute(); 
     return Optional.fromNullable(returnValue); 
    } 

    @Override 
    public void doGet(HttpServletRequest req, HttpServletResponse resp) 
      throws IOException, ServletException { 

     AuthorizationCodeFlow authFlow = initializeFlow(); 
     final String userId = getUserId(req); 
     Credential credential = authFlow.loadCredential(userId); 
     logger.info("Executing listener activation for user " + userId); 
     StringBuilder resultFromWatch = new StringBuilder(); 
     Drive drive = new Drive.Builder(Utils.HTTP_TRANSPORT, Utils.JSON_FACTORY, credential).setApplicationName("t").build(); 

     try { 

      Optional<Channel> channel = watchFile(drive, driveUtilityService.getFileId(), driveUtilityService.getChannelId(), "web_hook", driveUtilityService.getPushUrl()); 
      String channelStringTmp; 
      if (channel.isPresent()) { 
       channelStringTmp = channel.get().toString(); 
      } else { 
       channelStringTmp = "null..."; 
      } 
      resultFromWatch.append(channelStringTmp); 
     } catch (Exception e) { 
      resultFromWatch.append(e.getMessage()); 
     } 

     final UserService userService = UserServiceFactory.getUserService(); 
     final String thisUrl = req.getRequestURI(); 
     // Send the results as the response 
     PrintWriter respWriter = resp.getWriter(); 
     resp.setStatus(200); 
     resp.setContentType("text/html"); 

     addLoginLogoutButtons(req, resp, resultFromWatch, userService, thisUrl, respWriter); 

     logger.warning("user is " + userId + " sample has done its job and channel " + resultFromWatch.toString()); 
    } 

    @Override 
    protected AuthorizationCodeFlow initializeFlow() throws ServletException, IOException { 
     return Utils.initializeFlow(); 
    } 

    @Override 
    protected String getRedirectUri(HttpServletRequest req) throws ServletException, IOException { 
     return Utils.getRedirectUri(req); 
    } 
} 

的utils的類:

class Utils { 
    static final String MAIN_SERVLET_PATH = "/plussampleservlet"; 
    static final String AUTH_CALLBACK_SERVLET_PATH = "/oauth2callback"; 
    static final UrlFetchTransport HTTP_TRANSPORT = new UrlFetchTransport(); 
    static final JacksonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance(); 
    private final static Logger logger = Logger.getLogger(Utils.class.getName()); 
    /** 
    * Global instance of the {@link DataStoreFactory}. The best practice is to make it a single 
    * globally shared instance across your application. 
    */ 
    private static final AppEngineDataStoreFactory DATA_STORE_FACTORY = 
      AppEngineDataStoreFactory.getDefaultInstance(); 
    private static final Set<String> SCOPES = getScopes(); 
    private static GoogleClientSecrets clientSecrets = null; 


    private static Set<String> getScopes() { 
     List<String> scopeList = Arrays.asList(DriveScopes.DRIVE_READONLY, SheetsScopes.SPREADSHEETS_READONLY); 
     Set<String> scopes = Sets.newHashSet(); 
     scopes.addAll(scopeList); 
     return scopes; 
    } 

    private static GoogleClientSecrets getClientSecrets() throws IOException { 
     if (clientSecrets == null) { 
      clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, 
        new InputStreamReader(Utils.class.getResourceAsStream("/plus_secret.json"))); 
      Preconditions.checkArgument(!clientSecrets.getDetails().getClientId().startsWith("Enter ") 
          && !clientSecrets.getDetails().getClientSecret().startsWith("Enter "), 
        "Download client_secrets.json file from https://code.google.com/apis/console/?api=plus " 
          + "into plus-appengine-sample/src/main/resources/client_secrets.json"); 
     } 
     logger.info("Something asked for the secret"); 
     return clientSecrets; 
    } 

    static GoogleAuthorizationCodeFlow initializeFlow() throws IOException { 
     logger.info("flow is initialized soon"); 
     return new GoogleAuthorizationCodeFlow.Builder(
       HTTP_TRANSPORT, JSON_FACTORY, getClientSecrets(), SCOPES).setDataStoreFactory(
       DATA_STORE_FACTORY).setAccessType("offline").build(); 
    } 

    static String getRedirectUri(HttpServletRequest req) { 
     GenericUrl requestUrl = new GenericUrl(req.getRequestURL().toString()); 
     requestUrl.setRawPath(AUTH_CALLBACK_SERVLET_PATH); 
     logger.info("retrieved redirecturl"); 
     return requestUrl.build(); 
    } 
} 

的時候回調「登錄「已完成:

public class PlusSampleAuthCallbackServlet 
     extends AbstractAppEngineAuthorizationCodeCallbackServlet { 
    private final static Logger logger = Logger.getLogger(PlusSampleAuthCallbackServlet.class.getName()); 

    private static final long serialVersionUID = 1L; 

    @Override 
    protected void onSuccess(HttpServletRequest req, HttpServletResponse resp, Credential credential) 
      throws ServletException, IOException { 
     resp.sendRedirect(Utils.MAIN_SERVLET_PATH); 
     logger.info("ON success"); 
    } 

    @Override 
    protected void onError(
      HttpServletRequest req, HttpServletResponse resp, AuthorizationCodeResponseUrl errorResponse) 
      throws ServletException, IOException { 
     String nickname = UserServiceFactory.getUserService().getCurrentUser().getNickname(); 
     resp.getWriter().print("<h3>Hey " + nickname + ", why don't you want to play with me?</h1>"); 
     resp.setStatus(200); 
     resp.addHeader("Content-Type", "text/html"); 
     logger.info("ON error"); 
     return; 
    } 

    @Override 
    protected AuthorizationCodeFlow initializeFlow() throws ServletException, IOException { 
     logger.info("initializing flow"); 
     return Utils.initializeFlow(); 
    } 

    @Override 
    protected String getRedirectUri(HttpServletRequest req) throws ServletException, IOException { 
     logger.info("get redirect"); 
     return Utils.getRedirectUri(req); 
    } 

} 

回答

0

在第3步中,您需要保存(例如在數據存儲區中)綁定通知註冊,文檔和用戶的映射信息(註冊/通知中必須有一些上下文信息,因爲同一用戶可以觀看多個文檔和多個用戶可以觀看相同的文檔)

在步驟6,應用根據通知(發佈請求)上下文檢索保存的映射信息,然後可以識別當試圖打開時需要使用的用戶證書文件。

+0

謝謝。這聽起來很合理。不知道如何做到這一點。我添加了代碼以顯示步驟3(在PlusSampleServlet-> Utils-> PlusSampleAuthCallbackServlet中啓動),然後在ConcreteSheetWriter的授權方法中執行步驟6。我想要做你的建議,但我不知道如何。解決方案對你來說顯而易見嗎?非常感謝幫助。 –

+0

絕對不是在細節層面 - 我是一個pyton用戶:)也許沿着這些線? http://stackoverflow.com/questions/13777842/how-to-get-offline-token-and-refresh-token-and-auto-refresh-access-to-google-api –