zoukankan      html  css  js  c++  java
  • [原创]django+ldap实现单点登录(装饰器和缓存)

    前言

    参考本系列之前的文章,我们已经搭建了ldap并且可以通过django来操作ldap了,剩下的就是下游系统的接入了,现在的应用场景,我是分了2个层次,第一层次是统一认证,保证各个系统通过ldap来维护统一的用户名和密码,第二层次就是sso单点登录,即一个系统登录,其他系统即是登录状态,一个系统登出,其他系统也自动登出,也就是我们登录公司内部的N个系统,其实总共只需要登录一次即可。
    目前,django的下游系统可以接入单点,理论上,只要语言支持memcache客户端,通过session维持登录状态,都可以接入,但类似jira这种商用产品,由于没有二次开发的能力,所以只能支持到统一密码的层面了。

    代码实现

    首先在下游系统的settings.py需要进行一定的配置,其中,session要走我们的共享mc中,这样才能获取到sso系统传入的session,以达到单点登录的目的。

    LOGIN_URL = "http://sa.ssotest.net/account/login/"  # 跳转到sso系统的登录上
    PERM_DENY_URL = "http://sa.ssotest.net/403.html"  # 这个是自己加的一个配置,如果下游系统没有sso用户的权限,会跳转到deny页面上
    #LOGIN_URL = "/account/login/"  # 之前走本地的配置
    
    # ### session config BEGIN ### #
    SESSION_ENGINE = "django.contrib.sessions.backends.cache"
    SESSION_COOKIE_AGE = 86400  # 设置session有效期为一天,默认两周
    SESSION_COOKIE_DOMAIN = ".ssotest.net"
    CACHES = {
        'default': {
            'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
            'LOCATION': [
                'ldap1.prod.bj1.ssotest.net:11211',
                'ldap2.prod.bj1.ssotest.net:11211',
            ],
            'TIMEOUT': 60 * 15,  # 缓存默认过期时间
            'OPTIONS': {
                'MAX_ENTRIES': 3000  # 最大缓存个数
            }
        }
    }
    # ### session config END ### #
    

    设定一个下游系统专用的验证登录状态和登出的装饰器,放在backend/decorators/login_auth.py以代替你可能在用的django自带的login_required

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    from SQLaudit import settings_prod as settings
    from django.shortcuts import redirect
    from web_sql import models
    import json
    from django.db import transaction
    from django.core.cache import cache
    from django.contrib import auth
    
    # ########## 用于验证用户是否登陆的装饰器 ##########
    def login_sso(func):
        """ 如果用户已经登陆,则执行相应的Views中的函数,否则,跳转至 settings中设置的LOGIN_URL地址,即:  '/account/login/'"""
        def wrapper(request, *args, **kwargs):
            sso_dic = request.session.get('sso_dic', None) # 这个字典在sso系统中登录成功后,由sso设定在request.session中
            if not sso_dic:
                login_url = '%s?back=%s' % (settings.LOGIN_URL, request.get_raw_uri())  # 把当前站点的url传参给back,如果在sso中登录成功会自动redirect到back
                return redirect(login_url)
            try:
                sso_dic = json.loads(sso_dic)
                # 用户对象优先从cache缓存中取出,如没有,从数据库中取出,key: sso_uid_sqladmin.ssotest.net:8009_qudong
                cache_user = cache.get('sso_uid_%s_%s' % (request.get_host(), sso_dic.get('sso_username')))
                user_obj = cache_user if cache_user else 
                    models.User.objects.filter(username=sso_dic.get('sso_username'),
                                               userprofile__name=sso_dic.get('sso_chinese_name'),
                                               email=sso_dic.get('sso_email')).first()
                if not user_obj:  # 如果上下游用户信息不一致,创建或更新下游信息
                    with transaction.atomic():
                        ''' 以下部分需要根据各下游系统,注册用户方法不同,而进行修改 '''
                        user_obj, status = models.User.objects.update_or_create(username=sso_dic.get('sso_username'),
                                                                                defaults={'email': sso_dic.get('sso_email')})
                        group_obj = models.Group.objects.get(name='开发人员')  # 新增用户默认属于开发人员组
                        user_obj.groups.add(group_obj)
                        models.UserProfile.objects.update_or_create(user=user_obj,
                                                                    defaults={'name': sso_dic.get('sso_chinese_name')})
                if not user_obj.is_active:  # 用户在下游系统被禁用
                    raise Exception(u'用户被禁用')
            except Exception:
                login_url = '%s?back=%s' % (settings.PERM_DENY_URL, request.get_raw_uri())
                return redirect(login_url)  # 下游系统在同步信息中出现问题,跳转到deny页面
            else:
                # 如用户已同步,添加缓存,使用cache.add方法,如已有cache,不进行处理;cache.set方法会重新覆盖cache.缓存失效时间5分钟,期间,登录用户状态不会被修改
                cache.add('sso_uid_%s_%s' % (request.get_host(), sso_dic.get('sso_username')), user_obj, 60 * 5)
                request.user = user_obj
            return func(request, *args, **kwargs)
        return wrapper
    
    
    # ########## 用于用户登出的装饰器,主要用于session清除后,缓存的清除 ##########
    def logout_sso(func):
        def wrapper(request):
            try:
                sso_dic = request.session.get('sso_dic', None)
                auth.logout(request)
                sso_dic = json.loads(sso_dic)
                cache.delete('sso_uid_%s_%s' % (request.get_host(), sso_dic.get('sso_username')))  # 除了auth.logout额外添加取出之前添加的cache动作
            except Exception:
                pass
            return func(request)
        return wrapper
    
    

    顺道提一下django的cache,我们的session和cache都走的settings.py的CACHES属性,我们这里是缓存到memcache中,django cache部分的官网中文说明:http://python.usyiyi.cn/translate/django_182/topics/cache.html
    django可以实现站点级的缓存,某个url的缓存,某个view的缓存,某个模板片段的缓存,某个数据的缓存(底层cache api),具体可以参考上边的文档,写的很细致了,测试了一下,比如进行了view的缓存,它是把整个html包括静态和动态的都缓存住,这样如果我们的数据更新了,那就只能等到缓存过期了。我这里由于只是为了缓存一个数据对象(user_obj),所以只用了cache api,具体就是add,get,delete方法,注意add和set的区别,add如果已有cache将不处理,set是不管有没有,都重新设置
    如果要缓存某个view也很方便,直接在view上添加装饰器cache_page(60 * 15) ,缓存15分钟
    调用的时候,将原来的验证登录的装饰器替换成login_sso

    @logout_sso
    def sql_logout(request):
        return redirect(settings.LOGIN_URL)
        
    @login_sso
    def sql_base(request):
        user = request.user  # request.user通过login_sso已经定义,可以直接把当前登录的user对象拿来用了
        return render(request, 'audit/sql_base.html', {})
    
    

    结语

    经过以上一系列的博客,大体上熟悉了整个sso+统一认证的流程,期间还是有一些不太理想的地方,比如非django系统如何更好的支持sso,因为我的方案里,sso的核心是共享session,而session存在django的request中,不知道其他语言的系统如何获取这个request.session,另外整个单点系统只支持到同域的跨站级别,还没考虑跨域的问题,欢迎牛人指导。

    参考资料

    http://python.usyiyi.cn/translate/django_182/topics/cache.html

  • 相关阅读:
    Appium学习实践(二)Python简单脚本以及元素的属性设置
    Appium学习实践(三)测试用例脚本以及测试报告输出
    Appium学习实践(一)简易运行Appium
    Appium学习实践(四)结构优化
    js中对小数取整的函数
    C#基础 面试中常出现的问题
    repeater中的删除按钮实现
    js对fck编辑器取值 赋值
    jQuery对select操作
    进制转换(二进制 八进制 十进制 十六进制)
  • 原文地址:https://www.cnblogs.com/caseast/p/6091665.html
Copyright © 2011-2022 走看看