├── LICENSE ├── README.md ├── account ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ └── __init__.py ├── models.py ├── serializers.py ├── tests.py ├── urls.py └── views.py ├── django-sso ├── __init__.py ├── auth.py ├── gunicorn_config.py ├── settings.py ├── urls.py └── wsgi.py ├── logs └── .gitignore ├── manage.py ├── media └── imgs │ └── ssostruct.jpg ├── requirements.txt └── utils ├── __init__.py ├── permissions.py └── unitaryauth.py /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2019, chen kun 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # 基于django的单点登录系统 3 | 4 | ![image](https://github.com/myide/django-sso/blob/master/media/imgs/ssostruct.jpg) 5 | 6 | ## 实现机制 7 | 当用户第一次访问应用系统1的时候,因为还没有登录,会被引导到认证系统中进行登录;根据用户提供的登录信息,认证系统进行身份校验,如果通过校验,应该返回给用户一个认证的凭据--ticket;用户再访问别的应用的时候就会将这个ticket带上,作为自己认证的凭据,应用系统接受到请求之后会把ticket送到认证系统进行校验,检查ticket的合法性。如果通过校验,用户就可以在不用再次登录的情况下访问应用系统2和应用系统3了。 8 | 9 | 10 | ## 主要功能 11 | - 系统共享 12 | - 统一的认证系统是SSO的前提之一。认证系统的主要功能是将用户的登录信息和用户信息库相比较,对用户进行登录认证;认证成功后,认证系统应该生成统一的认证标志(ticket),返还给用户。另外,认证系统还应该对ticket进行校验,判断其有效性。 13 | 14 | - 信息识别 15 | - 要实现SSO的功能,让用户只登录一次,就必须让应用系统能够识别已经登录过的用户。应用系统应该能对ticket进行识别和提取,通过与认证系统的通讯,能自动判断当前用户是否登录过,从而完成单点登录的功能。 16 | - 另外: 17 | - 1、单一的用户信息数据库并不是必须的,有许多系统不能将所有的用户信息都集中存储,应该允许用户信息放置在不同的存储中,事实上,只要统一认证系统,统一ticket的产生和校验,无论用户信息存储在什么地方,都能实现单点登录。 18 | - 2、统一的认证系统并不是说只有单个的认证服务器 19 | 当用户在访问应用系统1时,由第一个认证服务器进行认证后,得到由此服务器产生的ticket。当他访问应用系统2的时候,认证服务器2能够识别此ticket是由第一个服务器产生的,通过认证服务器之间标准的通讯协议(例如SAML)来交换认证信息,仍然能够完成SSO的功能。 20 | 21 | 22 | ## 环境 23 | 24 | - Python 3.6 25 | - Django 2.0 26 | - Django Rest Framework 3.8 27 | 28 | ## 使用 29 | 30 | - sso服务端: django-sso 31 | - 启动服务端,端口8090,假定地址为 www.django-sso.com 32 | 33 | - 接入sso的子系统(工程名:projectName): 34 | - 从django-sso服务端拷贝文件django-sso/auth.py, 放置在子系统的工程目录下 35 | 36 | - 设置 settings.py 37 | ```bash 38 | SSO_URL = 'http://www.django-sso.com:8090/api/account/users/' 39 | MIDDLEWARE = [ 40 | ... 41 | 'projectName.auth.AuthMiddleware', 42 | ... 43 | ] 44 | ``` 45 | 46 | - 各系统前端 47 | - 设置用户的登录地址 'http://www.django-sso.com:8090/api/account/login/' 48 | - 获取登录地址的token并保存,对访问子系统的请求头加入 {'Authorization': 'JWT ' + token值} 49 | 50 | ## 交流学习 51 | - QQ群 630791951 52 | 53 | ## License 54 | 55 | - BSD 3-Clause License 56 | 57 | -------------------------------------------------------------------------------- /account/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myide/django-sso/f124384c10ccde6b229deccbdad3dbe72f7e10cd/account/__init__.py -------------------------------------------------------------------------------- /account/admin.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from .models import User 4 | from django.contrib import admin 5 | 6 | # Register your models here. 7 | 8 | class UserAdmin(admin.ModelAdmin): 9 | pass 10 | 11 | admin.site.register(User, UserAdmin) 12 | -------------------------------------------------------------------------------- /account/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class AccountConfig(AppConfig): 5 | name = 'account' 6 | -------------------------------------------------------------------------------- /account/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myide/django-sso/f124384c10ccde6b229deccbdad3dbe72f7e10cd/account/migrations/__init__.py -------------------------------------------------------------------------------- /account/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | 5 | from django.contrib.auth.models import AbstractUser 6 | 7 | class User(AbstractUser): 8 | bio = models.TextField(max_length=500, blank=True) 9 | location = models.CharField(max_length=30, blank=True) 10 | birth_date = models.DateField(null=True, blank=True) 11 | 12 | -------------------------------------------------------------------------------- /account/serializers.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | from rest_framework import serializers 3 | from .models import User 4 | 5 | class UserSerializer(serializers.ModelSerializer): 6 | 7 | class Meta: 8 | model = User 9 | fields = '__all__' 10 | 11 | def create(self, validated_data): 12 | instance = super(UserSerializer, self).create(validated_data) 13 | instance.set_password(validated_data['password']) 14 | instance.save() 15 | return instance 16 | 17 | def update(self, instance, validated_data): 18 | password = validated_data.pop('password', None) 19 | if instance.password != password: 20 | instance.set_password(password) 21 | return super(UserSerializer, self).update(instance, validated_data) -------------------------------------------------------------------------------- /account/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /account/urls.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | from django.conf.urls import include, url 3 | from rest_framework.routers import DefaultRouter 4 | from .views import * 5 | 6 | router = DefaultRouter() 7 | router.register(r'users', UserViewSet, base_name='UserViewSet') 8 | 9 | urlpatterns = [ 10 | url(r'^', include(router.urls)), 11 | url(r'^login/$', LoginView.as_view()) 12 | ] 13 | -------------------------------------------------------------------------------- /account/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from rest_framework.views import APIView 4 | from rest_framework.viewsets import ModelViewSet 5 | from rest_framework.exceptions import AuthenticationFailed 6 | from rest_framework.response import Response 7 | from utils.unitaryauth import UnitaryAuth 8 | from .serializers import UserSerializer 9 | from .models import User 10 | 11 | # Create your views here. 12 | 13 | class UserViewSet(ModelViewSet): 14 | ''' 15 | 用户信息 16 | ''' 17 | queryset = User.objects.all() 18 | serializer_class = UserSerializer 19 | search_fields = ['username'] 20 | 21 | def create(self, request, *args, **kwargs): 22 | serializer = self.serializer_class(instance=request.user) 23 | return Response(serializer.data) 24 | 25 | class LoginView(UnitaryAuth, APIView): 26 | ''' 27 | 接入统一认证 backend 28 | ''' 29 | serializer_class = UserSerializer 30 | authentication_classes = () 31 | permission_classes = () 32 | 33 | def post(self, request, *args, **kwargs): 34 | auth = self.authenticate 35 | user_query = self.serializer_class.Meta.model.objects.filter(username=request.data.get('username')) 36 | if user_query: 37 | serializer = self.serializer_class(user_query[0], data=request.data) 38 | else: 39 | serializer = self.serializer_class(data=request.data) 40 | serializer.is_valid(raise_exception=True) 41 | serializer.save() 42 | return Response(auth) 43 | -------------------------------------------------------------------------------- /django-sso/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myide/django-sso/f124384c10ccde6b229deccbdad3dbe72f7e10cd/django-sso/__init__.py -------------------------------------------------------------------------------- /django-sso/auth.py: -------------------------------------------------------------------------------- 1 | import requests 2 | try: 3 | from collections import UserDict 4 | except ImportError: 5 | from UserDict import UserDict 6 | from rest_framework import status 7 | 8 | from django.conf import settings 9 | from django.contrib.auth import get_user_model 10 | from django.http.response import JsonResponse 11 | from django.utils.deprecation import MiddlewareMixin 12 | 13 | UserModel = get_user_model() 14 | 15 | class User(UserDict): 16 | 17 | def __init__(self, data): 18 | self.data = data 19 | 20 | @property 21 | def email(self): 22 | return self.data.get("email") 23 | 24 | @property 25 | def is_active(self): 26 | return self.data.get("is_active") 27 | 28 | @property 29 | def username(self): 30 | return self.data.get("username") 31 | 32 | @property 33 | def cname(self): 34 | return self.data.get("cname") 35 | 36 | @property 37 | def departments(self): 38 | return self.data.get("departments") 39 | 40 | class HandleUser: 41 | 42 | def get_or_create_user(user): 43 | kwargs = { 44 | 'email': user.email, 45 | 'username': user.username 46 | } 47 | try: 48 | instance = UserModel.objects.get(username=user.username) 49 | instance.username = user.username 50 | instance.save() 51 | user = instance 52 | except UserModel.DoesNotExist: 53 | user, created = UserModel.objects.get_or_create(**kwargs) 54 | return user 55 | 56 | class AuthMiddleware(MiddlewareMixin): 57 | 58 | def process_request(self, request): 59 | data = request.META.get('HTTP_AUTHORIZATION') 60 | if not data: 61 | return JsonResponse(data={ 62 | "msg": 'Not Provide Authorization', 63 | 'status': status.HTTP_401_UNAUTHORIZED, 64 | 'data': {'detail': 'Not Provide Authorization'}}, 65 | status=status.HTTP_401_UNAUTHORIZED) 66 | auth = RequestAuth(data) 67 | if not auth.query(): 68 | return JsonResponse(data=auth.response, status=status.HTTP_403_FORBIDDEN) 69 | request.user = HandleUser.get_or_create_user(auth.user) 70 | 71 | class RequestAuth: 72 | 73 | sso_url = settings.SSO_URL 74 | 75 | def __init__(self, data): 76 | self.data = data 77 | self._response = None 78 | 79 | def _request(self): 80 | try: 81 | response = requests.post(self.sso_url, headers=self.header) 82 | return self._validate(response) 83 | except Exception as err: 84 | self.error_response(err) 85 | return False 86 | 87 | def _validate(self, response): 88 | self._response = response.json() 89 | if response.status_code > 200: 90 | self.error_response("response status > 200") 91 | return False 92 | return self._response.get('is_active') 93 | 94 | def query(self): 95 | return self._request() 96 | 97 | def error_response(self, err): 98 | self._response = { 99 | 'status': status.HTTP_401_UNAUTHORIZED, 100 | 'msg': 'Auth user token error:{}'.format(err), 101 | 'data': {} 102 | } 103 | 104 | @property 105 | def header(self): 106 | return {'Authorization': self.data} 107 | 108 | @property 109 | def response(self): 110 | return self._response 111 | 112 | @property 113 | def user(self): 114 | return User(self._response) 115 | -------------------------------------------------------------------------------- /django-sso/gunicorn_config.py: -------------------------------------------------------------------------------- 1 | # 配置服务器的监听ip和端口 2 | bind = '127.0.0.1:8090' 3 | # 以守护进程方式运行 4 | daemon = True 5 | # worker数量 6 | workers = 2 7 | # 错误日志路径 8 | errorlog = 'logs/gunicorn.error.log' 9 | # 访问日志路径 10 | accesslog = 'logs/gunicorn.access.log' -------------------------------------------------------------------------------- /django-sso/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for django-sso project. 3 | 4 | Generated by 'django-admin startproject' using Django 2.0.7. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.0/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/2.0/ref/settings/ 11 | """ 12 | import pymysql 13 | pymysql.install_as_MySQLdb() 14 | 15 | import os 16 | import datetime 17 | 18 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 19 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 20 | 21 | # extend User 22 | AUTH_USER_MODEL = 'account.User' 23 | 24 | # swagger login/out 25 | LOGIN_URL = 'rest_framework:login' 26 | LOGOUT_URL = 'rest_framework:logout' 27 | 28 | # Quick-start development settings - unsuitable for production 29 | # See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/ 30 | 31 | # SECURITY WARNING: keep the secret key used in production secret! 32 | SECRET_KEY = 'by#cphl_dh+7e(eeg#cd52@5k=6*@lwy^u4*5qmi+nof_$wl&^' 33 | 34 | # SECURITY WARNING: don't run with debug turned on in production! 35 | DEBUG = False 36 | 37 | ALLOWED_HOSTS = ['*'] 38 | 39 | REST_FRAMEWORK = { 40 | 'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.IsAuthenticated', ), 41 | 'DEFAULT_AUTHENTICATION_CLASSES': ( 42 | 'rest_framework_jwt.authentication.JSONWebTokenAuthentication', 43 | 'rest_framework.authentication.SessionAuthentication', 44 | 'rest_framework.authentication.BasicAuthentication', 45 | ) 46 | } 47 | 48 | AUTHENTICATION_BACKENDS = ( 49 | 'rest_framework.authentication.TokenAuthentication', 50 | 'django.contrib.auth.backends.ModelBackend', 51 | ) 52 | 53 | JWT_AUTH = { 54 | 'JWT_ENCODE_HANDLER': 55 | 'rest_framework_jwt.utils.jwt_encode_handler', 56 | 57 | 'JWT_DECODE_HANDLER': 58 | 'rest_framework_jwt.utils.jwt_decode_handler', 59 | 60 | 'JWT_PAYLOAD_HANDLER': 61 | 'rest_framework_jwt.utils.jwt_payload_handler', 62 | 63 | 'JWT_PAYLOAD_GET_USER_ID_HANDLER': 64 | 'rest_framework_jwt.utils.jwt_get_user_id_from_payload_handler', 65 | 66 | 'JWT_RESPONSE_PAYLOAD_HANDLER': 67 | 'rest_framework_jwt.utils.jwt_response_payload_handler', 68 | 69 | 'JWT_SECRET_KEY': SECRET_KEY, 70 | 'JWT_GET_USER_SECRET_KEY': None, 71 | 'JWT_PUBLIC_KEY': None, 72 | 'JWT_PRIVATE_KEY': None, 73 | 'JWT_ALGORITHM': 'HS256', 74 | 'JWT_VERIFY': True, 75 | 'JWT_VERIFY_EXPIRATION': True, 76 | 'JWT_LEEWAY': 0, 77 | 'JWT_EXPIRATION_DELTA': datetime.timedelta(seconds=7200), 78 | 'JWT_AUDIENCE': None, 79 | 'JWT_ISSUER': None, 80 | 81 | 'JWT_ALLOW_REFRESH': False, 82 | 'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=7), 83 | 84 | 'JWT_AUTH_HEADER_PREFIX': 'JWT', 85 | 'JWT_AUTH_COOKIE': None, 86 | 87 | } 88 | 89 | # Application definition 90 | 91 | INSTALLED_APPS = [ 92 | 'django.contrib.admin', 93 | 'django.contrib.auth', 94 | 'django.contrib.contenttypes', 95 | 'django.contrib.sessions', 96 | 'django.contrib.messages', 97 | 'django.contrib.staticfiles', 98 | 'corsheaders', 99 | 'rest_framework', 100 | 'rest_framework_swagger', 101 | 'account', 102 | ] 103 | 104 | MIDDLEWARE = [ 105 | 'corsheaders.middleware.CorsMiddleware', 106 | 'django.middleware.security.SecurityMiddleware', 107 | 'django.contrib.sessions.middleware.SessionMiddleware', 108 | 'django.middleware.common.CommonMiddleware', 109 | 'django.middleware.csrf.CsrfViewMiddleware', 110 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 111 | 'django.contrib.messages.middleware.MessageMiddleware', 112 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 113 | ] 114 | 115 | ROOT_URLCONF = 'django-sso.urls' 116 | 117 | CORS_ALLOW_CREDENTIALS = True 118 | CORS_ORIGIN_ALLOW_ALL = True 119 | CORS_ORIGIN_WHITELIST = ( 120 | '*' 121 | ) 122 | 123 | CORS_ALLOW_METHODS = ( 124 | 'DELETE', 125 | 'GET', 126 | 'OPTIONS', 127 | 'PATCH', 128 | 'POST', 129 | 'PUT', 130 | 'VIEW', 131 | ) 132 | 133 | CORS_ALLOW_HEADERS = ( 134 | 'XMLHttpRequest', 135 | 'X_FILENAME', 136 | 'accept-encoding', 137 | 'authorization', 138 | 'content-type', 139 | 'dnt', 140 | 'origin', 141 | 'user-agent', 142 | 'x-csrftoken', 143 | 'x-requested-with', 144 | 'Pragma', 145 | ) 146 | 147 | TEMPLATES = [ 148 | { 149 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 150 | 'DIRS': [], 151 | 'APP_DIRS': True, 152 | 'OPTIONS': { 153 | 'context_processors': [ 154 | 'django.template.context_processors.debug', 155 | 'django.template.context_processors.request', 156 | 'django.contrib.auth.context_processors.auth', 157 | 'django.contrib.messages.context_processors.messages', 158 | ], 159 | }, 160 | }, 161 | ] 162 | 163 | WSGI_APPLICATION = 'django-sso.wsgi.application' 164 | 165 | # Database 166 | # https://docs.djangoproject.com/en/2.0/ref/settings/#databases 167 | 168 | DATABASES = { 169 | 'default': { 170 | 'ENGINE': 'django.db.backends.mysql', 171 | 'NAME': 'sso', 172 | 'USER': 'root', 173 | 'PASSWORD': '123456', 174 | 'HOST':'127.0.0.1', 175 | 'PORT':'3306', 176 | }, 177 | } 178 | 179 | # Password validation 180 | # https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators 181 | 182 | AUTH_PASSWORD_VALIDATORS = [ 183 | { 184 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 185 | }, 186 | { 187 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 188 | }, 189 | { 190 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 191 | }, 192 | { 193 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 194 | }, 195 | ] 196 | 197 | 198 | # Internationalization 199 | # https://docs.djangoproject.com/en/2.0/topics/i18n/ 200 | 201 | LANGUAGE_CODE = 'en-us' 202 | 203 | TIME_ZONE = 'Asia/Shanghai' 204 | 205 | USE_I18N = True 206 | 207 | USE_L10N = True 208 | 209 | USE_TZ = False 210 | 211 | # Static files (CSS, JavaScript, Images) 212 | # https://docs.djangoproject.com/en/2.0/howto/static-files/ 213 | 214 | STATIC_URL = '/static/' 215 | 216 | AUTH_URL = 'http://127.0.0.1:8090/api/api-token-auth/' 217 | 218 | BASE_LOG_DIR = os.path.join(BASE_DIR, "logs") 219 | 220 | LOGGING = { 221 | 'version': 1, 222 | 'disable_existing_loggers': False, 223 | 'formatters': { 224 | 'standard': { 225 | 'format': '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]' 226 | '[%(levelname)s][%(message)s]' 227 | }, 228 | 'simple': { 229 | 'format': '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s' 230 | }, 231 | 'collect': { 232 | 'format': '%(message)s' 233 | } 234 | }, 235 | 'filters': { 236 | 'require_debug_true': { 237 | '()': 'django.utils.log.RequireDebugTrue', 238 | }, 239 | }, 240 | 'handlers': { 241 | 'console': { 242 | 'level': 'DEBUG', 243 | 'filters': ['require_debug_true'], 244 | 'class': 'logging.StreamHandler', 245 | 'formatter': 'simple' 246 | }, 247 | 'default': { 248 | 'level': 'INFO', 249 | 'class': 'logging.handlers.RotatingFileHandler', 250 | 'filename': os.path.join(BASE_LOG_DIR, "django-sso.info.log"), 251 | 'maxBytes': 1024 * 1024 * 50, 252 | 'backupCount': 3, 253 | 'formatter': 'standard', 254 | 'encoding': 'utf-8', 255 | }, 256 | 'error': { 257 | 'level': 'ERROR', 258 | 'class': 'logging.handlers.RotatingFileHandler', 259 | 'filename': os.path.join(BASE_LOG_DIR, "django-sso.error.log"), 260 | 'maxBytes': 1024 * 1024 * 50, 261 | 'backupCount': 5, 262 | 'formatter': 'standard', 263 | 'encoding': 'utf-8', 264 | }, 265 | 'collect': { 266 | 'level': 'INFO', 267 | 'class': 'logging.handlers.RotatingFileHandler', 268 | 'filename': os.path.join(BASE_LOG_DIR, "django-sso.collect.log"), 269 | 'maxBytes': 1024 * 1024 * 50, 270 | 'backupCount': 5, 271 | 'formatter': 'collect', 272 | 'encoding': "utf-8" 273 | } 274 | }, 275 | 'loggers': { 276 | '': { 277 | 'handlers': ['default', 'console', 'error'], 278 | 'level': 'DEBUG', 279 | 'propagate': True, 280 | }, 281 | 'collect': { 282 | 'handlers': ['console', 'collect'], 283 | 'level': 'INFO', 284 | } 285 | }, 286 | } 287 | -------------------------------------------------------------------------------- /django-sso/urls.py: -------------------------------------------------------------------------------- 1 | """django-sso URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/2.0/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | 17 | from django.contrib import admin 18 | from django.urls import path, include 19 | 20 | # swagger pakeage 21 | from rest_framework.schemas import get_schema_view 22 | from rest_framework_swagger.renderers import SwaggerUIRenderer, OpenAPIRenderer 23 | from rest_framework_jwt.views import obtain_jwt_token, verify_jwt_token 24 | 25 | schema_view = get_schema_view(title='Users API', renderer_classes=[OpenAPIRenderer, SwaggerUIRenderer]) 26 | 27 | urlpatterns = [ 28 | path('admin/', admin.site.urls), 29 | path('api/docs/', schema_view), 30 | path('api/api-auth/', include('rest_framework.urls', namespace='rest_framework')), 31 | path('api/api-token-auth/', obtain_jwt_token), 32 | path('api/account/', include('account.urls')), 33 | ] 34 | -------------------------------------------------------------------------------- /django-sso/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for django-sso project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django-sso.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /logs/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django-sso.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError as exc: 10 | raise ImportError( 11 | "Couldn't import Django. Are you sure it's installed and " 12 | "available on your PYTHONPATH environment variable? Did you " 13 | "forget to activate a virtual environment?" 14 | ) from exc 15 | execute_from_command_line(sys.argv) 16 | -------------------------------------------------------------------------------- /media/imgs/ssostruct.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myide/django-sso/f124384c10ccde6b229deccbdad3dbe72f7e10cd/media/imgs/ssostruct.jpg -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django==2.0.8 2 | django-cors-headers==2.4.0 3 | django-filter==2.0.0 4 | django-rest-swagger==2.2.0 5 | djangorestframework==3.8.2 6 | djangorestframework-jwt==1.11.0 7 | gunicorn==19.9.0 8 | PyMySQL==0.9.2 9 | -------------------------------------------------------------------------------- /utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myide/django-sso/f124384c10ccde6b229deccbdad3dbe72f7e10cd/utils/__init__.py -------------------------------------------------------------------------------- /utils/permissions.py: -------------------------------------------------------------------------------- 1 | from rest_framework import permissions 2 | 3 | SAFE_METHODS = ('GET', 'HEAD', 'OPTIONS') 4 | 5 | class IsSuperUser(permissions.BasePermission): 6 | """ 7 | Allows access only to super users. 8 | """ 9 | def has_permission(self, request, view): 10 | return request.method in SAFE_METHODS or request.user.is_superuser 11 | -------------------------------------------------------------------------------- /utils/unitaryauth.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from django.conf import settings 3 | from rest_framework.exceptions import AuthenticationFailed 4 | 5 | class UnitaryAuth(object): 6 | 7 | def check_sso(self, param): 8 | ''' 9 | 调sso认证后端,把用户和密码数据param作为参数; 返回True或False 10 | :param param: 11 | :return: True/False 12 | ''' 13 | return True 14 | 15 | def check_auth(self, param): 16 | url = settings.AUTH_URL 17 | res = requests.post(url, json=param) 18 | if not res.ok: 19 | raise AuthenticationFailed(res.content) 20 | return res.json() 21 | 22 | @property 23 | def authenticate(self): 24 | data = self.request.data 25 | param = { 26 | 'username':data.get('username'), 27 | 'password':data.get('password') 28 | } 29 | if not self.check_sso(param): 30 | raise AuthenticationFailed 31 | return self.check_auth(param) 32 | --------------------------------------------------------------------------------