How to customize Tomcat Realm to meet our user authentication requirements "suggestions collection"

Reading guide

Tomcat is no stranger to J2EE or Java web developers, but when it comes to real, some people may not know or even have heard of it. So what is real? In short: Realm is a mechanism in Tomcat that provides access authentication and role management for web applications. With Realm configured, you don't need to write web application login verification code in the program, manage user roles effortlessly, or even write your own login interface. Therefore, using Realm can reduce a lot of programming and management burden for developers. The following is a brief introduction to Tomcat real from several aspects to provide an entry-level tutorial for real learners.

catalogue

text

1. What is Realm?

Realm, which can be translated into "domain" in Chinese, is a "database" that stores user names, passwords and roles associated with user names. User names and passwords are used to verify the effectiveness of users to one or more web applications. You can think of realm as a group concept in Unix system, because the permission to access specific resources in the application is granted to users with special roles, not the relevant user names. By associating user names, a user can have any number of roles.

Although the servlet specification describes a mechanism that allows applications to declare their security requirements (in the web.xml deployment descriptor), there is no API to define an interface based on the servlet container and its associated user roles. However, in many cases, it is best to "connect" a servlet container to those existing authentication databases or mechanisms. Therefore, Tomcat defines a Java interface (org.apache.catalina.Realm), which can realize this connection in the form of "plug-in".

Therefore, Tomcat can be configured through the user name, password and role in the existing database to support container managed security. If you use a web program that includes one or more Element, and a that defines how users authenticate themselves You need these elements, Realm.

Summary: to put it simply, Realm is similar to the group in Unix. In Unix, a group corresponds to a group of resources of the system. A group cannot access resources that do not belong to it. Tomcat uses Realm to assign different applications (similar to system resources) to different users (similar to groups). Users without permission cannot access related applications.

2. How to configure and use Tomcat's own Realm?

Tomcat 7 provides six standard realms to support the connection with various authentication information sources: * JDBC Realm - access the authentication information stored in the relational database through the jdbc driver* DataSourceRealm - access the authentication information stored in the relational database through a datasource called JNDI JDBC* UserDatabaseRealm - access authentication information through a data source called UserDatabase JNDI, which is backed up by an XML file (CONF / Tomcat users. XML)* JNDIRealm - access the authentication information stored in the directory server based on LDAP (Lightweight Directory Access Protocol) through JNDI provider* MemoryRealm - access the authentication information stored in the computer memory. It is initialized through an XML file (CONF / Tomcat users. XML)* JAASRealm - access authentication information using java authentication & Authorization Service (JAAS).

Before using a standard Realm, it is important to understand how to configure a Realm. Usually, you need to add an XML element to your conf / server In the XML configuration file, it looks like this:

<Realm className="... class name for this implementation"
       ... other attributes for this implementation .../>

The element can be nested in any of the following Container elements. The location of the Realm element directly affects the scope of the Realm (for example, which web applications share the same authentication information):

stay Inside the element - this domain will be shared by all network programs on all virtual hosts unless it is nested in a lower level or The Realm element in the element is overwritten. stay Inside the element - this domain will be shared by all network programs on the virtual host unless it is nested in a lower level The Realm element in the element is overwritten. stay Element - this domain is only used by the network program.

How to use each standard Realm is also very simple, and the official documents are also very detailed. For details, please refer to the following references. The following focuses on how to configure and use our custom Realm.

3. How to configure and use our customized Realm?

Although the six realms provided by Tomcat can meet our needs in most cases, there are also special needs that Tomcat cannot meet. For example, one of my recent needs is: * * my user and password information are stored in LDAP, but the user role is stored in relational database (PostgreSQL). So how to authenticate**

We know that the jndiream provided by Tomcat can realize LDAP authentication, and JDBC Realm can realize relational database authentication. Can we first pass LDAP authentication, and then read the role information in the database? The answer is yes, which is to customize the Realm to meet our needs. All we need to do is:

  • Implementation org apache. catalina. Real interface;
  • Put the compiled Realm into $Catalina_ Inside home / lib;
  • Configure the standard realm on the server as above Declare your realm in the XML file;
  • Declare your realm in the MBeans descriptor.

Next, I will take my own needs as an example to show you how to customize and successfully configure real.

Requirements: customize a Realm, so that LDAP authentication can be realized like JNDIRealm, and our user's role information can be read from the database for authentication like JDBC Realm.

3.1 realize org apache. catalina. Real interface

From the perspective of requirements, it seems that we can combine the JNDIRealm and JDBC Realm provided by Tomcat to form our own Realm. Yes, it can be. Therefore, we first need to download the source code of tomcat, find the specific implementation code of the two realms, and extract the parts we need to reconstruct to form our own realms. For example, the instance variables required in my customized Realm are as follows: ` ` ` Java / / -------------------------------------------------------- directory server instance variables

This code is by Java Architect must see network-Structure Sorting
/**
 *  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";
It can be seen that JNDIRealm Not required in role Remove the information and add JDBCRealm Get users from role Just the information you need.

Then there is modification JNDIRealm Authentication method in authenticate()What we need for our own certification, that is, we will pass LDAP obtain role Change part of the information to use JDBC Connect to the database query. The code is not very complicated, but there are more than 2000 lines. It will not be posted here. If necessary, you can reply to the email below, and I can send it to you.

<h3 id="3.2">3.2 take Realm Compile into.class file</h3>
Write custom Realm After that, you need to compile. It is recommended to build a separate package to compile it.class File, note that only.class File, and the class The file depends on Tomcat relevant jar The bag is not needed. Why? because $CATALINA_HOME/lib It's already there.

<h3 id="3.3">3.3 stay MBeans Declare your name in the descriptor realm</h3>
What is? MBeans Descriptor?[here](https://tomcat.apache.org/tomcat-7.0-doc/mbeans-descriptor-howto.html). In short, Tomcat uses JMX MBeans technology to realize the remote monitoring and management of Tomcat. Under each package, there must be an MBeans descriptor configuration file called MBeans descriptor XML, if you do not define the configuration file for the custom component, you will throw an "ManagedBean is not found" exception.

mbeans-descriptor.xml The format of the file is as follows:

```java
  <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>

For details, please refer to the mbeans file under the realm package in the Tomcat source code. The configuration file is very important. The attribute element in it directly corresponds to the corresponding instance variable field in the custom real source code, that is, the code I posted above. However, not every instance variable needs to be added. What we add is some important ones that need to be added in the server The attributes specified in the XML file (described later), such as JDBC driver, database user name, password, URL, etc. the attribute name here must be exactly the same as the variable name in the code, and no error is allowed, otherwise the corresponding value cannot be read.

The MBeans descriptor defined by my requirements above is posted below XML file:

This code is by Java Architect must see network-Structure Sorting
<?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>  

You can see the one-to-one correspondence with the important instance variables of the code I posted above.

3.4 print the file compiled by Realm into jar package

Specifically, it is compiled from Realm class file and MBeans descriptor XML file into jar package and put it into $Catalina_ Inside home / lib.

Note: the class file is still in the package and cannot be packaged separately. We will use mbeans descriptor XML file into Class file in the same directory, for example, the package of my custom Realm (for example, CustomRealm.java) is: com ustc. Realm. CustomRealm.java, then Class and mbeans configuration file directories should be:

|-- com |-- ustc |-- realm |-- CustomRealm.class |-- mbeans-descriptor.xml

Then, enter the command line into the com root directory and use the following command to package:

jar cvf customrealm.jar .

customrealm.jar is your own jar name, the second parameter point It cannot be lost, which means packaging the current directory. After the package is successful, the customrealm Jar into $Catalina_ Just inside home / lib.

3.5 configure the standard realm on the server Declare your realm in the XML file

This step is very critical. Open conf / server XML file, search for Realm, and you will see the Realm declaration in the Tomcat configuration file: `` ` java ```Of course, this Realm is invalid, because it is not fully configured. Just as an example, we tell you to reconfigure your own Realm here. We comment out this Realm statement, and then declare our own Realm. How to declare it? It depends on your requirements. Tomcat's official documents contain detailed configuration statements for each standard Realm, such as JNDIRealm. You can declare it in the following format:

<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})"
/>

Note: This is about the configuration of LDAP server. You can query relevant information about how to use LDAP, which is not within the scope of this article.

In JDBCRealm, you can declare as follows:

<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"/>

If you are using Tomcat's own standard Realm, you only need to modify the corresponding attribute value above. What if it's a custom Realm? Then we also need to customize the declaration of Realm. Taking the above requirements as an example, the customized declaration of Realm is as follows:

<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" />

In fact, the above two standard Realm statements are combined to get what they need. Here are two issues to pay attention to:

  • The field name in the Realm declaration must be the same as the Realm source code and MBeans descriptor The field names in the XML file correspond, and the three must be consistent, otherwise the specific values we set here cannot be read;
  • No comment statement is allowed in the Realm declaration, otherwise an error will be reported.

OK, so far, we have finished writing and configuring the custom Realm. The next step is the test. Restart Tomcat and enter the login interface to try.

4. Advantages of realm

*Security: for each existing real implementation, the user's password (by default) is stored in clear text. In many environments, this is not ideal because anyone who sees the authentication data can collect enough information to successfully log in and impersonate other users. To avoid this problem, the standard implementation supports the concept of digesting user passwords. The stored password is encrypted (in a form that is not easy to be converted back), but the real implementation can still use it to authenticate. When a standard realm authenticates by taking the stored password and comparing it with the password value provided by the user, you can Specify the digest attribute on the and select the digested password. The value of this attribute must be Java security. One of the digest algorithms supported by messagedigest class (Sha, MD2, or MD5). When you select this option, the password stored in real must be in the plaintext form of the password, and then encrypted by the specified algorithm. When the authenticate() method of this realm is called, the (plaintext) password specified by the user is encrypted by the same algorithm, and its result is compared with the value returned by realm. If the two values are equal, it means that the plaintext version of the original password is the same as that provided by the user, so the user is authenticated* Convenient debugging: the troubleshooting and exception information of each realm will be recorded by the log configuration related to the container (Context, Host, or Engine) of the realm for our debugging.

reference material

Added by ksteuber on Thu, 10 Mar 2022 06:50:21 +0200