在公司平台的开发中,由于内部平台越来越多,本次要求我们开发的平台需要同步公司的 OA 账号。
那么怎么同步呢?简单来说就是采用 CAS 服务机制,实现 CAS 服务完成多应用单点登陆 功能。
image-20200909110936463
在了解 CAS 单点登陆之前,先来回顾一下 Django 默认的 Session + Cookie 的登陆机制:
下面再来看看 CAS 的单点登陆机制。
首先先不看 CAS 的一堆概念,我们直接上请求时序图,了解请求 CAS 对于服务登陆认证的过程先。
cas登陆机制-CAS服务登陆机制
从上面的时序图来看,可以清晰知道 CAS 服务就是用来统一管理 APP 服务登陆认证的 独立服务。在时序图我写了 16 个处理步骤,在这16 个处理步骤中,可以知道,APP 服务 与 CAS 服务验证登陆是否通过是基于 服务票据 ST 来确认的。
基本认证过程简略如下:
在清楚了 CAS 登陆服务请求的机制之后,我们来开始搭设服务,搭设一个完整的 CAS 服务。
image-20200909165844507
说明:本次示例服务代码分别创建一个 CAS 服务端的 项目,再创建一个 CAS 客户端的 项目,通过两个项目来实现完整的 CAS 服务登陆机制。
$ pip install Django==2.1.7
因为目前线上运行的是 2.1.7 的版本,还没有改用 3.x 系列版本,所以本次使用 2.1.7 的版本进行演示。
$ django-admin startproject django_cas_server .
image-20200909173638238
$ python manage.py runserver
image-20200909173737679
访问页面如下:
image-20200909173752193
停止服务,开始安装 django-mama-cas 库。
$ pip install django-mama-cas
INSTALLED_APPS = [
'mama_cas', # 安装 mama_cas 应用
...
]
image-20200909171302285
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
官网示例配置:
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',
}
]
本次项目配置:
# 配置CAS
MAMA_CAS_SERVICES = [
{
# 必填项,客户端允许访问的域名
'SERVICE': 'http://127.0.0.1:8000',
# 回调模式,具体参考官方文档
'CALLBACKS': [
'mama_cas.callbacks.user_model_attributes',
],
},
]
image-20200909200755361
$ python manage.py migrate
$ python manage.py runserver 0.0.0.0:3000
在这里我不占用 8000 端口号,开启为 3000 端口号作为 cas 服务。
访问 http://127.0.0.1:3000/cas/login
image-20200909201733073
那么账号、密码应该填写什么呢?
其实这个取决于Django的 User 表已经存储注册以及激活了的用户。在这里,我们就创建一个 admin 的 超级用户,作为 CAS 的用户。
$ 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.
image-20200909202310620
提示已经登陆成功,要注意,这里没有其他配置,所以不会跳至其他的页面。只是在上面提示已经登陆成功!
CAS 的 测试用户:casuser01 密码:123456
如果登陆失败,则会提示如下:
image-20200914110114011
下面首先写一个项目,然后再接入 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
在 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 安装:
pip install django-cas-ng
在项目的配置文件 settings.py
添加以下配置。
参考官网的配置文档:https://djangocas.dev/docs/latest/configuration.html
image-20200914141452552
INSTALLED_APPS
, 安装CAS应用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客户端的中间件类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
,指定认证授权的后端# 指定授权认证的后端
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
'django_cas_ng.backends.CASBackend',
)
# CAS 服务的访问地址
CAS_SERVER_URL = 'http://127.0.0.1:3000/cas/'
# CAS 版本
CAS_VERSION = '3'
# 存入所有 CAS 服务端返回的 User 数据。
CAS_APPLY_ATTRIBUTES_TO_USER = True
官网的示例配置:
# 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),
]
说明:也就是说配置了这两个路径之后,具体操作过程如下:
django_cas_ng
的相关数据表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
$ python manage.py runserver
image-20200914162201532
自动重定向至 CAS 服务如下:
image-20200914162617249
登陆成功之后,返回客户端的服务如下:
image-20200914162641249
访问之后,自动重定向至未登录状态:
image-20200914162842100
通过在 settings.py
配置自动同步用户数据:
# 存入所有 CAS 服务端返回的 User 数据。
CAS_APPLY_ATTRIBUTES_TO_USER = True
登陆成功之后,可以查询到登陆成功的用户数据,如下:
image-20200914163317309
首先确认一下,我定义用户模型类的角色字段默认值,如下:
image-20200914163639274
查询CAS同步用户 的 角色数据:
In [13]: User.objects.get(username="casuser01").role
Out[13]: 0
In [14]: User.objects.get(username="casuser01").get_role_display()
Out[14]: '组员'
因为 客户端项目的登陆 和 CAS服务的登陆 是通过不同的 url 访问的,并且都可以设置登陆的状态。
也就是说,我可以在一个页面中设置不同的登陆访问,如下:
image-20200914165552564
点击CAS登陆,显示如下:
image-20200914165617366
image-20200914165737365
image-20200914170313767
def get(self, request):
# get请求返回登录页面
# 判断用户是否已登陆
# 获取当前的用户
user = request.user # 获取当前的用户
# 判断用户是否已登陆
if user.is_authenticated: # 用户已登陆, 则跳至首页
return redirect('user:index')
# 用户未登陆,则进入登陆页
return render(request, "user/login.html")
从上面的尝试过程中,可以确认 客户端项目 是可以保留 两种登陆用户的 方式的,并且两种方式的用户数据都会保存在 客户端项目中。
而同步过来的用户则会采用默认的角色字段,所以在配置RBAC的时候,直接根据默认角色配置可以显示的菜单即可。
其实也就是在做 RBAC 功能开发 并不受 CAS 用户的影响,CAS 用户只是增加了一种登陆的方式而已。
在一开始我还担心 http 的客户端服务能否 对接 https 的CAS 服务,其实是可以的。