Django中Keystone/OpenStack API认证与会话共享方案问询
解决方案:Django中共享Keystone客户端会话的几种方法
你遇到的核心问题是Python对象无法序列化存入Django的request.session,所以得换思路,不用直接存客户端对象,而是通过以下几种方式实现跨视图共享认证状态:
1. 存储认证凭证,按需重建客户端
把Keystone认证所需的字符串参数(而非客户端对象)存在request.session里,在需要调用API的视图中重新初始化客户端。这种方式最简单,不需要额外依赖:
登录视图(存储凭证)
def keystone_login(request): # 这里先完成用户的Django登录验证,再初始化Keystone认证 auth_params = { 'auth_url': 'https://my.keystone.com:5000/v3', 'user_id': 'myuserid', 'password': 'mypassword', 'project_id': 'myprojectid' } # 存到session,这些都是可序列化的字符串 request.session['keystone_auth_params'] = auth_params return redirect('dashboard')
其他业务视图(重建客户端)
from keystoneauth1.identity import v3 from keystoneauth1 import session from keystoneclient.v3 import client def manage_projects(request): # 从session取出凭证 auth_params = request.session.get('keystone_auth_params') if not auth_params: return redirect('keystone_login') # 重建认证会话和客户端 auth = v3.Password(**auth_params) sess = session.Session(auth=auth) keystone = client.Client(session=sess, include_metadata=True) # 接下来就可以用keystone调用API了,比如获取项目列表 projects = keystone.projects.list() # ...后续业务逻辑
2. 用Django缓存存储会话对象
如果不想每次都重建客户端,可以用Django缓存(比如Redis、Memcached)存储session.Session对象——缓存支持存储非序列化Python对象(部分后端会自动用pickle序列化),只要给每个用户设置唯一缓存键即可:
登录视图(存会话到缓存)
from django.core.cache import cache def keystone_login(request): # 初始化认证会话 auth = v3.Password(auth_url='https://my.keystone.com:5000/v3', user_id='myuserid', password='mypassword', project_id='myprojectid') sess = session.Session(auth=auth) # 用用户ID作为缓存键,设置过期时间(和Keystone Token有效期匹配) cache_key = f"keystone_sess_{request.user.id}" cache.set(cache_key, sess, timeout=3600) # 同时把基础凭证存session,用于缓存失效时重建 request.session['keystone_auth_params'] = { 'auth_url': 'https://my.keystone.com:5000/v3', 'user_id': 'myuserid', 'password': 'mypassword', 'project_id': 'myprojectid' } return redirect('dashboard')
业务视图(从缓存取会话)
def manage_projects(request): cache_key = f"keystone_sess_{request.user.id}" sess = cache.get(cache_key) if not sess: # 缓存失效,从session取凭证重建 auth_params = request.session.get('keystone_auth_params') if not auth_params: return redirect('keystone_login') auth = v3.Password(**auth_params) sess = session.Session(auth=auth) cache.set(cache_key, sess, 3600) # 初始化客户端 keystone = client.Client(session=sess, include_metadata=True) # ...业务逻辑
3. 自定义中间件,自动注入客户端
写一个Django中间件,在每个请求处理前自动初始化Keystone客户端并绑定到request对象上,这样所有视图都能直接使用,不用重复写初始化代码:
编写中间件(myapp/middleware.py)
from keystoneauth1.identity import v3 from keystoneauth1 import session from keystoneclient.v3 import client from django.core.cache import cache from django.shortcuts import redirect class KeystoneClientMiddleware: def __init__(self, get_response): self.get_response = get_response def __call__(self, request): # 仅对已登录用户处理 if request.user.is_authenticated: cache_key = f"keystone_sess_{request.user.id}" sess = cache.get(cache_key) if not sess: # 从session取凭证重建会话 auth_params = request.session.get('keystone_auth_params') if auth_params: auth = v3.Password(**auth_params) sess = session.Session(auth=auth) cache.set(cache_key, sess, 3600) # 绑定客户端到request request.keystone = client.Client(session=sess, include_metadata=True) else: request.keystone = None else: request.keystone = client.Client(session=sess, include_metadata=True) else: request.keystone = None response = self.get_response(request) return response
注册中间件(settings.py)
在MIDDLEWARE列表末尾添加:
MIDDLEWARE = [ # ...其他中间件 'myapp.middleware.KeystoneClientMiddleware', ]
视图中直接使用
def manage_projects(request): if not request.keystone: return redirect('keystone_login') # 直接用request.keystone调用API projects = request.keystone.projects.list() # ...业务逻辑
4. 直接存储Token,调用REST API
如果不想用python-keystoneclient,可以直接调用Keystone的REST API:登录时获取Token并存入session,后续请求API时带上Token头即可:
登录视图(获取Token)
import requests def keystone_login(request): auth_url = 'https://my.keystone.com:5000/v3/auth/tokens' payload = { "auth": { "identity": { "methods": ["password"], "password": { "user": { "id": "myuserid", "password": "mypassword" } } }, "scope": { "project": { "id": "myprojectid" } } } } response = requests.post(auth_url, json=payload) if response.status_code == 201: # 提取Token和必要参数存session request.session['keystone_token'] = response.headers['X-Subject-Token'] request.session['keystone_api_url'] = 'https://my.keystone.com:5000/v3' return redirect('dashboard')
业务视图(调用REST API)
import requests def create_project(request): token = request.session.get('keystone_token') api_url = request.session.get('keystone_api_url') if not token or not api_url: return redirect('keystone_login') headers = { 'X-Auth-Token': token, 'Content-Type': 'application/json' } payload = { "project": { "name": "new-test-project", "description": "测试项目", "enabled": True } } response = requests.post(f"{api_url}/projects", json=payload, headers=headers) # 处理响应结果
内容的提问来源于stack exchange,提问作者Crouch
相关产品推荐
相关产品推荐

