SSO-аутентификация по активному каталогу с использованием sourceforge spnego
Я реализовал SSO-аутентификацию, используя исходный проект spnego.
Я впервые реализую любой вид аутентификации сервлетов, поэтому я могу упустить что-то очень простое с аутентификацией или сервлетами, о которых я просто не знаю...
Я использую SpnegoHttpFilter
который поставляется вместе с библиотекой в верхней части моей цепочки фильтров без переопределений, тогда я включил свой собственный фильтр QueryFilter
затем в цепочке фильтров, чтобы я мог сопоставить имя входа в базу данных user_id. Имя для входа (идентификатор пользователя NT в доменах Windows) возвращается getRemoteUser
вызов после того, как HttpRequest пройдет через SpnegoHttpFilter
Это все, кажется, работает нормально.
Мой собственный фильтр QueryFilter
делает то, что должен, он отображает имя входа в базу данных user_id правильно. У меня также есть логика в этом фильтре для отклонения запросов, которые не проходят мою аутентификацию, это тоже работает нормально: когда я имитирую неавторизованный запрос, этот фильтр останавливает его и никогда не попадает в сервлет.
Проблема в том, что все запросы возвращаются как 401 (статус запроса HTTP не авторизован), даже если они проходят проверку подлинности в моем QueryFilter
и выполнить прекрасно на сервлете.
Я попытался явно определить ответ как 200 (HTTP Request Status OK) в моем собственном фильтре, используя это: myHttpResponse.setStatus(HttpServletResponse.SC_OK)
но это ничего не изменило.
Чтобы изолировать проблему, я удалил HttpSpnegoFilter
и только что передал жестко запрограммированное имя для входа в систему (NT User ID) на мой QueryFilter
, Это работало нормально, и ответов больше не было 401 (Несанкционированный).
Это означает, что в упаковке HttpSpnegoFilter
каким-то образом преобразовывает запрос в Unauthorized
, И делать это так, чтобы оно не менялось, когда я говорю, что на самом деле все в порядке.
Кто-нибудь знает, как я могу установить заголовок ответа, чтобы возвращать как 200 (ОК), используя этот проект spnego sourceforge?
Моя полная цепочка фильтров из веб-приложения web.xml
ниже, как уже упоминалось, я использую упакованный HttpSpnegoFilter
в верхней части цепочки, а затем мой собственный фильтр (который, кажется, выполняет свою работу правильно) прямо под ним:
<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>
Я также включил мой QueryFilter
для полноты ниже (хотя, кажется, это не имеет никакого отношения к моей проблеме, потому что оно работает само по себе, когда я не использую SpnegoHttpFilter
класс и просто передать его жестко закодированный NT User ID). От второй до последней строки я явно говорю ответ OK
но безрезультатно:
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);
}
}
1 ответ
Поскольку мое приложение полностью основано на интрасети, я полностью отказался от протоколов безопасности.
Я просто создал таблицу базы данных со всеми ip-адресами моего домена и столбцом для текущего идентификатора пользователя, времени входа и выхода из системы.
Я написал некоторый код на стороне сервера, чтобы обновлять эту таблицу всякий раз, когда пользователь входит в систему или выходит из активной директории.
Теперь, поскольку мы можем довольно легко получить удаленный адрес, я написал фильтр сервлетов, который:
- проверяет, является ли их атрибут HttpSession для идентификатора пользователя
- если нет, запрашивает у базы данных идентификатор пользователя и сохраняет его в атрибуте сеанса
- выполняет некоторую пользовательскую аутентификацию на основе идентификатора пользователя и либо отклоняет, либо передает запрос
- если запрос прошел, фильтр отправляет сервлету завернутый запрос HttpRequest, который вызывает
getRemoteUser
вызов, чтобы вернуть мой идентификатор сеанса атрибута пользователя.
Я полагаю, что для пользователя в интрасети возможно изменить свой IP-адрес, чтобы скопировать кого-то другого, но когда я попытался, я просто получил ошибки, что существовал дублированный IP-адрес, и я не смог подключиться ни к чему в интрасети.
Обновление (3 месяца спустя):
В итоге я пошел с вафлей. Это было очень легко интегрировать. Мое решение выше не было работоспособным по ряду причин.