Skip to content

Commit 32b0b70

Browse files
authored
Iap sample using jjwt (#694)
* switching to jjwt library * adding known issues to README reg auth0 jwt on OpenJDK.
1 parent 7a1c431 commit 32b0b70

File tree

5 files changed

+77
-72
lines changed

5 files changed

+77
-72
lines changed

iap/README.md

+10-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Cloud Identity-Aware Proxy Java Samples
2-
Cloud Identity-Aware Proxy (Cloud IAP) lets you manage access to applications running in Compute Engine, App Engine standard environment, and Container Engine. Cloud IAP establishes a central authorization layer for applications accessed by HTTPS, enabling you to adopt an application-level access control model instead of relying on network-level firewalls. When you enable Cloud IAP, you must also use signed headers or the App Engine standard environment Users API to secure your app.
2+
Cloud Identity-Aware Proxy (Cloud IAP) lets you manage access to applications running in Compute Engine, App Engine standard environment, and Container Engine.
3+
Cloud IAP establishes a central authorization layer for applications accessed by HTTPS,
4+
enabling you to adopt an application-level access control model instead of relying on network-level firewalls.
5+
When you enable Cloud IAP, you must also use signed headers or the App Engine standard environment Users API to secure your app.
36

47
## Setup
58
- A Google Cloud project with billing enabled
@@ -29,6 +32,9 @@ It will be used to test both the authorization of an incoming request to an IAP
2932
```
3033

3134
## References
32-
[JWT library for Java](https://github.com/auth0/java-jwt)
33-
[Cloud IAP docs](https://cloud.google.com/iap/docs/)
34-
[Service account credentials](https://cloud.google.com/docs/authentication#getting_credentials_for_server-centric_flow)
35+
- [JWT library for Java (jjwt)](https://github.com/jwtk/jjwt)
36+
- [Cloud IAP docs](https://cloud.google.com/iap/docs/)
37+
- [Service account credentials](https://cloud.google.com/docs/authentication#getting_credentials_for_server-centric_flow)
38+
39+
## Known issues
40+
- [Auth0 JWT library](https://github.com/auth0/java-jwt) has intermittent IAP token verification issues on OpenJDK.

iap/pom.xml

+3-3
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@
5252
<version>0.6.0</version>
5353
</dependency>
5454
<dependency>
55-
<groupId>com.auth0</groupId>
56-
<artifactId>java-jwt</artifactId>
57-
<version>3.2.0</version>
55+
<groupId>io.jsonwebtoken</groupId>
56+
<artifactId>jjwt</artifactId>
57+
<version>0.7.0</version>
5858
</dependency>
5959
<!-- [END dependencies] -->
6060

iap/src/main/java/com/example/iap/BuildIapRequest.java

+14-12
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@
1313
*/
1414
package com.example.iap;
1515

16-
import com.auth0.jwt.JWT;
17-
import com.auth0.jwt.algorithms.Algorithm;
1816
import com.google.api.client.http.GenericUrl;
1917
import com.google.api.client.http.HttpHeaders;
2018
import com.google.api.client.http.HttpRequest;
@@ -28,9 +26,11 @@
2826
import com.google.api.client.util.GenericData;
2927
import com.google.auth.oauth2.GoogleCredentials;
3028
import com.google.auth.oauth2.ServiceAccountCredentials;
29+
import io.jsonwebtoken.Jwts;
30+
import io.jsonwebtoken.SignatureAlgorithm;
31+
3132
import java.io.IOException;
3233
import java.net.URL;
33-
import java.security.interfaces.RSAPrivateKey;
3434
import java.time.Clock;
3535
import java.time.Instant;
3636
import java.util.Collections;
@@ -71,16 +71,18 @@ private static String getSignedJWToken(ServiceAccountCredentials credentials, St
7171
throws IOException {
7272
Instant now = Instant.now(clock);
7373
long expirationTime = now.getEpochSecond() + EXPIRATION_TIME_IN_SECONDS;
74+
7475
// generate jwt signed by service account
75-
return JWT.create()
76-
.withKeyId(credentials.getPrivateKeyId())
77-
.withAudience(OAUTH_TOKEN_URI)
78-
.withIssuer(credentials.getClientEmail())
79-
.withSubject(credentials.getClientEmail())
80-
.withIssuedAt(Date.from(now))
81-
.withExpiresAt(Date.from(Instant.ofEpochSecond(expirationTime)))
82-
.withClaim("target_audience", baseUrl)
83-
.sign(Algorithm.RSA256(null, (RSAPrivateKey) credentials.getPrivateKey()));
76+
return Jwts.builder()
77+
.setHeaderParam("kid", credentials.getPrivateKeyId())
78+
.setIssuer(credentials.getClientEmail())
79+
.setAudience(OAUTH_TOKEN_URI)
80+
.setSubject(credentials.getClientEmail())
81+
.setIssuedAt(Date.from(now))
82+
.setExpiration(Date.from(Instant.ofEpochSecond(expirationTime)))
83+
.claim("target_audience", baseUrl)
84+
.signWith(SignatureAlgorithm.RS256, credentials.getPrivateKey())
85+
.compact();
8486
}
8587

8688
private static String getGoogleIdToken(String jwt) throws Exception {

iap/src/main/java/com/example/iap/VerifyIapRequestHeader.java

+42-42
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,6 @@
1313
*/
1414
package com.example.iap;
1515

16-
import com.auth0.jwt.JWT;
17-
import com.auth0.jwt.JWTVerifier;
18-
import com.auth0.jwt.algorithms.Algorithm;
19-
import com.auth0.jwt.exceptions.JWTVerificationException;
20-
import com.auth0.jwt.interfaces.DecodedJWT;
21-
import com.auth0.jwt.interfaces.ECDSAKeyProvider;
2216
import com.fasterxml.jackson.core.type.TypeReference;
2317
import com.fasterxml.jackson.databind.ObjectMapper;
2418
import com.google.api.client.http.GenericUrl;
@@ -28,13 +22,20 @@
2822
import com.google.api.client.http.javanet.NetHttpTransport;
2923
import com.google.api.client.util.PemReader;
3024
import com.google.api.client.util.PemReader.Section;
25+
import io.jsonwebtoken.Claims;
26+
import io.jsonwebtoken.JwsHeader;
27+
import io.jsonwebtoken.Jwt;
28+
import io.jsonwebtoken.Jwts;
29+
import io.jsonwebtoken.SigningKeyResolver;
30+
import io.jsonwebtoken.impl.DefaultClaims;
31+
3132
import java.io.IOException;
3233
import java.io.StringReader;
3334
import java.net.URL;
35+
import java.security.Key;
3436
import java.security.KeyFactory;
3537
import java.security.NoSuchAlgorithmException;
3638
import java.security.PublicKey;
37-
import java.security.interfaces.ECPrivateKey;
3839
import java.security.interfaces.ECPublicKey;
3940
import java.security.spec.InvalidKeySpecException;
4041
import java.security.spec.X509EncodedKeySpec;
@@ -43,21 +44,32 @@
4344

4445
/** Verify IAP authorization JWT token in incoming request. */
4546
public class VerifyIapRequestHeader {
47+
4648
// [START verify_iap_request]
4749
private static final String PUBLIC_KEY_VERIFICATION_URL =
4850
"https://www.gstatic.com/iap/verify/public_key";
4951
private static final String IAP_ISSUER_URL = "https://cloud.google.com/iap";
5052

51-
private final Map<String, ECPublicKey> keyCache = new HashMap<>();
53+
private final Map<String, Key> keyCache = new HashMap<>();
5254
private final ObjectMapper mapper = new ObjectMapper();
5355
private final TypeReference<HashMap<String, String>> typeRef =
5456
new TypeReference<HashMap<String, String>>() {};
5557

56-
private ECDSAKeyProvider keyProvider =
57-
new ECDSAKeyProvider() {
58+
private SigningKeyResolver resolver =
59+
new SigningKeyResolver() {
60+
@Override
61+
public Key resolveSigningKey(JwsHeader header, Claims claims) {
62+
return resolveSigningKey(header);
63+
}
64+
5865
@Override
59-
public ECPublicKey getPublicKeyById(String kid) {
60-
ECPublicKey key = keyCache.get(kid);
66+
public Key resolveSigningKey(JwsHeader header, String payload) {
67+
return resolveSigningKey(header);
68+
}
69+
70+
private Key resolveSigningKey(JwsHeader header) {
71+
String keyId = header.getKeyId();
72+
Key key = keyCache.get(keyId);
6173
if (key != null) {
6274
return key;
6375
}
@@ -72,33 +84,20 @@ public ECPublicKey getPublicKeyById(String kid) {
7284
}
7385
Map<String, String> keys = mapper.readValue(response.parseAsString(), typeRef);
7486
for (Map.Entry<String, String> keyData : keys.entrySet()) {
75-
if (!keyData.getKey().equals(kid)) {
87+
if (!keyData.getKey().equals(keyId)) {
7688
continue;
7789
}
7890
key = getKey(keyData.getValue());
7991
if (key != null) {
80-
keyCache.putIfAbsent(kid, key);
92+
keyCache.putIfAbsent(keyId, key);
8193
}
8294
}
8395

8496
} catch (IOException e) {
8597
// ignore exception
8698
}
87-
8899
return key;
89100
}
90-
91-
@Override
92-
public ECPrivateKey getPrivateKey() {
93-
// ignore : only required for signing requests
94-
return null;
95-
}
96-
97-
@Override
98-
public String getPrivateKeyId() {
99-
// ignore : only required for signing requests
100-
return null;
101-
}
102101
};
103102

104103
private static String getBaseUrl(URL url) throws Exception {
@@ -108,7 +107,7 @@ private static String getBaseUrl(URL url) throws Exception {
108107
return (url.getProtocol() + "://" + url.getHost() + path).trim();
109108
}
110109

111-
DecodedJWT verifyJWTToken(HttpRequest request) throws Exception {
110+
Jwt verifyJWTToken(HttpRequest request) throws Exception {
112111
// Check for iap jwt header in incoming request
113112
String jwtToken =
114113
request.getHeaders().getFirstHeaderStringValue("x-goog-authenticated-user-jwt");
@@ -119,24 +118,25 @@ DecodedJWT verifyJWTToken(HttpRequest request) throws Exception {
119118
return verifyJWTToken(jwtToken, baseUrl);
120119
}
121120

122-
DecodedJWT verifyJWTToken(String jwtToken, String baseUrl) throws Exception {
123-
Algorithm algorithm = Algorithm.ECDSA256(keyProvider);
124-
125-
// Time constraints are automatically checked, use acceptLeeway to specify a leeway window
121+
Jwt verifyJWTToken(String jwtToken, String baseUrl) throws Exception {
122+
// Time constraints are automatically checked, use setAllowedClockSkewSeconds
123+
// to specify a leeway window
126124
// The token was issued in a past date "iat" < TODAY
127125
// The token hasn't expired yet "exp" > TODAY
128-
JWTVerifier verifier =
129-
JWT.require(algorithm).withAudience(baseUrl).withIssuer(IAP_ISSUER_URL).build();
130-
131-
DecodedJWT decodedJWT = verifier.verify(jwtToken);
132-
133-
if (decodedJWT.getSubject() == null) {
134-
throw new JWTVerificationException("Subject expected, not found");
126+
Jwt jwt =
127+
Jwts.parser()
128+
.setSigningKeyResolver(resolver)
129+
.requireAudience(baseUrl)
130+
.requireIssuer(IAP_ISSUER_URL)
131+
.parse(jwtToken);
132+
DefaultClaims claims = (DefaultClaims) jwt.getBody();
133+
if (claims.getSubject() == null) {
134+
throw new Exception("Subject expected, not found.");
135135
}
136-
if (decodedJWT.getClaim("email") == null) {
137-
throw new JWTVerificationException("Email expected, not found");
136+
if (claims.get("email") == null) {
137+
throw new Exception("Email expected, not found.");
138138
}
139-
return decodedJWT;
139+
return jwt;
140140
}
141141

142142
private ECPublicKey getKey(String keyText) throws IOException {

iap/src/test/java/com/example/iap/BuildAndVerifyIapRequestIT.java

+8-11
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,29 @@
11
/**
22
* Copyright 2017 Google Inc.
33
*
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
4+
* <p>Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5+
* except in compliance with the License. You may obtain a copy of the License at
76
*
8-
* http://www.apache.org/licenses/LICENSE-2.0
7+
* <p>http://www.apache.org/licenses/LICENSE-2.0
98
*
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
9+
* <p>Unless required by applicable law or agreed to in writing, software distributed under the
10+
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11+
* express or implied. See the License for the specific language governing permissions and
1412
* limitations under the License.
1513
*/
16-
1714
package com.example.iap;
1815

1916
import static com.example.iap.BuildIapRequest.buildIAPRequest;
2017
import static org.junit.Assert.assertEquals;
2118
import static org.junit.Assert.assertNotNull;
2219

23-
import com.auth0.jwt.interfaces.DecodedJWT;
2420
import com.google.api.client.http.GenericUrl;
2521
import com.google.api.client.http.HttpRequest;
2622
import com.google.api.client.http.HttpResponse;
2723
import com.google.api.client.http.HttpResponseException;
2824
import com.google.api.client.http.HttpTransport;
2925
import com.google.api.client.http.javanet.NetHttpTransport;
26+
import io.jsonwebtoken.Jwt;
3027
import org.apache.http.HttpStatus;
3128
import org.junit.Before;
3229
import org.junit.Test;
@@ -70,7 +67,7 @@ public void testGenerateAndVerifyIapRequestIsSuccessful() throws Exception {
7067
assertNotNull(split);
7168
assertEquals(split.length, 2);
7269
assertEquals(split[0].trim(), "x-goog-authenticated-user-jwt");
73-
DecodedJWT decodedJWT = verifyIapRequestHeader.verifyJWTToken(split[1].trim(), iapProtectedUrl);
70+
Jwt decodedJWT = verifyIapRequestHeader.verifyJWTToken(split[1].trim(), iapProtectedUrl);
7471
assertNotNull(decodedJWT);
7572
}
7673
}

0 commit comments

Comments
 (0)