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; import io.jsonwebtoken.JwsHeader; 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 { // 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, 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("subject -> " + claims.getSubject()); System.out.println("Signature is valid"); return claims.getSubject(); } catch (JOSEException e) { System.out.println("mess_1 -> " + e.getMessage()); System.out.println("Failed to verify signature: " + e.getMessage()); } } } catch (IOException | ParseException e) { System.out.println("mess_2 -> " + e.getMessage()); } return null; } }