Skip to content

Instantly share code, notes, and snippets.

@proton5000
Last active April 15, 2024 09:45
Show Gist options
  • Select an option

  • Save proton5000/672b5e9e81193ff38509384f2e4f7fa2 to your computer and use it in GitHub Desktop.

Select an option

Save proton5000/672b5e9e81193ff38509384f2e4f7fa2 to your computer and use it in GitHub Desktop.

Revisions

  1. proton5000 revised this gist Apr 15, 2024. 1 changed file with 38 additions and 25 deletions.
    63 changes: 38 additions & 25 deletions AppCheckFilter.java
    Original file line number Diff line number Diff line change
    @@ -4,6 +4,7 @@
    import com.nimbusds.jose.jwk.JWK;
    import com.nimbusds.jose.jwk.JWKSet;
    import io.jsonwebtoken.Claims;
    import io.jsonwebtoken.JwsHeader;
    import io.jsonwebtoken.Jwts;
    import jakarta.servlet.FilterChain;
    import jakarta.servlet.ServletException;
    @@ -29,7 +30,7 @@ public class AppCheckFilter extends OncePerRequestFilter {
    private static final String FIREBASE_APP_CHECK_URL = "https://firebaseappcheck.googleapis.com/v1/jwks";

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
    throws ServletException, IOException {

    String token = request.getHeader("HTTP_X_FIREBASE_APPCHECK");
    @@ -50,66 +51,78 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
    }

    private String checkToken(String token) {

    // Load JWKS keys from a file or URL

    try {

    // Not necessary to get the public keys each time, it can be cached for 6 hours,
    // info from docs https://firebase.google.com/docs/app-check/custom-resource-backend#other
    String jwksString = IOUtils.toString(new URL(FIREBASE_APP_CHECK_URL), Charset.defaultCharset());
    JWKSet jwks = JWKSet.parse(jwksString);

    // Extract JWK with given kid
    for (JWK jwk : jwks.toPublicJWKSet().getKeys()) {// Use JWK to verify JWT signature
    try {

    // If a getting claims not throw the exception it's like You can already trust your mobile application,
    // it has a signature, but to clarify that it is your application, you can check the following statements
    Claims claims = Jwts.parser()
    .verifyWith(jwk.toRSAKey().toRSAPublicKey())
    .build()
    .parseSignedClaims(token)
    .getPayload();

    JwsHeader header = Jwts.parser()
    .verifyWith(jwk.toRSAKey().toRSAPublicKey())
    .build()
    .parseSignedClaims(token)
    .getHeader();

    for (String claimName : claims.keySet()) {
    System.out.println(claimName + ": " + claims.get(claimName));
    }

    // Validate claims
    // if (!decodedJwt.getAlgorithm().equals("RS256")) {
    // return null;
    // }
    //
    // if (!decodedJwt.getType().equals("JWT")) {
    // return null;
    // }
    //
    // if (!decodedJwt.getIssuer().equals("https://firebaseappcheck.googleapis.com/#" + projectNumber)) {
    // return null;
    // }
    //
    // if (decodedJwt.getExpiresAt().before(new Date())) {
    // return null;
    // }
    //
    // List<String> audiences = decodedJwt.getAudience();
    // if (!audiences.contains("projects/" + projectNumber)) {
    // return null;
    // }
    // Validate claims, you can choose what is better to validate for your app
    if (!header.getAlgorithm().equals("RS256")) {
    return null;
    }

    if (!header.getType().equals("JWT")) {
    return null;
    }

    if (!claims.getIssuer().equals("https://firebaseappcheck.googleapis.com/#" + PROJECT_NUMBER)) {
    return null;
    }

    if (claims.getExpiration().before(new Date())) {
    return null;
    }

    if (!claims.getAudience().contains("projects/" + PROJECT_NUMBER)) {
    return null;
    }

    System.out.println("header -> " + header.getAlgorithm());
    System.out.println("type -> " + header.getType());

    System.out.println("issuer -> " + claims.getIssuer());
    System.out.println("audience -> " + claims.getAudience().contains("projects/" + PROJECT_NUMBER));
    System.out.println("expiration -> " + claims.getExpiration().before(new Date()));
    System.out.println("type -> " + claims.get("type"));
    System.out.println("subject -> " + claims.getSubject());

    System.out.println("Signature is valid");

    return claims.getSubject();

    } catch (JOSEException e) {
    // e.printStackTrace();
    System.out.println("mess_1 -> " + e.getMessage());
    System.out.println("Failed to verify signature: " + e.getMessage());
    }
    }

    } catch (IOException | ParseException e) {
    // e.printStackTrace();
    System.out.println("mess_2 -> " + e.getMessage());
    }

  2. proton5000 revised this gist Apr 15, 2024. No changes.
  3. proton5000 revised this gist Apr 15, 2024. 1 changed file with 3 additions and 2 deletions.
    5 changes: 3 additions & 2 deletions AppCheckFilter.java
    Original file line number Diff line number Diff line change
    @@ -1,5 +1,6 @@
    package ua.varus.scan.and.go.config.security;

    import com.nimbusds.jose.JOSEException;
    import com.nimbusds.jose.jwk.JWK;
    import com.nimbusds.jose.jwk.JWKSet;
    import io.jsonwebtoken.Claims;
    @@ -28,7 +29,7 @@ public class AppCheckFilter extends OncePerRequestFilter {
    private static final String FIREBASE_APP_CHECK_URL = "https://firebaseappcheck.googleapis.com/v1/jwks";

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
    throws ServletException, IOException {

    String token = request.getHeader("HTTP_X_FIREBASE_APPCHECK");
    @@ -100,7 +101,7 @@ private String checkToken(String token) {
    System.out.println("Signature is valid");
    return claims.getSubject();

    } catch (Exception e) {
    } catch (JOSEException e) {
    // e.printStackTrace();
    System.out.println("mess_1 -> " + e.getMessage());
    System.out.println("Failed to verify signature: " + e.getMessage());
  4. proton5000 created this gist Apr 15, 2024.
    117 changes: 117 additions & 0 deletions AppCheckFilter.java
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,117 @@
    package ua.varus.scan.and.go.config.security;

    import com.nimbusds.jose.jwk.JWK;
    import com.nimbusds.jose.jwk.JWKSet;
    import io.jsonwebtoken.Claims;
    import io.jsonwebtoken.Jwts;
    import jakarta.servlet.FilterChain;
    import jakarta.servlet.ServletException;
    import jakarta.servlet.http.HttpServletRequest;
    import jakarta.servlet.http.HttpServletResponse;
    import lombok.RequiredArgsConstructor;
    import org.apache.commons.io.IOUtils;
    import org.springframework.stereotype.Component;
    import org.springframework.web.filter.OncePerRequestFilter;

    import java.io.IOException;
    import java.net.URL;
    import java.nio.charset.Charset;
    import java.text.ParseException;
    import java.util.Date;


    @Component
    @RequiredArgsConstructor
    public class AppCheckFilter extends OncePerRequestFilter {

    private static final String PROJECT_NUMBER = "1050204332425";
    private static final String FIREBASE_APP_CHECK_URL = "https://firebaseappcheck.googleapis.com/v1/jwks";

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
    throws ServletException, IOException {

    String token = request.getHeader("HTTP_X_FIREBASE_APPCHECK");

    if (token == null) {
    response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthenticated");
    return;
    }

    String appId = checkToken(token);
    if (appId == null) {
    response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthenticated");
    return;
    }

    request.setAttribute("firebase.app", appId);
    filterChain.doFilter(request, response);
    }

    private String checkToken(String token) {
    // Load JWKS keys from a file or URL

    try {
    String jwksString = IOUtils.toString(new URL(FIREBASE_APP_CHECK_URL), Charset.defaultCharset());
    JWKSet jwks = JWKSet.parse(jwksString);

    // Extract JWK with given kid
    for (JWK jwk : jwks.toPublicJWKSet().getKeys()) {// Use JWK to verify JWT signature
    try {

    Claims claims = Jwts.parser()
    .verifyWith(jwk.toRSAKey().toRSAPublicKey())
    .build()
    .parseSignedClaims(token)
    .getPayload();

    for (String claimName : claims.keySet()) {
    System.out.println(claimName + ": " + claims.get(claimName));
    }

    // Validate claims
    // if (!decodedJwt.getAlgorithm().equals("RS256")) {
    // return null;
    // }
    //
    // if (!decodedJwt.getType().equals("JWT")) {
    // return null;
    // }
    //
    // if (!decodedJwt.getIssuer().equals("https://firebaseappcheck.googleapis.com/#" + projectNumber)) {
    // return null;
    // }
    //
    // if (decodedJwt.getExpiresAt().before(new Date())) {
    // return null;
    // }
    //
    // List<String> audiences = decodedJwt.getAudience();
    // if (!audiences.contains("projects/" + projectNumber)) {
    // return null;
    // }

    System.out.println("issuer -> " + claims.getIssuer());
    System.out.println("audience -> " + claims.getAudience().contains("projects/" + PROJECT_NUMBER));
    System.out.println("expiration -> " + claims.getExpiration().before(new Date()));
    System.out.println("type -> " + claims.get("type"));
    System.out.println("subject -> " + claims.getSubject());

    System.out.println("Signature is valid");
    return claims.getSubject();

    } catch (Exception e) {
    // e.printStackTrace();
    System.out.println("mess_1 -> " + e.getMessage());
    System.out.println("Failed to verify signature: " + e.getMessage());
    }
    }

    } catch (IOException | ParseException e) {
    // e.printStackTrace();
    System.out.println("mess_2 -> " + e.getMessage());
    }

    return null;
    }
    }
    28 changes: 28 additions & 0 deletions pom.xml
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,28 @@
    <dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.12.5</version>
    </dependency>
    <dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.12.5</version>
    <scope>runtime</scope>
    </dependency>
    <dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-gson</artifactId>
    <version>0.12.5</version>
    </dependency>
    <dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>0.12.5</version>
    <scope>runtime</scope>
    </dependency>
    <dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-orgjson</artifactId>
    <version>0.12.5</version>
    <scope>runtime</scope>
    </dependency>