Skip to content

Commit

Permalink
Iap sample using jjwt (#694)
Browse files Browse the repository at this point in the history
* switching to jjwt library
* adding known issues to README reg auth0 jwt on OpenJDK.
  • Loading branch information
jabubake committed Jun 8, 2017
1 parent 7a1c431 commit 32b0b70
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 72 deletions.
14 changes: 10 additions & 4 deletions iap/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Cloud Identity-Aware Proxy Java Samples
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.
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.

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

## References
[JWT library for Java](https://github.com/auth0/java-jwt)
[Cloud IAP docs](https://cloud.google.com/iap/docs/)
[Service account credentials](https://cloud.google.com/docs/authentication#getting_credentials_for_server-centric_flow)
- [JWT library for Java (jjwt)](https://github.com/jwtk/jjwt)
- [Cloud IAP docs](https://cloud.google.com/iap/docs/)
- [Service account credentials](https://cloud.google.com/docs/authentication#getting_credentials_for_server-centric_flow)

## Known issues
- [Auth0 JWT library](https://github.com/auth0/java-jwt) has intermittent IAP token verification issues on OpenJDK.
6 changes: 3 additions & 3 deletions iap/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@
<version>0.6.0</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.2.0</version>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.7.0</version>
</dependency>
<!-- [END dependencies] -->

Expand Down
26 changes: 14 additions & 12 deletions iap/src/main/java/com/example/iap/BuildIapRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@
*/
package com.example.iap;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpHeaders;
import com.google.api.client.http.HttpRequest;
Expand All @@ -28,9 +26,11 @@
import com.google.api.client.util.GenericData;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.auth.oauth2.ServiceAccountCredentials;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import java.io.IOException;
import java.net.URL;
import java.security.interfaces.RSAPrivateKey;
import java.time.Clock;
import java.time.Instant;
import java.util.Collections;
Expand Down Expand Up @@ -71,16 +71,18 @@ private static String getSignedJWToken(ServiceAccountCredentials credentials, St
throws IOException {
Instant now = Instant.now(clock);
long expirationTime = now.getEpochSecond() + EXPIRATION_TIME_IN_SECONDS;

// generate jwt signed by service account
return JWT.create()
.withKeyId(credentials.getPrivateKeyId())
.withAudience(OAUTH_TOKEN_URI)
.withIssuer(credentials.getClientEmail())
.withSubject(credentials.getClientEmail())
.withIssuedAt(Date.from(now))
.withExpiresAt(Date.from(Instant.ofEpochSecond(expirationTime)))
.withClaim("target_audience", baseUrl)
.sign(Algorithm.RSA256(null, (RSAPrivateKey) credentials.getPrivateKey()));
return Jwts.builder()
.setHeaderParam("kid", credentials.getPrivateKeyId())
.setIssuer(credentials.getClientEmail())
.setAudience(OAUTH_TOKEN_URI)
.setSubject(credentials.getClientEmail())
.setIssuedAt(Date.from(now))
.setExpiration(Date.from(Instant.ofEpochSecond(expirationTime)))
.claim("target_audience", baseUrl)
.signWith(SignatureAlgorithm.RS256, credentials.getPrivateKey())
.compact();
}

private static String getGoogleIdToken(String jwt) throws Exception {
Expand Down
84 changes: 42 additions & 42 deletions iap/src/main/java/com/example/iap/VerifyIapRequestHeader.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,6 @@
*/
package com.example.iap;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.ECDSAKeyProvider;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.api.client.http.GenericUrl;
Expand All @@ -28,13 +22,20 @@
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.util.PemReader;
import com.google.api.client.util.PemReader.Section;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SigningKeyResolver;
import io.jsonwebtoken.impl.DefaultClaims;

import java.io.IOException;
import java.io.StringReader;
import java.net.URL;
import java.security.Key;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
Expand All @@ -43,21 +44,32 @@

/** Verify IAP authorization JWT token in incoming request. */
public class VerifyIapRequestHeader {

// [START verify_iap_request]
private static final String PUBLIC_KEY_VERIFICATION_URL =
"https://www.gstatic.com/iap/verify/public_key";
private static final String IAP_ISSUER_URL = "https://cloud.google.com/iap";

private final Map<String, ECPublicKey> keyCache = new HashMap<>();
private final Map<String, Key> keyCache = new HashMap<>();
private final ObjectMapper mapper = new ObjectMapper();
private final TypeReference<HashMap<String, String>> typeRef =
new TypeReference<HashMap<String, String>>() {};

private ECDSAKeyProvider keyProvider =
new ECDSAKeyProvider() {
private SigningKeyResolver resolver =
new SigningKeyResolver() {
@Override
public Key resolveSigningKey(JwsHeader header, Claims claims) {
return resolveSigningKey(header);
}

@Override
public ECPublicKey getPublicKeyById(String kid) {
ECPublicKey key = keyCache.get(kid);
public Key resolveSigningKey(JwsHeader header, String payload) {
return resolveSigningKey(header);
}

private Key resolveSigningKey(JwsHeader header) {
String keyId = header.getKeyId();
Key key = keyCache.get(keyId);
if (key != null) {
return key;
}
Expand All @@ -72,33 +84,20 @@ public ECPublicKey getPublicKeyById(String kid) {
}
Map<String, String> keys = mapper.readValue(response.parseAsString(), typeRef);
for (Map.Entry<String, String> keyData : keys.entrySet()) {
if (!keyData.getKey().equals(kid)) {
if (!keyData.getKey().equals(keyId)) {
continue;
}
key = getKey(keyData.getValue());
if (key != null) {
keyCache.putIfAbsent(kid, key);
keyCache.putIfAbsent(keyId, key);
}
}

} catch (IOException e) {
// ignore exception
}

return key;
}

@Override
public ECPrivateKey getPrivateKey() {
// ignore : only required for signing requests
return null;
}

@Override
public String getPrivateKeyId() {
// ignore : only required for signing requests
return null;
}
};

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

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

DecodedJWT verifyJWTToken(String jwtToken, String baseUrl) throws Exception {
Algorithm algorithm = Algorithm.ECDSA256(keyProvider);

// Time constraints are automatically checked, use acceptLeeway to specify a leeway window
Jwt verifyJWTToken(String jwtToken, String baseUrl) throws Exception {
// Time constraints are automatically checked, use setAllowedClockSkewSeconds
// to specify a leeway window
// The token was issued in a past date "iat" < TODAY
// The token hasn't expired yet "exp" > TODAY
JWTVerifier verifier =
JWT.require(algorithm).withAudience(baseUrl).withIssuer(IAP_ISSUER_URL).build();

DecodedJWT decodedJWT = verifier.verify(jwtToken);

if (decodedJWT.getSubject() == null) {
throw new JWTVerificationException("Subject expected, not found");
Jwt jwt =
Jwts.parser()
.setSigningKeyResolver(resolver)
.requireAudience(baseUrl)
.requireIssuer(IAP_ISSUER_URL)
.parse(jwtToken);
DefaultClaims claims = (DefaultClaims) jwt.getBody();
if (claims.getSubject() == null) {
throw new Exception("Subject expected, not found.");
}
if (decodedJWT.getClaim("email") == null) {
throw new JWTVerificationException("Email expected, not found");
if (claims.get("email") == null) {
throw new Exception("Email expected, not found.");
}
return decodedJWT;
return jwt;
}

private ECPublicKey getKey(String keyText) throws IOException {
Expand Down
19 changes: 8 additions & 11 deletions iap/src/test/java/com/example/iap/BuildAndVerifyIapRequestIT.java
Original file line number Diff line number Diff line change
@@ -1,32 +1,29 @@
/**
* Copyright 2017 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* <p>http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* <p>Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.example.iap;

import static com.example.iap.BuildIapRequest.buildIAPRequest;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

import com.auth0.jwt.interfaces.DecodedJWT;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpResponse;
import com.google.api.client.http.HttpResponseException;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import io.jsonwebtoken.Jwt;
import org.apache.http.HttpStatus;
import org.junit.Before;
import org.junit.Test;
Expand Down Expand Up @@ -70,7 +67,7 @@ public void testGenerateAndVerifyIapRequestIsSuccessful() throws Exception {
assertNotNull(split);
assertEquals(split.length, 2);
assertEquals(split[0].trim(), "x-goog-authenticated-user-jwt");
DecodedJWT decodedJWT = verifyIapRequestHeader.verifyJWTToken(split[1].trim(), iapProtectedUrl);
Jwt decodedJWT = verifyIapRequestHeader.verifyJWTToken(split[1].trim(), iapProtectedUrl);
assertNotNull(decodedJWT);
}
}

0 comments on commit 32b0b70

Please sign in to comment.