如何在Django框架中实现CAC智能卡用户身份认证?
Django 框架下 CAC 智能卡认证落地实现方案
CAC 认证的核心是基于 TLS 双向认证(mTLS)实现,浏览器弹出证书选择提示的逻辑发生在 TLS 握手阶段,由前置 Web 服务器触发,Django 本身不直接处理 TLS 层交互,全流程实现分三层配置:
一、前置 Web 服务器配置(触发证书提交提示的核心)
这一步是实现访问时自动弹出CAC证书选择窗口的关键,必须用Nginx/Apache这类支持完整mTLS配置的Web服务器做反向代理,不要用Django自带的runserver承载生产流量,runserver不支持完整双向认证能力。
- 提前准备好签发CAC证书的根CA、中间CA证书,合并为一个CA证书链文件
cac_ca_bundle.crt,Web服务器将用这个文件校验客户端提交证书的合法性。 - Nginx 参考配置:
server { listen 443 ssl; server_name your-service-domain.com; # 常规服务端HTTPS证书配置 ssl_certificate /path/to/your/server_public.crt; ssl_certificate_key /path/to/your/server_private.key; # CAC双向认证核心配置 ssl_client_certificate /path/to/cac_ca_bundle.crt; ssl_verify_depth 3; # CAC证书通常为2-3级链,深度设3足够覆盖 ssl_verify_client optional; # 全局设为可选,仅在认证路径强制开启,避免影响普通页面访问 # 将证书校验结果、证书内容透传给后端Django proxy_set_header X-SSL-Client-Verify $ssl_client_verify; proxy_set_header X-SSL-Client-Cert $ssl_client_escaped_cert; proxy_set_header X-Forwarded-Proto $scheme; # 普通业务路径反向代理 location / { proxy_pass http://127.0.0.1:8000; # Django服务监听地址 } # CAC登录专属路径,强制要求客户端提交证书 location /auth/cac-login { ssl_verify_client on; proxy_pass http://127.0.0.1:8000; proxy_set_header X-SSL-Client-Verify $ssl_client_verify; proxy_set_header X-SSL-Client-Cert $ssl_client_escaped_cert; proxy_set_header X-Forwarded-Proto $scheme; } }
配置生效后,用户访问/auth/cac-login路径时,浏览器会自动读取系统中存储的可用客户端证书(包括智能卡通过PKCS11驱动映射到系统的CAC证书),弹出选择提示,无需前端做额外开发。如果使用Apache,逻辑完全一致,核心是开启SSLVerifyClient require配置,将证书信息通过请求头透传给后端即可。
二、Python侧X509证书解析实现
不需要手动调用OpenSSL命令行解析证书,直接用cryptography库即可完成稳定的x509格式解析,比命令行调用性能更好、安全性更高。
- 安装依赖:
pip install cryptography django
- 在Django配置文件
settings.py中添加安全配置,防止外部请求伪造证书头:
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') # *部署时必须配置防火墙限制Django端口仅允许本地Nginx访问*,避免外部直接请求伪造认证头 CAC_CA_CHAIN_PATH = "/path/to/cac_ca_bundle.crt"
- 编写证书解析工具函数,可放在项目
utils/cac.py路径下:
import urllib.parse from cryptography import x509 from cryptography.hazmat.backends import default_backend from cryptography.x509.oid import NameOID def parse_cac_cert(request): # 先校验Web服务器返回的证书验证结果 verify_status = request.META.get("HTTP_X_SSL_CLIENT_VERIFY") if verify_status != "SUCCESS": return None, "客户端证书未通过CA校验" # 读取透传的证书内容,解码为PEM格式 cert_encoded = request.META.get("HTTP_X_SSL_CLIENT_CERT") if not cert_encoded: return None, "未获取到客户端提交的证书" try: cert_pem = urllib.parse.unquote(cert_encoded).encode() cert = x509.load_pem_x509_certificate(cert_pem, default_backend()) # 提取CAC证书中的用户标识,CAC卡的用户唯一EDIPI号一般存在USER_ID字段 user_info = {} for attr in cert.subject: if attr.oid == NameOID.COMMON_NAME: user_info["full_name"] = attr.value if attr.oid == NameOID.USER_ID: # USER_ID格式通常为 xxx.EDIPI@域名,提取纯EDIPI作为用户唯一键 user_info["edipi"] = attr.value.split("@")[0].split(".")[-1] # 额外校验证书有效期 user_info["expire_at"] = cert.not_valid_after return user_info, None except Exception as e: return None, f"证书解析失败: {str(e)}"
三、Django侧认证逻辑
编写CAC登录视图,完成证书校验后的用户登录逻辑:
from django.contrib.auth import login from django.http import HttpResponseForbidden, HttpResponseRedirect from .models import User # 替换为你项目的用户模型 from .utils.cac import parse_cac_cert def cac_login(request): user_info, err = parse_cac_cert(request) if err: return HttpResponseForbidden(f"CAC认证失败:{err}") # 以EDIPI为唯一标识匹配本地用户,可根据业务需求调整匹配逻辑 try: user = User.objects.get(edipi=user_info["edipi"]) except User.DoesNotExist: # 首次登录自动创建用户的逻辑,可按需开启 user = User.objects.create_user( username=user_info["edipi"], edipi=user_info["edipi"], last_name=user_info.get("full_name", "") ) # 写入session完成登录 login(request, user, backend="django.contrib.auth.backends.ModelBackend") return HttpResponseRedirect("/system/dashboard/")
在urls.py中添加路由映射到上述视图即可完成全流程对接。
四、落地注意事项
- 服务端不需要集成pkcs11相关组件,pkcs11是客户端侧读取智能卡证书用的驱动,只需要在用户终端安装对应驱动(OpenSC、ActivClient等)确保浏览器能读取卡内证书即可,服务端仅需处理浏览器提交的标准x509证书。
- 生产环境建议额外增加CRL/OCSP证书吊销校验逻辑,避免已挂失的CAC卡通过认证。
- 本地调试阶段如果不想部署Nginx,可以用支持mTLS的ASGI/WSGI服务器临时调试,但不建议用于生产环境。
内容的提问来源于stack exchange,提问作者jpk
相关产品推荐
相关产品推荐

