ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

75. Django 集成 CAS 实现 SSO 单点登陆

2021-06-18 10:51:46  阅读:140  来源: 互联网

标签:服务 CAS image django cas SSO 登陆 Django


需求

在公司平台的开发中,由于内部平台越来越多,本次要求我们开发的平台需要同步公司的 OA 账号。

那么怎么同步呢?简单来说就是采用 CAS 服务机制,实现 CAS 服务完成多应用单点登陆 功能。

Django 默认的 Session + Cookie 的登陆机制

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=image-20200909110936463

在了解 CAS 单点登陆之前,先来回顾一下 Django 默认的 Session + Cookie 的登陆机制:

  1. 浏览器发送登陆请求 至 Django 服务

  2. Django 服务接收到 浏览器发送过来的请求之后,则创建 CSRFToken 以及 相关用户信息,存储到 Session 中,并且返回浏览器 Set-Cookie 的信息,通知浏览器设置相关 Cookie

  3. 浏览器再次发送请求 至 Django 服务,则会携带前面设置的 Cookie 信息

  4. Django 服务接收到 浏览器发送过来的请求之后,发现携带了 CSRFToken 以及 记录用户信息的 sessionID,根据 sessionID 查询服务器上的 session 数据。

下面再来看看 CAS 的单点登陆机制。

CAS 的 (Single Sign-On)SSO单点登陆机制

首先先不看 CAS 的一堆概念,我们直接上请求时序图,了解请求 CAS 对于服务登陆认证的过程先。

CAS 登陆服务请求时序图

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

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 示例服务

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=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

$ pip install Django==2.1.7

因为目前线上运行的是 2.1.7 的版本,还没有改用 3.x 系列版本,所以本次使用 2.1.7 的版本进行演示。

创建Django项目

$ django-admin startproject django_cas_server .
watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=image-20200909173638238

测试启动Django项目

$ python manage.py runserver
watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=image-20200909173737679

访问页面如下:

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=image-20200909173752193

停止服务,开始安装 django-mama-cas 库。

安装 django-mama-cas

$ pip install django-mama-cas

配置 settings,安装 mama-cas 应用

INSTALLED_APPS = [
'mama_cas', # 安装 mama_cas 应用
...
]
watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=image-20200909171302285

配置 url,设置访问 cas 服务的路由

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),
]
watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=image-20200909171604589

在settings 配置 CAS 回调:

官网示例配置:

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',
],
},
]
watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=image-20200909200755361

初始化表

$ python manage.py migrate

启动服务

$ python manage.py runserver 0.0.0.0:3000

在这里我不占用 8000 端口号,开启为 3000 端口号作为 cas 服务。

访问CAS登陆页面

访问 http://127.0.0.1:3000/cas/login

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=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.

登陆CAS服务

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=image-20200909202310620

提示已经登陆成功,要注意,这里没有其他配置,所以不会跳至其他的页面。只是在上面提示已经登陆成功!

CAS 的 测试用户:casuser01 密码:123456

如果登陆失败,则会提示如下:

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=image-20200914110114011

CAS 客户端项目

下面首先写一个项目,然后再接入 CAS 服务。

准备好客户端项目

首先准备好一个简单的客户端项目来进行演示,首先具备以下三个视图功能:

  • 注册:用来新增用户
  • 登陆:登陆项目新增的用户
  • 首页:用来演示登陆成功之后的视图页面。

注册页面

http://127.0.0.1:8000/register

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=image-20200914135239661

这个页面我只实现了最基础填写信息,然后点击注册按钮进行注册的功能,注册成功的话则自动跳转至登陆页面。

登陆页面

http://127.0.0.1:8000/login

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=image-20200914135857849

在登陆页面,我提供了填写用户、密码以及验证码,然后点击登录按钮的功能。

这里我自己注册的一个 测试用户为: testuser01 密码:123456

要注意:这个用户是在这个项目中注册的数据,后续对接 CAS ,要用的是 CAS 项目的用户。

登陆成功之后,则跳转至 index 页面如下:

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=image-20200914135935427

安装 CAS 的 Client 库

在 python 中对于 cas 的 client 客户端功能有不少开源库。例如:

  • python-cas:https://github.com/python-cas/python-cas
  • django-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

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=image-20200914141205269

使用 pip 安装:

pip install django-cas-ng

配置项目使用 CAS 的客户端

在项目的配置文件 settings.py 添加以下配置。

参考官网的配置文档:https://djangocas.dev/docs/latest/configuration.html

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=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 服务的访问地址
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

官网的示例配置:

# 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 的相关数据表

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

测试客户端访问 CAS 服务

  • 1.访问 http://127.0.0.1:8000/cas/login ,登陆用户
watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=image-20200914162201532

自动重定向至 CAS 服务如下:

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=image-20200914162617249

登陆成功之后,返回客户端的服务如下:

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=image-20200914162641249
  • 2.访问 http://127.0.0.1:8000/cas/logout 退出登陆状态

访问之后,自动重定向至未登录状态:

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=image-20200914162842100

总结

  • 1.成功访问CAS服务,登陆用户之后,通过配置,可以自动将用户同步在客户端项目的用户数据中

通过在 settings.py 配置自动同步用户数据:

# 存入所有 CAS 服务端返回的 User 数据。
CAS_APPLY_ATTRIBUTES_TO_USER = True

登陆成功之后,可以查询到登陆成功的用户数据,如下:

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=image-20200914163317309
  • 2.同步CAS的用户的其他字段根据默认值设置,例如:角色按照默认设置

首先确认一下,我定义用户模型类的角色字段默认值,如下:

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=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]: '组员'
  • 3.可以保留两个登陆页面

因为 客户端项目的登陆 和 CAS服务的登陆 是通过不同的 url 访问的,并且都可以设置登陆的状态。

也就是说,我可以在一个页面中设置不同的登陆访问,如下:

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=image-20200914165552564

点击CAS登陆,显示如下:

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=image-20200914165617366watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=image-20200914165737365
  • 4.在项目的登陆视图,增加用户的登陆状态判断,如果已登陆,则直接重定向至首页
watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=image-20200914170313767
    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

 

标签:服务,CAS,image,django,cas,SSO,登陆,Django
来源: https://blog.51cto.com/u_11239407/2920445

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有