Search code examples
jsptomcatrealmtomcat9

Tomcat authentication failed after adding UserDatabase ResourceLink


Problem:

After I added a Tomcat ResourceLink <ResourceLink name="UserDatabase" global="UserDatabase" type="org.apache.catalina.UserDatabase"/> my Authentication is broken. I can log in and do a REST CALL that gets me all User from the tomcat-users.xml. Then I just reload the page and there is a 403 - Forbidden error.

I have two Web Applications. One is a REST API that gets data from a database (backend) and the other Web Application is for the administration of the database (frontend).
So I added a FORM authentication for my frontend application and a BASIC authentication for my backend. The users are stored in the tomcat-users.xml file -> everything works fine
Now I want to programmatically edit the tomcat-users with the frontend/backend and I found this how to programmatically add users to tomcat UserDatabaseRealm?
I add the code from the link into my REST API and then I call the URL /api/admin/tomcat/user I got all Users and the User Roles. But after one call on this page, I always get a 403 - Forbidden error!
If I don't do the REST CALL or just remove the ResourceLink the problem does not occur.

Code / Error

(Look at the last two rows at the Tomcat Logs. Why did he found the role in the first time but not in the second one?)

Tomcat Logs:

Logs after I Logged in successfully / first time I call the Page:

[http-nio-8080-exec-8] org.apache.catalina.authenticator.AuthenticatorBase.invoke Security checking request GET /es_admin/admin/setting.jsp
[http-nio-8080-exec-8] org.apache.catalina.authenticator.AuthenticatorBase.invoke We have cached auth type FORM for principal GenericPrincipal[admin(CR8000,SOLIDWORKS,admin,)]
[http-nio-8080-exec-8] org.apache.catalina.realm.RealmBase.findSecurityConstraints   Checking constraint 'SecurityConstraint[Adminstrative Pages]' against GET /admin/setting.jsp --> true
[http-nio-8080-exec-8] org.apache.catalina.realm.RealmBase.findSecurityConstraints   Checking constraint 'SecurityConstraint[User Pages]' against GET /admin/setting.jsp --> false
[http-nio-8080-exec-8] org.apache.catalina.realm.RealmBase.findSecurityConstraints   Checking constraint 'SecurityConstraint[Adminstrative Pages]' against GET /admin/setting.jsp --> true
[http-nio-8080-exec-8] org.apache.catalina.realm.RealmBase.findSecurityConstraints   Checking constraint 'SecurityConstraint[User Pages]' against GET /admin/setting.jsp --> false
[http-nio-8080-exec-8] org.apache.catalina.authenticator.AuthenticatorBase.invoke Calling hasUserDataPermission()
[http-nio-8080-exec-8] org.apache.catalina.realm.RealmBase.hasUserDataPermission   User data constraint has no restrictions
[http-nio-8080-exec-8] org.apache.catalina.authenticator.AuthenticatorBase.invoke Calling authenticate()
[http-nio-8080-exec-8] org.apache.catalina.authenticator.AuthenticatorBase.checkForCachedAuthentication Bereits authentifiziert [admin]
[http-nio-8080-exec-8] org.apache.catalina.authenticator.AuthenticatorBase.invoke Calling accessControl()
[http-nio-8080-exec-8] org.apache.catalina.realm.RealmBase.hasResourcePermission   Checking roles GenericPrincipal[admin(CR8000,SOLIDWORKS,admin,)]
[http-nio-8080-exec-8] org.apache.catalina.realm.RealmBase.hasResourcePermission Role found:  admin
[http-nio-8080-exec-8] org.apache.catalina.authenticator.AuthenticatorBase.invoke Successfully passed all security constraints

Logs after I reload the Page or just switch to another one:

[http-nio-8080-exec-15] org.apache.catalina.authenticator.AuthenticatorBase.invoke Security checking request GET /es_admin/admin/setting.jsp
[http-nio-8080-exec-15] org.apache.catalina.authenticator.AuthenticatorBase.invoke We have cached auth type FORM for principal GenericPrincipal[admin(CR8000,SOLIDWORKS,admin,)]
[http-nio-8080-exec-15] org.apache.catalina.realm.RealmBase.findSecurityConstraints   Checking constraint 'SecurityConstraint[User Pages]' against GET /admin/setting.jsp --> false
[http-nio-8080-exec-15] org.apache.catalina.realm.RealmBase.findSecurityConstraints   Checking constraint 'SecurityConstraint[Adminstrative Pages]' against GET /admin/setting.jsp --> true
[http-nio-8080-exec-15] org.apache.catalina.realm.RealmBase.findSecurityConstraints   Checking constraint 'SecurityConstraint[User Pages]' against GET /admin/setting.jsp --> false
[http-nio-8080-exec-15] org.apache.catalina.realm.RealmBase.findSecurityConstraints   Checking constraint 'SecurityConstraint[Adminstrative Pages]' against GET /admin/setting.jsp --> true
[http-nio-8080-exec-15] org.apache.catalina.authenticator.AuthenticatorBase.invoke Calling hasUserDataPermission()
[http-nio-8080-exec-15] org.apache.catalina.realm.RealmBase.hasUserDataPermission   User data constraint has no restrictions
[http-nio-8080-exec-15] org.apache.catalina.authenticator.AuthenticatorBase.invoke Calling authenticate()
[http-nio-8080-exec-15] org.apache.catalina.authenticator.AuthenticatorBase.checkForCachedAuthentication Bereits authentifiziert [admin]
[http-nio-8080-exec-15] org.apache.catalina.authenticator.AuthenticatorBase.invoke Calling accessControl()
[http-nio-8080-exec-15] org.apache.catalina.realm.RealmBase.hasResourcePermission   Checking roles GenericPrincipal[admin(CR8000,SOLIDWORKS,admin,)]
[http-nio-8080-exec-15] org.apache.catalina.realm.RealmBase.hasResourcePermission No role found:  admin
[http-nio-8080-exec-15] org.apache.catalina.authenticator.AuthenticatorBase.invoke Failed accessControl() test

web.xml - frontend

 <!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
  <display-name>frontend</display-name>
   <welcome-file-list>
        <welcome-file>login.jsp</welcome-file>
   </welcome-file-list>

    <!--Defines Security Constraint -->
    <security-constraint>
        <display-name>frontend admin</display-name>
        <web-resource-collection>
            <web-resource-name>Adminstrative Pages</web-resource-name>
            <description/>
            <url-pattern>/admin/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <description/>
            <role-name>admin</role-name>
        </auth-constraint>
    </security-constraint>

    <security-constraint>
        <display-name>frontend user</display-name>
        <web-resource-collection>
            <web-resource-name>User Pages</web-resource-name>
            <description/>
            <url-pattern>/user/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <description/>
            <role-name>SOLIDWORKS</role-name>
        </auth-constraint>
        <auth-constraint>
            <description/>
            <role-name>CR8000</role-name>
        </auth-constraint>
    </security-constraint>

<!--Defines Login Config -->
    <login-config>
        <auth-method>FORM</auth-method>
        <realm-name>file</realm-name>
        <form-login-config>
            <form-login-page>/login.jsp</form-login-page>
            <form-error-page>/error.jsp</form-error-page>
        </form-login-config>
    </login-config>

<!--Defines Security Role -->
    <security-role>
        <description/>
        <role-name>admin</role-name>
    </security-role>
    <security-role>
        <description/>
        <role-name>SOLIDWORKS</role-name>
    </security-role>
    <security-role>
        <description/>
        <role-name>CR8000</role-name>
    </security-role>
</web-app>

context.xml

<Context>
    <!-- Default set of monitored resources. If one of these changes, the    -->
    <!-- web application will be reloaded.                                   -->
    <WatchedResource>WEB-INF/web.xml</WatchedResource>
    <WatchedResource>WEB-INF/tomcat-web.xml</WatchedResource>
    <WatchedResource>${catalina.base}/conf/web.xml</WatchedResource>

    <ResourceLink name="UserDatabase" global="UserDatabase" type="org.apache.catalina.UserDatabase" />

</Context>

tomcat-users.xml

<?xml version="1.0" encoding="UTF-8"?>
<tomcat-users xmlns="http://tomcat.apache.org/xml"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://tomcat.apache.org/xml tomcat-users.xsd"
              version="1.0">

<role rolename="admin"/>
<role rolename="CR8000"/>
<role rolename="SOLIDWORKS"/>

<user username="admin" password="password" roles="admin,CR8000,SOLIDWORKS"/>
<user username="testSolid" password="password" roles="SOLIDWORKS"/>
<user username="testZuken" password="password" roles="CR8000"/>
</tomcat-users>

Realm Config

<Realm className="org.apache.catalina.realm.LockOutRealm">
    <Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase"/>
</Realm>

After this Method the error came

    @GET
    @Path("/tomcat/user")
    @Produces(MediaType.APPLICATION_JSON)
    public ArrayList<TomcatUser> getTomcatUser() {

        getLogWriter().writeLog("GET call on /tomcat/user");
        ArrayList<TomcatUser> tomcatUsers = new ArrayList<TomcatUser>();

        try {
            UserDatabase ud =  (UserDatabase) new InitialContext().lookup("java:comp/env/UserDatabase");

            ud.open();

            Iterator<org.apache.catalina.User> iteratorUsers = ud.getUsers();

            while (iteratorUsers.hasNext()) {
                org.apache.catalina.User user = iteratorUsers.next();

                TomcatUser tmpTomcatUser = new TomcatUser();
                String username = user.getUsername();

                Iterator<Role> iteratorRoles = user.getRoles();
                ArrayList<Role> roles = new ArrayList<Role>();

                // Change from Iterator to ArrayList
                iteratorRoles.forEachRemaining(roles::add);

                ArrayList<String> strRoles = new ArrayList<String>();

                for (int x = 0; x < roles.size(); x++) {
                    strRoles.add(roles.get(x).getRolename());
                }

                tmpTomcatUser.setUsername(username);
                tmpTomcatUser.setRoles(strRoles);

                tomcatUsers.add(tmpTomcatUser);
                tmpTomcatUser = null;
            }

            ud.close();

        } catch (Exception e) {
            e.printStackTrace();
        }

        return tomcatUsers;
    }

Solution

  • I found two issues in my servlet to solve the problem:

    1. I removed the method call .open() from my UserDatabase
    2. I have to return a String and not the TomcatUser Object. To return a String I format my Object with Gson to a JSON String.

    So this is my final servlet method:

    @GET
    @Path("/tomcat/user")
    @Produces(MediaType.APPLICATION_JSON)
    public String getTomcatUser() {
    
        getLogWriter().writeLog("GET call on /tomcat/user");
        ArrayList<TomcatUser> tomcatUsers = new ArrayList<TomcatUser>();
    
        try {
            UserDatabase ud =  (UserDatabase) new InitialContext().lookup("java:comp/env/UserDatabase");
    
            Iterator<org.apache.catalina.User> iteratorUsers = ud.getUsers();
    
            while (iteratorUsers.hasNext()) {
    
                org.apache.catalina.User user = iteratorUsers.next();
                System.out.println(user.getUsername());
    
                TomcatUser tmpTomcatUser = new TomcatUser();
                String username = user.getUsername();
    
                Iterator<Role> iteratorRoles = user.getRoles();
                ArrayList<String> strRoles = new ArrayList<String>();
    
                while (iteratorRoles.hasNext()) {
                    Role role = iteratorRoles.next();
                    strRoles.add(role.getRolename());
                }
    
                tmpTomcatUser.setUsername(username);
                tmpTomcatUser.setRoles(strRoles);
    
                tomcatUsers.add(tmpTomcatUser);
                tmpTomcatUser = null;
            }
    
    
        } catch (Exception e) {
            e.printStackTrace();
        }
    
        return new Gson().toJson(tomcatUsers);
    }