目录
正文
1. 什么是Realm?
Realm,中文可以翻译为“域”,是一个存储用户名,密码以及和用户名相关联的角色的”数据库”,用户名和密码用来验证用户对一个或多个web应用程序的有效性。你可以将Realm看做Unix系统里的group组概念,因为访问应用程序中特定资源的权限是被授予了拥有特殊角色的用户,而不是相关的用户名。通过用户名相关联,一个用户可以有任意数量的角色。
尽管Servlet规范描述了一个可以让应用程序声明它们安全性要求(在web.xml部署描述符里)的机制,但是并没有的API来定义一个基于servlet容器和其相关用户角色之间的接口。然而在许多情况下,最好能把一个servlet容器和那些已经存在的认证数据库或机制“连接”起来。因此,Tomcat定义了一个Java接口(org.apache.catalina.Realm),它可以通过"插件"的形式来实现这种连接。
因此,可以通过现有数据库里的用户名、密码以及角色来配置Tomcat,从而来支持容器管理的安全性(container managed security)。如果你使用一个网络程序,而这个程序里包括了一个或多个<security-constraint>元素,以及一个定义用户怎样认证他们自己的<login-config>元素,那你就需要设置这些Realm。
总结:说的简单点就是Realm类似于Unix里面的group。在Unix中,一个group对应着系统的一组资源,某个group不能访问不属于它的资源。Tomcat用Realm来将不同的应用(类似系统资源)赋给不同的用户(类似group),没有权限的用户则不能访问相关的应用。
2. 如何配置使用Tomcat自带的Realm?
Tomcat 7中提供了六种标准Realm,用来支持与各个认证信息来源的连接:
- JDBCRealm - 通过JDBC驱动来访问贮存在关系数据库里的认证信息。
- DataSourceRealm - 通过一个叫做JNDI JDBC 的数据源(DataSource)来访问贮存在关系数据库里的认证信息。
- UserDatabaseRealm - 通过一个叫做UserDatabase JNDI 的数据源来访问认证信息,该数据源通过XML文件(conf/tomcat-users.xml)来进行备份使用。
- JNDIRealm - 通过JNDI provider来访问贮存在基于LDAP(轻量级目录访问协议)的目录服务器里的认证信息。
- MemoryRealm - 访问贮存在电脑内存里的认证信息,它是通过一个XML文件(conf/tomcat-users.xml)来进行初始化的。
- JAASRealm - 使用 Java Authentication & Authorization Service (JAAS)访问认证信息。
在使用标准Realm之前,弄懂怎样配置一个Realm是很重要的。通常,你需要把一个XML元素加入到你的conf/server.xml配置文件中,它看起来像这样:
<Realm className="... class name for this implementation"
... other attributes for this implementation .../>
<Realm>元素可以被套嵌在下列任何一个Container元素里面。这个Realm元素所处的位置直接影响到这个Realm的作用范围(比如,哪些web应用程序会共享相同的认证信息):
在<Engine>元素里边 - 这个域(Realm)将会被所有虚拟主机上的所有网络程序共享,除非它被嵌套在下级<Host> 或<Context>元素里的Realm元素覆盖。
在<Host>元素里边 - 这个域(Realm)将会被该虚拟主机上所有的网络程序所共享,除非它被嵌套在下级<Context>元素里的Realm元素覆盖。
在<Context>元素里边 - 这个域(Realm)只被该网络程序使用。
如何使用各个标准Realm也很简单,官方文档也讲的非常详细,具体可以参考我下面给出的几个参考资料。下面重点讲如何配置使用我们自定义的Realm。
3. 如何配置使用我们自定义的Realm?
虽然Tomcat自带的这六种Realm大部分情况下都能满足我们的需求,但也有特殊需求Tomcat不能满足的时候,比如我最近的一个需求就是: 我的用户和密码信息存储在LDAP中,但用户角色却存储在关系数据库(PostgreSQL)中,那么如何认证呢?
我们知道Tomcat自带的JNDIRealm可以实现LDAP认证,JDBCRealm可以实现关系数据库认证,那么我们可不可以首先通过LDAP认证,认证通过后,到数据库中读取角色信息呢?答案是肯定的,就是自定义Realm实现我们的需求。我们所需要做的就是:
- 实现org.apache.catalina.Realm接口;
- 把编译过的Realm放到 $CATALINA_HOME/lib里边;
- 像上面配置标准realm一样在server.xml文件中声明你的realm;
- 在MBeans描述符里声明你的realm。
下面我具体的以我自己的需求作为例子向大家演示如何自定义Realm并成功配置使用。
需求:自定义一个Realm,使得能够像JNDIRealm一样可以实现LDAP认证,又像JDBCRealm一样可以从数据库中读取我们用户的角色信息进行认证。
3.1 实现org.apache.catalina.Realm接口
从需求上看似乎我们可以将Tomcat自带的JNDIRealm和JDBCRealm结合起来,各取所需,形成我们自己的Realm。是的,的确可以这样,因此我们首先需要下载Tomcat的源码,找到这两个Realm的具体实现代码,基本看懂后,提取出我们所需要的部分进行重构形成自己的Realm。比如我自定义的Realm中所需要的实例变量有以下这些:
// -----------------------------------------------Directory Server Instance Variables
/**
* The type of authentication to use.
*/
protected String authentication = null;
/**
* The connection username for the directory server we will contact.
*/
protected String ldapConnectionName = null;
/**
* The connection password for the directory server we will contact.
*/
protected String ldapConnectionPassword = null;
/**
* The connection URL for the directory server we will contact.
*/
protected String ldapConnectionURL = null;
/**
* The directory context linking us to our directory server.
*/
protected DirContext context = null;
/**
* The JNDI context factory used to acquire our InitialContext. By
* default, assumes use of an LDAP server using the standard JNDI LDAP
* provider.
*/
protected String contextFactory = "com.sun.jndi.ldap.LdapCtxFactory";
/**
* How aliases should be dereferenced during search operations.
*/
protected String derefAliases = null;
/**
* Constant that holds the name of the environment property for specifying
* the manner in which aliases should be dereferenced.
*/
public final static String DEREF_ALIASES = "java.naming.ldap.derefAliases";
/**
* The protocol that will be used in the communication with the
* directory server.
*/
protected String protocol = null;
/**
* Should we ignore PartialResultExceptions when iterating over NamingEnumerations?
* Microsoft Active Directory often returns referrals, which lead
* to PartialResultExceptions. Unfortunately there's no stable way to detect,
* if the Exceptions really come from an AD referral.
* Set to true to ignore PartialResultExceptions.
*/
protected boolean adCompat = false;
/**
* How should we handle referrals? Microsoft Active Directory often returns
* referrals. If you need to follow them set referrals to "follow".
* Caution: if your DNS is not part of AD, the LDAP client lib might try
* to resolve your domain name in DNS to find another LDAP server.
*/
protected String referrals = null;
/**
* The base element for user searches.
*/
protected String userBase = "";
/**
* The message format used to search for a user, with "{0}" marking
* the spot where the username goes.
*/
protected String userSearch = null;
/**
* The MessageFormat object associated with the current
* <code>userSearch</code>.
*/
protected MessageFormat userSearchFormat = null;
/**