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,具体代码为:
<!DOCTYPE html>
<htmllang="en">
<head>
<metacharset="UTF-8">
<title>
Title
</title>
</head>
<body>
<divclass="login_content">
<divclass="page-header"id="page_header">
<h1>
登录
<small>
Login
</small></h1>
</div>
<divid="login_form">
<formmethod="post">
<divclass="form-group">
<labelfor="exampleInputEmail1">
Email address
</label>
<inputtype="input"class="form-control"name="usr"id="exampleInputEmail1"placeholder="username">
</div>
<divclass="form-group">
<labelfor="exampleInputPassword1">
密码
</label>
<inputtype="password"class="form-control"name="password"id="exampleInputPassword1"placeholder="密码">
</div>
<divid="login_butt">
<buttontype="submit"class="btn btn-default">
登录
</button>
<buttontype="button"class="btn btn-default"onclick="">
注册
</button>
</div>
</form>
</div>
</div>
</body>
</html>
然后在SSO1文件夹创建一个view.py文件,用来存放视图函数。(这里仅为演示SSO,就不分模块了。)
创建文件后的文件目录为:(SSO2项目一样)
.

├── 
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 
calllast
):

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
inmain
    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
inexecute
    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"<frozen importlib._bootstrap>"
, line 
1006
in
 _gcd_import

File"<frozen importlib._bootstrap>"
, line 
983
in
 _find_and_load

File"<frozen importlib._bootstrap>"
, line 
967
in
 _find_and_load_unlocked

File"<frozen importlib._bootstrap>"
, line 
677
in
 _load_unlocked

File"<frozen importlib._bootstrap_external>"
, line 
728
in
 exec_module

File"<frozen importlib._bootstrap>"
, 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'isnot
 defined

如果有这个报错的话,在setting.py中导入os即可:import os
然后我们在两个项目的view.py中写入登录和注销函数:
from
 django.http 
import
 HttpResponse

from
 django.shortcuts 
import
 render, redirect



deflogin(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))



deflogout(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
 admin

from
 django.urls 
import
 path

from
 . 
import
 view

urlpatterns = [

    path(
'admin/'
, admin.site.urls),

    path(
'login/'
,view.login),

    path(
'logout/'
,view.logout),

]


Django默认配置了csrf,需要将它注释掉,在settings.py文件中搜csrf,然后注释掉。
修改后的settings.py文件为:
"""

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
 Path

import
 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固定存储在同一个文件中,
  • 将session存储在Redis中

将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/   
文章转自:Python开发者

继续阅读
阅读原文