单点登录:Single Sign On,简称SSO,SSO使得在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。
CAS框架:CAS(Central Authentication Service)是实现SSO单点登录的框架。
django-mama-cas 这个插件在django中实现了cas服务端的功能
django-cas-ng 这个插件在django中实现了cas客户端的功能。
# 安装依赖
pip install django-mama-cas
# INSTALLED_APPS 加入‘mama_cas’
INSTALLED_APPS = [
...
'mama_cas',
]
# 路由
urlpatterns += [url(r'', include('mama_cas.urls'))]
# 配置cas
MAMA_CAS_SERVICES = [
{
# 必填项,此项为**Client** IP:Port,相当于白名单
'SERVICE': 'http://114.116.238.115:3389/',
# 回调模式,user_name_attributes只返回用户名,user_model_attributes返回除id,password之外的所有信息。
'CALLBACKS': [
'mama_cas.callbacks.user_model_attributes',
],
},
{
'SERVICE': 'http://117.78.2.32:3389/',
'CALLBACKS': [
'mama_cas.callbacks.user_model_attributes',
],
},
]
# 用户注销时将单个注销请求发送到所有访问的服务
MAMA_CAS_ENABLE_SINGLE_SIGN_OUT = True
# 在mama-cas的models.py中from django.utils.encoding import python_2_unicode_compatible代码注释掉,为了向后兼容,现在已经没有了。
# 安装依赖
pip install django-cas-ng
# 在 settings.py 中的 INSTALLED_APPS 和 AUTHENTICATION_BACKENDS 两处添加 django-cas-ng 的配置
INSTALLED_APPS = (
...
'django_cas_ng',
)
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
'django_cas_ng.backends.CASBackend',
)
# CAS 的地址
CAS_SERVER_URL = 'http://139.159.235.108:30000'
# CAS 版本
CAS_VERSION = '3'
# 存入所有 CAS 服务端返回的 User 数据。
CAS_APPLY_ATTRIBUTES_TO_USER = True
# 路由
import django_cas_ng.views
urlpatterns = [
path('accounts/login', django_cas_ng.views.LoginView.as_view(), name='cas_ng_login'),
path('accounts/logout', django_cas_ng.views.LogoutView.as_view(), name='cas_ng_logout'),
path('accounts/callback', django_cas_ng.views.CallbackView.as_view(), name='cas_ng_proxy_callback'),
]
# from django.utils.six.moves import urllib_parse 这行代码会报错,一个处理url的包现在已经单独拿出来了这样导入就没问题了
from six.moves import urllib_parse
分析三次请求
a. http://114.116.238.115:3389/ 直接访问受限资源,发现需要登陆,于是重定向到获取票据的url(注意此时的子系统已经没有以前单web系统时的登陆页面和登陆验证了,全部都是由认证中心完成,取而代之的是获取票据的操作)
b. http://114.116.238.115:3389/accounts/login?next=/ 重定向到获取票据的url发现也没有票据,于是继续重定向到认证中心。
c. http://139.159.235.108:30000/login?service=http%3A%2F%2F114.116.238.115%3A3389%2Faccounts%2Flogin%3Fnext%3D%252F 重定向到认证中心会把来源的url拼接上,会首先进行获取全局会话的操作,发现没有全局会话,注意此时的状态是没有局部会话,没有全局会话,也没有票据,确定这个用户是第一次登陆,返回认证中心的登陆页面。
分析请求:
c. http://139.159.235.108:30000/login?service=http%3A%2F%2F114.116.238.115%3A3389%2Faccounts%2Flogin%3Fnext%3D%252F 填写账户,密码,向认证中心发送的post的请求,认证中心会验证,验证失败重定向回登陆页面,验证成功重定向到系统A并且带上票据,注意此时产生了全局会话,同时在认证中心的数据库中也会保留此次的全局会话。
d. http://114.116.238.115:3389/accounts/login?next=%2F&ticket=ST-1577328205-MCzKlonQuygGYey4Qw7e2Dp8dE8hyuts 此时进行获取票据的操作,能获取到于是系统A与认证中心通讯,验证票据,根据票据获得用户信息。
# 系统A与认证中心通讯部分源码
def authenticate(self, request, ticket, service):
"""验证CAS票据并获取或创建用户对象"""
client = get_cas_client(service_url=service, request=request)
username, attributes, pgtiou = client.verify_ticket(ticket)
def get_cas_client(service_url=None, request=None):
# 获取认证中心地址
server_url = django_settings.CAS_SERVER_URL
if server_url and request and server_url.startswith('/'):
scheme = request.META.get("X-Forwarded-Proto", request.scheme)
server_url = scheme + "://" + request.META['HTTP_HOST'] + server_url
def verify_ticket(self, ticket):
# 发送请求,参数为票据
params = [('ticket', ticket), ('service', self.service_url)]
url = (urllib_parse.urljoin(self.server_url, 'validate') + '?' +
urllib_parse.urlencode(params))
page = requests.get(
url,
stream=True,
verify=self.verify_ssl_certificate
)
e. 用户登陆成功,产生局部会话(单web应用的cookie),用来维持用户与系统A的会话。
分析请求:
a. http://117.78.2.32:3389/ 第一个请求是访问系统B的首页,由于没有登录,于是重定向到获取票据的url(注意此时的子系统已经没有以前单web系统时的登陆页面和登陆验证了,全部都是由认证中心完成,取而代之的是获取票据的操作)
b. http://117.78.2.32:3389/accounts/login?next=/ 重定向到获取票据的url发现也没有票据,于是继续重定向到认证中心。
c. http://139.159.235.108:30000/login?service=http%3A%2F%2F117.78.2.32%3A3389%2Faccounts%2Flogin%3Fnext%3D%252F 前两步与用户首次访问系统A时的操作一样,区别在于这一步,系统A中由于没有获取到全局会话,于是重定向到登陆页面,而现在访问系统B,由于之前已经产生了全局会话,所有在这一步拿到了全局会话,就不再展示登录页面了, 而是直接产生票据重定向回系统B。
d. http://117.78.2.32:3389/accounts/login?next=%2F&ticket=ST-1577341593-vW6EuoWfUiNzjYtgal38ocv358WbYzwZ 此时进行获取票据的操作,能获取到于是系统A与认证中心通讯,验证票据,根据票据获得用户信息。
e. 用户登陆成功,产生局部会话(单web应用的cookie),用来维持用户与系统A的会话。
问题:
无法实现单点登出,MAMA_CAS_ENABLE_SINGLE_SIGN_OUT = True配置后不起作用。
在看源码,还没找出原因
具体应用到我们的各个子系统之间用户表外键问题。
由于用户认证都统一到了认证中心,所以要考虑各个子系统的用户表与系统其它表的外键关系,目前我想到的解决方案是认证中心维护一张表只保存用户核心数据,如用户名,邮箱,部门,这些数据不能在子系统修改,
同时各个子系统也维护自己的一个用户表,在保留核心字段用户名,邮箱,部门之外可以扩展自己需要的字段,如性别,电话等等。只要有验证票据的行为产生 http://117.78.2.32:3389/accounts/login?next=%2F&ticket=ST-1577341593-vW6EuoWfUiNzjYtgal38ocv358WbYzwZ (退出当前系统后重新登录,就会把最新的用户名,邮箱,部门更新一遍,如果是首次登陆则会创建)