标签:realm tomcat web开发 用户认证 ldap
尽管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),没有权限的用户则不能访问相关的应用。
在使用标准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。
我们知道Tomcat自带的JNDIRealm可以实现LDAP认证,JDBCRealm可以实现关系数据库认证,那么我们可不可以首先通过LDAP认证,认证通过后,到数据库中读取角色信息呢?答案是肯定的,就是自定义Realm实现我们的需求。我们所需要做的就是:
下面我具体的以我自己的需求作为例子向大家演示如何自定义Realm并成功配置使用。
需求:自定义一个Realm,使得能够像JNDIRealm一样可以实现LDAP认证,又像JDBCRealm一样可以从数据库中读取我们用户的角色信息进行认证。
从需求上看似乎我们可以将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;
/**
* Should we search the entire subtree for matching users?
*/
protected boolean userSubtree = false;
/**
* The attribute name used to retrieve the user password.
*/
protected String userPassword = null;
/**
* A string of LDAP user patterns or paths, ":"-separated
* These will be used to form the distinguished name of a
* user, with "{0}" marking the spot where the specified username
* goes.
* This is similar to userPattern, but allows for multiple searches
* for a user.
*/
protected String[] userPatternArray = null;
/**
* The message format used to form the distinguished name of a
* user, with "{0}" marking the spot where the specified username
* goes.
*/
protected String ldapUserPattern = null;
/**
* An array of MessageFormat objects associated with the current
* <code>userPatternArray</code>.
*/
protected MessageFormat[] userPatternFormatArray = null;
/**
* An alternate URL, to which, we should connect if ldapConnectionURL fails.
*/
protected String ldapAlternateURL;
/**
* The number of connection attempts. If greater than zero we use the
* alternate url.
*/
protected int connectionAttempt = 0;
/**
* The timeout, in milliseconds, to use when trying to create a connection
* to the directory. The default is 5000 (5 seconds).
*/
protected String connectionTimeout = "5000";
// --------------------------------------------------JDBC Instance Variables
/**
* The connection username to use when trying to connect to the database.
*/
protected String jdbcConnectionName = null;
/**
* The connection password to use when trying to connect to the database.
*/
protected String jdbcConnectionPassword = null;
/**
* The connection URL to use when trying to connect to the database.
*/
protected String jdbcConnectionURL = null;
/**
* The connection to the database.
*/
protected Connection dbConnection = null;
/**
* Instance of the JDBC Driver class we use as a connection factory.
*/
protected Driver driver = null;
/**
* The JDBC driver name to use.
*/
protected String jdbcDriverName = null;
/**
* The PreparedStatement to use for identifying the roles for
* a specified user.
*/
protected PreparedStatement preparedRoles = null;
/**
* The string manager for this package.
*/
protected static final StringManager sm =
StringManager.getManager(Constants.Package);
/**
* The column in the user role table that names a role
*/
protected String roleNameCol = null;
/**
* The column in the user role table that holds the user‘s name
*/
protected String userNameCol = null;
/**
* The table that holds the relation between user‘s and roles
*/
protected String userRoleTable = null;
/**
* Descriptive information about this Realm implementation.
*/
protected static final String info = "XXXXXX";
/**
* Descriptive information about this Realm implementation.
*/
protected static final String name = "XXXRealm";
可以看出,将JNDIRealm中不需要的role信息去掉,加上JDBCRealm中获取用户role所需要的信息即可。
然后就是修改JNDIRealm中的认证方法authenticate()为我们自己认证所需要的,也就是将通过LDAP获取role信息的部分改成使用JDBC连接数据库查询获得。代码不是很复杂但有两千多行,这里就不贴出来了,有需要的可以在下面回复邮箱,我可以发送给你们。
mbeans-descriptor.xml文件的格式如下:
<mbean name="XXXRealm"
description="Custom XXXRealm..."
domain="Catalina"
group="Realm"
type="com.myfirm.mypackage.XXXRealm">
<attribute name="className"
description="Fully qualified class name of the managed object"
type="java.lang.String"
writeable="false"/>
<attribute name="debug"
description="The debugging detail level for this component"
type="int"/>
...
</mbean>
具体的可用参考Tomcat源码中realm包下的mbeans文件。该配置文件十分重要,里面的attribute元素直接对应自定义Realm源码中对应的实例变量字段,也就是我上面贴出来的代码,不过并不是每个实例变量都要添加进来,添加的都是一些重要的需要我们自己在server.xml文件中指明的属性(后面讲),比如JDBC 驱动、数据库用户名、密码、URL等等,这里的attribute名必须与代码中的变量名完全一致,不能出错,否则读取不到相应的值。
下面贴出因上面我的需求所定义的mbeans-descriptor.xml文件:
<?xml version="1.0"?>
<mbeans-descriptors>
<mbean name="CoralXRRealm"
description="Implementation of Realm that works with a directory server accessed via the Java Naming and Directory Interface (JNDI) APIs and JDBC supported database"
domain="Catalina"
group="Realm"
type="org.opencoral.xreport.realm.CoralXRRealm">
<attribute name="className"
description="Fully qualified class name of the managed object"
type="java.lang.String"
writeable="false"/>
<attribute name="ldapConnectionName"
description="The connection username for the directory server we will contact"
type="java.lang.String"/>
<attribute name="ldapConnectionPassword"
description="The connection password for the directory server we will contact"
type="java.lang.String"/>
<attribute name="ldapConnectionURL"
description="The connection URL for the directory server we will contact"
type="java.lang.String"/>
<attribute name="contextFactory"
description="The JNDI context factory for this Realm"
type="java.lang.String"/>
<attribute name="digest"
description="Digest algorithm used in storing passwords in a non-plaintext format"
type="java.lang.String"/>
<attribute name="userBase"
description="The base element for user searches"
type="java.lang.String"/>
<attribute name="userPassword"
description="The attribute name used to retrieve the user password"
type="java.lang.String"/>
<attribute name="ldapUserPattern"
description="The message format used to select a user"
type="java.lang.String"/>
<attribute name="userSearch"
description="The message format used to search for a user"
type="java.lang.String"/>
<attribute name="userSubtree"
description="Should we search the entire subtree for matching users?"
type="boolean"/>
<attribute name="jdbcConnectionName"
description="The connection username to use when trying to connect to the database"
type="java.lang.String"/>
<attribute name="jdbcConnectionPassword"
description="The connection URL to use when trying to connect to the database"
type="java.lang.String"/>
<attribute name="jdbcConnectionURL"
description="The connection URL to use when trying to connect to the database"
type="java.lang.String"/>
<attribute name="jdbcDriverName"
description="The JDBC driver to use"
type="java.lang.String"/>
<attribute name="roleNameCol"
description="The column in the user role table that names a role"
type="java.lang.String"/>
<attribute name="userNameCol"
description="The column in the user role table that holds the user‘s username"
type="java.lang.String"/>
<attribute name="userRoleTable"
description="The table that holds the relation between user‘s and roles"
type="java.lang.String"/>
<operation name="start" description="Start" impact="ACTION" returnType="void" />
<operation name="stop" description="Stop" impact="ACTION" returnType="void" />
<operation name="init" description="Init" impact="ACTION" returnType="void" />
<operation name="destroy" description="Destroy" impact="ACTION" returnType="void" />
</mbean>
</mbeans-descriptors>
可以看到与我上面贴出的代码重要实例变量一一对应。
注意:class文件还是在package里面,不能单独拿出来打包,我们将mbeans-descriptor.xml文件放到.class文件同一目录下,比如我自定义的Realm(比如就叫CustomRealm.java)所在包为:com.ustc.realm.CustomRealm.java,那么.class和mbeans配置文件目录应该为:
|-- com
|-- ustc
|-- realm
|-- CustomRealm.class
|-- mbeans-descriptor.xml
然后命令行进入到com根目录下,使用下面命令打包:
jar cvf customrealm.jar .
customrealm.jar是你自己取的jar名称,第二个参数点.不能丢了,表示对当前的目录进行打包。打包成功后,将customrealm.jar放到$CATALINA_HOME/lib里边即可。
这个步骤非常关键,打开conf/server.xml文件,搜索Realm,你会看到Tomcat配置文件中自带的Realm声明:
<!-- This Realm uses the UserDatabase configured in the global JNDI
resources under the key "UserDatabase". Any edits
that are performed against this UserDatabase are immediately
available for use by the Realm. -->
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
当然这个Realm是无效的,因为没有配置完整,只是作为一个示例告诉你要在这里重新配置你自己的Realm,我们将这段Realm声明注释掉,然后声明我们自己的Realm,怎么声明?这要看你的需求了,Tomcat官方文档中有每个标准Realm的详细配置声明,比如JNDIRealm你可以按下面格式声明:
<Realm className="org.apache.catalina.realm.JNDIRealm"
connectionURL="ldap://localhost:389"
userPattern="uid={0},ou=people,dc=mycompany,dc=com"
roleBase="ou=groups,dc=mycompany,dc=com"
roleName="cn"
roleSearch="(uniqueMember={0})"
/>
注:这是有关LDAP服务器的配置,关于LDAP如何使用可以查询相关资料,不在本文讨论范围内。
而JDBCRealm你可以这样声明:
<Realm className="org.apache.catalina.realm.JDBCRealm"
driverName="org.gjt.mm.mysql.Driver"
connectionURL="jdbc:mysql://localhost/authority?user=dbuser&password=dbpass"
userTable="users" userNameCol="user_name" userCredCol="user_pass"
userRoleTable="user_roles" roleNameCol="role_name"/>
如果你是使用Tomcat自带的标准Realm,那么只需要修改上面对应的属性值即可。如果是自定义Realm呢?那么我们也需要自定义Realm的声明,以我上面的需求为例,自定义的Realm声明如下:
<Realm className="com.ustc.realm.CustomRealm"
ldapConnectionURL="ldap://server ip:389"
ldapUserPattern="uid={0},ou=people,dc=mycompany"
jdbcDriverName="org.postgresql.Driver"
jdbcConnectionURL="jdbc:postgresql://dbserver ip:port"
jdbcConnectionName="xxx"
jdbcConnectionPassword="xxx" digest="MD5"
userRoleTable="user_roles"
userNameCol="user_name"
roleNameCol="role_name" />
其实就是上面的两个标准Realm声明结合起来,各取所需,这里需要注意两个问题:
OK,到此为止,我们自定义Realm的编写与配置就完成了。接下来就是测试了,重启Tomcat,进入登录界面试试吧。
标签:realm tomcat web开发 用户认证 ldap
原文地址:http://blog.csdn.net/lanxuezaipiao/article/details/23176989