Google服务账号代授权批量复制Drive文件遇404错误求助
I'm trying to authenticate as a domain user using a service account, and I've already set up domain-wide delegation in the G Suite Admin Console. I can successfully retrieve an access token with the Google Apps Script code below, but when I send batch requests to copy Drive files, I get a code: 404, message: 'File not found:' error. Did I miss any steps in the service account creation or authentication flow?
My Code:
Access Token Retrieval:
var CREDENTIALS = { private_key: "-----BEGIN PRIVATE KEY----- XXXXXXX \n-----END PRIVATE KEY-----\n", client_email: "XXXXXX@fXXXXXX.iam.gserviceaccount.com", client_id: "1XXXXXXXXXXXXXXXX", user_email: "XXXXX@XXXX.XXX.XXX", scopes: [ "https://www.googleapis.com/auth/drive", "https://www.googleapis.com/auth/spreadsheets", "https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/script.external_request" ] }; function oAuthToken(){ var url = "https://www.googleapis.com/oauth2/v3/token"; var header = { alg: "RS256", typ: "JWT" }; var now = Math.floor(Date.now() / 1000); var claim = { iss: CREDENTIALS.client_id, sub: CREDENTIALS.user_email, scope: CREDENTIALS.scopes.join(" "), aud: url, exp: (now + 3600).toString(), iat: now.toString() }; var signature = Utilities.base64Encode(JSON.stringify(header)) + "." + Utilities.base64Encode(JSON.stringify(claim)); var jwt = signature + "." + Utilities.base64Encode(Utilities.computeRsaSha256Signature(signature, CREDENTIALS.private_key)); var params = { method: "post", payload: { assertion: jwt, grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer" } }; var res = UrlFetchApp.fetch(url, params).getContentText(); return JSON.parse(res); }
Batch Processing (Abbreviated):
var request={ batchPath: requests:[] }; var backoff = 0; function batch(request) { var oAuth = oAuthToken().access_token; var url = 'https://www.googleapis.com/'+request.batchPath; var body = request.requests; if(body.length < 1){ return []; } var boundary = 'xxxxxxxxxx'; var contentId = 0; var data = '--' + boundary + '\r\n'; for (var i in body) { if(typeof body[i]=='object'){ data += 'Content-Type: application/http\r\n'; data += 'Content-ID: ' + ++contentId + '\r\n\r\n'; data += body[i].method + ' ' + body[i].endpoint + '\r\n'; data += body[i].requestBody ? 'Content-Type: application/json; charset=utf-8\r\n\r\n' : '\r\n'; data += body[i].requestBody ? JSON.stringify(body[i].requestBody) + '\r\n' : ''; data += "--" + boundary + '\r\n'; } } var parseBatchRes = function(res) { var splittedRes = res.split('--batch'); return splittedRes.slice(1, splittedRes.length - 1).map(function(e) { return { contentId: Number(e.match(/Content-ID: response-(\d+)/)[1]), status: Number(e.match(/HTTP\/\d+.\d+ (\d+)/)[1]), object: JSON.parse(e.match(/{[\S\s]+}/)[0]) }; }); }; var payload = Utilities.newBlob(data).getBytes(); var head = {Authorization: 'Bearer ' + oAuth}; var options = { method: 'POST', contentType: 'multipart/mixed; boundary=' + boundary, payload: payload, headers: head, muteHttpExceptions: false }; var complete=false; var finalResponse=[]; for (var n=0; n<=backoff; n++) { if(complete){ break; } var complete = true; console.log('backoff',n); var response = UrlFetchApp.fetch(url, options).getContentText(); for(var j=0;j<response.length;j++){ if(response[r].status!=200){ var complete = false; } } } }
Let's break down the potential issues and fixes here:
1. JWT Encoding is Not URL-Safe
The biggest hidden issue here is your JWT encoding method. Utilities.base64Encode() generates standard Base64, but JWT requires URL-safe Base64 (replace + with -, / with _, and strip trailing = characters). Even if you get an access token, a non-url-safe JWT might not properly delegate the user's Drive permissions, leading to permission-related 404s.
Add this helper function for URL-safe encoding:
function base64UrlEncode(str) { return Utilities.base64Encode(str) .replace(/\+/g, '-') .replace(/\//g, '_') .replace(/=+$/, ''); }
Then update your oAuthToken() function to use this instead of the standard encoding:
var signature = base64UrlEncode(JSON.stringify(header)) + "." + base64UrlEncode(JSON.stringify(claim)); var signatureBytes = Utilities.computeRsaSha256Signature(signature, CREDENTIALS.private_key); var jwt = signature + "." + base64UrlEncode(signatureBytes);
2. Verify Batch Request Endpoints & File Permissions
A 404 "File not found" almost always points to an invalid file ID or missing access:
- Double-check that the file IDs in your
body[i].endpointare correct (no typos) and that the delegated user (CREDENTIALS.user_email) has view or edit access to those files. - Ensure your endpoint uses the correct Drive API version: For v3, the copy endpoint is
https://www.googleapis.com/drive/v3/files/{FILE_ID}/copy(POST method). - Confirm your
batchPathis set tobatch/drive/v3for Drive v3 batch requests.
3. Double-Check Domain-Wide Delegation Setup
Even if you think it's configured correctly, verify these steps in the G Suite Admin Console:
- Go to Security > API Controls > Domain-wide delegation.
- Confirm your service account's client ID is listed, and the scopes include
https://www.googleapis.com/auth/drive(not a restricted scope likedrive.readonly). - Ensure the delegated user (
CREDENTIALS.user_email) is a valid domain user with active Drive access.
4. Fix Batch Response Parsing Typo
Your current batch loop has a typo (response[r].status instead of response[j].status), which means you're not properly detecting failed requests. Fix this to debug which file is causing the 404:
var parsedResponses = parseBatchRes(response); for(var j=0;j<parsedResponses.length;j++){ if(parsedResponses[j].status !== 200){ complete = false; // Log the specific error to pinpoint the problematic file console.log('Failed request details:', parsedResponses[j].object); } }
5. Shared Drive Edge Case
If the files are in a shared drive, ensure the delegated user has access to the shared drive itself. In rare cases, you might need to add the service account as a member of the shared drive with edit/owner permissions, but this shouldn't be necessary if the delegated user already has access.
Start with the JWT encoding fix—it's the most common hidden issue with service account delegation in Apps Script. Once that's sorted, use the updated error logging to pinpoint exactly which file is failing and verify its permissions.
内容的提问来源于stack exchange,提问作者Clay Smith

