Reputation: 11486
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);
}
}
Upvotes: 1
Views: 3887
Reputation: 11486
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:
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.
Upvotes: 3