Skip to content

Commit 1407c26

Browse files
feat(cloudrun): add 'cloudrun_service_to_service_receive' sample (GoogleCloudPlatform#10100)
* feat(cloudrun): add 'cloudrun_service_to_service_receive' sample * fix lint issue * fix comments from gemini * add crossorigin * fix issues with header * improve testing times * remove crossorigin * solve comments from reviewer * add waitForServer method * Use Authorization header
1 parent a008bb8 commit 1407c26

File tree

3 files changed

+469
-0
lines changed

3 files changed

+469
-0
lines changed

run/service-auth/pom.xml

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
Copyright 2025 Google LLC
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
http://www.apache.org/licenses/LICENSE-2.0
8+
Unless required by applicable law or agreed to in writing, software
9+
distributed under the License is distributed on an "AS IS" BASIS,
10+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
See the License for the specific language governing permissions and
12+
limitations under the License.
13+
-->
14+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
15+
<modelVersion>4.0.0</modelVersion>
16+
<groupId>com.example.run</groupId>
17+
<artifactId>service-auth</artifactId>
18+
<version>0.0.1-SNAPSHOT</version>
19+
<packaging>jar</packaging>
20+
21+
<!-- The parent pom defines common style checks and testing strategies for our samples.
22+
Removing or replacing it should not affect the execution of the samples in anyway. -->
23+
24+
<parent>
25+
<groupId>com.google.cloud.samples</groupId>
26+
<artifactId>shared-configuration</artifactId>
27+
<version>1.2.2</version>
28+
</parent>
29+
<dependencyManagement>
30+
<dependencies>
31+
<dependency>
32+
<!-- Import dependency management from Spring Boot -->
33+
<groupId>org.springframework.boot</groupId>
34+
<artifactId>spring-boot-dependencies</artifactId>
35+
<version>${spring-boot.version}</version>
36+
<type>pom</type>
37+
<scope>import</scope>
38+
</dependency>
39+
</dependencies>
40+
</dependencyManagement>
41+
<properties>
42+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
43+
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
44+
<maven.compiler.target>17</maven.compiler.target>
45+
<maven.compiler.source>17</maven.compiler.source>
46+
<spring-boot.version>3.2.2</spring-boot.version>
47+
</properties>
48+
<dependencies>
49+
<dependency>
50+
<groupId>org.springframework.boot</groupId>
51+
<artifactId>spring-boot-starter-web</artifactId>
52+
</dependency>
53+
<dependency>
54+
<groupId>org.springframework.boot</groupId>
55+
<artifactId>spring-boot-starter-test</artifactId>
56+
<scope>test</scope>
57+
</dependency>
58+
<dependency>
59+
<groupId>org.junit.vintage</groupId>
60+
<artifactId>junit-vintage-engine</artifactId>
61+
<scope>test</scope>
62+
</dependency>
63+
<dependency>
64+
<groupId>junit</groupId>
65+
<artifactId>junit</artifactId>
66+
<scope>test</scope>
67+
</dependency>
68+
<dependency>
69+
<groupId>com.google.api-client</groupId>
70+
<artifactId>google-api-client</artifactId>
71+
<version>2.7.2</version>
72+
</dependency>
73+
<dependency>
74+
<groupId>com.google.http-client</groupId>
75+
<artifactId>google-http-client</artifactId>
76+
<version>1.47.0</version>
77+
</dependency>
78+
<dependency>
79+
<groupId>com.google.auth</groupId>
80+
<artifactId>google-auth-library-oauth2-http</artifactId>
81+
<version>1.35.0</version>
82+
</dependency>
83+
</dependencies>
84+
<build>
85+
<plugins>
86+
<plugin>
87+
<groupId>org.springframework.boot</groupId>
88+
<artifactId>spring-boot-maven-plugin</artifactId>
89+
<version>${spring-boot.version}</version>
90+
<executions>
91+
<execution>
92+
<goals>
93+
<goal>repackage</goal>
94+
</goals>
95+
</execution>
96+
</executions>
97+
</plugin>
98+
<plugin>
99+
<groupId>com.google.cloud.tools</groupId>
100+
<artifactId>jib-maven-plugin</artifactId>
101+
<version>3.4.0</version>
102+
<configuration>
103+
<to>
104+
<image>gcr.io/PROJECT_ID/service-auth</image>
105+
</to>
106+
</configuration>
107+
</plugin>
108+
</plugins>
109+
</build>
110+
</project>
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.example.serviceauth;
18+
19+
// [START cloudrun_service_to_service_receive]
20+
21+
import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
22+
import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier;
23+
import com.google.api.client.http.apache.v2.ApacheHttpTransport;
24+
import com.google.api.client.json.gson.GsonFactory;
25+
import java.util.Arrays;
26+
import java.util.Collection;
27+
import org.springframework.boot.SpringApplication;
28+
import org.springframework.boot.autoconfigure.SpringBootApplication;
29+
import org.springframework.http.HttpHeaders;
30+
import org.springframework.http.HttpStatus;
31+
import org.springframework.http.ResponseEntity;
32+
import org.springframework.web.bind.annotation.GetMapping;
33+
import org.springframework.web.bind.annotation.RequestHeader;
34+
import org.springframework.web.bind.annotation.RestController;
35+
36+
@SpringBootApplication
37+
public class Authentication {
38+
@RestController
39+
class AuthenticationController {
40+
41+
private final AuthenticationService authService = new AuthenticationService();
42+
43+
@GetMapping("/")
44+
public ResponseEntity<String> getEmailFromAuthHeader(
45+
@RequestHeader(value = "Authorization", required = false) String authHeader) {
46+
String responseBody;
47+
if (authHeader == null) {
48+
responseBody = "Error verifying ID token: missing Authorization header";
49+
return new ResponseEntity<>(responseBody, HttpStatus.UNAUTHORIZED);
50+
}
51+
52+
String email = authService.parseAuthHeader(authHeader);
53+
if (email == null) {
54+
responseBody = "Unauthorized request. Please supply a valid bearer token.";
55+
HttpHeaders headers = new HttpHeaders();
56+
headers.add("WWW-Authenticate", "Bearer");
57+
return new ResponseEntity<>(responseBody, headers, HttpStatus.UNAUTHORIZED);
58+
}
59+
60+
responseBody = "Hello, " + email;
61+
return new ResponseEntity<>(responseBody, HttpStatus.OK);
62+
}
63+
}
64+
65+
public class AuthenticationService {
66+
/*
67+
* Parse the authorization header, validate and decode the Bearer token.
68+
*
69+
* Args:
70+
* authHeader: String of HTTP header with a Bearer token.
71+
*
72+
* Returns:
73+
* A string containing the email from the token.
74+
* null if the token is invalid or the email can't be retrieved.
75+
*/
76+
public String parseAuthHeader(String authHeader) {
77+
// Split the auth type and value from the header.
78+
String[] authHeaderStrings = authHeader.split(" ");
79+
if (authHeaderStrings.length != 2) {
80+
System.out.println("Malformed Authorization header");
81+
return null;
82+
}
83+
String authType = authHeaderStrings[0];
84+
String tokenValue = authHeaderStrings[1];
85+
// Validate and decode the ID token in the header.
86+
if (!"bearer".equals(authType.toLowerCase())) {
87+
System.out.println("Unhandled header format: " + authType);
88+
return null;
89+
}
90+
91+
// Get the service URL from the environment variable
92+
// set at the time of deployment.
93+
String serviceUrl = System.getenv("SERVICE_URL");
94+
// Define the expected audience as the Service Base URL.
95+
Collection<String> audience = Arrays.asList(serviceUrl);
96+
97+
try {
98+
// Find more information about the verification process in:
99+
// https://developers.google.com/identity/sign-in/web/backend-auth#java
100+
// https://cloud.google.com/java/docs/reference/google-api-client/latest/com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier
101+
GoogleIdTokenVerifier verifier =
102+
new GoogleIdTokenVerifier.Builder(new ApacheHttpTransport(), new GsonFactory())
103+
.setAudience(audience)
104+
.build();
105+
GoogleIdToken googleIdToken = verifier.verify(tokenValue);
106+
107+
// More info about the structure for the decoded ID Token here:
108+
// https://cloud.google.com/docs/authentication/token-types#id
109+
// https://cloud.google.com/java/docs/reference/google-api-client/latest/com.google.api.client.googleapis.auth.oauth2.GoogleIdToken
110+
// https://cloud.google.com/java/docs/reference/google-api-client/latest/com.google.api.client.googleapis.auth.oauth2.GoogleIdToken.Payload
111+
GoogleIdToken.Payload payload = googleIdToken.getPayload();
112+
if (!payload.getEmailVerified()) {
113+
System.out.println("Invalid token. Email wasn't verified.");
114+
return null;
115+
}
116+
System.out.println("Email verified: " + payload.getEmail());
117+
return payload.getEmail();
118+
119+
} catch (Exception exception) {
120+
System.out.println("Ivalid token: " + exception);
121+
}
122+
return null;
123+
}
124+
}
125+
126+
public static void main(String[] args) {
127+
SpringApplication.run(Authentication.class, args);
128+
}
129+
130+
// [END cloudrun_service_to_service_receive]
131+
}

0 commit comments

Comments
 (0)