Merge remote-tracking branch 'origin/jetty-10.0.x' into jetty-11.0.x

This commit is contained in:
gregw 2023-09-18 16:08:28 +10:00
commit 9d96b4fc74
16 changed files with 189 additions and 53 deletions

View File

@ -3,7 +3,7 @@
<!-- =============================================================== -->
<!-- Configure the test-jndi webapp -->
<!-- =============================================================== -->
<Configure id='wac' class="org.eclipse.jetty.webapp.WebAppContext">
<Configure id="wac" class="org.eclipse.jetty.webapp.WebAppContext">
<New id="tx" class="org.eclipse.jetty.plus.jndi.Transaction">
<Arg>
@ -21,7 +21,7 @@
<!-- Define an env entry with Server scope for java:comp/env -->
<New id="woggle" class="org.eclipse.jetty.plus.jndi.EnvEntry">
<Arg>
<Property name='server' />
<Ref refid="Server" />
</Arg>
<Arg>woggle</Arg>
<Arg type="java.lang.Integer">4000</Arg>
@ -31,7 +31,7 @@
<!-- Define an env entry with webapp scope for java:comp/env -->
<New id="wiggle" class="org.eclipse.jetty.plus.jndi.EnvEntry">
<Arg>
<Ref refid='wac' />
<Ref refid="wac" />
</Arg>
<Arg>wiggle</Arg>
<Arg type="java.lang.Double">100</Arg>
@ -41,7 +41,7 @@
<!-- Mail Session setup -->
<New id="xxxmail" class="org.eclipse.jetty.plus.jndi.Resource">
<Arg>
<Ref refid='wac' />
<Ref refid="wac" />
</Arg>
<Arg>mail/Session</Arg>
<Arg>
@ -63,7 +63,7 @@
<!-- A mock DataSource -->
<New id="mydatasource" class="org.eclipse.jetty.plus.jndi.Resource">
<Arg>
<Ref refid='wac' />
<Ref refid="wac" />
</Arg>
<Arg>jdbc/mydatasource</Arg>
<Arg>

View File

@ -16,9 +16,6 @@ etc/jetty-deploy.xml
[ini-template]
# Monitored directory name (relative to $jetty.base)
# jetty.deploy.monitoredDir=webapps
# - OR -
# Monitored directory path (fully qualified)
# jetty.deploy.monitoredPath=/var/www/webapps
# Defaults Descriptor for all deployed webapps
# jetty.deploy.defaultsDescriptorPath=${jetty.base}/etc/webdefault.xml

View File

@ -37,4 +37,4 @@ jetty.session.hazelcast.hazelcastInstanceName=JETTY_DISTRIBUTED_SESSION_INSTANCE
jetty.session.hazelcast.useQueries=false
jetty.session.gracePeriod.seconds=3600
jetty.session.savePeriod.seconds=0
#jetty.session.hazelcast.configurationLocation
#jetty.session.hazelcast.configurationLocation=

View File

@ -38,5 +38,5 @@ jetty.session.hazelcast.onlyClient=true
jetty.session.hazelcast.useQueries=false
jetty.session.gracePeriod.seconds=3600
jetty.session.savePeriod.seconds=0
#jetty.session.hazelcast.configurationLocation
#jetty.session.hazelcast.configurationLocation=
#jetty.session.hazelcast.addresses=

View File

@ -117,7 +117,21 @@ public interface Authenticator
IdentityService getIdentityService();
/**
* Should session ID be renewed on authentication.
* @return true if the session ID should be renewed on authentication
*/
boolean isSessionRenewedOnAuthentication();
/**
* Get the interval in seconds, which if non-zero, will be set
* with {@link jakarta.servlet.http.HttpSession#setMaxInactiveInterval(int)}
* when a session is newly authenticated
* @return An interval in seconds; or 0 to not set the interval
* on authentication; or a negative number to make the
* session never timeout after authentication.
*/
int getSessionMaxInactiveIntervalOnAuthentication();
}
/**

View File

@ -68,7 +68,8 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
private final Map<String, String> _initParameters = new HashMap<>();
private LoginService _loginService;
private IdentityService _identityService;
private boolean _renewSession = true;
private boolean _renewSessionOnAuthentication = true;
private int _sessionMaxInactiveIntervalOnAuthentication = 0;
static
{
@ -433,7 +434,7 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
@Override
public boolean isSessionRenewedOnAuthentication()
{
return _renewSession;
return _renewSessionOnAuthentication;
}
/**
@ -446,7 +447,26 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
*/
public void setSessionRenewedOnAuthentication(boolean renew)
{
_renewSession = renew;
_renewSessionOnAuthentication = renew;
}
@Override
public int getSessionMaxInactiveIntervalOnAuthentication()
{
return _sessionMaxInactiveIntervalOnAuthentication;
}
/**
* Set the interval in seconds, which if non-zero, will be set with
* {@link jakarta.servlet.http.HttpSession#setMaxInactiveInterval(int)}
* when a session is newly authenticated.
* @param seconds An interval in seconds; or 0 to not set the interval
* on authentication; or a negative number to make the
* session never timeout after authentication.
*/
public void setSessionMaxInactiveIntervalOnAuthentication(int seconds)
{
_sessionMaxInactiveIntervalOnAuthentication = seconds;
}
/*

View File

@ -71,4 +71,10 @@ public class WrappedAuthConfiguration implements AuthConfiguration
{
return _configuration.isSessionRenewedOnAuthentication();
}
@Override
public int getSessionMaxInactiveIntervalOnAuthentication()
{
return _configuration.getSessionMaxInactiveIntervalOnAuthentication();
}
}

View File

@ -33,7 +33,8 @@ public abstract class LoginAuthenticator implements Authenticator
protected LoginService _loginService;
protected IdentityService _identityService;
private boolean _renewSession;
private boolean _sessionRenewedOnAuthentication;
private int _sessionMaxInactiveIntervalOnAuthentication;
protected LoginAuthenticator()
{
@ -87,7 +88,8 @@ public abstract class LoginAuthenticator implements Authenticator
_identityService = configuration.getIdentityService();
if (_identityService == null)
throw new IllegalStateException("No IdentityService for " + this + " in " + configuration);
_renewSession = configuration.isSessionRenewedOnAuthentication();
_sessionRenewedOnAuthentication = configuration.isSessionRenewedOnAuthentication();
_sessionMaxInactiveIntervalOnAuthentication = configuration.getSessionMaxInactiveIntervalOnAuthentication();
}
public LoginService getLoginService()
@ -109,35 +111,41 @@ public abstract class LoginAuthenticator implements Authenticator
*/
protected HttpSession renewSession(HttpServletRequest request, HttpServletResponse response)
{
HttpSession httpSession = request.getSession(false);
HttpSession session = request.getSession(false);
if (_renewSession && httpSession != null)
if (session != null && (_sessionRenewedOnAuthentication || _sessionMaxInactiveIntervalOnAuthentication != 0))
{
synchronized (httpSession)
synchronized (session)
{
//if we should renew sessions, and there is an existing session that may have been seen by non-authenticated users
//(indicated by SESSION_SECURED not being set on the session) then we should change id
if (httpSession.getAttribute(Session.SESSION_CREATED_SECURE) != Boolean.TRUE)
if (_sessionMaxInactiveIntervalOnAuthentication != 0)
session.setMaxInactiveInterval(_sessionMaxInactiveIntervalOnAuthentication < 0 ? -1 : _sessionMaxInactiveIntervalOnAuthentication);
if (_sessionRenewedOnAuthentication)
{
if (httpSession instanceof Session)
//if we should renew sessions, and there is an existing session that may have been seen by non-authenticated users
//(indicated by SESSION_SECURED not being set on the session) then we should change id
if (session.getAttribute(Session.SESSION_CREATED_SECURE) != Boolean.TRUE)
{
Session s = (Session)httpSession;
String oldId = s.getId();
s.renewId(request);
s.setAttribute(Session.SESSION_CREATED_SECURE, Boolean.TRUE);
if (s.isIdChanged() && (response instanceof Response))
((Response)response).replaceCookie(s.getSessionHandler().getSessionCookie(s, request.getContextPath(), request.isSecure()));
if (LOG.isDebugEnabled())
LOG.debug("renew {}->{}", oldId, s.getId());
if (session instanceof Session)
{
Session s = (Session)session;
String oldId = s.getId();
s.renewId(request);
s.setAttribute(Session.SESSION_CREATED_SECURE, Boolean.TRUE);
if (s.isIdChanged() && (response instanceof Response))
((Response)response).replaceCookie(s.getSessionHandler().getSessionCookie(s, request.getContextPath(), request.isSecure()));
if (LOG.isDebugEnabled())
LOG.debug("renew {}->{}", oldId, s.getId());
}
else
{
LOG.warn("Unable to renew session {}", session);
}
return session;
}
else
{
LOG.warn("Unable to renew session {}", httpSession);
}
return httpSession;
}
}
}
return httpSession;
return session;
}
}

View File

@ -55,6 +55,7 @@ import org.eclipse.jetty.server.UserIdentity;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.HandlerWrapper;
import org.eclipse.jetty.server.session.Session;
import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.TypeUtil;
@ -80,6 +81,7 @@ import static org.hamcrest.Matchers.startsWith;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class ConstraintTest
@ -89,6 +91,8 @@ public class ConstraintTest
private LocalConnector _connector;
private ConstraintSecurityHandler _security;
private HttpConfiguration _config;
private ContextHandler _contextHandler;
private SessionHandler _sessionhandler;
private Constraint _forbidConstraint;
private Constraint _authAnyRoleConstraint;
private Constraint _authAdminConstraint;
@ -106,8 +110,8 @@ public class ConstraintTest
_config = _connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration();
_server.setConnectors(new Connector[]{_connector});
ContextHandler contextHandler = new ContextHandler();
SessionHandler sessionHandler = new SessionHandler();
_contextHandler = new ContextHandler();
_sessionhandler = new SessionHandler();
TestLoginService loginService = new TestLoginService(TEST_REALM);
@ -118,14 +122,14 @@ public class ConstraintTest
loginService.putUser("user3", new Password("password"), new String[]{"foo"});
loginService.putUser("user4", new Password("password"), new String[]{"A", "B", "C", "D"});
contextHandler.setContextPath("/ctx");
_server.setHandler(contextHandler);
contextHandler.setHandler(sessionHandler);
_contextHandler.setContextPath("/ctx");
_server.setHandler(_contextHandler);
_contextHandler.setHandler(_sessionhandler);
_server.addBean(loginService);
_security = new ConstraintSecurityHandler();
sessionHandler.setHandler(_security);
_sessionhandler.setHandler(_security);
RequestHandler requestHandler = new RequestHandler(new String[]{"user", "user4"}, new String[]{"user", "foo"});
_security.setHandler(requestHandler);
@ -1189,6 +1193,93 @@ public class ConstraintTest
assertThat(response, not(containsString("JSESSIONID=" + session)));
}
public static Stream<Arguments> onAuthenticationTests()
{
return Stream.of(
Arguments.of(false, 0),
Arguments.of(false, -1),
Arguments.of(false, 2400),
Arguments.of(true, 0),
Arguments.of(true, -1),
Arguments.of(true, 2400)
);
}
@ParameterizedTest
@MethodSource("onAuthenticationTests")
public void testSessionOnAuthentication(boolean sessionRenewOnAuthentication, int sessionMaxInactiveIntervalOnAuthentication) throws Exception
{
final int UNAUTH_SECONDS = 1200;
// Use a FormAuthenticator as an example of session authentication
_security.setAuthenticator(new FormAuthenticator("/testLoginPage", "/testErrorPage", false));
_sessionhandler.setMaxInactiveInterval(UNAUTH_SECONDS);
_security.setSessionRenewedOnAuthentication(sessionRenewOnAuthentication);
_security.setSessionMaxInactiveIntervalOnAuthentication(sessionMaxInactiveIntervalOnAuthentication);
_server.start();
String response;
response = _connector.getResponse("GET /ctx/auth/info HTTP/1.0\r\n\r\n");
assertThat(response, containsString(" 302 Found"));
assertThat(response, containsString("/ctx/testLoginPage"));
assertThat(response, containsString("JSESSIONID="));
String sessionId = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf("; Path=/ctx"));
response = _connector.getResponse("GET /ctx/testLoginPage HTTP/1.0\r\n" +
"Cookie: JSESSIONID=" + sessionId + "\r\n" +
"\r\n");
assertThat(response, containsString(" 200 OK"));
assertThat(response, containsString("URI=/ctx/testLoginPage"));
assertThat(response, not(containsString("JSESSIONID=" + sessionId)));
response = _connector.getResponse("POST /ctx/j_security_check HTTP/1.0\r\n" +
"Cookie: JSESSIONID=" + sessionId + "\r\n" +
"Content-Type: application/x-www-form-urlencoded\r\n" +
"Content-Length: 35\r\n" +
"\r\n" +
"j_username=user&j_password=password");
assertThat(response, startsWith("HTTP/1.1 302 "));
assertThat(response, containsString("Location"));
assertThat(response, containsString("/ctx/auth/info"));
if (sessionRenewOnAuthentication)
{
// check session ID has changed.
assertNull(_sessionhandler.getSession(sessionId));
assertThat(response, containsString("Set-Cookie:"));
assertThat(response, containsString("JSESSIONID="));
assertThat(response, not(containsString("JSESSIONID=" + sessionId)));
sessionId = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf("; Path=/ctx"));
}
else
{
// check session ID has not changed.
assertThat(response, not(containsString("Set-Cookie:")));
assertThat(response, not(containsString("JSESSIONID=")));
}
if (sessionMaxInactiveIntervalOnAuthentication == 0)
{
// check max interval has not been updated
Session session = _sessionhandler.getSession(_sessionhandler.getSessionIdManager().getId(sessionId));
assertThat(session.getMaxInactiveInterval(), is(UNAUTH_SECONDS));
}
else
{
// check max interval has not been updated
Session session = _sessionhandler.getSession(_sessionhandler.getSessionIdManager().getId(sessionId));
assertThat(session.getMaxInactiveInterval(), is(sessionMaxInactiveIntervalOnAuthentication));
}
// check session still there.
response = _connector.getResponse("GET /ctx/auth/info HTTP/1.0\r\n" +
"Cookie: JSESSIONID=" + sessionId + "\r\n" +
"\r\n");
assertThat(response, startsWith("HTTP/1.1 200 OK"));
}
@Test
public void testFormPostRedirect() throws Exception
{

View File

@ -9,7 +9,7 @@
<New id="DebugListener" class="org.eclipse.jetty.server.DebugListener">
<Arg name="outputStream">
<New class="org.eclipse.jetty.util.RolloverFileOutputStream">
<Arg type="String"><Property name="jetty.logs" default="./logs"/>/yyyy_mm_dd.debug.log</Arg>
<Arg type="String"><Property name="jetty.debug.logs" deprecated="jetty.logs" default="./logs"/>/yyyy_mm_dd.debug.log</Arg>
<Arg type="boolean"><Property name="jetty.debug.append" default="true"/></Arg>
<Arg type="int"><Property name="jetty.debug.retainDays" default="14"/></Arg>
<Arg>

View File

@ -43,9 +43,6 @@
<Set name="acceptedTcpNoDelay"><Property name="jetty.http.acceptedTcpNoDelay" default="true"/></Set>
<Set name="acceptedReceiveBufferSize" property="jetty.http.acceptedReceiveBufferSize" />
<Set name="acceptedSendBufferSize" property="jetty.http.acceptedSendBufferSize" />
<Get name="SelectorManager">
<Set name="connectTimeout"><Property name="jetty.http.connectTimeout" default="15000"/></Set>
</Get>
</New>
</Arg>
</Call>

View File

@ -36,9 +36,6 @@
<Set name="acceptedTcpNoDelay"><Property name="jetty.ssl.acceptedTcpNoDelay" default="true"/></Set>
<Set name="acceptedReceiveBufferSize" property="jetty.ssl.acceptedReceiveBufferSize" />
<Set name="acceptedSendBufferSize" property="jetty.ssl.acceptedSendBufferSize" />
<Get name="SelectorManager">
<Set name="connectTimeout" property="jetty.ssl.connectTimeout"/>
</Get>
</New>
</Arg>
</Call>

View File

@ -23,6 +23,12 @@ etc/jetty-debug.xml
## How many days to retain old log files
# jetty.debug.retainDays=14
## Should existing log be appended to
# jetty.debug.append=true
## Log directory for jetty debug logs
# jetty.debug.logs=./logs
## Timezone of the log entries
# jetty.debug.timezone=GMT

View File

@ -18,9 +18,6 @@ etc/jetty-gzip.xml
## Minimum content length after which gzip is enabled
# jetty.gzip.minGzipSize=32
## Check whether a file with *.gz extension exists
# jetty.gzip.checkGzExists=false
## Inflate request buffer size, or 0 for no request inflation
# jetty.gzip.inflateBufferSize=0

View File

@ -39,6 +39,9 @@ etc/jetty.xml
## Max response content write length that is buffered (in bytes)
# jetty.httpConfig.outputAggregationSize=8192
## If HTTP/1.x persistent connections should be enabled
# jetty.httpConfig.persistentConnectionsEnabled=true
## Max request headers size (in bytes)
# jetty.httpConfig.requestHeaderSize=8192

View File

@ -828,7 +828,7 @@ public class SessionHandler extends ScopedHandler
/**
* Sets the max period of inactivity, after which the session is invalidated, in seconds.
*
* @param seconds the max inactivity period, in seconds.
* @param seconds the max inactivity period, in seconds. If less than or equal to zero, then the session is immortal
* @see #getMaxInactiveInterval()
*/
public void setMaxInactiveInterval(int seconds)