sso单点登录
单点登录:Single Sign On,简称SSO,SSO使得在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。
CAS框架:CAS(Central Authentication Service)是实现SSO单点登录的框架。
django-mama-cas 这个插件在django中实现了cas服务端的功能
django-cas-ng 这个插件在django中实现了cas客户端的功能。
基本概念
- 从结构上看cas包含两个部分,cas 服务端,cas客户端,其中cas服务端需要独立部署,cas客户端需要和子系统部署在一起。
- cas服务端以下称为认证中心 。子系统就是需要单点登陆的几个相互信任的web应用。
- 全局会话:用户到认证中心登陆成功后,用户和认证中心之间建立的会话,因为当用户后续访问子系统时不可能每次请求都到认证中心去判断是否登录。
- 局部会话:子系统与用户浏览器建立的会话。类似单web应用的cookie
- 票据ticket:用户登陆认证中心后会重定向回对应的子系统,并且会在url中附带上认证中心产生的ticket, ticket将用来查询username
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代码注释掉,为了向后兼容,现在已经没有了。
cas客户端配置
# 安装依赖
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
-
用户首次访问系统A需登陆受限的资源时,此时进行登陆检测,发现没有登录(也就是没有局部会话),进行获取票据的操作,发现没有票据。
-
系统A发现这个请求需要登陆,就将请求重定向到认证中心,进行获取全局会话的操作,没有,进行登陆
分析三次请求
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拼接上,会首先进行获取全局会话的操作,发现没有全局会话,注意此时的状态是没有局部会话,没有全局会话,也没有票据,确定这个用户是第一次登陆,返回认证中心的登陆页面。
-
登陆页面出现,用户登陆,登陆成功后认证中心重定向到系统A,并且会附带上认证通过的票据,此时认证中心会同时生成全局会话。
-
重定向会A后,发现没有登录,再次进行获取票据的操作,此时可以获得票据,系统A会用此票据与认证中心通讯(系统内部的通讯,用户感知不到),验证票据有效,证明用户已经登陆
-
系统A将受限资源返回用户
分析请求:
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的会话。
已登录用户首次访问子系统B
-
用户通过浏览器访问系统B的登录受限资源,此时进行登录检查,发现没有登录,然后进行获取票据的操作,发现没有票据
-
系统B把请求重定向到认证中心,获取全局会话的操作,获得了全局会话,认证中心知道已经登录过了
-
认证中心发放票据,并携带该票据重定向回系统B
-
此时系统B再进行登录检查,发现没有登录,进行获取票据的操作,发现可以获得票据,系统B就与认证中心通讯,验证票据有效,证明用户确实已经登录。
-
系统B将受限资源返回给客户端
分析请求:
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 (退出当前系统后重新登录,就会把最新的用户名,邮箱,部门更新一遍,如果是首次登陆则会创建)