Search code examples
javatomcatloggingtomcat-valve

Extending ValveBase in Tomcat always returns null for request.getRemoteUser()


I have tested this in both Tomcat 8.0.51 and 9.0.14 with same results. Running on Ubuntu Linux 16.04.

This example code simply logs all requests to the console (or catalina.out). The main issue is that request.getRemoteUser() always returns null - even when a user is authenticated.

I noticed that the AccessLogValve and JDBCAccessLogValve valves correctly return the login/user name when the user has authenticated. I show this below by showing the access_log lines for the sames lines of code that my Valve logs to the standard console log.

Is there a class I need to extend or implement for my LoginValve to return the login/user when the user is authenticated? I also tried the calls to request.getUserPrincipal() and it also always returns null.

Any help on why this is blocked for the ValveBase but works for AccessLogs is greatly appreciated.

package org.apache.catalina.connector;

import java.io.IOException;
import java.time.LocalDateTime;
import javax.servlet.ServletException;
import javax.servlet.http.HttpSession;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.valves.ValveBase;

public class LoginValve extends ValveBase {
    public void invoke(Request request, Response response) throws IOException, ServletException {
        String reqlogin=request.getRemoteUser();
        String method=request.getMethod();
        String requestURI=request.getRequestURI();
        HttpSession session = request.getRequest().getSession();
        String sid=session.getId();
        String user_id = (String) session.getAttribute("UserID");
        String sesslogin=(String) session.getAttribute("login");  // put in session by app code
        LocalDateTime now=LocalDateTime.now();
        System.out.println("LoginValve: "+now+" sid: "+sid+" UserID: "+user_id+" method: "+method+" requestURI: "+requestURI+" reqlogin: "+reqlogin+" sesslogin: "+sesslogin);

        getNext().invoke(request, response);
    }
}

The output if the LoginValve is below. Note that the field supplied by the call to request.getRemoteUser() is reqlogin. The sesslogin field is populated into the session by the app and has nothing to do with the getRemoteUser() call.

LoginValve: 2019-02-02T15:58:06.034 sid: 79D54B40DB8131E2A74375A67CE72ECD UserID: 3776559 method: GET requestURI: /app/auth/logoff.jsp reqlogin: null sesslogin: joe_user
LoginValve: 2019-02-02T15:58:06.050 sid: 41D1AD1DBBE5FA1D6EE4CB7A350D7DD6 UserID: null method: GET requestURI: /app/index.jsp reqlogin: null sesslogin: null
LoginValve: 2019-02-02T15:58:07.958 sid: 41D1AD1DBBE5FA1D6EE4CB7A350D7DD6 UserID: null method: GET requestURI: /app/user/index.jsp reqlogin: null sesslogin: null
LoginValve: 2019-02-02T15:58:10.006 sid: F75895CAE2521849902CF9894E39A039 UserID: null method: POST requestURI: /app/user/j_security_check reqlogin: null sesslogin: null
LoginValve: 2019-02-02T15:58:10.020 sid: F75895CAE2521849902CF9894E39A039 UserID: null method: GET requestURI: /app/user/index.jsp reqlogin: null sesslogin: null
LoginValve: 2019-02-02T15:58:13.743 sid: 0BBD2544A3C117E32FEE0A03840EAEAE UserID: 3776559 method: GET requestURI: /app/user/transactions.jsp reqlogin: null sesslogin: joe_user
LoginValve: 2019-02-02T15:58:18.127 sid: 0BBD2544A3C117E32FEE0A03840EAEAE UserID: 3776559 method: GET requestURI: /app/user/profile.jsp reqlogin: null sesslogin: joe_user
LoginValve: 2019-02-02T15:58:20.961 sid: 0BBD2544A3C117E32FEE0A03840EAEAE UserID: 3776559 method: GET requestURI: /app/user/feedback.jsp reqlogin: null sesslogin: joe_user
LoginValve: 2019-02-02T15:58:22.930 sid: 0BBD2544A3C117E32FEE0A03840EAEAE UserID: 3776559 method: GET requestURI: /app/user/ reqlogin: null sesslogin: joe_user
LoginValve: 2019-02-02T15:58:24.849 sid: 0BBD2544A3C117E32FEE0A03840EAEAE UserID: 3776559 method: GET requestURI: /app/auth/logoff.jsp reqlogin: null sesslogin: joe_user
LoginValve: 2019-02-02T15:58:24.872 sid: 879B16BB6831D4E07B486C7C0712AE5D UserID: null method: GET requestURI: /app/index.jsp reqlogin: null sesslogin: null

Here is the output of the localhost_access_log file (which does log the user joe_user from its call to request.getRemoteUser() :

127.0.0.1 - joe_user [02/Feb/2019:15:58:06 -0600] "GET /app/auth/logoff.jsp HTTP/1.1" 302 -
127.0.0.1 - - [02/Feb/2019:15:58:06 -0600] "GET /app/index.jsp HTTP/1.1" 200 9303
127.0.0.1 - - [02/Feb/2019:15:58:07 -0600] "GET /app/user/index.jsp HTTP/1.1" 200 6297
127.0.0.1 - - [02/Feb/2019:15:58:10 -0600] "POST /app/user/j_security_check HTTP/1.1" 303 -
127.0.0.1 - joe_user [02/Feb/2019:15:58:10 -0600] "GET /app/user/index.jsp HTTP/1.1" 200 29637
127.0.0.1 - joe_user [02/Feb/2019:15:58:14 -0600] "GET /app/user/transactions.jsp HTTP/1.1" 200 1742146
127.0.0.1 - joe_user [02/Feb/2019:15:58:18 -0600] "GET /app/user/profile.jsp HTTP/1.1" 200 9793
127.0.0.1 - joe_user [02/Feb/2019:15:58:21 -0600] "GET /app/user/feedback.jsp HTTP/1.1" 200 5476
127.0.0.1 - joe_user [02/Feb/2019:15:58:23 -0600] "GET /app/user/ HTTP/1.1" 200 29637
127.0.0.1 - joe_user [02/Feb/2019:15:58:24 -0600] "GET /app/auth/logoff.jsp HTTP/1.1" 302 -
127.0.0.1 - - [02/Feb/2019:15:58:24 -0600] "GET /app/index.jsp HTTP/1.1" 200 9303

Again, the question is, how do I get the call to request.getRemoteUser() to return the logged in user when they are authenticated.


Solution

  • Is there a class I need to extend or implement for my LoginValve to return the login/user when the user is authenticated?

    Yes, you need to implement the Interface AccessLog which states in its doc "Intended for use by a Valve to indicate that the Valve provides access logging."

    This is what are doing both JDBCAccessLogValve and AccessLogValve.

    Here is sample code :

    package org.stack;
    
    import java.io.IOException;
    import java.time.LocalDateTime;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpSession;
    import org.apache.catalina.connector.Request;
    import org.apache.catalina.connector.Response;
    import org.apache.catalina.valves.ValveBase;
    import org.apache.catalina.AccessLog;
    
    public class LoginValve extends ValveBase implements AccessLog {
    
        // Should this valve set request attributes for IP address, hostname, protocol and port used for the request?
        boolean requestAttributesEnabled = false;
    
        @Override
        public void invoke(Request request, Response response) throws IOException, ServletException {
            // null 
            System.out.println("LoginValve-invoke: " + request.getRemoteUser());
            getNext().invoke(request, response);
        }
    
        @Override
        public void log(Request request, Response response, long time) {
            // remote user
            System.out.println("LoginValve-log: " + request.getRemoteUser());
        }
    
        @Override
        public void setRequestAttributesEnabled(boolean requestAttributesEnabled) {
            this.requestAttributesEnabled = requestAttributesEnabled;
        }
    
        @Override
        public boolean getRequestAttributesEnabled() {
            return requestAttributesEnabled;
        }
    }
    

    When enabling tomcat-users.xml file to allow authentication on the manager webapp, I get a positive result :

    curl -u stack:stack 'http://localhost:8080/manager/html'
    ..
    $ grep 'LoginValve' catalina.out
    LoginValve-invoke: null
    LoginValve-log: stack