使用默认服务账号从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()); } } }
这里的验证逻辑很严谨:
- 先检查请求头是否携带正确的授权令牌
- 验证令牌是Google官方签发的(防止伪造)
- 验证令牌的受众是我们的servlet地址(防止令牌被拿到其他地方复用)
- 验证令牌的主体是我们授权的服务账号(确保只有指定的CE实例能发起请求)
额外的安全小Tips
- 强制GAE只接受HTTPS请求,HTTP请求直接返回403,防止令牌被窃听
- 遵循最小权限原则,给服务账号只加必要的权限,不要给多余的权限
- 如果是长期运行的CE实例,定期检查服务账号的权限配置,确保没有被误修改
内容的提问来源于stack exchange,提问作者Alberto Geniola
相关产品推荐
相关产品推荐

