【问题标题】:Tomcat authentication with request headers带有请求标头的 Tomcat 身份验证
【发布时间】:2016-03-15 03:21:20
【问题描述】:

我有一个在 Tomcat 7 上运行的 Web 应用程序。目前用户通过 JDBCRealm 使用密码登录。

在我的组织中有一个身份验证服务器。用户向该服务器发送 HTTP 请求,服务器以某种方式对其进行身份验证,然后使用自定义 HTTP 标头将请求转发到我的应用程序,该标头指定经过身份验证的用户名(无密码)。

我想使用这种机制对我的应用程序的用户进行身份验证,同时保留 JDBC 领域。当用户向/login 发送请求时,他们的请求将使用标头进行身份验证,并且他们将被重定向到主页,就像他们使用标准j_security_check 表单登录一样,但无需提供密码。

这是我到目前为止的想法:

@WebFilter("/login/*")
public class LoginFilter implements Filter {

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

    HttpServletRequest httpRequest = (HttpServletRequest) req;
    HttpServletResponse httpResponse = (HttpServletResponse) res;

    String username = ... // Extract username from httpRequest, not an issue

    httpRequest.login(username, "???"); // Need to provide a password!

    httpResponse.sendRedirect(httpRequest.getContextPath() + "/pages/home.xhtml");

  }

}

当我为 JDBCRealm 提供正确的密码时,此方法有效,但该密码在过滤器中不可用。

更新:我最终用自定义阀门解决了这个问题:

public class LoginValve extends ValveBase {

    @Override
    public void invoke(Request req, Response res) throws IOException, ServletException {

    if (!req.getRequestURI().equals(req.getContextPath() + "/login")) {
        getNext().invoke(req, res);
        return;
    }

    Session session = req.getSessionInternal();

    if (session.getPrincipal() == null) {
        String username = ... // From req
        List<String> roles = ... // From req
        session.setPrincipal(new GenericPrincipal(username, null, roles));
    }

    req.setUserPrincipal(session.getPrincipal());

    getNext().invoke(req, res);

}

我从稍后调用的过滤器进行重定向。

【问题讨论】:

  • 只从与领域使用的相同用户数据库中获取它?
  • 密码是在数据库中散列的,而且我不需要密码,我只是想将用户设置为已登录,这似乎很浪费资源。
  • 然后将领域配置为不再对其进行哈希处理。
  • 我无法以明文形式保存密码
  • 那么用户注册是通过同一个领域发生的吗?这是非常出乎意料的,因为您显然有一个单独的身份验证服务器。好吧,只需添加另一个不会散列密码并将用作只读领域的领域,这样您就可以使用已经散列的密码登录。

标签: tomcat servlets login passwords jdbcrealm


【解决方案1】:

我还差三个声望点才能发表评论,所以我必须根据给定的信息来回答,用猜测填补一些空白。

您可以通过继承javax.servlet.http.HttpServletRequestWrapper来实现“身份验证”。

覆盖 getRemoteUser() 和 getUserPrincipal() 以便它们返回经过身份验证的用户的用户名和一个 Principal 对象,该对象基本上可以是您想要的任何东西。 JMXPrincipal 应该使用方便。您可能还应该重写 getAuthType() 以返回 HttpServletRequest.FORM_AUTH 以使所有内容都很好地啮合。并根据 Javadoc 实现一个快速 logout() 方法,该方法说“将 null 设置为在请求上调用 getUserPrincipal、getRemoteUser 和 getAuthType 时返回的值。”

然后,在您的过滤器中,将传入的 HttpServletRequest 包装在您的包装器对象中,并将包装器对象传递给 chain.doFilter()。

如果你想变得花哨,你也可以在包装器上重写 login() 方法,它接受任何(或空)密码并创建将由 getRemoteUser()、getUserPrincipal() 返回的内部实例变量和 getAuthType()。

【讨论】:

  • 我对 HttpServletRequestWrapper 进行了子类化,当过滤器设置为 @WebFilter("/*") 时,您的解决方案似乎有效。但是,当我将其设置为 @WebFilter("/login/*") 时,我收到“找不到资源”错误,因为在 chain.doFilter 之后什么都没有。我需要以某种方式将新请求重定向到“/pages/home.xhtml”,但 sendRedirect 不采用 doFilter 之类的请求参数。我还尝试使用 getRequestDispatcher(...).forward(...),初始重定向有效并且用户已登录,但所有后续 AJAX 请求似乎都被破坏了。
  • 我不知道这是否是导致 AJAX 失败的原因,但我注意到一些奇怪的事情:在每个 XHR 上,服务器都会使用新的 JSESSIONID cookie 进行响应,即使过滤器只被调用一次
  • 感谢支持我回答的人。它让我超过了发表评论的门槛。
  • @liad,JSESSIONID cookie 问题让我问,所有请求,包括 XHR、图像等,都是以反向代理方式通过身份验证服务器,还是只是一些?还是认证服务器使用 302 响应将请求重定向到应用服务器?
  • 请记住,会话 cookie 与主机名相关联。由于身份验证服务器正在插入请求标头,我假设这意味着会话 cookie(至少来自身份验证请求)与身份验证服务器的主机名相关联。任何未通过身份验证服务器的后续请求都将没有正确的会话 cookie。当您说每个 XHR 响应都设置了一个新的 JSESSIONID cookie 时,这表明请求没有发送任何 JSESSIONID cookie?如果没有,为什么不呢?
猜你喜欢
  • 2015-07-20
  • 2017-05-08
  • 2014-02-01
  • 1970-01-01
  • 2015-09-21
  • 2020-05-06
  • 1970-01-01
  • 2016-07-05
  • 2012-11-29
相关资源
最近更新 更多