You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

GitHub Apps用户认证授权架构级方案咨询(Django)

GitHub Apps 用户授权与持久化登录实现方案(Django)

核心概念区分

从GitHub OAuth过渡到GitHub Apps,核心差异在于:

  • GitHub Apps是基于安装/授权的模型,权限粒度更细(可针对单个仓库、组织),且支持长期有效的刷新令牌
  • 你需要的是User-to-Server OAuth Flow:用于用户登录你的网站,并授权App访问其个人/组织的仓库、PR等资源

所需API端点与密钥说明

关键密钥

  • Client ID:GitHub App设置页面「OAuth credentials」板块生成
  • Client Secret:同上,生成后需保密存储(建议用环境变量管理)

核心API端点

  1. 授权引导端点

    GET https://github.com/login/oauth/authorize
    

    必填参数:client_id、redirect_uri、scope(如repo,pull_request)、state(防CSRF随机字符串)

  2. 令牌交换端点

    POST https://github.com/login/oauth/access_token
    

    用授权回调获取的code交换access_token和refresh_token,参数包括client_id、client_secret、code、redirect_uri

  3. 令牌刷新端点

    POST https://github.com/login/oauth/access_token
    

    传入grant_type=refresh_token+已保存的refresh_token,获取新的access_token(无需用户再次授权)

  4. 用户/资源访问端点

    • 获取用户基础信息:GET https://api.github.com/user(需携带access_token)
    • 获取用户授权的仓库:GET https://api.github.com/user/repos
    • 获取PR列表:GET https://api.github.com/repos/{owner}/{repo}/pulls

持久化授权(免重复授权)实现逻辑

核心是保存用户的refresh_token,并在需要时自动刷新令牌:

  1. 首次授权:用户完成GitHub授权后,将refresh_token与用户账户关联,加密存储到数据库
  2. 后续登录:
    • 用户访问网站时,检查数据库中是否存在该用户的有效refresh_token
    • 若存在,直接调用令牌刷新端点获取新的access_token,无需用户跳转GitHub授权
    • 若refresh_token失效(如用户撤销授权),引导用户重新完成授权流程

Django代码示例

1. 配置与模型

settings.py(添加GitHub配置)

import os
from pathlib import Path

# GitHub Apps配置
GITHUB_APP_CLIENT_ID = os.environ.get('GITHUB_APP_CLIENT_ID')
GITHUB_APP_CLIENT_SECRET = os.environ.get('GITHUB_APP_CLIENT_SECRET')
GITHUB_REDIRECT_URI = 'http://localhost:8000/github/callback/'
GITHUB_SCOPES = 'repo,pull_request'  # 按需调整权限

models.py(自定义用户关联模型)

from django.db import models
from django.contrib.auth.models import User

class GitHubUserProfile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    github_id = models.IntegerField(unique=True)
    access_token = models.CharField(max_length=255)
    refresh_token = models.CharField(max_length=255, blank=True, null=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        verbose_name = "GitHub用户信息"

2. 视图实现

views.py(登录引导、回调、令牌刷新)

import requests
import random
import string
from django.shortcuts import redirect, render
from django.contrib.auth import login
from django.contrib.auth.models import User
from django.conf import settings
from .models import GitHubUserProfile

def github_login(request):
    # 生成CSRF防护用的state
    state = ''.join(random.choices(string.ascii_letters + string.digits, k=32))
    request.session['github_state'] = state
    auth_url = (
        f"https://github.com/login/oauth/authorize?client_id={settings.GITHUB_APP_CLIENT_ID}"
        f"&redirect_uri={settings.GITHUB_REDIRECT_URI}&scope={settings.GITHUB_SCOPES}&state={state}"
    )
    return redirect(auth_url)

def github_callback(request):
    # 验证CSRF state
    state = request.GET.get('state')
    if state != request.session.pop('github_state', None):
        return render(request, 'error.html', {'message': 'CSRF验证失败'})
    
    code = request.GET.get('code')
    if not code:
        return render(request, 'error.html', {'message': '未获取到授权码'})
    
    # 交换令牌
    token_res = requests.post(
        "https://github.com/login/oauth/access_token",
        data={
            'client_id': settings.GITHUB_APP_CLIENT_ID,
            'client_secret': settings.GITHUB_APP_CLIENT_SECRET,
            'code': code,
            'redirect_uri': settings.GITHUB_REDIRECT_URI
        },
        headers={'Accept': 'application/json'}
    )
    token_data = token_res.json()
    if 'error' in token_data:
        return render(request, 'error.html', {'message': token_data['error_description']})
    
    # 获取GitHub用户信息
    user_info = requests.get(
        "https://api.github.com/user",
        headers={'Authorization': f'token {token_data["access_token"]}'}
    ).json()
    
    # 创建/关联Django用户
    django_user, _ = User.objects.get_or_create(
        username=user_info['login'],
        defaults={'email': user_info.get('email', '')}
    )
    
    # 保存/更新GitHub令牌信息
    github_profile, _ = GitHubUserProfile.objects.get_or_create(
        user=django_user,
        defaults={
            'github_id': user_info['id'],
            'access_token': token_data['access_token'],
            'refresh_token': token_data.get('refresh_token')
        }
    )
    if not _.created:
        github_profile.access_token = token_data['access_token']
        if token_data.get('refresh_token'):
            github_profile.refresh_token = token_data['refresh_token']
        github_profile.save()
    
    # 登录用户
    login(request, django_user)
    return redirect('/dashboard/')

def refresh_github_token(request):
    if not request.user.is_authenticated:
        return redirect('/github/login/')
    
    try:
        github_profile = GitHubUserProfile.objects.get(user=request.user)
    except GitHubUserProfile.DoesNotExist:
        return redirect('/github/login/')
    
    if not github_profile.refresh_token:
        return redirect('/github/login/')
    
    # 刷新令牌
    refresh_res = requests.post(
        "https://github.com/login/oauth/access_token",
        data={
            'client_id': settings.GITHUB_APP_CLIENT_ID,
            'client_secret': settings.GITHUB_APP_CLIENT_SECRET,
            'grant_type': 'refresh_token',
            'refresh_token': github_profile.refresh_token
        },
        headers={'Accept': 'application/json'}
    )
    refresh_data = refresh_res.json()
    
    if 'error' in refresh_data:
        # 刷新失败,引导重新授权
        return redirect('/github/login/')
    
    # 更新令牌
    github_profile.access_token = refresh_data['access_token']
    if 'refresh_token' in refresh_data:
        github_profile.refresh_token = refresh_data['refresh_token']
    github_profile.save()
    
    return redirect('/dashboard/')

3. URL配置(urls.py)

from django.urls import path
from . import views

urlpatterns = [
    path('github/login/', views.github_login, name='github_login'),
    path('github/callback/', views.github_callback, name='github_callback'),
    path('github/refresh-token/', views.refresh_github_token, name='github_refresh_token'),
    # 其他URL...
]

注意事项

  • 密钥安全:永远不要将Client Secret硬编码到代码中,用环境变量或密钥管理工具存储
  • 权限最小化:只申请业务必需的scope,避免过度授权
  • 令牌加密:refresh_token需加密存储在数据库,防止泄露
  • 异常处理:当调用GitHub API返回401状态码时,自动触发令牌刷新流程;若刷新失败,引导用户重新授权

内容的提问来源于stack exchange,提问作者not-a-bot

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 14:40:47