【问题标题】:SSO authentication against active directory using sourceforge spnego使用 sourceforge spnego 对活动目录进行 SSO 身份验证
【发布时间】:2012-05-06 08:23:13
【问题描述】:

我已经使用sourceforge spnego project 实现了 SSO 身份验证。

这是我第一次实现任何类型的 servlet 身份验证,所以我可能会遗漏一些非常基本的身份验证或我不知道的 servlet...

我使用的是SpnegoHttpFilter,它与我的过滤器链顶部的库一起打包,没有覆盖,然后我在过滤器链中包含我自己的过滤器QueryFilter,以便我可以将登录名映射到数据库用户 ID。 HttpRequest 通过SpnegoHttpFilter 后,getRemoteUser 调用返回登录名(Windows 域上的 NT 用户 ID),这一切似乎都工作正常。

我自己的过滤器QueryFilter 正在做它应该做的事情,它将登录名正确地映射到数据库user_id。我在此过滤器中也有逻辑来拒绝未通过我的身份验证的请求,这也可以正常工作:当我模拟未经授权的请求时,此过滤器将其停止并且永远不会到达 servlet。

问题是 所有 请求返回为 401(HTTP 请求状态未经授权),即使它们在我的 QueryFilter 中通过了身份验证并在 servlet 上执行完全正常。

我尝试在我自己的过滤器中使用 myHttpResponse.setStatus(HttpServletResponse.SC_OK) 将响应明确定义为 200(HTTP 请求状态正常),但这并没有改变任何东西。

为了隔离问题,我完全删除了HttpSpnegoFilter,只是将一个硬编码的登录名(NT 用户 ID)传递给了我的QueryFilter。这工作正常,响应不再是 401(未经授权)。

这意味着打包的HttpSpnegoFilter 以某种方式将请求转换为Unauthorized。并且以一种当我说它实际上没问题时它不会改变的方式来做。

有谁知道如何使用这个 spnego sourceforge 项目将响应标头设置为返回 200(OK)?

下面是我来自网络应用程序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 用户 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);
}

}

【问题讨论】:

    标签: java authentication servlets servlet-filters spnego


    【解决方案1】:

    因为我的应用完全基于 Intranet,所以我最终完全放弃了安全协议。

    我只是创建了一个数据库表,其中包含我所有的域 IP 地址和一个用于当前用户 ID、登录时间和注销时间的列。

    每当用户登录或注销活动目录时,我都会编写一些服务器端代码来更新此表。

    现在,因为我们可以很容易地获得远程地址,所以我编写了一个 servlet 过滤器:

    1. 检查它们是否是用户 ID 的 HttpSession 属性
    2. 如果没有,则在数据库中查询用户 ID 并将其存储在会话属性中
    3. 根据用户 ID 执行一些自定义身份验证并拒绝或通过请求
    4. 如果请求通过过滤器,则会向我的 servlet 发送一个封装的 HttpRequest,该请求会强制 getRemoteUser 调用返回我的用户 ID 会话属性。

    我认为 Intranet 上的用户可以更改他们的 IP 地址以复制其他人,但是当我尝试时,我得到了错误,指出存在重复的 IP 地址并且我无法连接到任何内网。

    更新(3 个月后):

    我最终选择了waffle。它很容易集成。由于多种原因,我的上述解决方案不可行。

    【讨论】:

      猜你喜欢
      • 2010-09-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-07-15
      • 1970-01-01
      • 2013-09-28
      • 2019-07-05
      相关资源
      最近更新 更多