如何在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服务的请求才能执行任务逻辑,否则返回错误。求实现代码和必要的项目配置步骤。
解决方案
一、前置配置
- 授权Cloud Tasks服务账号访问你的Servlet
确保创建任务时使用的服务账号(serviceAccountEmail)拥有访问Servlet所在服务的权限:- 若Servlet部署在Cloud Run,给该服务账号添加
Cloud Run Invoker角色; - 若部署在App Engine,添加
App Engine Invoker角色。
- 若Servlet部署在Cloud Run,给该服务账号添加
- 配置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
相关产品推荐
相关产品推荐

