Wednesday, April 28, 2010

Custom Realm in Tomcat 6.0.20

I needed to define a custom realm in Tomcat, in order to customize the way user accounts are checked before being allowed to login. I found several websites that explained bits and pieces, but nothing that explained the whole thing in a way that someone who has never worked with custom realms (i.e., me) could understand. Here is an explanation.

Disclaimer: please note that when I say things like "You should not do X," I am explaining a way to get this up and running simply. This is the way that worked for me. You might need to make changes based on your configuration or if you want it to work differently than I wanted it to work. If you know what you're doing or want to experiment, don't let my suggestions stop you! Of course if that's the case, you probably don't need to read this.

First some links. These helped me, but did not get me all the way there.
Apache Tomcat Configuration Reference
Realm Configuration HOW-TO
HOW-TO again - the section of the document that (at the end) has a very brief paragraph listing some necessary steps.
MBean Descriptor How To
Tomcat 6.0 API

Now, the steps I followed:

  1. Write a class that implements org.apache.catalina.Realm. In my case, I chose to subclass JDBCRealm. This is where you do whatever customization it is that you need to.

  2. In the same package (the same folder) as the class you wrote in the previous step, place a file called mbeans-descriptor.xml. Any name for the XML file should work, since you'll be pointing Tomcat to it later. If you already have one of these and you know what you're doing, you could just add an mbean tag to it. If you have no idea what an MBean is, here is an example:


    <?xml version="1.0" encoding="UTF-8"?>

    <!DOCTYPE mbeans-descriptors PUBLIC
    "-//Apache Software Foundation//DTD Model MBeans Configuration File"
    "http://jakarta.apache.org/commons/dtds/mbeans-descriptors.dtd">
    <mbeans-descriptors>


    <mbean name="MyCustomRealmClass"
    className="org.apache.catalina.mbeans.ClassNameMBean"
    description="My description for this realm"
    domain="Catalina"
    group="Realm"
    type="me.custom.MyCustomRealmClass">

    <attribute name="className"
    description="Fully qualified class name of the managed object"
    type="java.lang.String"
    writeable="false"/>

    <attribute name="connectionURL"
    description="Database URL"
    type="String"/>

    <attribute name="connectionName"
    description="Database connection username"
    type="String"/>

    <attribute name="connectionPassword"
    description="Database connection password"
    type="String"/>

    <attribute name="connectionPassword"
    description="Database connection password"
    type="String"/>

    <attribute name="debug"
    description="The debugging detail level for this component"
    type="int"/>

    <attribute name="driverName"
    description="Database driver"
    type="String"/>

    <attribute name="userTable"
    description="The name of the user account database table"
    type="String"/>

    <attribute name="userNameCol"
    description="The name of the column that holds the username in the database"
    type="String"/>

    <attribute name="userCredCol"
    description="The name of the column that holds the user's encrypted password in the database"
    type="String"/>

    <attribute name="digest"
    description="The encryption method for the user's password"
    type="String"/>

    <attribute name="userRoleTable"
    description="Name of the database table that holds user roles"
    type="String"/>

    <attribute name="roleNameCol"
    description="Name of the column that holds user roles in the database user role table"
    type="String"/>

    <attribute name="validate"
    description="Validate"
    type="Boolean"/>



    </mbean>
    </mbeans-descriptors>


    I went ahead and gave you the whole file that I used.

    In the mbean tag, the only values you should change are the name, description, and type. Don't change className, unless you really know what you're doing.

    Now, what the heck are those attributes? They are whatever fields you want your custom realm to be given when it is created. Take connectionName, for example. We're declaring it here as a String. In your custom realm class, you should provide a getter and a setter for it:

    public void setConnectionName(String connectionName) {
    this.connectionName = connectionName; // or whatever
    }

    public String getConnectionName() {
    return connectionName; // or whatever
    }


    If your custom realm extends an existing realm, as mine does, then you do not have to re-define these getters and setters if they already exist in the superclass. You do still have to declare the fields in your mbean XML.

  3. Point $CATALINA_HOME/conf/server.xml to the mbean XML file. To do this, modify the following Listener to include the location of that XML file; it should look like this:
    <listener classname="org.apache.catalina.mbeans.ServerLifecycleListener"
    descriptors="/me/custom/mbeans-descriptor.xml">
    </listener>


    The descriptors attribute is the one you will/may need to add.

  4. In your application's context.xml file, declare your use of the realm as you would with a built-in realm. Here is where you specify the values to be given to your realm when it is created - those fields you declared in the mbeans file, which you created getters and setters for in your custom realm class. Here is an example - you will obviously want to use different values:

    <Realm className="me.custom.MyCustomRealmClass"
    debug="9"
    connectionURL="jdbc:sqlserver://localhost:1433; SelectMethod=cursor; databasename=MyDbName;"
    connectionName="myAppUser"
    connectionPassword="myPassword"
    driverName="com.microsoft.sqlserver.jdbc.SQLServerDriver"
    userTable="UUSER"
    userNameCol="username"
    userCredCol="passwordEncrypt"
    digest="SHA-256"
    userRoleTable="UUSER"
    roleNameCol="role"
    validate="true" />


  5. Whenever I start up Tomcat, Tomcat copies my web app's context file from webapps/<webapp name>/META-INF/context.xml into $CATALINA_HOME/conf/Catalina/localhost/<webapp name>.xml. Redeploying the web app the dirty way (shutting off Tomcat and replacing the appropriate directory within the webapps folder, then turning on Tomcat again) leaves the old version of this context file in $CATALINA_HOME/conf/Catalina/localhost/. I have to manually delete it before restarting Tomcat in order to get the new version to copy into that directory. Having an old version of your context file there means any changes you make won't actually take effect - at least not this custom realm example.

    I suspect if I cleanly redeployed the webapp, this would not be a problem. However, I have not tried that, and I wanted to point it out.

No comments:

Post a Comment