Question

I have implemented SSO authentication using the sourceforge spnego project.

This is my first time implementing any kind of servlet authentication so I may be missing something very basic with authentication or servlets that I just don't know about...

I am using the SpnegoHttpFilter that comes packaged with the library at the top of my filter chain with no overrides, then I included my own filter QueryFilter next in the filter chain so that I can map the logon name to the database user_id. The logon name (NT User ID on windows domains) is returned by a getRemoteUser call after the HttpRequest passes through the SpnegoHttpFilter, this all seems to be working fine.

My own filter QueryFilter is doing what it is supposed to do, it is mapping the logon name to the database user_id correctly. I also have logic in this filter to reject requests that don't pass my authentication, this is working fine also: when I simulate an unauthorized request this filter stops it and it never makes it to the servlet.

The trouble is that all requests return as 401 (HTTP Request Status Unauthorized) even when they pass authentication in my QueryFilter and execute totally fine on the servlet.

I tried explicitly defining the response as 200 (HTTP Request Status OK) in my own filter using this: myHttpResponse.setStatus(HttpServletResponse.SC_OK) but that didn't change anything.

To isolate the problem I removed the HttpSpnegoFilter altogether and just passed a hard-coded logon name (NT User ID) to my QueryFilter. This worked fine and the responses were no longer 401 (Unauthorized).

That means the packaged HttpSpnegoFilter is somehow converting the request to Unauthorized. And doing it in a way that it doesn't change when I say it is actually OK.

Does anyone know how I can set the response header to return as 200 (OK) using this spnego sourceforge project?

My full filter chain from the web-app's web.xml is below, as mentioned I use the packaged HttpSpnegoFilter at the top of the chain and then my own filter (which seems to be doing it's job correctly) right below it:

<filter>
    <filter-name>SpnegoHttpFilter</filter-name>
    <filter-class>net.sourceforge.spnego.SpnegoHttpFilter</filter-class>

    <init-param>
        <param-name>spnego.allow.basic</param-name>
        <param-value>true</param-value>
    </init-param>

    <init-param>
        <param-name>spnego.allow.delegation</param-name>
        <param-value>true</param-value>
    </init-param>

    <init-param>
        <param-name>spnego.allow.localhost</param-name>
        <param-value>true</param-value>
    </init-param>

    <init-param>
        <param-name>spnego.allow.unsecure.basic</param-name>
        <param-value>true</param-value>
    </init-param>

    <init-param>
        <param-name>spnego.login.client.module</param-name>
        <param-value>spnego-client</param-value>
    </init-param>

    <init-param>
        <param-name>spnego.krb5.conf</param-name>
        <param-value>krb5.conf</param-value>
    </init-param>

    <init-param>
        <param-name>spnego.login.conf</param-name>
        <param-value>login.conf</param-value>
    </init-param>

    <init-param>
        <param-name>spnego.preauth.username</param-name>
        <param-value>myADServicePrincipal</param-value>
    </init-param>

    <init-param>
        <param-name>spnego.preauth.password</param-name>
        <param-value>myADServicePrincipalPassword</param-value>
    </init-param>

    <init-param>
        <param-name>spnego.login.server.module</param-name>
        <param-value>spnego-server</param-value>
    </init-param>

    <init-param>
        <param-name>spnego.prompt.ntlm</param-name>
        <param-value>true</param-value>
    </init-param>

    <init-param>
        <param-name>spnego.logger.level</param-name>
        <param-value>1</param-value>
    </init-param>
</filter>

<filter-mapping>
    <filter-name>SpnegoHttpFilter</filter-name>
    <servlet-name>QueryServlet</servlet-name>
</filter-mapping>

<filter>
    <filter-name>QueryFilter</filter-name>
    <filter-class>my.package.name.QueryFilter</filter-class>

    <init-param>
        <param-name>query.permission.list</param-name>
        <param-value>getQueryPermission</param-value>
    </init-param>

    <init-param>
        <param-name>remote.user.column</param-name>
        <param-value>nt_user_id</param-value>
    </init-param>

    <init-param>
        <param-name>user.id.column</param-name>
        <param-value>user_id</param-value>
    </init-param>
</filter>

<filter-mapping>
    <filter-name>QueryFilter</filter-name>
    <servlet-name>QueryServlet</servlet-name>
</filter-mapping>

I also included my QueryFilter for completeness below (Even though it doesn't seem to have any bearing on my problem because it works fine by itself when I don't use the SpnegoHttpFilter class and just pass it a hard coded NT User ID). The second to last line is where I explicitly tell the response to be OK to no avail:

import java.io.IOException;
import java.util.Map;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public final class QueryFilter implements Filter {

    private MapListDAO myMapListDAO;
    private String myPermissionsList;
    private String myRemoteUserColumn;
    private String myUserIdColumn;


    @Override
    public void init(final FilterConfig filterConfig) throws ServletException {
        myMapListDAO = Config.getInstance(filterConfig.getServletContext()).getMapListDAO();
        myPermissionsList = filterConfig.getInitParameter("query.permission.list");
        myRemoteUserColumn = filterConfig.getInitParameter("remote.user.column");
        myUserIdColumn = filterConfig.getInitParameter("user.id.column");
    }

    @Override
    public void destroy() {
        // TODO ...?
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
            throws IOException, ServletException {

        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        String queryName = request.getParameter("queryName");
        // because I have SpnegoHttpFilter earlier in my filter chain
        // this returns the NT User ID (what the user logged in to the domain with)
        String remoteUser = httpRequest.getRemoteUser();
        Map<String, Object> queryPermissions = myMapListDAO.getEntry(myPermissionsList, myRemoteUserColumn, remoteUser);

        // if there is no queryName defined
        if (null == queryName) {
            httpResponse.sendError(HttpServletResponse.SC_BAD_REQUEST, 
                    "Missing queryName parameter.");
            return;
        }

        // if this query is protected perform the gauntlet
        if (myMapListDAO.getList(myPermissionsList).get(0).containsKey(queryName)) {

            // if there is no remoteUser
            if (null == remoteUser || remoteUser.isEmpty()) {
                httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, 
                        "Cannot get remoteUser.");
                return;
            }

            // if the remoteUser does not have any queryPermissions
            if (null == queryPermissions) {
                httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, 
                        "Cannot find queryPermissions for " + remoteUser + ".");
                return;
            }

            // if this remoteUser does not have permission to execute the queryName
            if ((Boolean) queryPermissions.get(queryName)) {
                httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, 
                        "The remoteUser: " + remoteUser + " does not have permission to access queryName: " + queryName + ".");
                return;
            }

        }

        // attempt to add the userId to this request as an attribute we can get later
        if (null != queryPermissions) {
            httpRequest.setAttribute("userId", String.valueOf(queryPermissions.get(myUserIdColumn)));
        }

        // continue to servlet
        httpResponse.setStatus(HttpServletResponse.SC_OK);
        chain.doFilter(request, response);
    }

}

    // attempt to add the userId to this request as an attribute we can get later
    if (null != queryPermissions) {
        httpRequest.setAttribute("userId", String.valueOf(queryPermissions.get(myUserIdColumn)));
    }

    // continue to servlet
    httpResponse.setStatus(HttpServletResponse.SC_OK);
    chain.doFilter(request, response);
}

}

Was it helpful?

Solution

Because my app is wholly intranet based I ended up dropping security protocols altogether.

I simply created a database table with all of my domain ip-addresses and a column for the current user ID, logon time and logoff time.

I wrote some server side code to update this table whenever a user logs on or logs off of active directory.

Now, because we can get the remote address pretty easy, I wrote a servlet filter that:

  1. checks if their is an HttpSession attribute for the user ID
  2. if not, queries the database for the user ID and stores it in a session attribute
  3. performs some custom authentication based on the user ID and either rejects or passes the request
  4. if the request passed the filter sends my servlet a wrapped HttpRequest that forces getRemoteUser call to return my user ID session attribute.

I suppose it would be possible for a user on the intranet to change their IP address to copy someone else, but when I tried I just got errors that a duplicate IP address existed and I wasn't able to connect to anything on the intranet.

Update (3 months later):

I ended up going with waffle in the end. It was very easy to integrate. My solution above was not workable for a number of reasons.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top