统一用户认证系统CUAS实现要点

背景:
基于目前存在多套员工使用的日常工作子系统,现状为各系统各自有一套用户体系,员工需要记住各系统的用户名、密码等信息,还需要登录多个系统,重复工作量颇多。统一用户认证组件将用户名、密码等信息统一存储与管理,对各子系统提供统一认证接口。二期引入OpenLDAP,解决Jira、Git、Hue、CM、SVN等国外常用的开源软件不支持统一认证,而修改源码对接CUAS又成本过高的痛点。

名词解析:
Centraly User Auth Service(集中用户认证服务)以下简称CUAS;
AccessToken:访问码,默认有效期为24小时,当用户重新登录后,授权码过期。
Cectoken:跳转码,默认有效期为5分钟,当系统之间互相跳转时带上该token;
CuasToken:类似于网银的UKey,采用GoogleAuthenticator算法,30S刷新一次;
LDAP:轻量级目录访问协议,是一种广泛被遵循的协议。

二次认证:
采用GoogleAuthenticator算法,二维码生成算法结构otpauth://totp/masg?secret=3456ABCDEFGIDM。需要注意提供密钥刷新接口,当用户密钥泄露后可生成新密钥。

架构图:



关键数据库表:
用户表:T_USER
用户信息操作日志(新增、修改、修改密码): T_USER_LOG
用户密钥表(二次验证用): T_USER_SECRETKEY
用户认证日志:T_USER_AUTHLOG
Access Token表:T_USER_ACCESSTOKEN
CEC Token日志表(获取、验证):T_USER_CECTOKEN_LOG

Redis结构设计:
Redis CEC Token结构:Redis Hash结构,key=CuasService:cectoken:${cecToken}

LDAP设计
一级目录节点为dc=com,二级目录节点为dc=mzsg,不可更改;
三级目录节点定义:
用户信息节点:ou=users,存储用户及账号信息
组织信息节点:ou=organizations,存放组织信息
群组信息节点:ou=groups,存放群组信息
结构如下图:

用户信息
1.所有用户都存储在ou=users下
2.用户以uid来标识,uid为用户姓名拼音全拼,有重复时加数字
3.使用业界标准的inetOrgPerson作为基类,加入displayName、status、secAuth、companyCode、departmentCode等扩展;

组织信息
1.所有组织都存储在ou=organizations下
2.使用业界标准的organizationalUnit对象类

群组信息
1.所有群组都存储在ou=groups下
2.使用业界标准的posixGroup对象类

用户权限设计
用户组:
一个用户可以属于一到多个用户组,当用户组编码为空时为默认用户组。举例:小M是jira的管理员,在jenkins是普通用户,则小M的群组信息如下:
jira-administrators JIRA管理员
jenkins-users Jenkins普通用户
如果用户未加入jira的任何群组(jira-sofeware-users,jira-administrators),则此时用户无jira权限,无法登陆jira。

用户组设计
不是所有的第三方软件都支持userGroups的meberUid关联查找,所以还需要在用户schema上增加userGroups以对这种情况进行支持,用属性会让搜索语句变得更加简洁,当然也会增加同步维护的复杂度。

LDAP同步设计
由于需要拿到密码同步给LDAP服务器,所以用户密码需要可逆加密存储,可采用AES等对称加密。LDAP同步采用异步方式同步,即将用户修改日志T_USER_LOG按严格有序同步给LDAP服务器,如同步失败则后续跳过此用户的记录,直到问题解决。

LDAP同步编码
我用的是novell的jldap包,相比一般的JDBC操作,ldap的同步对字段要求很严格,属性值不能为空值或null值,在操作前需要先进行判断,要自行区分对属性的增、删、改操作,在增删改之前要先检查记录或属性是否存在。

OpenLDAP自定义结构
这部分网上资料较少,而且互相抄袭也比较严格,缺少一些较权威和详细的操作过程。这里鸣筝给大家一个亲测可行的步骤
1、编写schema
/etc/openldap/schema/local.schema

参考扩展mzsgCnblogsPerson结构

attributetype ( 1.1.2.1.2 NAME 'companyCode'
                DESC 'company code'
                EQUALITY caseIgnoreMatch
                SUBSTR caseIgnoreSubstringsMatch
                SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
                SINGLE-VALUE )
attributetype ( 1.1.2.1.3 NAME 'departmentCode'
                DESC 'department code'
                EQUALITY caseIgnoreMatch
                SUBSTR caseIgnoreSubstringsMatch
                SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
                SINGLE-VALUE )
attributetype ( 1.1.2.1.4 NAME 'status'
                DESC 'status 1 means normal 0 means login forbid'
                EQUALITY integerMatch
                SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
                SINGLE-VALUE )
attributetype ( 1.1.2.1.5 NAME 'birthday'
                DESC 'birthday'
                EQUALITY caseIgnoreMatch
                SUBSTR caseIgnoreSubstringsMatch
                SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
                SINGLE-VALUE )
attributetype ( 1.1.2.1.6 NAME 'phone'
                DESC 'phone'
                EQUALITY caseIgnoreMatch
                SUBSTR caseIgnoreSubstringsMatch
                SYNTAX  1.3.6.1.4.1.1466.115.121.1.15
                SINGLE-VALUE )
attributetype ( 1.1.2.1.7 NAME 'sex'
                DESC 'sex 0 means unknow 1 means male 2 means female'
                EQUALITY integerMatch
                SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
                SINGLE-VALUE )
attributetype ( 1.1.2.1.8 NAME 'certType'
                DESC 'cert type'
                EQUALITY caseIgnoreMatch
                SUBSTR caseIgnoreSubstringsMatch
                SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
                SINGLE-VALUE )
attributetype ( 1.1.2.1.9 NAME 'employeeNum'
                DESC 'employee number'
                EQUALITY caseIgnoreMatch
                SUBSTR caseIgnoreSubstringsMatch
                SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
                SINGLE-VALUE )
attributetype ( 1.1.2.1.10 NAME 'certId'
                DESC 'certId'
                EQUALITY caseIgnoreMatch
                SUBSTR caseIgnoreSubstringsMatch
                SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
                SINGLE-VALUE )
attributetype ( 1.1.2.1.11 NAME 'secAuth'
                DESC 'second auth switch 0 means on 1 means off'
                EQUALITY integerMatch
                SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
                SINGLE-VALUE )
attributetype ( 1.1.2.1.12 NAME 'cnName'
                DESC 'cn name'
                EQUALITY caseIgnoreMatch
                SUBSTR caseIgnoreSubstringsMatch
                SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
                SINGLE-VALUE )
attributetype ( 1.1.2.1.13 NAME 'userGroups'
                DESC 'user groups'
                EQUALITY caseExactIA5Match
                SUBSTR caseExactIA5SubstringsMatch
                SYNTAX 1.3.6.1.4.1.1466.115.121.1.26)
objectclass ( 1.1.2.2.2 NAME 'mzsgCnblogsPerson'
                DESC 'https://www.cnblogs.com/mzsg/ person'
                SUP inetOrgPerson
                MUST ( status )
                MAY ( companyCode $ departmentCode $ status $ birthday $ phone $ sex $ certType $ employeeNum $

2、修改sladp.conf,增加对local.schema的include
include /etc/openldap/schema/local.schema

3、编译
在/tmp下建立myConf.conf文件,添加所需依赖
include /etc/openldap/schema/core.schema
include /etc/openldap/schema/collective.schema
include /etc/openldap/schema/corba.schema
include /etc/openldap/schema/cosine.schema
include /etc/openldap/schema/duaconf.schema
include /etc/openldap/schema/inetorgperson.schema
include /etc/openldap/schema/java.schema
include /etc/openldap/schema/misc.schema
include /etc/openldap/schema/nis.schema
include /etc/openldap/schema/openldap.schema
include /etc/openldap/schema/pmi.schema
include /etc/openldap/schema/ppolicy.schema
include /etc/openldap/schema/local.schema

/etc/init.d/httpd restart
注:如果/tmp下有cn=config,则先删除。
之后运行,slapcat -f /tmp/myConf.conf -F /tmp/ -n0

把生成的的cn=config/cn=schema目录下对应的ldif文件去替换ldap中原先cn=config/cn=schema中的ldif文件。
位置在:/etc/ldap/slapd.d/cn=config/cn=schema

注意有时phpLdapAdmin会显示自定义Schema报错,JXplorer能正常支持
cd /etc/openldap/slapd.d/cn=config/cn=schema
rm -rf *
cp /tmp/cn=config/cn=schema/* .
chmod 777 *
注意,要先删除,再加,否则容易有重复定义的问题

接入LDAP
在git、svn、jenkins、jira、confluence、Kibana接入LDAP的过程中,注意事项有
在接入git之前必须保证用户信息中的email信息齐全并与历史git中的用户的邮箱一致,否则需做相应修改,相同用户名邮箱名不同的记录用户名会被自动修改,我就在这里花了挺长的时间。SVN接入时,使用SASL进行用户验证,svn的用户管理还是要在authz中管理,相当于加SVN的用户需要在CUAS和SVN的服务器authz中分别新增。

几点思考
1、为什么不去掉Mysql,改为使用OpenLDAP存储?
OpenLDAP更适合写少读多的场景,OpenLDAP不支持事务。最为重要的是我们在关系型数据库上具有丰富的经验,万一在OpenLDAP中的数据被玩坏了,我们可以轻松地从Mysql中同步恢复过来。

2、为什么LDAP服务器用的是OpenLDAP?
LDAP比较知名的开源实现是OpenLDAP和ApacheDS,两者都是优秀的产品。相比之下,前者在网络上的文档资料更多,也更轻量。

3、为什么不采用CAS而自行建设CUAS?
由于公司内部存在不少遗留系统,如果引入CAS需进行大量定制和裁减以减少各系统的集成难度和减少推行压力,而CUAS灵活定制化后结合公司内部的研发框架,可大大减少集成复杂度。

4、LDAP设计为什么要采用用户、组织、群组指定DN,为什么用户不挂在组织下形成严格的树状结构?
严格的树状结构会导致组织和用户的维护严重耦合,另外扁平的结构更方便条件查找。

效果
各业务系统管理后台通过统一门户进行统一认证后进行单点登陆。git、svn、jenkins、jira、confluence、Kibana以ldap进行统一认证。员工离职只需要在统一门户进行操作,所有系统均不可登录。员工入职通过统一门户进行录入可同步给各业务系统并分配最低权限(实施中),通过用户组,由各系统自行映射用户组对应的权限列表。该实现方式的不足是CUAS只管到用户组,权限仍散落到各业务系统各自管理。

好了,如果你对企业统一用户认证系统建设或LDAP有其它疑问,可在评论区讨论。如果你有关于企业统一用户认证系统建设或LDAP的开发、培训或咨询需求,也请站内信联系我。

原文地址:https://www.cnblogs.com/mzsg/p/12011900.html