跳至主要内容

为 GitHub App 生成 JSON Web Token (JWT)

了解如何创建 JSON Web Token (JWT) 以使用您的 GitHub App 认证到某些 REST API 端点。

关于 JSON Web Token (JWT)

为了以应用身份进行身份验证或生成安装访问令牌,您必须生成一个 JSON Web Token (JWT)。如果 REST API 端点需要 JWT,则该端点的文档会指示您必须使用 JWT 才能访问该端点。

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

声明含义详情
iat发出时间JWT 创建的时间。为了防止时钟漂移,我们建议您将其设置为过去 60 秒,并确保您的服务器日期和时间设置准确(例如,使用网络时间协议)。
exp过期时间JWT 的过期时间,在此时间之后,它不能用于请求安装令牌。时间不得超过未来 10 分钟。
iss发行者您的 GitHub App 的客户端 ID 或应用程序 ID。此值用于查找正确的公钥以验证 JWT 的签名。您可以在 GitHub App 的设置页面上找到您的应用 ID。建议使用客户端 ID。有关导航到 GitHub App 设置页面的更多信息,请参阅“修改 GitHub App 注册”。
alg消息认证码算法这应该是RS256,因为您的 JWT 必须使用RS256算法进行签名。

要使用 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 Token (JWT)

大多数编程语言都包含可以生成 JWT 的包。在所有情况下,您都必须拥有私钥和 GitHub App 的 ID。有关生成私钥的更多信息,请参阅“管理 GitHub App 的私钥”。您可以使用GET /appREST 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.cn/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 client ID
  iss: "YOUR_CLIENT_ID"
}

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

示例:使用 Python 生成 JWT

注意

您必须运行pip install PyJWT以安装PyJWT包才能使用此脚本。

Python
#!/usr/bin/env python3
import sys
import time

import jwt

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

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

# Open PEM
with open(pem, 'rb') as pem_file:
    signing_key = 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 client ID
    'iss': client_id
}

# Create JWT
encoded_jwt = jwt.encode(payload, signing_key, algorithm='RS256')

print(f"JWT:  {encoded_jwt}")

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

示例:使用 Bash 生成 JWT

注意

在运行此脚本时,您必须将您的客户端 ID 和存储私钥的文件路径作为参数传递。

Bash
#!/usr/bin/env bash

set -o pipefail

client_id=$1 # Client 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\":\"${client_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_CLIENT_ID替换为您应用的 ID。请确保将YOUR_PATH_TO_PEM的值用双引号括起来。

PowerShell
#!/usr/bin/env pwsh

$client_id = YOUR_CLIENT_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 = $client_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