Skip to main content

为 GitHub 应用生成 JSON Web 令牌 (JWT)

了解如何创建 JSON Web 令牌 (JWT),以便使用 GitHub App 对某些 REST API 终结点进行身份验证。

关于 JSON Web 令牌 (JWT)

若要作为应用进行身份验证或生成安装访问令牌,必须生成 JSON Web 令牌 (JWT)。 如果 REST API 终结点需要 JWT,该终结点的文档将指示必须使用 JWT 来访问该终结点。

JWT 必须使用 RS256 算法进行签名,并且必须包含以下声明。

声明含义详细信息
iat颁发时间JWT 创建时间。 为了防止时钟偏移,建议你将此时间设置在 60 秒之前,并确保服务器的日期和时间已正确设置(例如,通过使用网络时间协议)。
exp到期时间JWT 的过期时间,在此时间之后不能用于请求安装令牌。 时间不能超过未来 10 分钟。
iss颁发者GitHub App 的 ID。 此值用于查找正确的公钥来验证 JWT 的签名。 可以使用 GET /app REST API 终结点查找应用的 ID。 有关详细信息,请参阅 REST API 文档中的“应用”。
alg消息身份验证代码算法这应该是 RS256,因为必须使用 RS256 算法对 JWT 进行签名。

若要使用 JWT,请在 API 请求的 Authorization 标头中传递它。 例如:

curl --request GET \
--url "https://api.github.com/app" \
--header "Accept: application/vnd.github+json" \
--header "Authorization: Bearer YOUR_JWT" \
--header "X-GitHub-Api-Version: 2022-11-28"

在大多数情况下,可以使用 Authorization: BearerAuthorization: token 传递令牌。 但是,如果要传递 JSON Web 令牌 (JWT),则必须使用 Authorization: Bearer

生成 JSON Web 令牌 (JWT)

大多数编程语言都有一个可以生成 JWT 的包。 在所有情况下,你必须具有私钥和 GitHub App 的 ID。 有关生成私钥的详细信息,请参阅“管理 GitHub 应用的私钥”。 可以使用 GET /app REST API 终结点查找应用的 ID。 有关详细信息,请参阅 REST API 文档中的“应用”。

注意:可以使用 GitHub 的 Octokit SDK 作为应用进行身份验证,而不是创建 JWT。 SDK 将负责为你生成 JWT,并在令牌过期后重新生成 JWT。 有关详细信息,请参阅“使用 REST API 和 JavaScript 编写脚本”。

示例:使用 Ruby 生成 JWT

注意:必须运行 gem install jwt 才能安装 jwt 包,以便使用此脚本。

在以下示例中,将 YOUR_PATH_TO_PEM 替换为存储私钥的文件路径。 将 YOUR_APP_ID 替换为你的应用的 ID。 请确保以双引号括住 YOUR_PATH_TO_PEMYOUR_APP_ID 的值。

require 'openssl'
require 'jwt'  # https://rubygems.org/gems/jwt

# Private key contents
private_pem = File.read("YOUR_PATH_TO_PEM")
private_key = OpenSSL::PKey::RSA.new(private_pem)

# Generate the JWT
payload = {
  # issued at time, 60 seconds in the past to allow for clock drift
  iat: Time.now.to_i - 60,
  # JWT expiration time (10 minute maximum)
  exp: Time.now.to_i + (10 * 60),
  # GitHub App's identifier
  iss: "YOUR_APP_ID"
}

jwt = JWT.encode(payload, private_key, "RS256")
puts jwt

示例:使用 Python 生成 JWT

注意:必须运行 pip install jwt 才能安装 jwt 包,以便使用此脚本。

Python
#!/usr/bin/env python3
from jwt import JWT, jwk_from_pem
import time
import sys

# Get PEM file path
if len(sys.argv) > 1:
    pem = sys.argv[1]
else:
    pem = input("Enter path of private PEM file: ")

# Get the App ID
if len(sys.argv) > 2:
    app_id = sys.argv[2]
else:
    app_id = input("Enter your APP ID: ")

# Open PEM
with open(pem, 'rb') as pem_file:
    signing_key = jwk_from_pem(pem_file.read())

payload = {
    # Issued at time
    'iat': int(time.time()),
    # JWT expiration time (10 minutes maximum)
    'exp': int(time.time()) + 600,
    # GitHub App's identifier
    'iss': app_id
}

# Create JWT
jwt_instance = JWT()
encoded_jwt = jwt_instance.encode(payload, signing_key, alg='RS256')

print(f"JWT:  {encoded_jwt}")

此脚本将提示输入存储私钥的文件路径和应用的 ID。 或者,可以在执行脚本时将这些值作为内联参数传递。

示例:使用 Bash 生成 JWT

注意:**** 运行此脚本时,必须传递应用 ID 和私钥存储为参数的文件路径。

Bash
#!/usr/bin/env bash

set -o pipefail

app_id=$1 # App ID as first argument
pem=$( cat $2 ) # file path of the private key as second argument

now=$(date +%s)
iat=$((${now} - 60)) # Issues 60 seconds in the past
exp=$((${now} + 600)) # Expires 10 minutes in the future

b64enc() { openssl base64 | tr -d '=' | tr '/+' '_-' | tr -d '\n'; }

header_json='{
    "typ":"JWT",
    "alg":"RS256"
}'
# Header encode
header=$( echo -n "${header_json}" | b64enc )

payload_json='{
    "iat":'"${iat}"',
    "exp":'"${exp}"',
    "iss":'"${app_id}"'
}'
# Payload encode
payload=$( echo -n "${payload_json}" | b64enc )

# Signature
header_payload="${header}"."${payload}"
signature=$(
    openssl dgst -sha256 -sign <(echo -n "${pem}") \
    <(echo -n "${header_payload}") | b64enc
)

# Create JWT
JWT="${header_payload}"."${signature}"
printf '%s\n' "JWT: $JWT"

示例:使用 PowerShell 生成 JWT

在以下示例中,将 YOUR_PATH_TO_PEM 替换为存储私钥的文件路径。 将 YOUR_APP_ID 替换为你的应用的 ID。 请确保将 YOUR_PATH_TO_PEM 的值括在双引号中。

PowerShell
#!/usr/bin/env pwsh

$app_id = YOUR_APP_ID
$private_key_path = "YOUR_PATH_TO_PEM"

$header = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes((ConvertTo-Json -InputObject @{
  alg = "RS256"
  typ = "JWT"
}))).TrimEnd('=').Replace('+', '-').Replace('/', '_');

$payload = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes((ConvertTo-Json -InputObject @{
  iat = [System.DateTimeOffset]::UtcNow.AddSeconds(-10).ToUnixTimeSeconds()
  exp = [System.DateTimeOffset]::UtcNow.AddMinutes(10).ToUnixTimeSeconds()
  iss = $app_id
}))).TrimEnd('=').Replace('+', '-').Replace('/', '_');

$rsa = [System.Security.Cryptography.RSA]::Create()
$rsa.ImportFromPem((Get-Content $private_key_path -Raw))

$signature = [Convert]::ToBase64String($rsa.SignData([System.Text.Encoding]::UTF8.GetBytes("$header.$payload"), [System.Security.Cryptography.HashAlgorithmName]::SHA256, [System.Security.Cryptography.RSASignaturePadding]::Pkcs1)).TrimEnd('=').Replace('+', '-').Replace('/', '_')
$jwt = "$header.$payload.$signature"
Write-Host $jwt