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端点
授权引导端点
GET https://github.com/login/oauth/authorize必填参数:
client_id、redirect_uri、scope(如repo,pull_request)、state(防CSRF随机字符串)令牌交换端点
POST https://github.com/login/oauth/access_token用授权回调获取的
code交换access_token和refresh_token,参数包括client_id、client_secret、code、redirect_uri令牌刷新端点
POST https://github.com/login/oauth/access_token传入
grant_type=refresh_token+已保存的refresh_token,获取新的access_token(无需用户再次授权)用户/资源访问端点
- 获取用户基础信息:
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,并在需要时自动刷新令牌:
- 首次授权:用户完成GitHub授权后,将
refresh_token与用户账户关联,加密存储到数据库 - 后续登录:
- 用户访问网站时,检查数据库中是否存在该用户的有效
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
相关产品推荐
相关产品推荐

