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

如何在Google Cloud HTTP任务的HttpServlet中验证OIDC令牌

问题描述

我用官方示例代码创建了带OIDC令牌的Cloud Tasks HTTP目标任务,代码如下:

import com.google.cloud.tasks.v2.CloudTasksClient;
import com.google.cloud.tasks.v2.HttpMethod;
import com.google.cloud.tasks.v2.HttpRequest;
import com.google.cloud.tasks.v2.OidcToken;
import com.google.cloud.tasks.v2.QueueName;
import com.google.cloud.tasks.v2.Task;
import com.google.protobuf.ByteString;
import java.io.IOException;
import java.nio.charset.Charset;

public class CreateHttpTaskWithToken {

  public static void main(String[] args) throws IOException {
    // TODO(developer): Replace these variables before running the sample.
    String projectId = "my-project-id";
    String locationId = "us-central1";
    String queueId = "my-queue";
    String serviceAccountEmail =
        "java-docs-samples-testing@java-docs-samples-testing.iam.gserviceaccount.com";
    createTask(projectId, locationId, queueId, serviceAccountEmail);
  }

  // Create a task with a HTTP target and authorization token using the Cloud Tasks client.
  public static void createTask(
      String projectId, String locationId, String queueId, String serviceAccountEmail)
      throws IOException {

    // Instantiates a client.
    try (CloudTasksClient client = CloudTasksClient.create()) {
      String url =
          "https://example.com/taskhandler"; // The full url path that the request will be sent to
      String payload = "Hello, World!"; // The task HTTP request body

      // Construct the fully qualified queue name.
      String queuePath = QueueName.of(projectId, locationId, queueId).toString();

      // Add your service account email to construct the OIDC token.
      // in order to add an authentication header to the request.
      OidcToken.Builder oidcTokenBuilder =
          OidcToken.newBuilder().setServiceAccountEmail(serviceAccountEmail);

      // Construct the task body.
      Task.Builder taskBuilder =
          Task.newBuilder()
              .setHttpRequest(
                  HttpRequest.newBuilder()
                      .setBody(ByteString.copyFrom(payload, Charset.defaultCharset()))
                      .setHttpMethod(HttpMethod.POST)
                      .setUrl(url)
                      .setOidcToken(oidcTokenBuilder)
                      .build());

      // Send create task request.
      Task task = client.createTask(queuePath, taskBuilder.build());
      System.out.println("Task created: " + task.getName());
    }
  }
}

同时我有一个处理任务的HttpServlet:

@WebServlet(
    name = "Tasks",
    description = "Create Cloud Task",
    urlPatterns = "/tasks/create"
)
public class TaskServlet extends HttpServlet {    
  @Override
  public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    // Do some long running operation
  }
}

现在需要在这个Servlet里验证OIDC令牌,确保只有来自我的Cloud Tasks服务的请求才能执行任务逻辑,否则返回错误。求实现代码和必要的项目配置步骤。


解决方案

一、前置配置

  1. 授权Cloud Tasks服务账号访问你的Servlet
    确保创建任务时使用的服务账号(serviceAccountEmail)拥有访问Servlet所在服务的权限:
    • 若Servlet部署在Cloud Run,给该服务账号添加Cloud Run Invoker角色;
    • 若部署在App Engine,添加App Engine Invoker角色。
  2. 配置OIDC令牌的受众(Audience)
    在创建任务的代码中,给OidcToken添加受众配置,确保令牌的aud字段与你的Servlet实际URL完全匹配,避免令牌被非法复用:
    OidcToken.Builder oidcTokenBuilder = OidcToken.newBuilder()
        .setServiceAccountEmail(serviceAccountEmail)
        .setAudience("https://your-domain.com/tasks/create"); // 替换为你的Servlet实际URL
    

二、Servlet中验证OIDC令牌的实现

使用Google官方的google-auth-library-java库来完成令牌验证,步骤如下:

1. 添加依赖

如果使用Maven,在pom.xml中加入:

<dependency>
    <groupId>com.google.auth</groupId>
    <artifactId>google-auth-library-oauth2-http</artifactId>
    <version>1.20.0</version> <!-- 使用最新稳定版本 -->
</dependency>

2. 验证代码实现

修改TaskServlet,添加完整的令牌验证逻辑:

@WebServlet(
    name = "Tasks",
    description = "Handle Cloud Task",
    urlPatterns = "/tasks/create"
)
public class TaskServlet extends HttpServlet {    
    // 预期的服务账号邮箱(创建任务时使用的那个)
    private static final String EXPECTED_SERVICE_ACCOUNT = "java-docs-samples-testing@java-docs-samples-testing.iam.gserviceaccount.com";
    // 预期的受众(和创建任务时设置的audience一致)
    private static final String EXPECTED_AUDIENCE = "https://your-domain.com/tasks/create";

    private final IdTokenVerifier verifier;

    public TaskServlet() throws IOException {
        // 初始化令牌验证器,配置签发者和受众规则
        GoogleIdTokenVerifier.Builder builder = new GoogleIdTokenVerifier.Builder(new NetHttpTransport(), GsonFactory.getDefaultInstance())
                .setAudience(Collections.singletonList(EXPECTED_AUDIENCE))
                .setIssuer("https://accounts.google.com"); // Google OIDC令牌固定签发者
        this.verifier = builder.build();
    }

    @Override
    public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        // 从请求头获取Authorization令牌
        String authHeader = req.getHeader("Authorization");
        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
            resp.sendError(HttpServletResponse.SC_UNAUTHORIZED, "缺少或无效的Authorization请求头");
            return;
        }

        String idToken = authHeader.substring(7); // 移除"Bearer "前缀

        try {
            GoogleIdToken token = verifier.verify(idToken);
            if (token == null) {
                resp.sendError(HttpServletResponse.SC_UNAUTHORIZED, "无效的ID令牌");
                return;
            }

            // 验证令牌主体是否为预期的服务账号
            GoogleIdToken.Payload payload = token.getPayload();
            String issuer = payload.getIssuer();
            String serviceAccount = payload.getAuthorizedParty();

            if (!"https://accounts.google.com".equals(issuer) || !EXPECTED_SERVICE_ACCOUNT.equals(serviceAccount)) {
                resp.sendError(HttpServletResponse.SC_UNAUTHORIZED, "令牌并非由预期的服务账号签发");
                return;
            }

            // 验证通过,执行任务逻辑
            handleTaskLogic(req, resp);

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

    private void handleTaskLogic(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        // 这里编写你的任务处理逻辑
        resp.setStatus(HttpServletResponse.SC_OK);
        resp.getWriter().write("任务处理成功");
    }
}

三、关键验证点说明

  • 签发者验证:确保令牌由Google官方账号服务(https://accounts.google.com)签发,排除伪造令牌;
  • 受众匹配:验证令牌的aud字段与你的服务URL一致,防止令牌被复用在其他服务;
  • 服务账号匹配:通过azp(authorizedParty)字段确认令牌由你指定的Cloud Tasks服务账号生成,避免其他账号的非法请求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 17:52:32