在交换授权代码时,在Cross Client google oauth2.0中接收访问令牌的“null”值

时间:2014-12-30 08:30:07

标签: android youtube-api google-oauth

我在Android应用上实施Google Play服务登录时遇到问题,并将授权码传递给我的后端服务器,因此服务器会交换代码以获取访问令牌和刷新令牌。

首先让我写几行已经尝试过的内容:

在code.google.com/apis/console上我创建了一个新项目,其中有两个客户端(WEB客户端和Android安装的客户端)阅读https://developers.google.com/+/mobile/android/sign-in#cross-platform_single_sign_onhttp://android-developers.blogspot.com/2013/01/verifying-back-end-calls-from-android.html上的文章

我在客户端收到一次性授权代码,并在我的服务器上使用它来交换访问令牌。但它总是返回一个值为" null"的访问令牌。作为回应。但是,它会返回正确的电子邮件地址。

以下是我的客户端代码,用于检索授权代码和IdToken:

public class MainActivity extends Activity {

  final private String CLIENT_ID = "MY Web Server's Client Id";
  final private List<String> SCOPES = Arrays.asList(new String[]{
      "https://www.googleapis.com/auth/plus.login",
      "https://www.googleapis.com/auth/drive",
      "https://www.googleapis.com/auth/youtube",
      "https://www.googleapis.com/auth/youtube.readonly"
  });

  // I have modified the above line of code.  

  private GoogleAccountCredential mCredential;

  private EditText mExchangeCodeEditText;
  private EditText mIdTokenEditText;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_main);
    mExchangeCodeEditText = (EditText) findViewById(R.id.editTextExchangeCode);
    mIdTokenEditText = (EditText) findViewById(R.id.editTextIdToken);

    // initiate a credential object with drive and plus.login scopes
    // cross identity is only available for tokens retrieved with plus.login
    mCredential = GoogleAccountCredential.usingOAuth2(this, null);

    // user needs to select an account, start account picker
    startActivityForResult(
        mCredential.newChooseAccountIntent(), REQUEST_ACCOUNT_PICKER);

  }

  /**
   * Handles the callbacks from result returning
   * account picker and permission requester activities.
   */
  @Override
  protected void onActivityResult(
      final int requestCode, final int resultCode, final Intent data) {
    switch (requestCode) {
    // user has  returned back from the account picker,
    // initiate the rest of the flow with the account he/she has chosen.
    case REQUEST_ACCOUNT_PICKER:
      String accountName = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
      if (accountName != null) {
        mCredential.setSelectedAccountName(accountName);
        new RetrieveExchangeCodeAsyncTask().execute();
        new RetrieveJwtAsyncTask().execute();
      }
      break;
    // user has returned back from the permissions screen,
    // if he/she has given enough permissions, retry the the request.
    case REQUEST_AUTHORIZATION:
      if (resultCode == Activity.RESULT_OK) {
        // replay the same operations
        new RetrieveExchangeCodeAsyncTask().execute();
        new RetrieveJwtAsyncTask().execute();
      }
      break;
    }
  }

  /**
   * Retrieves the exchange code to be sent to the
   * server-side component of the app.
   */

  public class RetrieveExchangeCodeAsyncTask
      extends AsyncTask<Void, Boolean, String> {


    @Override
    protected String doInBackground(Void... params) {
      String scope = String.format("oauth2:server:client_id:%s:api_scope:%s",
          CLIENT_ID, TextUtils.join(" ", SCOPES));
      try {
        return GoogleAuthUtil.getToken(
            MainActivity.this, mCredential.getSelectedAccountName(), scope);
      } catch (UserRecoverableAuthException e) {
        startActivityForResult(e.getIntent(), REQUEST_AUTHORIZATION);
      } catch (Exception e) {
        e.printStackTrace(); // TODO: handle the exception
      }
      return null;
    }

    @Override
    protected void onPostExecute(String code) {
      // exchange code with server-side to retrieve an additional
      // access token on the server-side.
        Log.v("first One ","code 1 is: "+ code);
      mExchangeCodeEditText.setText(code);
    }
  }

  /**
   * Retrieves a JWT to identify the user without the
   * regular client-side authorization flow. The jwt payload needs to be
   * sent to the server-side component.
   */
  public class RetrieveJwtAsyncTask
      extends AsyncTask<Void, Boolean, String> {

    @Override
    protected String doInBackground(Void... params) {
      String scope = "audience:server:client_id:" + CLIENT_ID;
      try {
        return GoogleAuthUtil.getToken(
            MainActivity.this, mCredential.getSelectedAccountName(), scope);
      } catch(UserRecoverableAuthIOException e) {
        startActivityForResult(e.getIntent(), REQUEST_AUTHORIZATION);
      } catch (Exception e) {
        e.printStackTrace(); // TODO: handle the exception
      }
      return null;
    }

我在客户端收到一次性授权代码,并在我的服务器上使用它来交换访问令牌。但它总是返回一个值为&#34; null&#34;的访问令牌。作为回应。但是,它会返回正确的电子邮件地址。

这是我的服务器端代码,用于交换授权代码以检索访问权限和刷新令牌:

public class MyMainDemo {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
          try {
            Credential fetchedCredential =MyClass.getCredentials("4/jobEmAGQ-Dd3zR8KBu-8Umlr_qaSGwSw7cxau1jbw7w.cp4vYbS-DLgZcp7tdiljKKYHRzY0lQI", "state");
        } catch (CodeExchangeException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (NoRefreshTokenException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}

public class MyClass {


    // Path to client_secrets.json which should contain a JSON document such as:
      //   {
      //     "web": {
      //       "client_id": "[[YOUR_CLIENT_ID]]",
      //       "client_secret": "[[YOUR_CLIENT_SECRET]]",
      //       "auth_uri": "https://accounts.google.com/o/oauth2/auth",
      //       "token_uri": "https://accounts.google.com/o/oauth2/token"
      //     }
      //   }
      private static final String CLIENTSECRETS_LOCATION = "/client_secrets.json";

      // private static final String REDIRECT_URI = "<YOUR_REGISTERED_REDIRECT_URI>";

      private static String REDIRECT_URI ="urn:ietf:wg:oauth:2.0:oob";

    /*  private static final List<String> SCOPES = Arrays.asList(
          "https://www.googleapis.com/auth/drive.file",
          "email",
          "profile");
    */

      private static final List<String> SCOPES = Arrays.asList(
              "https://www.googleapis.com/auth/plus.login",
              "https://www.googleapis.com/auth/youtube");

      private static GoogleAuthorizationCodeFlow flow = null;

      /**
       * Exception thrown when an error occurred while retrieving credentials.
       */
      public static class GetCredentialsException extends Exception {

        protected String authorizationUrl;

        /**
         * Construct a GetCredentialsException.
         *
         * @param authorizationUrl The authorization URL to redirect the user to.
         */
        public GetCredentialsException(String authorizationUrl) {
          this.authorizationUrl = authorizationUrl;
        }

        /**
         * Set the authorization URL.
         */
        public void setAuthorizationUrl(String authorizationUrl) {
          this.authorizationUrl = authorizationUrl;
        }

        /**
         * @return the authorizationUrl
         */
        public String getAuthorizationUrl() {
          return authorizationUrl;
        }
      }

      /**
       * Exception thrown when a code exchange has failed.
       */
      public static class CodeExchangeException extends GetCredentialsException {

        /**
         * Construct a CodeExchangeException.
         *
         * @param authorizationUrl The authorization URL to redirect the user to.
         */
        public CodeExchangeException(String authorizationUrl) {
          super(authorizationUrl);
        }

      }

      /**
       * Exception thrown when no refresh token has been found.
       */
      public static class NoRefreshTokenException extends GetCredentialsException {

        /**
         * Construct a NoRefreshTokenException.
         *
         * @param authorizationUrl The authorization URL to redirect the user to.
         */
        public NoRefreshTokenException(String authorizationUrl) {
          super(authorizationUrl);
        }

      }

      /**
       * Exception thrown when no user ID could be retrieved.
       */
      private static class NoUserIdException extends Exception {
      }

      /**
       * Retrieved stored credentials for the provided user ID.
       *
       * @param userId User's ID.
       * @return Stored Credential if found, {@code null} otherwise.
       */
      static Credential getStoredCredentials(String userId) {
        // TODO: Implement this method to work with your database. Instantiate a new
        // Credential instance with stored accessToken and refreshToken.
        throw new UnsupportedOperationException();
      }

      /**
       * Store OAuth 2.0 credentials in the application's database.
       *
       * @param userId User's ID.
       * @param credentials The OAuth 2.0 credentials to store.
       */
      static void storeCredentials(String userId, Credential credentials) {
        // TODO: Implement this method to work with your database.
        // Store the credentials.getAccessToken() and credentials.getRefreshToken()
        // string values in your database.

    //    throw new UnsupportedOperationException();
      }

      /**
       * Build an authorization flow and store it as a static class attribute.
       *
       * @return GoogleAuthorizationCodeFlow instance.
       * @throws IOException Unable to load client_secrets.json.
       */
      static GoogleAuthorizationCodeFlow getFlow() throws IOException {
        if (flow == null) {
          HttpTransport httpTransport = new NetHttpTransport();
          JacksonFactory jsonFactory = new JacksonFactory();    //...this was the original line....
        //  JsonFactory jsonFactory = new JacksonFactory();

         //my code....        
          Reader clientSecretReader = new InputStreamReader(MyClass.class.getResourceAsStream(CLIENTSECRETS_LOCATION));

          GoogleClientSecrets clientSecrets =
                  GoogleClientSecrets.load(jsonFactory,clientSecretReader);

        //  REDIRECT_URI =clientSecrets.getDetails().getRedirectUris().get(0);
          // my code ends...

    /*      GoogleClientSecrets clientSecrets =
              GoogleClientSecrets.load(jsonFactory,
                  MyClass.class.getResourceAsStream(CLIENTSECRETS_LOCATION));
      */
          flow =
              new GoogleAuthorizationCodeFlow.Builder(httpTransport, jsonFactory, clientSecrets, SCOPES)
                  .setAccessType("offline").setApprovalPrompt("force").build();
        }
        return flow;
      }

      /**
       * Exchange an authorization code for OAuth 2.0 credentials.
       *
       * @param authorizationCode Authorization code to exchange for OAuth 2.0
       *        credentials.
       * @return OAuth 2.0 credentials.
       * @throws CodeExchangeException An error occurred.
       */
      static Credential exchangeCode(String authorizationCode)
          throws CodeExchangeException {
        try {
          GoogleAuthorizationCodeFlow flow = getFlow();
          GoogleTokenResponse response = 
             flow.newTokenRequest(authorizationCode).setRedirectUri(REDIRECT_URI).execute();
          return flow.createAndStoreCredential(response, null);
        } catch (IOException e) {
          System.err.println("An error occurred: " + e);
          throw new CodeExchangeException(null);
        }
      }

      /**
       * Send a request to the UserInfo API to retrieve the user's information.
       *
       * @param credentials OAuth 2.0 credentials to authorize the request.
       * @return User's information.
       * @throws NoUserIdException An error occurred.
       */
      static Userinfo getUserInfo(Credential credentials)
          throws NoUserIdException {
        Oauth2 userInfoService =
            new Oauth2.Builder(new NetHttpTransport(), new JacksonFactory(), credentials).build();
        Userinfo userInfo = null;
        try {
          userInfo = userInfoService.userinfo().get().execute();
          // my code line is as under
          System.out.println("access token is: " + userInfoService.tokeninfo().getAccessToken());
        } catch (IOException e) {
          System.err.println("An error occurred: " + e);
        }
        if (userInfo != null && userInfo.getId() != null) {
          return userInfo;
        } else {
          throw new NoUserIdException();
        }
      }

      /**
       * Retrieve the authorization URL.
       *
       * @param emailAddress User's e-mail address.
       * @param state State for the authorization URL.
       * @return Authorization URL to redirect the user to.
       * @throws IOException Unable to load client_secrets.json.
       */
      public static String getAuthorizationUrl(String emailAddress, String state) throws IOException {
        GoogleAuthorizationCodeRequestUrl urlBuilder =getFlow().newAuthorizationUrl().setRedirectUri(REDIRECT_URI);
          //  getFlow().newAuthorizationUrl().setRedirectUri(REDIRECT_URI).setState(state);
        urlBuilder.set("user_id", emailAddress);
        return urlBuilder.build();
      }

      /**
       * Retrieve credentials using the provided authorization code.
       *
       * This function exchanges the authorization code for an access token and
       * queries the UserInfo API to retrieve the user's e-mail address. If a
       * refresh token has been retrieved along with an access token, it is stored
       * in the application database using the user's e-mail address as key. If no
       * refresh token has been retrieved, the function checks in the application
       * database for one and returns it if found or throws a NoRefreshTokenException
       * with the authorization URL to redirect the user to.
       *
       * @param authorizationCode Authorization code to use to retrieve an access
       *        token.
       * @param state State to set to the authorization URL in case of error.
       * @return OAuth 2.0 credentials instance containing an access and refresh
       *         token.
       * @throws NoRefreshTokenException No refresh token could be retrieved from
       *         the available sources.
       * @throws IOException Unable to load client_secrets.json.
       */
      public static Credential getCredentials(String authorizationCode, String state)
          throws CodeExchangeException, NoRefreshTokenException, IOException {
        String emailAddress = "";
        try {
          Credential credentials = exchangeCode(authorizationCode);
          Userinfo userInfo = getUserInfo(credentials);
          String userId = userInfo.getId();
          emailAddress = userInfo.getEmail();
          //..........
          System.out.println("user id is:  "+ userId);
          System.out.println("emailAddress is:  "+ emailAddress);
          //..........

          if (credentials.getRefreshToken() != null) {
            storeCredentials(userId, credentials);
            return credentials;
          } else {
            credentials = getStoredCredentials(userId);
            if (credentials != null && credentials.getRefreshToken() != null) {
              return credentials;
            }
          }
        } catch (CodeExchangeException e) {
          e.printStackTrace();
          // Drive apps should try to retrieve the user and credentials for the current
          // session.
          // If none is available, redirect the user to the authorization URL.
          e.setAuthorizationUrl(getAuthorizationUrl(emailAddress, state));
          throw e;
        } catch (NoUserIdException e) {
          e.printStackTrace();
        }
        // No refresh token has been retrieved.
        String authorizationUrl = getAuthorizationUrl(emailAddress, state);
        throw new NoRefreshTokenException(authorizationUrl);
      }


}

它还向我显示了一个警告:     警告:未设置应用程序名称。调用Builder#setApplicationName。

为什么它会让我回归&#34; null&#34;用于访问令牌?请帮帮我解决这个问题!!! 提前完成。

0 个答案:

没有答案