需求
在公司平台的开发中,由于内部平台越来越多,本次要求我们开发的平台需要同步公司的 OA 账号。
那么怎么同步呢?简单来说就是采用 CAS 服务机制,实现 CAS 服务完成多应用单点登陆 功能。
Django 默认的 Session Cookie 的登陆机制
image-20200909110936463
在了解 CAS 单点登陆之前,先来回顾一下 Django 默认的 Session Cookie 的登陆机制:
- 浏览器发送登陆请求 至 Django 服务
- Django 服务接收到 浏览器发送过来的请求之后,则创建 CSRFToken 以及 相关用户信息,存储到 Session 中,并且返回浏览器 Set-Cookie 的信息,通知浏览器设置相关 Cookie
- 浏览器再次发送请求 至 Django 服务,则会携带前面设置的 Cookie 信息
- Django 服务接收到 浏览器发送过来的请求之后,发现携带了 CSRFToken 以及 记录用户信息的 sessionID,根据 sessionID 查询服务器上的 session 数据。
下面再来看看 CAS 的单点登陆机制。
CAS 的 (Single Sign-On)SSO单点登陆机制
首先先不看 CAS 的一堆概念,我们直接上请求时序图,了解请求 CAS 对于服务登陆认证的过程先。
CAS 登陆服务请求时序图
cas登陆机制-CAS服务登陆机制
从上面的时序图来看,可以清晰知道 CAS 服务就是用来统一管理 APP 服务登陆认证的 独立服务。在时序图我写了 16 个处理步骤,在这16 个处理步骤中,可以知道,APP 服务 与 CAS 服务验证登陆是否通过是基于 服务票据 ST 来确认的。
基本认证过程简略如下:
- 前端访问 APP 服务的一个页面, 此时未携带相关登陆参数。
- 后端发现该请求未登陆,则返回前端 302 ,并 重定向到 CAS 服务器的登录页面,并携带当前用户访问的网页链接
- 在CAS 服务器上,用户填写登录信息,浏览器发送请求到 CAS 服务器进行认证
- CAS 服务 认证通过,将本次登录保存到会话,返回 服务票据 ST 并 重定向 浏览器至 APP 服务
- APP服务接收前端重定向请求过来路径 以及 服务票据 ST ,APP服务 再将 服务票据 ST 请求至 CAS 服务,验证 ST。验证通过,则创建该用户给登陆成功的 session 数据;反之,返回 前端 302, 重定向至 CAS 登陆页面。
- APP 服务验证 ST 通过之后,返回 前端 登陆页面的 页面内容。
在清楚了 CAS 登陆服务请求的机制之后,我们来开始搭设服务,搭设一个完整的 CAS 服务。
CAS 示例服务
image-20200909165844507
说明:本次示例服务代码分别创建一个 CAS 服务端的 项目,再创建一个 CAS 客户端的 项目,通过两个项目来实现完整的 CAS 服务登陆机制。
相关使用库的 Github 地址
- https://github.com/jbittel/django-mama-cas
- https://django-mama-cas.readthedocs.io/en/latest/
- https://github.com/django-cas-ng/django-cas-ng
CAS 服务端项目
安装Django
代码语言:javascript复制$ pip install Django==2.1.7
因为目前线上运行的是 2.1.7 的版本,还没有改用 3.x 系列版本,所以本次使用 2.1.7 的版本进行演示。
创建Django项目
代码语言:javascript复制$ django-admin startproject django_cas_server .
image-20200909173638238
测试启动Django项目
代码语言:javascript复制$ python manage.py runserver
image-20200909173737679
访问页面如下:
image-20200909173752193
停止服务,开始安装 django-mama-cas 库。
安装 django-mama-cas
代码语言:javascript复制$ pip install django-mama-cas
配置 settings,安装 mama-cas 应用
代码语言:javascript复制INSTALLED_APPS = [
'mama_cas', # 安装 mama_cas 应用
...
]
image-20200909171302285
配置 url,设置访问 cas 服务的路由
代码语言:javascript复制from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('cas/', include('mama_cas.urls')), # 导入mama_cas应用的urls.py
path('admin/', admin.site.urls),
]
image-20200909171604589
在settings 配置 CAS 回调:
官网示例配置:
代码语言:javascript复制MAMA_CAS_SERVICES = [
{
'SERVICE': '^https://[^.] .example.com',
'CALLBACKS': [
'mama_cas.callbacks.user_name_attributes',
],
'LOGOUT_ALLOW': True,
'LOGOUT_URL': 'https://www.example.com/logout',
'PROXY_ALLOW': True,
'PROXY_PATTERN': '^https://proxy.example.com',
}
]
本次项目配置:
代码语言:javascript复制# 配置CAS
MAMA_CAS_SERVICES = [
{
# 必填项,客户端允许访问的域名
'SERVICE': 'http://127.0.0.1:8000',
# 回调模式,具体参考官方文档
'CALLBACKS': [
'mama_cas.callbacks.user_model_attributes',
],
},
]
image-20200909200755361
初始化表
代码语言:javascript复制$ python manage.py migrate
启动服务
代码语言:javascript复制$ python manage.py runserver 0.0.0.0:3000
在这里我不占用 8000 端口号,开启为 3000 端口号作为 cas 服务。
访问CAS登陆页面
访问 http://127.0.0.1:3000/cas/login
image-20200909201733073
那么账号、密码应该填写什么呢?
其实这个取决于Django的 User 表已经存储注册以及激活了的用户。在这里,我们就创建一个 admin 的 超级用户,作为 CAS 的用户。
创建超级用户
代码语言:javascript复制$ python manage.py createsuperuser
Username (leave blank to use 'lijw'): casuser01
Email address:
Password:
Password (again):
This password is too short. It must contain at least 8 characters.
This password is too common.
This password is entirely numeric.
Bypass password validation and create user anyway? [y/N]: y
Superuser created successfully.
登陆CAS服务
image-20200909202310620
提示已经登陆成功,要注意,这里没有其他配置,所以不会跳至其他的页面。只是在上面提示已经登陆成功!
CAS 的 测试用户:casuser01 密码:123456
如果登陆失败,则会提示如下:
image-20200914110114011
CAS 客户端项目
下面首先写一个项目,然后再接入 CAS 服务。
准备好客户端项目
首先准备好一个简单的客户端项目来进行演示,首先具备以下三个视图功能:
- 注册:用来新增用户
- 登陆:登陆项目新增的用户
- 首页:用来演示登陆成功之后的视图页面。
注册页面
http://127.0.0.1:8000/register
image-20200914135239661
这个页面我只实现了最基础填写信息,然后点击注册按钮进行注册的功能,注册成功的话则自动跳转至登陆页面。
登陆页面
http://127.0.0.1:8000/login
image-20200914135857849
在登陆页面,我提供了填写用户、密码以及验证码,然后点击登录按钮的功能。
这里我自己注册的一个 测试用户为: testuser01 密码:123456
要注意:这个用户是在这个项目中注册的数据,后续对接 CAS ,要用的是 CAS 项目的用户。 ”
登陆成功之后,则跳转至 index 页面如下:
image-20200914135935427
安装 CAS 的 Client 库
在 python 中对于 cas 的 client 客户端功能有不少开源库。例如:
python-cas
:https://github.com/python-cas/python-casdjango-cas-ng
: https://github.com/django-cas-ng/django-cas-ng
因为我的项目采用的是 django 框架,所以安装 django-cas-ng
即可。
django-cas-ng 的安装文档:https://djangocas.dev/docs/latest/install.html
image-20200914141205269
使用 pip 安装:
代码语言:javascript复制pip install django-cas-ng
配置项目使用 CAS 的客户端
在项目的配置文件 settings.py
添加以下配置。
参考官网的配置文档:https://djangocas.dev/docs/latest/configuration.html
image-20200914141452552
配置INSTALLED_APPS
, 安装CAS应用
代码语言:javascript复制INSTALLED_APPS = [
'user.apps.UserConfig', # 注册user应用
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django_cas_ng', # 安装cas客户端应用
]
配置 MIDDLEWARE_CLASSES
,设置CAS客户端的中间件类
代码语言:javascript复制MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django_cas_ng.middleware.CASMiddleware', # 设置cas客户端的中间件类
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
配置AUTHENTICATION_BACKENDS
,指定认证授权的后端
代码语言:javascript复制# 指定授权认证的后端
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
'django_cas_ng.backends.CASBackend',
)
配置准备接入的 CAS 服务地址和版本,添加几个对应的配置:
代码语言:javascript复制# CAS 服务的访问地址
CAS_SERVER_URL = 'http://127.0.0.1:3000/cas/'
# CAS 版本
CAS_VERSION = '3'
# 存入所有 CAS 服务端返回的 User 数据。
CAS_APPLY_ATTRIBUTES_TO_USER = True
配置 CAS客户端 访问 CAS服务的视图页面 URL
官网的示例配置:
代码语言:javascript复制# Django 2.0
from django.urls import path
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'),
]
配置项目的路由 urls.py
如下:
from django.contrib import admin
from django.urls import path, include
import django_cas_ng.views # 导入cas的登陆视图
urlpatterns = [
# path('user/', include('user.urls')), # 导入user应用的urls.py
path('', include('user.urls')), # 导入user应用的urls.py
path('cas/login', django_cas_ng.views.LoginView.as_view(), name='cas_ng_login'), # 访问cas服务的登陆
path('cas/logout', django_cas_ng.views.LogoutView.as_view(), name='cas_ng_logout'), # 访问cas服务的登出
path('admin/', admin.site.urls),
]
说明:也就是说配置了这两个路径之后,具体操作过程如下:
- 访问客户端服务:http://127.0.0.1:8000/cas/login 判断如果未登陆服务,则自动重定向至 后台配置的 CAS 服务 http://127.0.0.1:3000/cas/login ,然后在 cas 服务器上登陆成功之后,重新重定向回客户端服务。
- 访问客户端服务:http://127.0.0.1:8000/cas/logout,则自动重定向至 后台配置的 CAS 服务 http://127.0.0.1:3000/cas/logout,则注销退出用户。
初始化 django_cas_ng
的相关数据表
代码语言:javascript复制You have 1 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): django_cas_ng.
Run 'python manage.py migrate' to apply them.
$ python manage.py migrate
启动客户端的服务
代码语言:javascript复制$ python manage.py runserver
测试客户端访问 CAS 服务
- 1.访问 http://127.0.0.1:8000/cas/login ,登陆用户
image-20200914162201532
自动重定向至 CAS 服务如下:
image-20200914162617249
登陆成功之后,返回客户端的服务如下:
image-20200914162641249
- 2.访问 http://127.0.0.1:8000/cas/logout 退出登陆状态
访问之后,自动重定向至未登录状态:
image-20200914162842100
总结
- 1.成功访问CAS服务,登陆用户之后,通过配置,可以自动将用户同步在客户端项目的用户数据中
通过在 settings.py
配置自动同步用户数据:
# 存入所有 CAS 服务端返回的 User 数据。
CAS_APPLY_ATTRIBUTES_TO_USER = True
登陆成功之后,可以查询到登陆成功的用户数据,如下:
image-20200914163317309
- 2.同步CAS的用户的其他字段根据默认值设置,例如:角色按照默认设置
首先确认一下,我定义用户模型类的角色字段默认值,如下:
image-20200914163639274
查询CAS同步用户 的 角色数据:
代码语言:javascript复制In [13]: User.objects.get(username="casuser01").role
Out[13]: 0
In [14]: User.objects.get(username="casuser01").get_role_display()
Out[14]: '组员'
- 3.可以保留两个登陆页面
因为 客户端项目的登陆 和 CAS服务的登陆 是通过不同的 url 访问的,并且都可以设置登陆的状态。
也就是说,我可以在一个页面中设置不同的登陆访问,如下:
image-20200914165552564
点击CAS登陆,显示如下:
image-20200914165617366
image-20200914165737365
- 4.在项目的登陆视图,增加用户的登陆状态判断,如果已登陆,则直接重定向至首页
image-20200914170313767
代码语言:javascript复制 def get(self, request):
# get请求返回登录页面
# 判断用户是否已登陆
# 获取当前的用户
user = request.user # 获取当前的用户
# 判断用户是否已登陆
if user.is_authenticated: # 用户已登陆, 则跳至首页
return redirect('user:index')
# 用户未登陆,则进入登陆页
return render(request, "user/login.html")
- 5.登陆、用户数据、RBAC的方案策略
从上面的尝试过程中,可以确认 客户端项目 是可以保留 两种登陆用户的 方式的,并且两种方式的用户数据都会保存在 客户端项目中。
而同步过来的用户则会采用默认的角色字段,所以在配置RBAC的时候,直接根据默认角色配置可以显示的菜单即可。
其实也就是在做 RBAC 功能开发 并不受 CAS 用户的影响,CAS 用户只是增加了一种登陆的方式而已。
- 6.客户端采用 http 服务,可以配置 https 的 CAS 服务
在一开始我还担心 http 的客户端服务能否 对接 https 的CAS 服务,其实是可以的。
演示项目的仓库地址
- CAS 服务端演示项目:https://gitee.com/kubernete/django_cas_server
- CAS 客户端演示项目:https://gitee.com/kubernete/django_cas_client