Tomcat 和 OpenLDAP, 从配置到应用
by Darren Duke
05/31/2005
几乎所有的 Java Web 应用都需要某种类型的安全性的控制, 并且这通常会借助于 Lightweight Directory Access Protocol (LDAP) 目录服务来实现. 作为一个开发者,很有必要在本地安装一个 OpenLDAP 和 Web 容器来提高开发效率. 配置 Tomcat 连接到 OpenLDAP 的过程假如你能理解的话,还是很直白的. 这篇文章详细描述了这一配置过程,并用 Apache Jakarta 项目下的标签库 (taglib) 来演示简单的方式去测试这一安全机制.
内容
- 什么是 LDAP?
- OpenLDAP 的安装和配置
- JXplorer: 可视化 LDAP
- Tomcat 为 OpenLDAP 的配置
- Tomcat 服务器配置
- Web 应用配置
- Jakarta 相应标签库
- 小结
- 资源
什么是 LDAP?
LDAP 对于许多人来说不是几句话就能说清的. 对于本文来讲, 只需理解为是通过用户名和密码对用户进行验证的简单通用协议就够了. 换句话就是, 它提供了一种验证机制. 再进一步讲,它还要验证用户是否允许访问特定应用程序区域. 这是一个鉴权的过程. 验证和鉴权是有明显区别的. 它们并非一回事.
不同的 LDAP 产品在理论上是可互换了. 然而, 还是应该要注意到它们之间配置的不同. 一个 LDAP 目录是由下面那些层级组成树型结构的:
- 根 (树的源头), 由此产生分支
- 国家, 每项可有分支
- 组织, 可有分支
- 组织单元 (分部, 部门, 片区, 等)., 可有分支 (或是最终节点)
- 个体.
LDAP 目录能通过 Lightweight Directory Interchange Format (LDIF) 文件格式导入或导出它们的内容. LDIF 是一个文本格式的文件,它能用来在 LDAP 之间交换信息, 或者像本文中那样用了它来初始化目录的配置. 由于是开源的, OpenLDAP 是许多开发者的首选的目录服务器.
OpenLDAP 的安装和配置
下载并安装好了 OpenLDAP 之后, 我们需要对安装目录下的 slapd.conf 文件作些很重要的修改.
首先是要包含 InetOrgPerson 模式. 这个模式里预定义了许多针对网络用户的有用的属性, 包括作为用户登陆用 ID 的 uid 属性. InetOrgPerson 模式依赖于 Cosine 模式, 因此应包含它.
1 2 3 4 5 6 7 8 9 10 11 12 |
ucdata-path C:/openldap/ucdata include C:/openldap/etc/schema/core.schema include C:/openldap/etc/schema/cosine.schema include C:/openldap/etc/schema/inetorgperson.schema database bdb suffix dc="mycompany",dc="com" rootdn "cn=Manager,dc=mycompany,dc=com" rootpw secret directory C:/openldap/var/openldap-data index objectClass eq |
第二方面的修改是要告诉 OpenLDAP 使用什么类型的数据库和根的识别名及密码是什么. 这是进入城市的钥匙, 所以应该选择并设任何其他的密码.
一旦所有对 slapd.conf 文件修改完毕之后, 就可以启动 OpenLDAP 了. 到命令行下进入到安装目录中(默认是 c:\openldap). 输入命令 .\slapd -d 1,
然后服务器就会启动,界面如图 1.
|
如果服务器报错后启动失败, 应检查上面说的那两个模式是否已加到 slapd.conf 文件中. 再就是, 假如 OpenLDAP 不是安装在默认目录中, 需检验模式文件的路径是否正确. 一旦服务器起来了, 可以来检验我们的安装. 打开另一个命令行窗口并输入命令 ldapsearch -x -b "dc=mycompany,dc=com" "(objectclass=*)"
. 执行的结果应该像下图 2 那样.
|
要组装一个目录结构, 需导入一个 LDIF 文件. 要写这种格式的文件是非人所愿的, 因此这里准备好了一个 LDIF 文件供下载. 要导入这个文件, 在命令行下执行 ldapadd -x -D "cn=Manager,dc=mycompany,dc=com" -W -f setup.ldif,
然后会提示你输入密码, 就是配置在 slapd.conf 文件中的密码. 输入密码之后, 会得到如图 3 所示那样的结果.
|
这都非常好, 但是怎么才知道顺利完成了呢? 嗯, 主要是 ldapadd
加入了两个组织单元 (people 和 roles), 三个用户角色 (在 roles OU 下) 和三个用户 (在 people OU 下). 这些用户还被指定为不用的角色. 通过导入 LDIF 文件创建的层级如下:
- com
-
- mycompany
-
- people
-
- admin
- jbloggs
- sspecial
- roles
-
- Admin Users
-
- uid=admin/ou=people/...
- Special Users
-
- uid=sspecial/ou=people/...
- Test Users
-
- uid=sspecial/ou=people/...
- uid=jbloggs/ou=people/...
- uid=admin/ou=people/...
现在你可以输入另一个 ldapsearch -x -b "dc=mycompany,dc=com" "(objectclass=*)" 指令
, 这一次的输出会包含一些更有意义的东西了(像用户属和角色成员之类的), 如图 4 所示. 虽然在完全在命令行下敲击指令是足够的, 但是有一个相当好的开源的, Java Swing 界面的 LDAP 浏览器能帮到你,它就是 JXplorer.
|
JXplorer: 可视化 LDAP
JXplorer 是一个 LDAP 浏览器. 它能用来查看、添加、 修改, 和删除目录中的任何元素或属性. 如果必要的话,它甚至可用来修改密码. 安装 JXplorer 并依据你的操作系统运行 jxplorer.bat 或 jxplorer.sh. 运行后,就可使用根用户名和密码连接到你本地的 OpenLDAP 目录上, 见图 Figure 5 所示.
图 5. JXplorer 连接截屏
JXplorer 可用来浏览目录和修改任何值. 在你还未熟悉 LDAP 之前,修改除用户属性 (email, 密码等) 之外的任何信息都要小心些. 看图 6 的例子.
|
Tomcat 为 OpenLDAP 的配置
Servlet 2.3 的规范在对用户进行验证和鉴权时, 允许应用服务器使用容器管理的安全性连接到一个已存在的用户仓库. 然而,还没有一种标准的方式能在不同容器间完成对用户的验证和鉴权. 每一种容器能 (通常就是) 以不同的方式实现. Tomcat 容器用域 (Realm
) 的概念来实现这种安全性. Realm 可描述为一个存有用户名和密码的仓库, 它们唯一标识了 web 应用的有效用户. Tomcat 4 容器有四种 Realm 类型, 每一种类型存储和获取是在不同类型的仓库中.
JDBCRealm
使用一个关系型数据库.DataSourceRealm
通过 JNDI 连接使用一个关系型数据库.JNDIRealm
使用 JNDI , 通常是使用到一个 LDAP 目录.MemoryRealm
使用内存文件, 常为 tomcat-users.xml.
Tomcat 默认的 Realm
是 MemoryRealm
, 它是通过 tomcat-users.xml 文件配置的. Tomcat 需要手动去配置使用 JNDIRealm
以允许通过 LDAP 目录来难证用户.
配置 Tomcat 使用 JNDIRealm 需要以下几个步骤.
- 在文件 server.xml 中设置 Realm.
- 创建一个 JSP 登录表单页.
- 在 web.xml 文件中设置任意的
<security-constraint>
来保护资源. - 在 web.xml 文件中设置
<login-config>
指示使用登录表单页. - 在 web.xml 文件中配置所有的
<security-role>
映射.
Tomcat 服务器配置
每一个应用可以在各自的 <Context>
元素中配置自己的 Realm; 这个 realm 仅仅是对该应用是有效的. 然而, realm 也能配置到 <Engine>
或 <Host>
级别上. 它们对 realm 的行为和范围有影响. 这样允许一个 realm 很容易的在多个应用间共享.
这里有一个 server.xml 的 realm, 它允许应用连接到 OpenLDAP.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<Realm className="org.apache.catalina.realm.JNDIRealm" debug="99" connectionName="cn=Manager,dc=mycompany,dc=com" connectionPassword="secret" connectionURL="ldap://localhost:389" roleBase="ou=roles,dc=mycompany,dc=com" roleName="cn" roleSearch="(uniqueMember={0})" roleSubtree="false" userSearch="(uid={0})" userPassword="userPassword" userPattern="uid={0},ou=people,dc=mycompany,dc=com" /> |
在声明式配置 LDAP 时,userSearch
和 userPattern
属性是最容易产生混淆和错误的地方. 假如你使用其他的 LDAP 服务器应特别注意这些属性.
Web 应用配置
要完成 Tomcat 对 OpenLDAP 的配置, 应用的 web.xml 文件必须更新的. 提供下载的应用中有 6 个 JSP 页面组成, 其中三个是针对 LDAP 目录中设置的不同角色的受保护资源. 应用必须配置为允许基于 form 的验证,并被告知存在着哪些角色. 首先, 创建一个 login.jsp 文件. 当使用基于 form 的验证时, 这个 JSP 必须包含如下内容.
1 2 3 4 5 6 7 8 9 |
<body> <form method="POST" action="j_security_check"> <input type="text" name="j_username"> <br> <input type="password" name="j_password"> <br> <input type="submit"> </form> </body> |
必须参考新的 OpenLDAP realm 来更新web.xml 文件, 并在其中使用 LDAP 指定的角色. 应用还需要让对 login.jsp 页面的访问权限是公开的; 不然的话, 没有用户能登陆.
要告知应用什么角色能访问什么资源, 就要在应用 (或安全约束) 中用到 URL 映射. 这种映射既可以是一个文件名 (/admin.jsp) 也可以是一个路径 (/jsp/*
将保护 jsp 目录中的任何东西). 下面的 XML 表示列出的这两个 .jsp 文件不受到保护.
1 2 3 4 5 6 7 8 |
<security-constraint> <web-resource-collection> <web-resource-name>Public Area</web-resource-name> <!-- Define the context-relative URL(s) to be protected --> <url-pattern>/index.jsp</url-pattern> <url-pattern>/login.jsp</url-pattern> </web-resource-collection> </security-constraint> |
为什么这个代码会标记其他的 .jsp 文件为不受保护呢? 这是由于未列出的不受保护, 而非列出来的才不受保护. 下面的 web.xml 片断标明了 user.jsp
资源是受保护的, 并指定了什么角色可访问它.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<security-constraint> <web-resource-collection> <web-resource-name>Protected Area</web-resource-name> <!-- Define the context-relative URL(s) to be protected --> <url-pattern>/user.jsp</url-pattern> </web-resource-collection> <auth-constraint> <!-- Anyone with one of the listed roles may access this area --> <role-name>Test Users</role-name> <role-name>Special Users</role-name> <role-name>Admin Users</role-name> </auth-constraint> </security-constraint> |
注意到那个受保护资源有还有另一个节点配置, <auth-constraint>
, 它指定了什么应用角色可以访问上面的资源. 假如存在 <auth-constraint>
配置, 资源就会是安全的. 否则, 它们就是公开的. 除非整个应用都是受保护的 (例如, <url-pattern>/</url-pattern>
), 公开的权限约束完全是多余.
欲配置 web.xml 文件利用前面创建的 login.jsp , 要添加下面的代码到 web.xml 文件中.
1 2 3 4 5 6 7 8 |
<!-- uses form-based authentication --> <login-config> <auth-method>FORM</auth-method> <form-login-config> <form-login-page>/login.jsp</form-login-page> <form-error-page>/fail_login.html</form-error-page> </form-login-config> </login-config> |
最后是对 web.xml 的一个附加配置步骤: 应用需要被知会我们要用到哪些角色.
1 2 3 4 5 6 7 8 9 10 |
<!-- Security roles referenced by this web application --> <security-role> <role-name>Test Users</role-name> </security-role> <security-role> <role-name>Special Users</role-name> </security-role> <security-role> <role-name>Admin Users</role-name> </security-role> |
现在 Tomcat 已被配置为使用 OpenLDAP 并且我们的应用也对 web.xml 文件进行了正确的设置. 当用户首次被导航到 <security-constraint>
中列出的任何资源时 (只要不是公开的资源) , 服务器会自动显示 login.jsp 页面要求用户验证. 假如用户验证失败, 将会显示在 <form-error-page>
中指定的页面. 要是验证用户是通过了, 但是未授权访问某个资源 (比如, 不是某个角色的成员), 服务器会返回一个 403
错误页. 想要的话, 错误页可以在 web.xml 文件中定制, 使用 <error-code>
元素.
注, web.xml 中的元素有一个特定的顺序 (定义在 DTD 文件中), 所以你应该看看例子代码 (在后面 资源 一节) 中完整的 web.xml 文件. 尤其是 <login-config>
和 <security-role>
中的元素顺序.
现在用户可以登陆了, 但是该如何登出呢? 可以写代码来让用户的会话失效, 或者我们可以使用 Apache Jakarta 项目提供的便利的会话标签库.
Jakarta 相应标签库
通过加入 logout.jsp 到应用中并使用 Apache Jakarta Project 的会话标签库 , 我们能在不写任何定制代码情况下就失效掉的用户 (在实际的应用中, 你也许还需为清除一个用户会话做更多一些事情, 你也可能不怎么满意这么做).
1 2 3 4 5 |
<body> <sess:invalidate/> You are now logged out<br> <a href="index.jsp">Return to index</a> </body> |
一旦包含用 <sess:invalidate/>
标签的 JSP 页面显示之后, 用户会话便被移除, 该用户被有效的注销掉了. 简单的放置一个链接指向 logout.jsp 页面, 就能让用户导航该页, 这足以应付简单的应用. 另, 除会话标签库外, 我们还能利用 request 标签库来定制化基于用户和角色 JSP 页面的内容.
1 2 3 |
<req:isUserInRole role="Admin Users"> The remote user is in role "Admin Users".<br /> </req:isUserInRole> |
前述的 JSP 片断只有在当前用户在请求的角色中时才显示内容. 要验证角色安全性的行为, 可以用 JXplorer 快速的从角色中添加和移除用户.
小结
对于开发人员来说, 非常值的在本地工作站安装一个 LDAP 目录服务器. 可能不再是为了让网络管理员来访问它, 而可以为自己创建测试帐户. 所有的这些任务可以在很短的时间内搞定, 而不是几天或者几星期. 那不是说 LDAP 就很简单. 非也; LDAP 是一个很强大且复杂的系统. 但是只要对它有了一些实际的了解及用上正确的工具, 搭建起来还是相当简单的.
感谢两个跨平台的 Tomcat 和 OpenLDAP, 持续构建服务器, CruiseControl, Anthill 等等, 它们都能在生产系统中保持完全的独立性. 这种解耦形式对它们都很有利.
值得注意的是实际上所有 LDAP 服务器是不能对换的. 例如, OpenLDAP 不支持 memberOf
属性对角色成员的检查, 这有时修也让人很扫兴的. 在 Tomcat 中针对 OpenLDAP 配置的 realm 在改变了目录服务器和相应值时未必能正常工作. 这或许是使用 LDAP 时唯一也是最大的挑战, 但是只要使用 ldapsearch
你就能最终指出需要哪些值.
资源
Darren Duke 是在他自已创建的总部位于乔治亚州亚特兰大的 Simplified Technology Solutions, Inc 公司 (简单化科技解决方案公司) 的软件开发顾问领导 .
[译者 Unmi 后注] 因公司的生产系统是基于 LDAP 实现的 SSO 验证,而平时开发不可能抱一个笨重的商业应用服务器。开发主要还是基于 Tomcat 来进行的,所以每次部署都得重新改一下配置文件以适应生产环境。于是乎就想能不能用 Tomcat 也连接 LDAP 来进行相仿的验证,网上找了些资料,也参考过 Tomcat 的配置文档,但都没有一个完整的流程。唯独这篇文章我最喜欢,欣喜间不忘把它翻译过来以饷诸位战斗在同一战线的同仁 (忙乱中拣些闲暇,历经数日方成);其实,自私、坦白讲是为了锻炼一下自己的英文阅读能力,也难免不明之处欠些琢磨更要紧是水平的不到位,见谅并希望能得到无情的指摘。同时 Unmi 也对例子有所实践,并非平白引进;还有就是注意到文中用的是 Form 的验证方式,读者可自己试验 Basic 的验证方式,更多的属性配置就该参考 Tomcat 关于 Realm 的配置文档了。
本文链接 https://yanbin.blog/tomcat-openldap-config-application/, 来自 隔叶黄莺 Yanbin Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。