You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用默认服务账号从Compute Engine向App Engine发送认证HTTP请求

授权Google Compute Engine实例安全访问同项目下的App Engine Servlet

我来给你梳理一套靠谱的方案,专门解决同项目下Compute Engine(CE)实例安全调用App Engine(GAE)维护servlet的问题——毕竟这个任务绝对不能被未授权用户触发,安全必须拉满对吧?

一、先给CE实例配置专属的服务账号

首先得给CE实例绑定一个权限可控的服务账号,别用太宽泛的权限:

  • 建议创建一个专用服务账号(当然如果现有账号权限合适也能复用),专门用来让CE实例调用GAE的servlet,这样权限更清晰。
  • 给这个服务账号加上roles/iam.serviceAccountTokenCreator权限,这个权限是用来生成身份令牌的核心权限,没有它CE实例没法生成合法的授权凭证。
  • 把CE实例关联的服务账号换成这个专用账号,或者给现有实例的服务账号补上上述权限就行。

二、在CE的Java程序里生成身份令牌并发起请求

直接用Google官方的Auth Library来生成令牌,不用自己折腾加密逻辑,代码示例给你准备好了:

import com.google.auth.oauth2.IdTokenCredentials;
import com.google.auth.oauth2.IdTokenProvider;
import com.google.auth.oauth2.GoogleCredentials;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Collections;

public class GaeMaintenanceClient {
    public static void main(String[] args) throws IOException {
        // 替换成你的GAE servlet的完整HTTPS地址,这个地址会作为令牌的"受众"参数
        String targetServletUrl = "https://your-app-id.uc.r.appspot.com/maintenance-task";
        
        // 获取CE实例默认的服务账号凭证(不需要手动配置密钥,Google会自动处理)
        GoogleCredentials credentials = GoogleCredentials.getApplicationDefault()
                .createScoped(Collections.emptyList());
        
        // 创建身份令牌凭证,指定目标受众就是你的servlet地址
        IdTokenCredentials idTokenCreds = IdTokenCredentials.newBuilder()
                .setIdTokenProvider((IdTokenProvider) credentials)
                .setTargetAudience(targetServletUrl)
                .build();
        
        // 获取生成的身份令牌
        String idToken = idTokenCreds.refreshAccessToken().getTokenValue();
        
        // 发起HTTPS请求,把令牌放在Authorization头里
        URL url = new URL(targetServletUrl);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("POST"); // 对应你servlet的请求方法,GET的话改一下
        conn.setRequestProperty("Authorization", "Bearer " + idToken);
        
        // 这里可以添加请求体或者处理响应,根据你的需求来
        int responseCode = conn.getResponseCode();
        System.out.println("请求响应码: " + responseCode);
    }
}
  • 别忘了在项目里引入依赖,比如Maven的话加这段:
<dependency>
    <groupId>com.google.auth</groupId>
    <artifactId>google-auth-library-oauth2-http</artifactId>
    <version>1.20.0</version> <!-- 用最新稳定版就行 -->
</dependency>

三、在GAE的Servlet里严格验证令牌

这是最关键的一步,必须确保只有合法的CE请求才能通过验证,代码示例如下:

import com.google.auth.oauth2.IdTokenVerifier;
import com.google.auth.oauth2.GoogleIdToken;
import com.google.auth.oauth2.GoogleCredentials;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Collections;

@WebServlet("/maintenance-task")
public class MaintenanceServlet extends HttpServlet {
    // 替换成你的servlet的完整HTTPS地址,要和CE里的targetServletUrl完全一致
    private static final String EXPECTED_AUDIENCE = "https://your-app-id.uc.r.appspot.com/maintenance-task";
    // 替换成你给CE实例绑定的专用服务账号邮箱
    private static final String ALLOWED_SERVICE_ACCOUNT = "maintenance-client@your-project-id.iam.gserviceaccount.com";

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
        String authHeader = request.getHeader("Authorization");
        // 先检查Authorization头是否存在且格式正确
        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "缺少或无效的授权头");
            return;
        }

        String idToken = authHeader.substring(7); // 去掉"Bearer "前缀
        try {
            // 创建令牌验证器,自动验证签名、发行者、受众
            IdTokenVerifier verifier = new IdTokenVerifier.Builder(GoogleCredentials.getApplicationDefault())
                    .setAudience(Collections.singletonList(EXPECTED_AUDIENCE))
                    .build();

            GoogleIdToken token = GoogleIdToken.parse(verifier.getJsonFactory(), idToken);
            // 验证令牌的合法性
            if (!verifier.verify(token)) {
                response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "无效的身份令牌");
                return;
            }

            GoogleIdToken.Payload payload = token.getPayload();
            // 检查发行者是否是官方的Google服务账号发行者
            String issuer = payload.getIssuer();
            if (!"https://accounts.google.com".equals(issuer) && !"https://oauth2.googleapis.com/token".equals(issuer)) {
                response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "无效的令牌发行者");
                return;
            }

            // 检查令牌的主体是否是我们授权的服务账号
            String serviceAccountEmail = payload.getSubject();
            if (!ALLOWED_SERVICE_ACCOUNT.equals(serviceAccountEmail)) {
                response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "未授权的服务账号");
                return;
            }

            // 所有验证都通过了,执行维护任务
            response.setStatus(HttpServletResponse.SC_OK);
            response.getWriter().write("维护任务执行成功");

        } catch (GeneralSecurityException | IOException e) {
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "令牌验证失败: " + e.getMessage());
        }
    }
}

这里的验证逻辑很严谨:

  1. 先检查请求头是否携带正确的授权令牌
  2. 验证令牌是Google官方签发的(防止伪造)
  3. 验证令牌的受众是我们的servlet地址(防止令牌被拿到其他地方复用)
  4. 验证令牌的主体是我们授权的服务账号(确保只有指定的CE实例能发起请求)

额外的安全小Tips

  • 强制GAE只接受HTTPS请求,HTTP请求直接返回403,防止令牌被窃听
  • 遵循最小权限原则,给服务账号只加必要的权限,不要给多余的权限
  • 如果是长期运行的CE实例,定期检查服务账号的权限配置,确保没有被误修改

内容的提问来源于stack exchange,提问作者Alberto Geniola

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 11:51:28