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-адресами моего домена и столбцом для текущего идентификатора пользователя, времени входа и выхода из системы.

Я написал некоторый код на стороне сервера, чтобы обновлять эту таблицу всякий раз, когда пользователь входит в систему или выходит из активной директории.

Теперь, поскольку мы можем довольно легко получить удаленный адрес, я написал фильтр сервлетов, который:

  1. проверяет, является ли их атрибут HttpSession для идентификатора пользователя
  2. если нет, запрашивает у базы данных идентификатор пользователя и сохраняет его в атрибуте сеанса
  3. выполняет некоторую пользовательскую аутентификацию на основе идентификатора пользователя и либо отклоняет, либо передает запрос
  4. если запрос прошел, фильтр отправляет сервлету завернутый запрос HttpRequest, который вызывает getRemoteUser вызов, чтобы вернуть мой идентификатор сеанса атрибута пользователя.

Я полагаю, что для пользователя в интрасети возможно изменить свой IP-адрес, чтобы скопировать кого-то другого, но когда я попытался, я просто получил ошибки, что существовал дублированный IP-адрес, и я не смог подключиться ни к чему в интрасети.

Обновление (3 месяца спустя):

В итоге я пошел с вафлей. Это было очень легко интегрировать. Мое решение выше не было работоспособным по ряду причин.

Другие вопросы по тегам