具有多个 WebAppContext 实例的单点登录 Jetty

Single Sign On Jetty with multiple instances of WebAppContext

我有一个嵌入式码头服务器,它正在从许多不同的位置迭代一个 web 应用程序列表(该列表因部署而异)。我正在尝试从基本身份验证过渡到表单身份验证。

我想做的是:

// create constraint
Constraint usersOnly = new Constraint(Constraint.__FORM_AUTH, "user");
usersOnly.setAuthenticate(true);
ConstraintMapping requireAuthentication = new ConstraintMapping();
requireAuthentication.setConstraint(usersOnly);
requireAuthentication.setPathSpec("/*");

// create login service
LoginService loginService = new HashLoginService("realm");
loginService.setConfig("users.txt");

// create form authentication
FormAuthenticator formAuthenticator = new FormAuthenticator("/login", "/login", true);

// create /login route
ServletHolder loginServlet = new ServletHolder(new DefaultServlet() {
@Override
  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    response.getWriter().append("<html>\n<head>\n<title>Login</title>\n</head>\n<body>\n"
    + "<form method='POST' action='/j_security_check'>\n"
    + "<input type='text' name='j_username'/>\n"
    + "<input type='password' name='j_password'/>\n"
    + "<input type='submit' value='Login'/>\n</form>\n</body>\n</html>\n");
  }
});

ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler();
securityHandler.addMapping(requireAuthentication);
securityHandler.setLoginService(loginService);
securityHandler.setAuthenticator(formAuthenticator);

// assign security to each webapp
for (WebAppContext webapp : webapps) {
  webapp.setSecurityHandler(securityHandler);
  webapp.addServlet(loginServlet, "/login");
}

如果 webapps 中只有一个 webapps,这会按预期工作,但如果有多个,每次从一个 webapps 跟随 link 到另一个 webapps 时,以及每次重新验证时,系统都会提示您登录,您将被重定向到路由“/”处的基本 Web 应用程序,并且仅针对该应用程序进行身份验证。

我想获取我的上下文以共享会话。

根据 this question,每个 WebAppContext 实例都有一个通用的 SessionManager 应该可以解决问题,但提问者只有一个 WebAppContext 实例。如果我尝试为每个 WebAppContext 分配相同的 SessionManager 实例,我会得到 NPE。

我还看到一些资源指向将每个上下文的 SessionCookieConfig 的路径设置为公共上下文路径,并将每个 WebAppContext 的 SessionManager 的 useRequestedId 设置为 true,但此解决方案适用于 org.mortbay.jetty 并且已过时。

如果您对为具有多个 WebAppContext 的嵌入式码头服务器设置 SSO 有任何见解或经验,或者如果您能想到一种更好的方法来使用一个公共服务器提供多个不同的 Web 应用程序,请指出我正确的地方方向.

我如何允许用户通过填写​​一个表单对一台服务器处理的所有 Web 应用程序进行身份验证?

如果我不清楚或者您有任何问题,请告诉我。

我的解决方案是扩展 HashSessionManager class 以便它在创建新会话之前查询 SessionIdManager。结果是同一 SessionIdManager 下的 CrossContextSessionManager 实例都共享会话内容,而不仅仅是会话 ID。因此,登录一个webapp就意味着登录所有webapp。

import java.util.Collection;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.eclipse.jetty.server.session.AbstractSession;
import org.eclipse.jetty.server.session.HashSessionIdManager;
import org.eclipse.jetty.server.session.HashSessionManager;

/**
 * Allows the WebAppContext to check the server's SessionIdManager before creating a new session
 * so that WebAppContext can share session contents for each client rather than just session ids.
 */
public class CrossContextSessionManager extends HashSessionManager {

  // Number of seconds before the user is automatically logged out of an idle webapp session
  private int defaultSessionTimeout = 1800;

  /**
   * Check for an existing session in the session id manager by the requested id.
   * If no session has that id, create a new HttpSession for the request.
   */
  @Override
  public HttpSession newHttpSession(HttpServletRequest request) {
    AbstractSession session = null;

    String requestedId = request.getRequestedSessionId();
    if (requestedId != null) {
      String clusterId = getSessionIdManager().getClusterId(requestedId);
      Collection<HttpSession> sessions = ((HashSessionIdManager) getSessionIdManager()).getSession(clusterId);
      for (HttpSession httpSession : sessions) {
        session = (AbstractSession) httpSession;
        break;
      }
    }

    if (session == null) {
      session = newSession(request);
      session.setMaxInactiveInterval(defaultSessionTimeout);
      addSession(session,true);
    }

    return session;
  }
}

如果请求已经携带了一个id,newSessionId 只会从中提取那个id。否则,它将创建一个与所有现有 ID 唯一的新 ID。