• Djnago 实现单点登陆SSO


    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>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <div class="login_content">
        <div class="page-header" id="page_header">
          <h1>登录<small>Login</small></h1>
        </div>
        <div id="login_form">
            <form method="post">
              <div class="form-group">
                <label for="exampleInputEmail1">Email address</label>
                <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,就不分模块了。)

    创建文件后的文件目录为:(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 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 "<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' is not defined
    

      

     

    如果有这个报错的话,在setting.py中导入os即可:import os

     


    然后我们在两个项目的view.py中写入登录和注销函数:

    from django.http import HttpResponse
    from django.shortcuts import render, redirect
    
    
    def 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 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:2bs2nx2iq879epxu7au7o1zq63o095v7,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相关的开发博客。

  • 相关阅读:
    Minutes和TotalMinutes的区别
    C#的"?"修饰符和"??"运算符
    Navicat 连接MySQL 8.0.11 出现2059错误
    EL1004E: Method call: Method fmtdate(java.util.Date,java.lang.String) cannot be found on org.thymele
    es nested结构判断不为空
    es nested嵌套查询
    CPU基础知识线程切换
    CPU基础知识CPU的组成 运算器、控制器、寄存器
    几个常用寄存器
    Linux笔记用户态与内核态
  • 原文地址:https://www.cnblogs.com/rianley/p/14504530.html
Copyright © 2020-2023  润新知