SSO简介
单点登录(Single Sign On)功能是一个非常常用的功能,尤其是我们在多个系统之间需要登录同步的时候,例如我们在登录QQ空间后,再去QQ的其他网站,都是默认登录的状态,这就是单点登录。 单点登录有很多种实现方法,这里介绍一个通过共享session的实现方法。实现共享session要做的就是要让多个不同应用共用同一个session,但是session默认的是每个应用一个独立的session和cookie的,所以这里要对session的存储进行配置。 除了默认的session存储,我也可以设置让session存储在文件、缓存或者数据库中。 如果我们让session存储在一个固定位置或者数据库中,然后我们设置各个应用cookie的domain为父域地址即可实现各个cookie的相同,从而时候各个cookie中存储的sessionID一致。 搭建测试环境 下面我们来创建两个空的Django项目来进行演示,SSO1和SSO2,这里采用pycharm直接创建两个Django项目,也可以在命令行中使用 django-admin startproject sso
来创建,其中 sso
是创建的项目名称。这里也可以使用两个完全相同的项目,在不同地址启动,但是为了演示效果,这里创建了2个。 创建好两个项目后,我们要给项目写一个模拟的登录,注销的功能。 在 templates
文件夹下创建文件 login.html
文件。这里直接使用之前写过的登录页面的代码,样式就不加了,在SSO1和SSO2中都加入 login.html
,具体代码为: html ><html lang ="en" > <head > <meta charset ="UTF-8" > <title > Titletitle >head ><body > <div class ="login_content" > <div class ="page-header" id ="page_header" > <h1 > 登录<small > Loginsmall >h1 > div > <div id ="login_form" > <form method ="post" > <div class ="form-group" > <label for ="exampleInputEmail1" > Email addresslabel > <input type ="input" class ="form-control" name ="usr" id ="exampleInputEmail1" placeholder
="username" > div > <div class ="form-group" > <label for ="exampleInputPassword1" > 密码label > <input type ="password" class ="form-control" name ="password" id ="exampleInputPassword1" placeholder ="密码" > div > <div id ="login_butt" > <button type ="submit" class ="btn btn-default" > 登录button > <button type ="button" class ="btn btn-default" onclick ="" > 注册button > div > form > div >div >body >html >
然后在 SSO1
文件夹创建一个 view.py
文件,用来存放视图函数。(这里仅为演示SSO,就不分模块了。) . ├── SSO1 │ ├── __init__ .py │ ├── asgi .py │ ├── settings .py │ ├── urls .py │ ├── view .py │ └── wsgi .py ├── manage .py ├── templates │ └── login .html └── venv ├── bin ├── include ├── lib └── pyvenv .cfg
插入一个小BUG macbook运行环境,pycharm创建的Django应用有时候初始化有个bug,缺少os库,会报错: Traceback (most recent call last ): File "/Users/qiguan/Documents/develop_files/python_files/SSO1/manage.py" , line 22 , in <module > main () File "/Users/qiguan/Documents/develop_files/python_files/SSO1/manage.py" , line 18 , in main execute_from_command_line(sys.argv) File "/Users/qiguan/Documents/develop_files/python_files/SSO1/venv/lib/python3.7/site-packages/django/core/management/__init__.py"
, line 401 , in execute_from_command_line utility.execute() File "/Users/qiguan/Documents/develop_files/python_files/SSO1/venv/lib/python3.7/site-packages/django/core/management/__init__.py" , line 345 , in execute settings.INSTALLED_APPS File "/Users/qiguan/Documents/develop_files/python_files/SSO1/venv/lib/python3.7/site-packages/django/conf/__init__.py" , line 82 , in __getattr__ self._setup(name ) File "/Users/qiguan/Documents/develop_files/python_files/SSO1/venv/lib/python3.7/site-packages/django/conf/__init__.py" , line 69 , in _setup self._wrapped = Settings (settings_module) File "/Users/qiguan/Documents/develop_files/python_files/SSO1/venv/lib/python3.7/site-packages/django/conf/__init__.py" , line 170 , in __init__ mod = importlib.import_module(self.SETTINGS_MODULE) File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/importlib/__init__.py" , line 127 , in import_module return _bootstrap._gcd_import(name [level :], package , level ) File "" , line 1006 , in _gcd_import File "" , line 983 , in _find_and_load File "" , line 967 , in _find_and_load_unlocked File "" , line 677 , in _load_unlocked File "" , line 728 , in exec_module File "" , line 219 , in _call_with_frames_removed File "/Users/qiguan/Documents/develop_files/python_files/SSO1/SSO1/settings.py" , line 57 , in <module > 'DIRS' : [os.path.join(BASE_DIR, 'templates' )] NameError: name 'os' is not defined
如果有这个报错的话,在 setting.py
中导入os即可: import os
然后我们在两个项目的view.py中写入登录和注销函数: from django.http import HttpResponsefrom django.shortcuts import render, redirectdef login (request) : if request.method == 'GET' : if 'usr' in request.session: # 如果session中已有信息,则显示 usr = request.session['usr' ] password = request.session['password' ] return HttpResponse("usr:{},password:{},sessionid:{},cookie:{}" .format(usr,password,request.session.session_key,request.COOKIES)) return render(request,'login.html' ) if request.method == 'POST' : usr = request.POST['usr' ] password = request.POST['password' ] request.session['usr' ] = usr request.session['password' ] = password return HttpResponse( "usr:{},password:{},sessionid:{},cookie:{}"
.format(usr, password, request.session.session_key, request.COOKIES))def logout (request) : request.session.clear() return redirect('/login' )
在 url.py
中添加路由信息:
"""SSO1 URL Configuration The `urlpatterns` list routes URLs to views. For more information please see: https://docs.djangoproject.com/en/3.1/topics/http/urls/ Examples: Function views 1. Add an import: from my_app import views 2. Add a URL to urlpatterns: path('', views.home, name='home') Class-based views 1. Add an import: from other_app.views import Home 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') Including another URLconf 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.contrib import adminfrom django.urls import pathfrom . import view urlpatterns = [ path('admin/' , admin.site.urls), path('login/' ,view.login), path('logout/' ,view.logout), ]
Django默认配置了csrf,需要将它注释掉,在 settings.py
文件中搜csrf,然后注释掉。 """ Django settings for SSO1 project. Generated by 'django-admin startproject' using Django 3.1.7. For more information on this file, see https://docs.djangoproject.com/en/3.1/topics/settings/ For the full list of settings and their values, see https://docs.djangoproject.com/en/3.1/ref/settings/ """ from pathlib import Pathimport os# Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent# Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = 'o=blc^vzeb1&g*b!si(wtxe44_=i5cv(3jqm2*u2u&7vgj%&=%' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True ALLOWED_HOSTS = []# Application definition INSTALLED_APPS = [ 'django.contrib.admin' , 'django.contrib.auth' , 'django.contrib.contenttypes' , 'django.contrib.sessions' , 'django.contrib.messages' , 'django.contrib.staticfiles' , ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware' , 'django.contrib.sessions.middleware.SessionMiddleware' , 'django.middleware.common.CommonMiddleware' , # 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware' , 'django.contrib.messages.middleware.MessageMiddleware' , 'django.middleware.clickjacking.XFrameOptionsMiddleware' , ] ROOT_URLCONF = 'SSO1.urls' TEMPLATES = [ { 'BACKEND' : 'django.template.backends.django.DjangoTemplates' , 'DIRS' : [os.path.join(BASE_DIR, 'templates' )] , 'APP_DIRS' : True , 'OPTIONS' : { 'context_processors' : [ 'django.template.context_processors.debug' , 'django.template.context_processors.request' , 'django.contrib.auth.context_processors.auth' , 'django.contrib.messages.context_processors.messages' , ], }, }, ] WSGI_APPLICATION = 'SSO1.wsgi.application' # Database # https://docs.djangoproject.com/en/3.1/ref/settings/#databases DATABASES = { 'default' : { 'ENGINE'
: 'django.db.backends.sqlite3' , 'NAME' : BASE_DIR / 'db.sqlite3' , } }# Password validation # https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ { 'NAME' : 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator' , }, { 'NAME' : 'django.contrib.auth.password_validation.MinimumLengthValidator' , }, { 'NAME' : 'django.contrib.auth.password_validation.CommonPasswordValidator' , }, { 'NAME' : 'django.contrib.auth.password_validation.NumericPasswordValidator' , }, ]# Internationalization # https://docs.djangoproject.com/en/3.1/topics/i18n/ LANGUAGE_CODE = 'en-us' TIME_ZONE = 'UTC' USE_I18N = True USE_L10N = True USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/3.1/howto/static-files/ STATIC_URL = '/static/'
然后分别为两个项目做数据库迁移,创建一些Django项目的基础库: python3 manage.py migrate
两个项目都是同样的配置,这样我们目前两个测试的项目就搭建好了,然后我们分别启动他们在不同的端口。这里我们就直接手动启动了,分别启动在5000和6000端口。 python3 manage.py runserver 127.0.0.1:5000
python3 manage.py runserver 127.0.0.1:7000
现在我们分别在浏览器中打开 http://127.0.0.1:5000/login/
和 http://127.0.0.1:7000/login/
,显示的页面都是登录页面,显示如下: 这时我们在 http://127.0.0.1:5000/login/
随意输入账户密码点击登录,显示: usr:123 ,password:123 ,sessionid:None ,cookie:{'csrftoken' : '8YPzJbY03sHJUZH6kdFZzr9TkDtdVTKflgDDeIn0wgGC6cAeudcrkXLyIxXBEnzG' }
此时我们进入 http://127.0.0.1:7000/login/
,发现这个应用中,显示的还是之前的页面,登录没有同步。下面我们来实现我们的SSO,这里的实现方法非常的简单,这里提供2中实现方法: 将session存储在同一个文件中实现SSO 我们在SSO2文件下创建了一个session文件夹,这个文件夹位置任意,写绝对路径即可。 然后我们在两个项目的 settings.py
中对cookie和session进行配置 # 设置cookie的domain为父域domain, # 如果是使用域名,以百度为例,主域名为`www.baidu.com`,旗下各个应用为:'asd.baidu.com' # 则这里设置为:`.baidu.com` SESSION_COOKIE_DOMAIN = '127.0.0.1' # 设置session存储在文件中 SESSION_ENGINE = 'django.contrib.sessions.backends.file' # 设置存储位置,这里设为绝对路径 SESSION_FILE_PATH = '/Users/qiguan/Documents/develop_files/python_files/SSO2/session'
注意一下,这里配置的都是一样的,但是如果两个项目名称不一样的话,是不能直接将完整的 settings.py
直接复制到另一个的,因为里面有一些项目的配置,例如 ROOT_URLCONF = 'SSO1.urls'
、 WSGI_APPLICATION = 'SSO1.wsgi.application'
这些前面的都是项目名,需要主要区分。 此时我们在打开 http://127.0.0.1:5000/login/
,输入账号密码,此页面显示: usr:123 ,password :123 ,sessionid :2 bs2nx2iq879epxu7au7o1zq63o095v7,cookie :{'sessionid' : '2bs2nx2iq879epxu7au7o1zq63o095v7' , 'csrftoken' : '8YPzJbY03sHJUZH6kdFZzr9TkDtdVTKflgDDeIn0wgGC6cAeudcrkXLyIxXBEnzG' }
此时我们在打开 http://127.0.0.1:7000/login/
,我们直接访问,而不用登录,发现显示同样的内容,即我们使用的是同样的内容,实现了SSO。 使用Redis实现SSO 使用文件系统上实现共享session在小并发系统上不会出现问题,但是并发量大的话,会出现一些问题,所以我们这里再介绍一下使用Redis的实现。 需要自行安装Redis,并且在两个项目使用的Python中安装Django-redis: pip3 install django-redis
在做好这些之后,修改settings.py文件,将使用文件存储session的配置注释掉,修改为: # # 设置session存储在文件中 # SESSION_ENGINE = 'django.contrib.sessions.backends.file' # # 设置存储位置,这里设为绝对路径 # SESSION_FILE_PATH = '/Users/qiguan/Documents/develop_files/python_files/SSO2/session' # 使用Redis存储session CACHES = { "default" : { "BACKEND" : "django_redis.cache.RedisCache" , "LOCATION" : "redis://127.0.0.1:6379" , "OPTIONS" : { "CLIENT_CLASS" : "django_redis.client.DefaultClient" , "CONNECTION_POOL_KWARGS" : {"max_connections" : 100} # "PASSWORD": "123", } } } SESSION_ENGINE = 'django.contrib.sessions.backends.cache' SESSION_CACHE_ALIAS = 'default' SESSION_COOKIE_AGE = 60 * 5
此时我们再来测试一下两个应用,这时我们先访问一下logout,将session清空,然后访问: http://127.0.0.1:5000/login/
,输入账户密码后显示:
usr:123 ,password:123 ,sessionid:None ,cookie:{'csrftoken' : '8YPzJbY03sHJUZH6kdFZzr9TkDtdVTKflgDDeIn0wgGC6cAeudcrkXLyIxXBEnzG' }
此时我们访问 http://127.0.0.1:7000/login/
(不登录),显示同样的usr和password信息。
此时我们的SSO也可以正常实现。
好了,本文就先到这里,大家如有需要,可以根据具体的业务进行实现,这里就不赘述了。等以后有空再写一些Django相关的开发博客。
原文来源:https://qiguanjie.blog.csdn.net/