2017年末开始在某高校实习,接手的第一个项目是继续开发别人未完成的校内后勤应用并调试上线,其中学生用户登录要求使用腾讯微校平台。其大致思路是应用登录时调用腾讯接口并附上回调地址,学生在腾讯提供的登录页面输入本校的帐号和密码,腾讯将帐号和密码发送给回调地址,回调地址中的代码对该帐号和密码进行校验并返回学生信息。在该回调地址的代码中,就是用到了LDAP的验证方式。
之前从未了解过LDAP,在网上查到了相关资料:
1.什么是LDAP认证?
LDAP认证是通过WSS3.0加上轻量目录LDAP协议搭建的一种认证方式,使用https加密传输,主要用于做文档管理。
LDAP认证就是把用户数据放在LDAP服务器上,通过LDAP服务器上的数据对用户进行认证处理。
2.有几种实现的原理,简单讲解两种:
a).每一个登陆,连接请求先去拉取所有的可通过用户的列表,然后去查找是否在已注册用户列表。(不推荐)
b).每一个登陆,连接请求去发送本地的用户、密码给LDAP服务器,然后在LDAP服务器上进行匹配,然后判断是否可以通过认证。(推荐)
3.为什么用LDAP做身份验证?
1).LDAP数据库是一种对读操作进行优化的数据库,在读写比例大于7比1的情况下,LDAP会体现出高的性能。
2).更灵活添加数据类型,LDAP是根据schema的内容定义各种属性之间的从属关系及匹配模式的。 例如:在传统的结构化数据库mysql中添加一个字段,就需要在用户表中添加一个字段。但是在数据量 极大的时候是很耗时间的,效率低,用户体验差,但是LDAP只需要在Schema中加入新的属性,不会 由于用户的属性增多而影响查询性能。
3).LDAP是个开放的标准协议,不同于一般的SQL数据库,LDAP的客户端是跨平台的,方便简洁。
4).在存储上LDAP是以树形结构存储数据,任何一个分支都可以单独在服务器中进行分布式管理, 不仅有利于服务器的负载均衡,还方便做跨区域的服务器部署。
5).LDAP支持强认证方式,可以达到很高的安全级别,根据UTF-8编码。
一些关键字介绍:在 LDAP 目录中,
- DC (Domain Component)
- CN (Common Name)
- OU (Organizational Unit)
LDAP 目录类似于文件系统目录。下列目录:DC=redmond,DC=wa,DC=microsoft,DC=com如果我们类比文件系统的话,可被看作如下文件路径:Com/Microsoft/Wa/Redmond例如:CN=test,OU=developer,DC=domainname,DC=com在上面的代码中 cn=test 可能代表一个用户名,ou=developer 代表一个 active directory 中的组织单位。这句话的含义可能就是说明 test 这个对象处在domainname.com 域的 developer 组织单元中以下是项目中的实现方法(b方式):
public static AuthenResult authen(String userNum, String psw) {
AuthenResult authenResult = new AuthenResult(); //自定义的认证结果对象
authenResult.result = AuthenResult.AUTHEN_RESULT_OK;
Control[] connCtls = null;
Hashtable<String, String> env = new Hashtable<String, String>(); //参数集合
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.ldap.LdapCtxFactory"); //通用的,名字上是个工厂对象,个人感觉类似于连接关系型数据库时所需的驱动
env.put(Context.PROVIDER_URL, Constants.URL + Constants.BASEDN);//URL(LDAP的连接地址,格式为:ldap://ip:port/),BASEDN(LDAP的根DN,dc=Xxxx,dc=edu,dc=cn)
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, Constants.PRINCIPAL);//LDAP的连接帐号(具体为身份认证管理平台添加的应用帐号,uid=xxx,ou=xxx,dc=xxx,dc=edu,dc=en)
env.put(Context.SECURITY_CREDENTIALS, Constants.PASSWORD);//LDAP的连接密码,如果这里没有指定帐号密码,则会采用匿名登录的方式
LdapContext ctx = null;
String userDN = "";
try {
// 链接ldap
ctx = new InitialLdapContext(env, connCtls);
// 获取用户DN
SearchControls constraints = new SearchControls();
constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);
NamingEnumeration en = ctx
.search("", "uid=" + userNum, constraints);
while (en != null && en.hasMoreElements()) {
Object obj = en.nextElement();
if (obj instanceof SearchResult) {
SearchResult si = (SearchResult) obj;
userDN += si.getName();
userDN += ",";
userDN += Constants.BASEDN;
authenResult.setAttrs(si.getAttributes());
}
}
// 用户是否存在
if (!"".equals(userDN)) {
// 验证用户密码是否正确
ctx.addToEnvironment(Context.SECURITY_PRINCIPAL, userDN);
ctx.addToEnvironment(Context.SECURITY_CREDENTIALS, psw);
ctx.reconnect(connCtls);
} else {
authenResult.result = AuthenResult.AUTHEN_RESULT_USERNAME_PSW_ERR;
}
} catch (AuthenticationException e) {
authenResult.result = AuthenResult.AUTHEN_RESULT_USERNAME_PSW_ERR;
e.printStackTrace();
} catch (NamingException e) {
authenResult.result = AuthenResult.AUTHEN_RESULT_SERVER_ERR;
e.printStackTrace();
} catch (Exception e) {
authenResult.result = AuthenResult.AUTHEN_RESULT_SERVER_ERR;
e.printStackTrace();
} finally {
if (ctx != null) {
try {
ctx.close();
} catch (NamingException e) {
e.printStackTrace();
}
ctx = null;
}
}
return authenResult;
}
这里从LDAP里取到了学生信息,再将这些信息封装到学生对象中,转成json格式加密发送到微校,方便接下来的应用身份调用。