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

Java 具有多个WebAppContext实例的单点登录Jetty,java,servlets,jetty,single-sign-on,form-authentication,Java,Servlets,Jetty,Single Sign On,Form Authentication,我有一个嵌入式jetty服务器,它正在迭代来自许多不同位置的Web应用程序列表(列表因部署而异)。我正在尝试从基本身份验证过渡到表单身份验证 我想做的是: // create constraint Constraint usersOnly = new Constraint(Constraint.__FORM_AUTH, "user"); usersOnly.setAuthenticate(true); ConstraintMapping requireAuthentication = new C

我有一个嵌入式jetty服务器,它正在迭代来自许多不同位置的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");
}
//创建约束
Constraint usersOnly=新约束(Constraint.\u FORM\u AUTH,“user”);
usersOnly.setAuthenticate(true);
ConstraintMapping requireAuthentication=新的ConstraintMapping();
requireAuthentication.setConstraint(usersOnly);
requireAuthentication.setPathSpec(“/*”);
//创建登录服务
LoginService LoginService=新的HashLoginService(“领域”);
loginService.setConfig(“users.txt”);
//创建表单身份验证
FormAuthenticator FormAuthenticator=新的FormAuthenticator(“/login”,“/login”,true);
//创建/登录路由
ServletHolder loginServlet=新的ServletHolder(新的DefaultServlet(){
@凌驾
受保护的void doGet(HttpServletRequest请求,HttpServletResponse响应)抛出ServletException,IOException{
response.getWriter().append(“\n\nLogin\n\n\n”
+“\n”
+“\n”
+“\n”
+“\n\n\n\n”);
}
});
ConstraintSecurityHandler securityHandler=新的ConstraintSecurityHandler();
securityHandler.addMapping(需要重新验证);
securityHandler.setLoginService(loginService);
setAuthenticator(formAuthenticator);
//为每个webapp分配安全性
用于(WebAppContext webapp:webapps){
webapp.setSecurityHandler(securityHandler);
addServlet(loginServlet,“/login”);
}
如果webapps中只有一个webapp,则此功能可以正常工作,但如果存在多个webapp,则每次跟踪从一个webapp到另一个webapp的链接时,都会提示您登录,并且每次重新验证时,您都会被重定向到路由“/”处的基本webapp,并且仅对该webapp进行身份验证

我想让我的上下文共享会话

根据,为每个WebAppContext实例提供一个公共会话管理器应该可以解决这个问题,但是询问者只有一个WebAppContext实例。如果我尝试将同一个SessionManager实例分配给每个WebAppContext,就会得到NPE

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

如果您对使用多个WebAppContext为嵌入式jetty服务器设置SSO有任何见解或经验,或者如果您能想出更好的方法使用一个通用服务器为多个不同的webapps提供服务,请为我指出正确的方向

我如何允许用户通过填写一个表单来验证由一台服务器处理的所有webapp


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

我的解决方案是扩展HashSessionManager类,以便它在创建新会话之前查询SessiondManager。结果是,同一SessiondManager下的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;
  }
}
import java.util.Collection;
导入javax.servlet.http.HttpServletRequest;
导入javax.servlet.http.HttpSession;
导入org.eclipse.jetty.server.session.AbstractSession;
导入org.eclipse.jetty.server.session.hashSessiondManager;
导入org.eclipse.jetty.server.session.HashSessionManager;
/**
*允许WebAppContext在创建新会话之前检查服务器的会话管理器
*因此WebAppContext可以共享每个客户端的会话内容,而不仅仅是会话ID。
*/
公共类CrossContextSessionManager扩展了HashSessionManager{
//用户自动注销空闲webapp会话前的秒数
private int defaultSessionTimeout=1800;
/**
*根据请求的id在会话id管理器中检查现有会话。
*如果没有会话具有该id,请为请求创建新的HttpSession。
*/
@凌驾
公共HttpSession newHttpSession(HttpServletRequest请求){
AbstractSession=null;
String requestedId=request.getRequestedSessionId();
if(requestedId!=null){
字符串clusterId=getSessionIdManager().getClusterId(RequesteId);
收集会话=((HashSessionIdManager)getSessionIdManager()).getSession(clusterId);
用于(HttpSession HttpSession:会话){
会话=(AbstractSession)httpSession;
打破
}
}
if(会话==null){
会话=新闻会话(请求);
setMaxInactiveInterval(defaultSessionTimeout);
addSession(session,true);
}
返回会议;
}
}

如果请求已经带有一个id,newSessionId将从中取出该id。否则,它将从所有现有id中创建一个唯一的新id。

您是否在询问如何将基于Jetty容器的安全性(JAAS的form post变体)扩展到Jetty服务器中运行的多个应用程序?是的,特别是单点登录。确定。。我在那里没有经验。但如果所有这些都失败了,您可以在“应用程序内”而不是“容器内”进行安全保护。使用过滤器等工具。有许多可用的安全过滤器、spring security等可以为您做到这一点。容器是否专门将用户发送到表单servlet,并且随后您的用户没有被转发到其原始请求的URL?如何以及在何处初始化此CrossContextSessionManager?我们正在使用Equinox OSGI容器来启动嵌入式Jetty。所以我想知道在哪里可以连接到启动过程来传递这个CrossContextSessionManager。你能分享一些代码片段来说明你是如何初始化它的吗?对于(WebAppContext webapp:webapps){CrossContextSessionManager sessionManager=new CrossCon