django 实现统一登录

前言
公司的各种运维平台、工具越来越多,搞得用户需要记住不同平台不同的密码,当然,用户可以选择各个平台都使用一个密码,奈何我身后坐着一个超烦的安全,天天瞎搞。忍无可忍,终于要祭出大招。搞一把统一登录。


前期需求分析,及技术调研

其实就是看看能不能找到可以直接拿来抄的。 不要重复造轮子么。
找啊找啊找啊找。对比了不同统一登录的实现方式,或简单、或复杂。但是都不太适用。

  1. 实现方式一,共享session。
    大致说的就是把cookie 跨域,然后同域的去拿这个cookie找服务端的session,如果session 是有效,那么就登录成功,当然,服务端的session 也得共享一下,而且格式还得保持统一。
    这样不好不好,刚有这个想法,就被后面的大傻个安全给否了,说那你的一个站瘫痪了,其他的不就玩完了。
  2. 实现方式二,跳转验证。
    大致就是子系统登录时,把要访问的地址信息啥的通过加密后的token,作为参数去请求统一登录,然后统一登录解密token发现里面包含的信息啥的是对的,登录成功后,然后就把用户的信息和其他的一些玩意返回给子系统,子系统直接login就ok了。
    这种方式感觉可搞,后面的大傻个也挑不出毛病。

最后找到两个轮子
django-sso-simple
django-auth-ldap

具体需求实现

论如何更好的组装轮子

  1. 集成ldap 登录
# 新建一个django项目,安装两个轮子,创建一个app# 配置settings
AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend','django_auth_ldap.backend.LDAPBackend',
)
#在认证后端要加上ldap认证,当然加前加都可以,用户认证的时候是会按照顺序认证的,认证成功就返回,如果都失败了,那么就失败了。 import ldap
from django_auth_ldap.config import LDAPSearch, GroupOfNamesType, PosixGroupType, LDAPGroupQuery
# 这些意思就是配置ldap基础信息,同步那些用户信息到django的user里去。
AUTH_LDAP_SERVER_URI = "ldap://xxx.com:389"
AUTH_LDAP_USER_DN_TEMPLATE = "uid=%(user)s,ou=People,dc=xxx,dc=com,dc=cn"
AUTH_LDAP_USER_ATTR_MAP = {"username": "uid", "first_name": "cn",
}
AUTH_LDAP_ALWAYS_UPDATE_USER = True# posixGroup
# 下面是同步用户组到django的用户组中去。
AUTH_LDAP_GROUP_SEARCH = LDAPSearch('ou=group,dc=xxx,dc=com,dc=cn', ldap.SCOPE_SUBTREE,"(objectClass=posixGroup)")
# 这个type 很重要,各位看客注意自己的ldap 组是什么类型
AUTH_LDAP_GROUP_TYPE = PosixGroupType()# 同步ldap的用户组到用户信息里去,比如dev 或者研发组的 用户就是激活的.
AUTH_LDAP_FIND_GROUP_PERMS = True
AUTH_LDAP_USER_FLAGS_BY_GROUP = {"is_active":LDAPGroupQuery("cn=dev,ou=group,dc=xxx,dc=com,dc=cn") |LDAPGroupQuery("cn=ops,ou=group,dc=xxx,dc=com,dc=cn")"is_staff": "cn=ops,ou=group,dc=xxx,dc=com,dc=cn","is_superuser": "cn=admin,ou=group,dc=xxx,dc=com,dc=cn"
}
AUTH_LDAP_CACHE_GROUPS = True
AUTH_LDAP_GROUP_CACHE_TIMEOUT = 3600
AUTH_LDAP_MIRROR_GROUPS = True# 以上配置完成之后可以接入ldap验证一下。
# 如果有问题的直接看django-ldap-auth 文档就好啦。
  1. sso 实现
# django的登录,还是那个登录
def sso_login(request):"""登录成功后要设置用户信息到session 中去 。"""if request.method == 'GET':if request.user.is_authenticated:return HttpResponseRedirect(redirect_url)return render(request, 'login.html')else:username = request.POST.get('username')password = request.POST.get('password')next_url = request.GET.get('next', None)user = authenticate(username=username, password=password)if user:ret = login(request, user)if not next_url:return HttpResponseRedirect(redirect_url)else:return HttpResponseRedirect(next_url)else:error_message = '用户名或密码错误'return render(request, 'login.html', locals())

url 路由部分 需要include sso的url

# server 比较关键
sso_server = SSOServer()
urlpatterns = [url(r'^admin/', admin.site.urls),url(r'^success/', success,name="success"),url(r'^logout/', sso_logout,name="logout"),url(r'^ssologin', sso_login,name="ssologin"),url(r'^server/', include(sso_server.get_urls())),url(r'^$', sso_login,name="ssologin"),
]

以下其实就是django-sso-simple 的源码了,让我来试着解释解释。下面先是server端。


#!/bin/env python
# -*- coding:utf-8 -*-
# created by hanszimport datetime
import urllibfrom django.conf.urls import url
from django.contrib import admin
from django.contrib.admin.options import ModelAdmin
from django.db.models import Q
from django.http import (HttpResponseForbidden, HttpResponseBadRequest, HttpResponseRedirect, QueryDict)
from django.utils import timezone
from django.views.generic.base import View
from itsdangerous import URLSafeTimedSerializer
from webservices.models import Provider
from webservices.sync import provider_for_django
from django.contrib.auth.models import User
from  SSO.models import Token, Consumer, Site_info# itdangerous 和 webservices 这两个比较眼生 一会单独说它。try:# python 3# noinspection PyCompatibilityfrom urllib.parse import urlparse, urlunparse
except ImportError:# python 2# noinspection PyCompatibilityfrom urlparse import urlparse, urlunparse# 集成webservices 的Provider
class BaseProvider(Provider):max_age = 5def __init__(self, server):self.server = serverdef get_private_key(self, public_key):try:self.consumer = Consumer.objects.get(public_key=public_key)except Consumer.DoesNotExist:return Nonereturn self.consumer.private_keyclass RequestTokenProvider(BaseProvider):def provide(self, data):redirect_to = data['redirect_to']token = Token.objects.create(consumer=self.consumer, redirect_to=redirect_to)return {'request_token': token.request_token}class AuthorizeView(View):"""The client get's redirected to this view with the `request_token` obtainedby the Request Token Request by the client application beforehand.This view checks if the user is logged in on the server application and ifthat user has the necessary rights.If the user is not logged in, the user is prompted to log in."""server = Nonedef get(self, request):request_token = request.GET.get('token', None)if not request_token:return self.missing_token_argument()try:self.token = Token.objects.select_related('consumer').get(request_token=request_token)except Token.DoesNotExist:return self.token_not_found()if not self.check_token_timeout():return self.token_timeout()self.token.refresh()if request.user.is_authenticated():return self.handle_authenticated_user()else:return self.handle_unauthenticated_user()def missing_token_argument(self):return HttpResponseBadRequest('Token missing')def token_not_found(self):return HttpResponseForbidden('Token not found')def token_timeout(self):return HttpResponseForbidden('Token timed out')def check_token_timeout(self):delta = timezone.now() - self.token.timestampif delta > self.server.token_timeout:self.token.delete()return Falseelse:return Truedef handle_authenticated_user(self):if self.server.has_access(self.request.user, self.token.consumer):return self.success()else:return self.access_denied()def handle_unauthenticated_user(self):next = '%s?%s' % (self.request.path, urllib.urlencode([('token', self.token.request_token)]))# url = '%s?%s' % (reverse(self.server.auth_view_name), urllib.urlencode([('next', next)]))url = '%s?%s' % ("/ssologin", urllib.urlencode([('next', next)]))return HttpResponseRedirect(url)def access_denied(self):# return HttpResponseForbidden("用户权限不允许登录该系统")return HttpResponseRedirect('/success')def success(self):self.token.user = self.request.userself.token.save()serializer = URLSafeTimedSerializer(self.token.consumer.private_key)parse_result = urlparse(self.token.redirect_to)query_dict = QueryDict(parse_result.query, mutable=True)query_dict['access_token'] = serializer.dumps(self.token.access_token)url = urlunparse((parse_result.scheme, parse_result.netloc, parse_result.path, '', query_dict.urlencode(), ''))return HttpResponseRedirect(url)class VerificationProvider(BaseProvider, AuthorizeView):def provide(self, data):print datatoken = data['access_token']try:self.token = Token.objects.select_related('user').get(access_token=token, consumer=self.consumer)except Token.DoesNotExist:return self.token_not_found()if not self.check_token_timeout():return self.token_timeout()if not self.token.user:return self.token_not_bound()extra_data = data.get('extra_data', None)return self.server.get_user_data(self.token.user, self.consumer, extra_data=extra_data)def token_not_bound(self):return HttpResponseForbidden("Invalid token")class ConsumerAdmin(ModelAdmin):readonly_fields = ['public_key', 'private_key']class Server(object):request_token_provider = RequestTokenProviderauthorize_view = AuthorizeViewverification_provider = VerificationProvidertoken_timeout = datetime.timedelta(minutes=5)client_admin = ConsumerAdminauth_view_name = 'django.contrib.auth.views.login'def __init__(self, **kwargs):for key, value in kwargs.items():setattr(self, key, value)self.register_admin()def register_admin(self):admin.site.register(Consumer, self.client_admin)admin.site.register(Site_info)def has_access(self, user, consumer):from SSO.models import Site_infoq = Q(user=user) | Q(group__in=user.groups.all())all_site = Site_info.objects.filter(q).distinct()if Site_info.objects.get(consumer=consumer) in all_site:return Trueelse:return Falsedef get_user_extra_data(self, user, consumer, extra_data):if extra_data == "group":return "group"else:return "test"def get_user_data(self, user, consumer, extra_data=None):user_data = {'username': user.username,'email': user.email,'first_name': user.first_name,'last_name': user.last_name,'is_staff': False,'is_superuser': False,'is_active': user.is_active,}if extra_data:user_data['extra_data'] = self.get_user_extra_data(user, consumer, extra_data)return user_datadef get_urls(self):return [url(r'^request-token/$', provider_for_django(self.request_token_provider(server=self)),name='zjxl-sso-request-token'),url(r'^authorize/$', self.authorize_view.as_view(server=self), name='zjxl-sso-authorize'),url(r'^verify/$', provider_for_django(self.verification_provider(server=self)), name='zjxl-sso-verify'),]

再说client 端

#!/bin/env python
# -*- coding:utf-8 -*-
# created by hanszfrom django.conf.urls import url
from django.contrib.auth import login
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect
from django.views.generic import View
from itsdangerous import URLSafeTimedSerializer
from webservices.sync import SyncConsumertry:# python 3# noinspection PyCompatibilityfrom urllib.parse import urlparse, urlunparse, urljoin, urlencode
except ImportError:# python 2# noinspection PyCompatibilityfrom urlparse import urlparse, urlunparse, urljoinfrom urllib import urlencodeclass LoginView(View):client = Nonedef get(self, request):next = self.get_next()scheme = 'https' if request.is_secure() else 'http'query = urlencode([('next', next)])netloc = request.get_host()path = reverse('zjxl-sso-authenticate')redirect_to = urlunparse((scheme, netloc, path, '', query, ''))request_token = self.client.get_request_token(redirect_to)host = urljoin(self.client.server_url, 'authorize/')url = '%s?%s' % (host, urlencode([('token', request_token)]))return HttpResponseRedirect(url)def get_next(self):"""Given a request, returns the URL where a user should be redirected toafter login. Defaults to '/'"""next = self.request.GET.get('next', None)if not next:return '/'netloc = urlparse(next)[1]# Heavier security check -- don't allow redirection to a different# host.# Taken from django.contrib.auth.views.loginif netloc and netloc != self.request.get_host():return '/'return nextclass AuthenticateView(LoginView):client = Nonedef get(self, request):raw_access_token = request.GET['access_token']access_token = URLSafeTimedSerializer(self.client.private_key).loads(raw_access_token)user = self.client.get_user(access_token)user.backend = self.client.backendlogin(request, user)next = self.get_next()return HttpResponseRedirect(next)class Client(object):login_view = LoginViewauthenticate_view = AuthenticateViewbackend = "%s.%s" % (ModelBackend.__module__, ModelBackend.__name__)user_extra_data = Nonedef __init__(self, server_url, public_key, private_key,user_extra_data=None):self.server_url = server_urlself.public_key = public_keyself.private_key = private_keyself.consumer = SyncConsumer(self.server_url, self.public_key, self.private_key)if user_extra_data:self.user_extra_data = user_extra_data@classmethoddef from_dsn(cls, dsn):parse_result = urlparse(dsn)public_key = parse_result.usernameprivate_key = parse_result.passwordnetloc = parse_result.hostnameif parse_result.port:netloc += ':%s' % parse_result.portserver_url = urlunparse((parse_result.scheme, netloc, parse_result.path, parse_result.params, parse_result.query, parse_result.fragment))return cls(server_url, public_key, private_key)def get_request_token(self, redirect_to):return self.consumer.consume('/request-token/', {'redirect_to': redirect_to})['request_token']def get_user(self, access_token):data = {'access_token': access_token}if self.user_extra_data:data['extra_data'] = self.user_extra_datauser_data = self.consumer.consume('/verify/', data)user = self.build_user(user_data)return userdef build_user(self, user_data):try:user = User.objects.get(username=user_data['username'])except User.DoesNotExist:user = User(**user_data)user.set_unusable_password()user.save()return userdef get_login_urls(self):return [url(r'^$', self.login_view.as_view(client=self), name='zjxl-sso-login'),url(r'^authenticate/$', self.authenticate_view.as_view(client=self), name='zjxl-sso-authenticate'),]

认证流程
1.客户端访问某个需要登录的地方,到了login_view这里,会把要访问的地址信息放到 redirect_to中去
2.然后把redirect_to 当做参数 去服务端request-token,请求一下token,服务端会返回一个token的request_token 。
3.然后带着这个token去访问authorize,登录成功后 服务端去验证token并检测这个用户是否有效。
4.然后客户端带着access_token 去获取用户信息,获取之后再登录。
(说的有点乱,看图吧)

django 实现统一登录(未完待续)相关推荐

  1. 《图解 HTTP》读书笔记(未完待续)

    ARP 协议(Address Resolution Protocol)一种以解析地址的协议,根据通信双方的 IP 地址就可以查出对应的 MAC 地址. MAC( Media Access Contro ...

  2. pythonb超分辨成像_Papers | 超分辨 + 深度学习(未完待续)

    1. SRCNN 1.1. Contribution end-to-end深度学习应用在超分辨领域的开山之作(非 end-to-end 见 Story.3 ). 指出了超分辨方向上传统方法( spar ...

  3. APP被苹果App Store拒绝的79个原因(未完待续)

    作为iOS开发者,估计有很多都遇到过APP提交到App Store被拒,然后这些被拒的原因多种多样,今天dApps收集了常见的被拒的原因,以便更多开发者了解. APP被苹果APPStore拒绝的各种原 ...

  4. 学完oracle 再学mysql_一篇文章让Oracle程序猿学会MySql【未完待续】

    一篇文章让Oracle DB学会MySql[未完待续] 随笔前言: 本篇文章是针对已经能够熟练使用Oracle数据库的DB所写的快速学会MySql,为什么敢这么说,是因为本人认为Oracle在功能性方 ...

  5. 苹果IOS企业开发者账号怎么申请——苹果账号申请记录(未完待续)

    乐易考教育新开发了一款求职APP--<职场星座>,安卓版本已经开发上线,目前正在持续更新中,包括页面图片.按钮.表格内容等.iOS版本同时也在开发当中.因此,我需要申请一个苹果iOS企业开 ...

  6. 省钱版----查找 IoT 设备TTL线序__未完待续

    作者:仙果 原文来自:省钱版--查找 IoT 设备TTL线序 省钱版----查找 IoT 设备TTL线序__未完待续 缘由 在IoT固件调试分析的过程中,建议首先在IoT设备的板子上焊接调试线,这是能 ...

  7. python线程和进程-未完待续

    python线程和进程-未完待续 环境变量 0. 概念 1. 并行/并发 并行 并发 并行与并发的关系 2.进程/线程 基本概念 线程 多线程 队列 互斥锁/线程共享 阻塞 锁 条件锁 进程 多进程 ...

  8. php编码规范(未完待续)

    php编码规范(未完待续) php编码规范(未完待续) 标准化的重要性和好处 程序员可以了解任何代码,弄清程序的状况 新人可以很快的适应环境 防止新接触PHP的人出于节省时间的需要,自创一套风格并养成 ...

  9. mega raid linux,在lsi megaraid sas 8204elp 装linux系统(未完待续)

    在lsi megaraid sas 8204elp 装linux系统(未完待续) (2009-05-06 13:24:00) 标签: 杂谈 在目前最新的linux内核中,没有8204elp的raid驱 ...

最新文章

  1. 【python】多进程共享变量
  2. Web测试介绍2一 安全测试
  3. 使用 .NET WinForm 开发所见即所得的 IDE 开发环境,实现不写代码直接生成应用程序...
  4. java 接口的泛型方法_Java泛型/泛型方法/通配符/泛型接口/泛型泛型擦出
  5. C#调用C++dll
  6. NSIS 的 Modern UI 教程(一)
  7. Qt 实现数据协议控制--组帧、组包、解析帧、解析包
  8. VS2015 Cordova Ionic移动开发(五)
  9. P2转P3'dict' object has no attribute 'has_key'
  10. 鸿蒙系统都有谁参与,华为鸿蒙系统功能有什么特别的-华为鸿蒙系统有什么新功能...
  11. FreeRTOS(2)---学习FreeRTOS前的准备工作
  12. 以太坊开发入门,完整入门篇
  13. eclipse中svn从分支合并到主干及冲突解决
  14. 如何在移动硬盘上安装Linux系统?WIN10 + Ubuntu 18.0.4(LTS)
  15. snagit 10.0.1.58和Patch_SnagIt.exe汉化补丁安装流程图
  16. ANSYS——模态分析的理论基础
  17. 椭圆曲线数字签名算法
  18. 程序、算法和数据结构的关系
  19. Eclipse项目上传码云
  20. win10添加计算机语言,win10输入法,详细教您怎么在win10里添加输入法

热门文章

  1. 计算机技术非全日制调剂2020,2020年硕士研究生部分非全日制专业接收调剂的公告...
  2. html从入门到精通胡菘,高职电商网页设计教学实践.docx
  3. PMP考试关键字和黄金法则大全
  4. 云计算开发学习教程,简单介绍云计算
  5. 做网赚如何引流,这些方法你都试了么
  6. 数据分析系列:绩效(效率)评价与python实现(层析分析、topsis、DEA)
  7. 教室外的风景(宁波市第25届小学组)
  8. 论文写作 9: 引言需要讲述完整的故事
  9. day03_注释丶关键字丶标识符丶常量
  10. GAN介绍 - 相关研究课题