GitHub bietet Webhooks an, die es einem ermöglichen, sich über Repository-Änderungen informieren zu lassen. Je nach Auswahl sendet GitHub dann einen JSON-Payload an die hinterlegte URL. Damit man sich auf der Serverseite sicher sein kann, dass die Daten auch wirklich von GitHub kommen und korrekt sind, sendet GitHub eine x-hub-signature
mit. Die von GitHub versendete Signatur kann dann validiert werden. Wie das geht, zeigt folgendes Beispiel in Java.
Code-Beispiel
WebHookServlet.java
import java.io.BufferedReader; import java.io.IOException; import java.io.PrintWriter; import java.util.logging.Level; import java.util.logging.Logger; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet(name = "WebHookServlet", urlPatterns = {"/webhook/*"}) public class WebHookServlet extends HttpServlet { private static final Logger LOG = Logger.getLogger(WebHookServlet.class.getName()); protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); try (PrintWriter out = response.getWriter()) { out.println("<!DOCTYPE html>"); out.println("<html>"); out.println("<head>"); out.println("<title>WebHookServlet</title>"); out.println("</head>"); out.println("<body>"); out.println("<p>OK</p>"); out.println("</body>"); out.println("</html>"); } } @Override protected void doPost(HttpServletRequest servletRequest, HttpServletResponse response) throws ServletException, IOException { String userAgent = servletRequest.getHeader("user-agent"); String event = servletRequest.getHeader("x-github-event"); if (userAgent.contains("GitHub-Hookshot") && event.equals("push")) { String signature = servletRequest.getHeader("x-hub-signature"); String secret = "abc123"; // Read request body StringBuilder sb = new StringBuilder(); String line; try { BufferedReader reader = servletRequest.getReader(); while ((line = reader.readLine()) != null) { sb.append(line); } } catch (IOException ex) { LOG.log(Level.WARNING, ex.getLocalizedMessage()); } String payload = sb.toString(); boolean isValid = GitHubUtility.verifySignature(payload, signature, secret); LOG.log(Level.INFO, "Valid GitHub Webhook Payload: {0}", isValid); } processRequest(servletRequest, response); } } |
GitHubUtility.java
import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.logging.Level; import java.util.logging.Logger; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; public class GitHubUtility { private static final Logger LOG = Logger.getLogger(GitHubUtility.class.getName()); private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1"; private static final char[] HEX = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; public static boolean verifySignature(String payload, String signature, String secret) { boolean isValid = false; try { Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM); SecretKeySpec signingKey = new SecretKeySpec(secret.getBytes(), HMAC_SHA1_ALGORITHM); mac.init(signingKey); byte[] rawHmac = mac.doFinal(payload.getBytes()); String expected = signature.substring(5); String actual = new String(encode(rawHmac)); isValid = expected.equals(actual); } catch (NoSuchAlgorithmException | InvalidKeyException | IllegalStateException ex) { LOG.log(Level.WARNING, ex.getLocalizedMessage()); } return isValid; } private static char[] encode(byte[] bytes) { final int amount = bytes.length; char[] result = new char[2 * amount]; int j = 0; for (int i = 0; i < amount; i++) { result[j++] = HEX[(0xF0 & bytes[i]) >>> 4]; result[j++] = HEX[(0x0F & bytes[i])]; } return result; } } |