首页 > 解决方案 > Google App Script API 访问,在 android 应用程序中使用 OAuth idToken

问题描述

我正在尝试从 android 应用程序更新 google 电子表格。

我发现这种操作的流程应该是:

  1. 从 android App 发送请求
  2. 在 Google App Script 上接收请求
  3. 在脚本中修改电子表格数据

我使用 Oauth 来授权用户,我收到了与他的会话相关的 idToken。但是,当我创建附加此令牌的 HTTP 请求时,我得到响应 401 未经授权

E/Volley: [676] NetworkUtility.shouldRetryException: Unexpected response code 401 for <HERE GOES URL OF MY SCRIPT>
2021-11-13 20:47:01.498 20626-20626/com.example.e2 W/System.err: com.android.volley.AuthFailureError
2021-11-13 20:47:01.498 20626-20626/com.example.e2 W/System.err:     at com.android.volley.toolbox.NetworkUtility.shouldRetryException(NetworkUtility.java:189)
2021-11-13 20:47:01.498 20626-20626/com.example.e2 W/System.err:     at com.android.volley.toolbox.BasicNetwork.performRequest(BasicNetwork.java:145)
2021-11-13 20:47:01.499 20626-20626/com.example.e2 W/System.err:     at com.android.volley.NetworkDispatcher.processRequest(NetworkDispatcher.java:132)
2021-11-13 20:47:01.499 20626-20626/com.example.e2 W/System.err:     at com.android.volley.NetworkDispatcher.processRequest(NetworkDispatcher.java:111)
2021-11-13 20:47:01.499 20626-20626/com.example.e2 W/System.err:     at com.android.volley.NetworkDispatcher.run(NetworkDispatcher.java:90)

我应该附加这个令牌的正确方法是什么,让谷歌脚本“知道”这个请求已经过身份验证?

我的脚本代码:

var ssa = SpreadsheetApp.openByUrl('<URL OF MY SPREADSHEET>');
var sheet = ssa.getSheetByName('<NAME OF TAB>');

function doPost(e){
  var action = e.parameter.action;
  if(action=='ADD'){
    return addItem(e);    
  }
  return 'Unsuported operation';
}

function addItem(e){
  var date = new Date();
  var id = "Item"+sheet.getLastRow();
  var data = e.parameter.data;

  sheet.appendRow([date,id,data]);

  return ContentService.createTextOutput("Inserting Successfull").setMimeType(ContentService.MimeType.TEXT);
}

我的安卓应用程序代码:

public class MainActivity extends AppCompatActivity {

    private static final int RC_SIGN_IN = 11;
    private static String oAuthAccessToken;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        GoogleSignInAccount account = GoogleSignIn.getLastSignedInAccount(this);
        if (account != null) {
            oAuthAccessToken = account.getIdToken();
        }

        Button btn1 = (Button) this.findViewById(R.id.btn_login);
        btn1.setOnClickListener((v) -> {
            loginViaGoogle();
        });

        Button btn2 = (Button) this.findViewById(R.id.btn_send);
        btn2.setOnClickListener((v) -> {
            volleySendDataToGSheet();
        });

        Button btn3 = (Button) this.findViewById(R.id.btn_logout);
        btn3.setOnClickListener((v) -> {
            logout();
        });

    }

    @NonNull
    private GoogleSignInOptions getGoogleSignOptions() {
        return new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
                .requestIdToken(getString(R.string.server_client_id))
                .requestScopes(
                        new Scope("https://www.googleapis.com/auth/spreadsheets"),
                        new Scope("https://www.googleapis.com/auth/drive.scripts"),
                        new Scope("https://www.googleapis.com/auth/script.projects"),
                        new Scope("https://www.googleapis.com/auth/script.processes"),
                        new Scope("https://www.googleapis.com/auth/script.projects.readonly"))
                .build();
    }

    private void logout() {
        GoogleSignInOptions gso = getGoogleSignOptions();

        GoogleSignInClient mGoogleSignInClient = GoogleSignIn.getClient(this, gso);
        mGoogleSignInClient.signOut();
    }

    private void loginViaGoogle() {
        GoogleSignInOptions gso = getGoogleSignOptions();

        GoogleSignInClient mGoogleSignInClient = GoogleSignIn.getClient(this, gso);
        Intent signInIntent = mGoogleSignInClient.getSignInIntent();

        startActivityForResult(signInIntent, RC_SIGN_IN);
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if (requestCode == RC_SIGN_IN) {
            Task<GoogleSignInAccount> task = GoogleSignIn.getSignedInAccountFromIntent(data);
            handleSignInResult(task);
        }
    }

    private void handleSignInResult(Task<GoogleSignInAccount> completedTask) {
        try {
            GoogleSignInAccount account = completedTask.getResult(ApiException.class);

            oAuthAccessToken = account.getIdToken();

            Log.w(TAG, "Login success, user: " + account.getDisplayName() + ", token length:" + oAuthAccessToken.length());

        } catch (ApiException e) {
            Log.w(TAG, "signInResult:failed code=" + e.getStatusCode());
        }
    }


    private void volleySendDataToGSheet() {
        String googleScriptAppUrl = "<URL OF MY SCRIPT>";

        StringRequest request = new StringRequest(Request.Method.POST, googleScriptAppUrl,
                successResponse -> {
                    Toast.makeText(this, "success: " + successResponse, Toast.LENGTH_LONG).show();
                },
                volleyError -> {
                    String message = "error: " + volleyError.getMessage() + " " + volleyError.networkResponse.statusCode;
                    Toast.makeText(this, message, Toast.LENGTH_LONG).show();
                    volleyError.printStackTrace();
                }
        ) {
            @Override
            protected Map<String, String> getParams() {
                Map<String, String> params = new HashMap<>();
                params.put("action", "ADD");
                params.put("data", "Data value");

                Log.i(TAG, "sending params: " + params.toString());
                return params;
            }

            @Override
            public Map<String, String> getHeaders() throws AuthFailureError {
                HashMap<String, String> headers = new HashMap<String, String>();
                headers.put("Content-Type", "application/json");

                headers.put("idToken", oAuthAccessToken);
                headers.put("id_token", oAuthAccessToken);
                headers.put("accessToken", oAuthAccessToken);
                headers.put("access_token", oAuthAccessToken);
                headers.put("authorization", oAuthAccessToken);
                headers.put("Authorization", oAuthAccessToken);

                Log.i(TAG, "sending headers:" + headers.toString());
                return headers;
            }
        };

        int socketTimeout = 50000;
        int retries = 1;

        request.setRetryPolicy(new DefaultRetryPolicy(socketTimeout, retries, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));

        RequestQueue requestQueue = Volley.newRequestQueue(this);
        requestQueue.add(request);
    }
}

Google App Script 本身运行良好,当我使用 chrome Postman 扩展对其进行测试时,它会正确地将数据添加到电子表格中。所以我希望它只是修复android请求中的授权标头的问题。

提前致谢!

标签: javaandroidgoogle-apps-scriptgoogle-oauthhttp-status-code-401

解决方案


我找到了解决此授权问题的方法。如果我部署我的 Google Apps 脚本,允许任何人执行它,它不会拒绝我的 401 请求。

实际上,对于这个项目,授权用户不需要使用我的 API,所以我接受了它现在的工作方式。

否则可能需要承载授权,正如@TheMaster 提到的


推荐阅读