跳至主要内容

为 GitHub 应用生成 JSON Web Token (JWT)

了解如何创建 JSON Web Token (JWT) 以使用您的 GitHub 应用对某些 REST API 端点进行身份验证。

关于 JSON Web Token (JWT)

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

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

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

大多数编程语言都有一个可以生成 JWT 的包。在所有情况下,您都必须拥有私钥和 GitHub 应用程序的 ID。有关生成私钥的更多信息,请参阅“管理 GitHub 应用程序的私钥”。您可以在 REST API 端点 GET /app 中找到应用程序的 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 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 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 = 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 client ID
    'iss': client_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

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