前言
接之前我的文章,django+ldap+memcache实现单点登录+统一认证 ,ldap部署相关,ldap双机\LAM配置管理\ldap备份还原,目前来说,我们已经有了高可用性的ldap环境了,里边也有了一些用户信息,后边要说一说通过django调用ldap的实现方式,里边主要涉及3个模块,django-auth-ldap:用于从ldap同步账户、登录验证,它和ldap结合的很好,但它不能反向直接操作ldap,只能进行从ldap向下游系统的同步,所以还需要python-ldap模块,以便实现反向对ldap的增删改查,我这边的具体需求就是注册用户、重置密码等,另外还需要一个python-memcached,用于把生成的session放到mc中
查了网上大量的关于django+ldap实现统一认证的文章,大部分都是只写了django-auth-ldap或者python-ldap来实现,但是如果要实现一套完整的统一认证系统,实际上这2个模块都是需要的
django-auth-ldap
这个模块,基本在settings.py通过配置就可以拿来用了,基本不需要对代码做什么修改。用于从ldap里拿到信息传给sso系统做后续处理,但通过它无法让sso系统反向操作ldap,无论django的前台和admin都适用
官方文档: https://pythonhosted.org/django-auth-ldap/authentication.html
中文翻译: https://darkcooking.gitbooks.io/django-auth-ldap/content/chapter9.html
不过这个翻译版是个简化版本,有的东西不全,不过基本也够用了,并且网站打开很慢,我把它的pdf放在这里,可以自行查看 http://files.cnblogs.com/files/caseast/django-auth-ldap.pdf
django官网(session部分):https://docs.djangoproject.com/en/1.10/topics/http/sessions/
ok!直接上代码吧,具体的配置说明,不明确的还请参考官方文档,里边我把log模块和session这部分配置也写一下顺道,不单独开篇描述了
---settings.py---
SESSION_ENGINE = "django.contrib.sessions.backends.cache" # 另外还有个cached_db,这两个的区别是,cache只写缓存,cached_db除了写缓存还同时写数据库,如果对于session的安全性要求高可以选择cached_db
SESSION_COOKIE_AGE = 86400 # 设置session有效期为一天,默认两周
SESSION_COOKIE_DOMAIN = ".ssotest.net" # 此配置不能解决跨域问题,但是能解决a.ssotest.net与b.ssotest.net的session共享问题,不加此属性,跨站(非跨域)时,无法传递session
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': [ # 连接的是2个mc,python-memcached本身有监活,挂一个mc会自动将请求分配到好的mc上
'ldap1.prod.bj1.ssotest.net:11211',
'ldap2.prod.bj1.ssotest.net:11211',
],
'TIMEOUT': 30,
'OPTIONS': {
'MAX_ENTRIES': 3000
}
}
}
LOGIN_URL = "/account/login/"
# ### ldap 配置部分BEGIN ### #
import ldap
from django_auth_ldap.config import LDAPSearch, PosixGroupType
AUTHENTICATION_BACKENDS = (
'django_auth_ldap.backend.LDAPBackend', # 配置为先使用LDAP认证,如通过认证则不再使用后面的认证方式
'django.contrib.auth.backends.ModelBackend', # sso系统中手动创建的用户也可使用,优先级靠后。注意这2行的顺序
)
base_dn = 'dc=ldap,dc=ssotest,dc=net'
AUTH_LDAP_SERVER_URI = 'ldap://ldap.ssotest.net'
AUTH_LDAP_BIND_DN = 'uid=ssoadmin,ou=People,dc=ldap,dc=ssotest,dc=net'
AUTH_LDAP_BIND_PASSWORD = 'ssotest@123'
AUTH_LDAP_USER_SEARCH = LDAPSearch('ou=People,%s' % base_dn, ldap.SCOPE_SUBTREE, "(uid=%(user)s)") # 用户的DN是uid=caojun,ou=People,dc=ldap,dc=ssotest,dc=net,所以用uid
AUTH_LDAP_ALWAYS_UPDATE_USER = True # This is the default, but I like to be explicit.
AUTH_LDAP_USER_ATTR_MAP = { # key为数据库字段名,value为ldap中字段名,此字典解决django model与ldap字段名可能出现的不一致问题
"username": "uid",
"name": "cn",
"email": "mail"
}
# 组权限管理 #
AUTH_LDAP_GROUP_SEARCH = LDAPSearch('ou=Group,dc=ldap,dc=ssotest,dc=net', ldap.SCOPE_SUBTREE, "(objectClass=posixGroup)")
AUTH_LDAP_GROUP_TYPE = PosixGroupType(name_attr="cn") # 组的DN是cn=员工,ou=Group,dc=ldap,dc=ssotest,dc=net,所以type是cn
AUTH_LDAP_USER_FLAGS_BY_GROUP = { # django admin的is_staff|superuser属性映射为ldap的管理员
"is_staff": u"cn=管理员,ou=Group,dc=ldap,dc=ssotest,dc=net",
"is_superuser": u"cn=管理员,ou=Group,dc=ldap,dc=ssotest,dc=net"
}
AUTH_LDAP_REQUIRE_GROUP = u"cn=员工,ou=Group,dc=ldap,dc=ssotest,dc=net" # 只有此group可用ldap进行认证
AUTH_LDAP_DENY_GROUP = u"cn=黑名单,ou=Group,dc=ldap,dc=ssotest,dc=net" # 此group不能使用ldap进行认证,直接deny掉,不会后续往django创建信息
AUTH_LDAP_MIRROR_GROUPS = True # 直接把ldap的组复制到django一份,和AUTH_LDAP_FIND_GROUP_PERMS互斥.用户每次登录会根据ldap来更新数据库的组关系
# AUTH_LDAP_FIND_GROUP_PERMS = True # django从ldap的组权限中获取权限,这种方式,django自身不创建组,每次请求都调用ldap
# AUTH_LDAP_CACHE_GROUPS = True # 如打开FIND_GROUP_PERMS后,此配置生效,对组关系进行缓存,不用每次请求都调用ldap
# AUTH_LDAP_GROUP_CACHE_TIMEOUT = 600 # 缓存时间
# ### ldap 配置部分END ### #
# ### log 配置部分BEGIN ### #
LDAP_LOGS = os.path.join(BASE_DIR, 'logs/ldap.log')
stamdard_format = '[%(asctime)s][%(threadName)s:%(thread)d]' + \
'[task_id:%(name)s][%(filename)s:%(lineno)d] ' + \
'[%(levelname)s]- %(message)s'
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'standard': { # 详细
'format': stamdard_format
},
},
'handlers': {
'default': {
'level': 'DEBUG',
'class': 'logging.handlers.RotatingFileHandler',
'filename': LDAP_LOGS,
'maxBytes': 1024 * 1024 * 100, # 5 MB
'backupCount': 5,
'formatter': 'standard',
},
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
}
},
'loggers': {
'': { # default日志,存放于log中
'handlers': ['default'],
'level': 'DEBUG',
},
'django_auth_ldap': { # django_auth_ldap模块相关日志打印到console
'handlers': ['console'],
'level': 'DEBUG',
'propagate': False, # 选择关闭继承,不然这个logger继承自默认,日志就会被记录2次了(''一次,自己一次)
},
# 'django.db.backends': { # 数据库相关执行过程log打印到console
# 'handlers': ['console'],
# 'level': 'DEBUG',
# },
}
}
# ### log 配置部分END ### #
logging模块的说明,可以参考这篇文章 http://www.jianshu.com/p/d615bf01e37b ,这个配置可以配置到settings.py中,这样django项目都会自动调用,或者单独写入一个log_config文件中,需要时手动调用,例子如下
log_config.py
import logging
from logging.config import dictConfig
logging_config={
........
}
dictConfig(logging_config) #注册一下配置
调用方法:
import logging
logger = logging.getLogger()
logger.error('ldap conn失败,原因为: %s' % str(e))
额外说一点,以上配置是在sso系统进行,如果下游系统接入到sso系统的话,顺序就是ldap--> sso --> 项目A,默认接入后,项目A的前台账户就可以使用ldap进行管理,但项目A如果使用了django admin的话(因为前台的登录动作会强制转到sso上,而admin如果不改源码做不到这点),django admin是会走本地的账户的,这样就会出现,一个下游系统的管理员用户,前台一个密码,后台一个密码,所以我们需要给下游系统的settings.py做一定配置,让下游的admin账户也从ldap中同步,下游同样需要开篇说的3个python模块
项目A的settings.py
# ### ldap 配置部分BEGIN (If use Django-admin,configure it!) ### #
'''just for admin,前台认证统一走认证平台,所以下游系统不保存密码,导致下游无法登录admin(如使用),所以admin添加ldap认证,只用ldap只读账户即可'''
import ldap
from django_auth_ldap.config import LDAPSearch, PosixGroupType
AUTHENTICATION_BACKENDS = (
'django_auth_ldap.backend.LDAPBackend', # 配置为先使用LDAP认证,如通过认证则不再使用后面的认证方式
'django.contrib.auth.backends.ModelBackend', # 同时打开本地认证,因为下游系统的权限和组关系需要用到
)
base_dn = 'dc=ldap,dc=ssotest,dc=net'
AUTH_LDAP_SERVER_URI = 'ldap://ldap.ssotest.net'
AUTH_LDAP_BIND_DN = 'uid=ssoread,ou=People,dc=ldap,dc=ssotest,dc=net' # read only ldap user
AUTH_LDAP_BIND_PASSWORD = 'ssotest@123'
AUTH_LDAP_USER_SEARCH = LDAPSearch('ou=People,%s' % base_dn, ldap.SCOPE_SUBTREE, "(uid=%(user)s)")
AUTH_LDAP_ALWAYS_UPDATE_USER = False # Default is True,是否登录后从ldap同步用户,不进行同步,因为下游的用户表是什么样的不能确定,只能确定它也使用邮箱前缀
# 下游系统不从ldap同步group staff/superuser相关,但需要从ldap验证用户是否离职
AUTH_LDAP_GROUP_SEARCH = LDAPSearch('ou=Group,dc=ldap,dc=ssotest,dc=net', ldap.SCOPE_SUBTREE, "(objectClass=posixGroup)")
AUTH_LDAP_GROUP_TYPE = PosixGroupType(name_attr="cn")
AUTH_LDAP_REQUIRE_GROUP = u"cn=员工,ou=Group,dc=ldap,dc=ssotest,dc=net"
AUTH_LDAP_DENY_GROUP = u"cn=黑名单,ou=Group,dc=ldap,dc=ssotest,dc=net"
AUTH_LDAP_FIND_GROUP_PERMS = True # django从ldap的组权限中获取权限,这种方式,django自身不创建组,每次请求都调用ldap,下游子系统,我们并不需要让他同步ldap里的"员工","管理员"这种表,所以不用mirror_groups
AUTH_LDAP_CACHE_GROUPS = True # 如打开FIND_GROUP_PERMS后,才生效,对组关系进行缓存,不用每次请求都调用ldap
AUTH_LDAP_GROUP_CACHE_TIMEOUT = 600
# ### ldap 配置部分END ### #
以上,我们通过配置,就可以完成django对于ldap的调用了,我们可以通过ldap来管理我们的前台和admin中的账户和密码,但是这个动作是向下进行的,如果想通过我们的单点登录系统来操作ldap,这个django-auth-ldap是没有这个功能的。这就需要后边的python-ldap了.
篇幅限制,python-ldap请参考我的这篇文章[原创]django+ldap实现统一认证部分二(python-ldap实践)
参考资料
https://pythonhosted.org/django-auth-ldap/authentication.html
https://darkcooking.gitbooks.io/django-auth-ldap/content/chapter9.html
https://docs.djangoproject.com/en/1.10/topics/http/sessions/
http://www.jianshu.com/p/d615bf01e37b