# 웹후크 제공 유효성 검사하기

웹훅 시크릿을 사용하여 웹훅 전달이 GitHub에서 전송된 것인지 확인할 수 있습니다.

## 웹후크 제공 유효성 검사 알아보기

페이로드를 수신하도록 서버를 구성하고 나면, 구성한 엔드포인트에 제공된 모든 페이로드를 수신 대기합니다. 서버에서 보낸 GitHub 웹후크 배달만 처리하고 배달이 변조되지 않았는지 확인하려면 배달을 더 처리하기 전에 웹후크 서명의 유효성을 검사해야 합니다. 이렇게 하면 GitHub에서 온 것이 아닌 전달 요청을 처리하느라 서버 시간이 낭비되는 것을 방지하고 중간자 공격도 예방하는 데 도움이 됩니다.

이렇게 하려면 다음을 수행해야 합니다.

1. 웹후크에 대한 비밀 토큰을 만듭니다.
2. 서버에 토큰을 안전하게 저장합니다.
3. 들어오는 웹훅 페이로드를 토큰과 대조하여 검증하고, 해당 페이로드가 GitHub에서 온 것이며 변조되지 않았는지 확인합니다.

## 비밀 토큰 만들기

비밀 토큰을 사용하여 새 웹후크를 만들거나 기존 웹후크에 비밀 토큰을 추가할 수 있습니다. 비밀 토큰을 만들 때는 엔트로피가 높은 임의의 텍스트 문자열을 선택해야 합니다.

* *비밀 토큰을 사용하여 새 웹후크를 만들려면*[웹후크 만들기](/ko/enterprise-server@3.22/webhooks/using-webhooks/creating-webhooks)을(를) 참조하세요.
* *기존 웹후크에 비밀 토큰을 추가하려면* 웹후크의 설정을 편집합니다. “비밀”로 이동해 `secret` 키로 사용할 문자열을 입력합니다. 자세한 내용은 [웹후크 편집하기](/ko/enterprise-server@3.22/webhooks/using-webhooks/editing-webhooks)을(를) 참조하세요.

## 비밀 토큰 안전하게 저장하기

비밀 토큰을 만든 후에는 서버에서 액세스할 수 있는 안전한 위치에 저장해야 합니다. 토큰을 애플리케이션에 하드 코딩하거나 리포지토리에 푸시하지 마세요. 코드에서 인증 자격 증명을 안전하게 사용하는 방법에 대한 자세한 내용은 [해당 API 자격 증명 보안 유지](/ko/enterprise-server@3.22/rest/authentication/keeping-your-api-credentials-secure#use-authentication-credentials-securely-in-your-code)을(를) 참조하세요.

## 웹후크 제공 유효성 검사하기

GitHub 는 비밀 토큰을 사용하여 각 페이로드와 함께 사용자에게 전송되는 해시 서명을 만듭니다. 해시 서명은 각 제공에서 `X-Hub-Signature-256` 헤더 값으로 표시됩니다. 자세한 내용은 [웹후크 이벤트 및 페이로드](/ko/enterprise-server@3.22/webhooks/webhook-events-and-payloads#delivery-headers)을(를) 참조하세요.

웹후크 제공을 처리하는 코드에서 비밀 토큰을 사용하여 해시를 계산해야 합니다. 그런 다음, 보낸 해시 GitHub 를 계산한 예상 해시와 비교하고 일치하는지 확인합니다. 다양한 프로그래밍 언어로 해시의 유효성을 검사하는 방법의 예시는 [Examples예시](#examples)를 참조하세요.

웹후크 페이로드의 유효성을 검사할 때는 다음과 같은 사항에 유의해야 합니다.

* GitHub 는 HMAC 16진수 다이제스트를 사용하여 해시를 계산합니다.
* 해시 서명은 언제나 텍스트 `sha256=`(으)로 시작합니다.
* 해시 서명은 웹후크의 비밀 토큰 및 페이로드 콘텐츠를 사용하여 생성됩니다.
* 언어 및 서버 구현에서 문자 인코딩을 지정하는 경우 페이로드를 UTF-8로 처리해야 합니다. 웹후크 페이로드는 유니코드 문자를 포함할 수 있습니다.
* 일반 `==` 연산자를 사용하지 마세요. 대신, 정규 동격 연산자에 대한 특정 타이밍 공격을 완화하는 데 도움이 되는 "일정한 시간" 문자열 비교를 수행하는 [`secure_compare`](https://www.rubydoc.info/gems/rack/Rack%2FUtils:secure_compare) 또는 [`crypto.timingSafeEqual`](https://nodejs.org/api/crypto.html#cryptotimingsafeequala-b) 같은 메서드나 JIT 최적화 언어의 정규 루프를 사용하는 것이 좋습니다.

### 웹후크 페이로드 유효성 검사 테스트하기

다음 `secret` 및 `payload` 값을 사용하여 올바르게 구현되었는지 확인할 수 있습니다.

* `secret`: `It's a Secret to Everybody`
* `payload`: `Hello, World!`

올바르게 구현되었다면 생성하는 서명이 다음 서명 값과 일치해야 합니다.

* 서명: `757107ea0eb2509fc211221cce984b8a37570b6d7586c22c46f4379c8b043e17` <!-- markdownlint-disable-line GHD034 -->
* X-Hub-Signature-256: `sha256=757107ea0eb2509fc211221cce984b8a37570b6d7586c22c46f4379c8b043e17`

### 예시

선택한 프로그래밍 언어를 사용하여 코드에서 HMAC 검증을 구현할 수 있습니다. 다음은 구현이 다양한 프로그래밍 언어로 표시되는 방식을 보여 주는 몇 가지 예입니다.

#### Ruby 예제

예를 들어 다음의 `verify_signature` 함수를 정의할 수 있습니다.

```ruby
def verify_signature(payload_body)
  signature = 'sha256=' + OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), ENV['SECRET_TOKEN'], payload_body)
  return halt 500, "Signatures didn't match!" unless Rack::Utils.secure_compare(signature, request.env['HTTP_X_HUB_SIGNATURE_256'])
end
```

그런 다음 웹후크 페이로드를 받을 때 호출할 수 있습니다.

```ruby
post '/payload' do
  request.body.rewind
  payload_body = request.body.read
  verify_signature(payload_body)
  push = JSON.parse(payload_body)
  "I got some JSON: #{push.inspect}"
end
```

#### Python 예제

예를 들어 다음의 `verify_signature` 함수를 정의하고 웹후크 페이로드를 받을 때 호출할 수 있습니다.

```python
import hashlib
import hmac
def verify_signature(payload_body, secret_token, signature_header):
    """Verify that the payload was sent from GitHub by validating SHA256.

    Raise and return 403 if not authorized.

    Args:
        payload_body: original request body to verify (request.body())
        secret_token: GitHub app webhook token (WEBHOOK_SECRET)
        signature_header: header received from GitHub (x-hub-signature-256)
    """
    if not signature_header:
        raise HTTPException(status_code=403, detail="x-hub-signature-256 header is missing!")
    hash_object = hmac.new(secret_token.encode('utf-8'), msg=payload_body, digestmod=hashlib.sha256)
    expected_signature = "sha256=" + hash_object.hexdigest()
    if not hmac.compare_digest(expected_signature, signature_header):
        raise HTTPException(status_code=403, detail="Request signatures didn't match!")
```

#### JavaScript 예제

예를 들어 다음의 `verifySignature` 함수를 정의하고 웹후크 페이로드를 받을 때 모든 JavaScript 환경에서 호출할 수 있습니다.

```javascript
let encoder = new TextEncoder();

async function verifySignature(secret, header, payload) {
    let parts = header.split("=");
    let sigHex = parts[1];

    let algorithm = { name: "HMAC", hash: { name: 'SHA-256' } };

    let keyBytes = encoder.encode(secret);
    let extractable = false;
    let key = await crypto.subtle.importKey(
        "raw",
        keyBytes,
        algorithm,
        extractable,
        [ "sign", "verify" ],
    );

    let sigBytes = hexToBytes(sigHex);
    let dataBytes = encoder.encode(payload);
    let equal = await crypto.subtle.verify(
        algorithm.name,
        key,
        sigBytes,
        dataBytes,
    );

    return equal;
}

function hexToBytes(hex) {
    let len = hex.length / 2;
    let bytes = new Uint8Array(len);

    let index = 0;
    for (let i = 0; i < hex.length; i += 2) {
        let c = hex.slice(i, i + 2);
        let b = parseInt(c, 16);
        bytes[index] = b;
        index += 1;
    }

    return bytes;
}
```

#### TypeScript 예제

예를 들어 다음의 `verify_signature` 함수를 정의하고 웹후크 페이로드를 받을 때 호출할 수 있습니다.

```javascript copy
import { Webhooks } from "@octokit/webhooks";

const webhooks = new Webhooks({
  secret: process.env.WEBHOOK_SECRET,
});

const handleWebhook = async (req, res) => {
  const signature = req.headers["x-hub-signature-256"];
  const body = await req.text();

  if (!(await webhooks.verify(body, signature))) {
    res.status(401).send("Unauthorized");
    return;
  }

  // The rest of your logic here
};
```

## 문제 해결

페이로드가 GitHub에서 온 것으로 확신하지만 서명 검증이 실패하는 경우:

* 웹후크에 대한 비밀을 구성했는지 확인하세요. 웹후크에 대한 비밀을 구성하지 않은 경우 `X-Hub-Signature-256` 머리글이 표시되지 않습니다. 웹후크 비밀에 관한 자세한 내용은 [웹후크 편집하기](/ko/enterprise-server@3.22/webhooks/using-webhooks/editing-webhooks)을(를) 참조하세요.
* 올바른 머리글을 사용하고 있는지 확인하세요. GitHub에서는 HMAC-SHA256 알고리즘을 사용하는 `X-Hub-Signature-256` 머리글글을 사용하는 것이 좋습니다. `X-Hub-Signature` 머리글은 HMAC-SHA1 알고리즘을 사용하며 레거시 용도로만 포함됩니다.
* 올바른 알고리즘을 사용하고 있는지 확인하세요. `X-Hub-Signature-256` 머리글을 사용하는 경우, HMAC-SHA256 알고리즘을 사용해야 합니다.
* 올바른 웹후크 비밀을 사용하고 있는지 확인하세요. 웹후크 비밀 값을 모르는 경우, 웹후크 비밀을 업데이트할 수 있습니다. 자세한 내용은 [웹후크 편집하기](/ko/enterprise-server@3.22/webhooks/using-webhooks/editing-webhooks)을(를) 참조하세요.
* 검증 전에 페이로드 및 머리글이 수정되지 않았는지 확인하세요. 예를 들어 프록시 또는 부하 분산 장치를 사용하는 경우, 프록시 또는 부하 분산 장치가 페이로드 또는 머리글을 수정하지 않는지 확인합니다.
* 언어 및 서버 구현에서 문자 인코딩을 지정하는 경우 페이로드를 UTF-8로 처리해야 합니다. 웹후크 페이로드는 유니코드 문자를 포함할 수 있습니다.

## 추가 참고 자료

* [웹후크 배달 처리](/ko/enterprise-server@3.22/webhooks/using-webhooks/handling-webhook-deliveries)
* [웹후크 사용에 대한 모범 사례](/ko/enterprise-server@3.22/webhooks/using-webhooks/best-practices-for-using-webhooks)