From 32c17858e13146a61caa25830d3cbf20097e31aa Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 27 Sep 2014 12:30:35 +0200 Subject: [PATCH] =?UTF-8?q?[Major]=20Added=20Session=20timeout=20handling?= =?UTF-8?q?=20SessionHandler=20now=20has=20a=20timer=20checking=20to=20mak?= =?UTF-8?q?e=20sure=20that=20sessions=20which=20haven=E2=80=99t=20been=20a?= =?UTF-8?q?ccessed=20for=20a=20time=20defined=20by=20=E2=80=98session.ttl.?= =?UTF-8?q?minutes=E2=80=99=20are=20invalidated?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ch.eitchnet.privilege | 2 +- .../DefaultStrolchPrivilegeHandler.java | 36 +----- .../runtime/privilege/PrivilegeHandler.java | 7 ++ .../rest/DefaultStrolchSessionHandler.java | 116 +++++++++++++----- .../strolch/rest/RestfulStrolchComponent.java | 5 +- .../strolch/rest/StrolchSessionHandler.java | 8 +- .../rest/endpoint/AuthenticationService.java | 20 ++- .../strolch/rest/endpoint/VersionQuery.java | 8 +- .../filters/AuthenicationRequestFilter.java | 8 +- .../config/StrolchConfiguration.xml | 2 +- .../WEB-INF/config/StrolchConfiguration.xml | 2 +- 11 files changed, 122 insertions(+), 92 deletions(-) diff --git a/ch.eitchnet.privilege b/ch.eitchnet.privilege index aa16887d6..67271d611 160000 --- a/ch.eitchnet.privilege +++ b/ch.eitchnet.privilege @@ -1 +1 @@ -Subproject commit aa16887d674876a160bb8002a8d7899c85b2f7b1 +Subproject commit 67271d611ee66d67477843ca7fc388e5fb50750c diff --git a/li.strolch.agent/src/main/java/li/strolch/runtime/privilege/DefaultStrolchPrivilegeHandler.java b/li.strolch.agent/src/main/java/li/strolch/runtime/privilege/DefaultStrolchPrivilegeHandler.java index 460167f08..ee0b934a3 100644 --- a/li.strolch.agent/src/main/java/li/strolch/runtime/privilege/DefaultStrolchPrivilegeHandler.java +++ b/li.strolch.agent/src/main/java/li/strolch/runtime/privilege/DefaultStrolchPrivilegeHandler.java @@ -106,13 +106,6 @@ public class DefaultStrolchPrivilegeHandler extends StrolchComponent implements } } - /** - * @param username - * @param password - * @return - * - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#authenticate(String, byte[]) - */ @Override public Certificate authenticate(String username, byte[] password) { assertContainerStarted(); @@ -123,46 +116,29 @@ public class DefaultStrolchPrivilegeHandler extends StrolchComponent implements } } - /** - * @param certificate - * @throws PrivilegeException - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#isCertificateValid(Certificate) - */ @Override public void isCertificateValid(Certificate certificate) throws PrivilegeException { assertContainerStarted(); this.privilegeHandler.isCertificateValid(certificate); } - /** - * @param certificate - * @return - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#invalidateSession(ch.eitchnet.privilege.model.Certificate) - */ + @Override + public void checkPassword(Certificate certificate, byte[] password) throws PrivilegeException { + assertContainerStarted(); + this.privilegeHandler.checkPassword(certificate, password); + } + @Override public boolean invalidateSession(Certificate certificate) { assertContainerStarted(); return this.privilegeHandler.invalidateSession(certificate); } - /** - * @param certificate - * @return - * @throws PrivilegeException - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#getPrivilegeContext(ch.eitchnet.privilege.model.Certificate) - */ @Override public PrivilegeContext getPrivilegeContext(Certificate certificate) throws PrivilegeException { return this.privilegeHandler.getPrivilegeContext(certificate); } - /** - * @param systemUsername - * @param action - * @throws PrivilegeException - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#runAsSystem(java.lang.String, - * ch.eitchnet.privilege.handler.SystemUserAction) - */ @Override public void runAsSystem(String systemUsername, SystemUserAction action) throws PrivilegeException { this.privilegeHandler.runAsSystem(systemUsername, action); diff --git a/li.strolch.agent/src/main/java/li/strolch/runtime/privilege/PrivilegeHandler.java b/li.strolch.agent/src/main/java/li/strolch/runtime/privilege/PrivilegeHandler.java index 3977ac0d3..0175dc083 100644 --- a/li.strolch.agent/src/main/java/li/strolch/runtime/privilege/PrivilegeHandler.java +++ b/li.strolch.agent/src/main/java/li/strolch/runtime/privilege/PrivilegeHandler.java @@ -73,4 +73,11 @@ public interface PrivilegeHandler { public abstract ch.eitchnet.privilege.handler.PrivilegeHandler getPrivilegeHandler(Certificate certificate) throws PrivilegeException; + /** + * @param certificate + * @param password + * @throws PrivilegeException + * @see {@link ch.eitchnet.privilege.handler.PrivilegeHandler#checkPassword(Certificate, byte[])} + */ + public void checkPassword(Certificate certificate, byte[] password) throws PrivilegeException; } \ No newline at end of file diff --git a/li.strolch.rest/src/main/java/li/strolch/rest/DefaultStrolchSessionHandler.java b/li.strolch.rest/src/main/java/li/strolch/rest/DefaultStrolchSessionHandler.java index bfef478d4..c527bd4e3 100644 --- a/li.strolch.rest/src/main/java/li/strolch/rest/DefaultStrolchSessionHandler.java +++ b/li.strolch.rest/src/main/java/li/strolch/rest/DefaultStrolchSessionHandler.java @@ -18,12 +18,19 @@ package li.strolch.rest; import java.text.MessageFormat; import java.util.HashMap; import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.TimeUnit; import li.strolch.agent.api.ComponentContainer; import li.strolch.agent.api.StrolchComponent; import li.strolch.exception.StrolchException; import li.strolch.runtime.configuration.ComponentConfiguration; import li.strolch.runtime.privilege.PrivilegeHandler; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.utils.dbc.DBC; @@ -32,11 +39,12 @@ import ch.eitchnet.utils.dbc.DBC; */ public class DefaultStrolchSessionHandler extends StrolchComponent implements StrolchSessionHandler { - private static final String PARAM_SESSION_ORIGIN = "session.origin"; //$NON-NLS-1$ - private static final String PARAM_VALIDATE_ORIGIN = "validateOrigin"; //$NON-NLS-1$ + private static final Logger logger = LoggerFactory.getLogger(DefaultStrolchSessionHandler.class); + private static final String PARAM_SESSION_TTL_MINUTES = "session.ttl.minutes"; //$NON-NLS-1$ private PrivilegeHandler privilegeHandler; private Map certificateMap; - private boolean validateOrigin; + private long sessionTtl; + private Timer sessionTimeoutTimer; /** * @param container @@ -48,7 +56,7 @@ public class DefaultStrolchSessionHandler extends StrolchComponent implements St @Override public void initialize(ComponentConfiguration configuration) { - this.validateOrigin = configuration.getBoolean(PARAM_VALIDATE_ORIGIN, false); + this.sessionTtl = TimeUnit.MINUTES.toMillis(configuration.getInt(PARAM_SESSION_TTL_MINUTES, 30)); super.initialize(configuration); } @@ -56,17 +64,30 @@ public class DefaultStrolchSessionHandler extends StrolchComponent implements St public void start() { this.privilegeHandler = getContainer().getComponent(PrivilegeHandler.class); this.certificateMap = new HashMap<>(); + + this.sessionTimeoutTimer = new Timer("SessionTimeoutTimer"); + long checkInterval = TimeUnit.MINUTES.toMillis(1); + this.sessionTimeoutTimer.schedule(new SessionTimeoutTask(), checkInterval, checkInterval); + super.start(); } @Override public void stop() { if (this.certificateMap != null) { - for (Certificate certificate : this.certificateMap.values()) { - this.privilegeHandler.invalidateSession(certificate); + synchronized (this.certificateMap) { + for (Certificate certificate : this.certificateMap.values()) { + this.privilegeHandler.invalidateSession(certificate); + } + this.certificateMap.clear(); } - this.certificateMap.clear(); } + + if (this.sessionTimeoutTimer != null) { + this.sessionTimeoutTimer.cancel(); + } + + this.sessionTimeoutTimer = null; this.privilegeHandler = null; super.stop(); } @@ -78,52 +99,89 @@ public class DefaultStrolchSessionHandler extends StrolchComponent implements St } @Override - public Certificate authenticate(String origin, String username, byte[] password) { + public Certificate authenticate(String username, byte[] password) { DBC.PRE.assertNotEmpty("Origin must be set!", username); //$NON-NLS-1$ DBC.PRE.assertNotEmpty("Username must be set!", username); //$NON-NLS-1$ DBC.PRE.assertNotNull("Passwort must be set", password); //$NON-NLS-1$ - Certificate certificate = this.privilegeHandler.authenticate(username, password); - certificate.getSessionDataMap().put(PARAM_SESSION_ORIGIN, origin); - this.certificateMap.put(certificate.getAuthToken(), certificate); + synchronized (this.certificateMap) { + Certificate certificate = this.privilegeHandler.authenticate(username, password); + this.certificateMap.put(certificate.getAuthToken(), certificate); - return certificate; + logger.info(this.certificateMap.size() + " sessions currently active."); + return certificate; + } } @Override - public Certificate validate(String origin, String authToken) { - DBC.PRE.assertNotEmpty("Origin must be set!", origin); //$NON-NLS-1$ + public Certificate validate(String authToken) { DBC.PRE.assertNotEmpty("SessionId must be set!", authToken); //$NON-NLS-1$ - Certificate certificate = this.certificateMap.get(authToken); + Certificate certificate; + synchronized (this.certificateMap) { + certificate = this.certificateMap.get(authToken); + } + if (certificate == null) throw new StrolchException(MessageFormat.format("No certificate exists for sessionId {0}", authToken)); //$NON-NLS-1$ + return validate(certificate); + } + + @Override + public Certificate validate(Certificate certificate) { this.privilegeHandler.isCertificateValid(certificate); - - if (this.validateOrigin && !origin.equals(certificate.getSessionDataMap().get(PARAM_SESSION_ORIGIN))) { - String msg = MessageFormat.format("Illegal request for origin {0} and sessionId {1}", origin, authToken); //$NON-NLS-1$ - throw new StrolchException(msg); - } - + certificate.setLastAccess(System.currentTimeMillis()); return certificate; } @Override - public void invalidateSession(String origin, Certificate certificate) { - DBC.PRE.assertNotEmpty("Origin must be set!", origin); //$NON-NLS-1$ + public void invalidateSession(Certificate certificate) { DBC.PRE.assertNotNull("Certificate must bet given!", certificate); //$NON-NLS-1$ - if (this.validateOrigin && !origin.equals(certificate.getSessionDataMap().get(PARAM_SESSION_ORIGIN))) { - String msg = MessageFormat.format("Illegal request for origin {0} and sessionId {1}", origin, //$NON-NLS-1$ - certificate.getAuthToken()); - throw new StrolchException(msg); + Certificate removedCert; + synchronized (this.certificateMap) { + removedCert = this.certificateMap.remove(certificate.getAuthToken()); } - - Certificate removedCert = this.certificateMap.remove(certificate.getAuthToken()); if (removedCert == null) logger.error(MessageFormat.format("No session was registered with token {0}", certificate.getAuthToken())); //$NON-NLS-1$ this.privilegeHandler.invalidateSession(certificate); } + + /** + * @return the certificateMap + */ + protected Map getCertificateMap() { + return this.certificateMap; + } + + /** + * Simpler {@link TimerTask} to check for sessions which haven't been active for + * {@link DefaultStrolchSessionHandler#PARAM_SESSION_TTL_MINUTES} minutes. + * + * @author Robert von Burg + */ + private class SessionTimeoutTask extends TimerTask { + + @Override + public void run() { + + Map map = getCertificateMap(); + Map certificateMap; + synchronized (map) { + certificateMap = new HashMap<>(map); + } + + long reqLastAccessTime = System.currentTimeMillis() - sessionTtl; + + for (Certificate certificate : certificateMap.values()) { + if (certificate.getLastAccess() < reqLastAccessTime) { + String msg = "Session {0} for user {1} has expired, invalidating session..."; + logger.info(MessageFormat.format(msg, certificate.getAuthToken(), certificate.getUsername())); + invalidateSession(certificate); + } + } + } + } } diff --git a/li.strolch.rest/src/main/java/li/strolch/rest/RestfulStrolchComponent.java b/li.strolch.rest/src/main/java/li/strolch/rest/RestfulStrolchComponent.java index a875c7572..5d2f77f14 100644 --- a/li.strolch.rest/src/main/java/li/strolch/rest/RestfulStrolchComponent.java +++ b/li.strolch.rest/src/main/java/li/strolch/rest/RestfulStrolchComponent.java @@ -19,7 +19,6 @@ import li.strolch.agent.api.ComponentContainer; import li.strolch.agent.api.StrolchComponent; import li.strolch.rest.filters.AccessControlResponseFilter; import li.strolch.runtime.configuration.ComponentConfiguration; -import li.strolch.runtime.privilege.PrivilegeHandler; import ch.eitchnet.utils.dbc.DBC; /** @@ -82,7 +81,7 @@ public class RestfulStrolchComponent extends StrolchComponent { return getContainer().getComponent(clazz); } - public PrivilegeHandler getPrivilegeHandler() { - return getContainer().getPrivilegeHandler(); + public StrolchSessionHandler getSessionHandler() { + return getContainer().getComponent(StrolchSessionHandler.class); } } diff --git a/li.strolch.rest/src/main/java/li/strolch/rest/StrolchSessionHandler.java b/li.strolch.rest/src/main/java/li/strolch/rest/StrolchSessionHandler.java index ab228549d..ac0962e10 100644 --- a/li.strolch.rest/src/main/java/li/strolch/rest/StrolchSessionHandler.java +++ b/li.strolch.rest/src/main/java/li/strolch/rest/StrolchSessionHandler.java @@ -22,9 +22,11 @@ import ch.eitchnet.privilege.model.Certificate; */ public interface StrolchSessionHandler { - public Certificate authenticate(String origin, String username, byte[] password); + public Certificate authenticate(String username, byte[] password); - public Certificate validate(String origin, String authToken); + public Certificate validate(String authToken); - public void invalidateSession(String origin, Certificate certificate); + public Certificate validate(Certificate certificate); + + public void invalidateSession(Certificate certificate); } diff --git a/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/AuthenticationService.java b/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/AuthenticationService.java index 5852eda62..0fd91094e 100644 --- a/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/AuthenticationService.java +++ b/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/AuthenticationService.java @@ -20,7 +20,6 @@ import java.util.Arrays; import java.util.List; import java.util.Locale; -import javax.servlet.http.HttpServletRequest; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.POST; @@ -62,7 +61,7 @@ public class AuthenticationService { @POST @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) - public Response login(Login login, @Context HttpServletRequest request, @Context HttpHeaders headers) { + public Response login(Login login, @Context HttpHeaders headers) { LoginResult loginResult = new LoginResult(); GenericEntity entity = new GenericEntity(loginResult, LoginResult.class) { @@ -84,16 +83,14 @@ public class AuthenticationService { return Response.status(Status.UNAUTHORIZED).entity(loginResult).build(); } - StrolchSessionHandler sessionHandler = RestfulStrolchComponent.getInstance().getComponent( - StrolchSessionHandler.class); - String origin = request == null ? "test" : request.getRemoteAddr(); //$NON-NLS-1$ - Certificate certificate = sessionHandler.authenticate(origin, login.getUsername(), login.getPassword() - .getBytes()); + RestfulStrolchComponent restfulStrolchComponent = RestfulStrolchComponent.getInstance(); + StrolchSessionHandler sessionHandler = restfulStrolchComponent.getComponent(StrolchSessionHandler.class); + Certificate certificate = sessionHandler.authenticate(login.getUsername(), login.getPassword().getBytes()); Locale locale = RestfulHelper.getLocale(headers); certificate.setLocale(locale); - PrivilegeHandler privilegeHandler = RestfulStrolchComponent.getInstance().getPrivilegeHandler(); + PrivilegeHandler privilegeHandler = restfulStrolchComponent.getContainer().getPrivilegeHandler(); PrivilegeContext privilegeContext = privilegeHandler.getPrivilegeContext(certificate); loginResult.setSessionId(certificate.getAuthToken()); loginResult.setUsername(certificate.getUsername()); @@ -124,7 +121,7 @@ public class AuthenticationService { @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) @Path("{authToken}") - public Response logout(@PathParam("authToken") String authToken, @Context HttpServletRequest request) { + public Response logout(@PathParam("authToken") String authToken) { LogoutResult logoutResult = new LogoutResult(); @@ -135,9 +132,8 @@ public class AuthenticationService { StrolchSessionHandler sessionHandlerHandler = RestfulStrolchComponent.getInstance().getComponent( StrolchSessionHandler.class); - String origin = request == null ? "test" : request.getRemoteAddr(); //$NON-NLS-1$ - Certificate certificate = sessionHandlerHandler.validate(origin, authToken); - sessionHandlerHandler.invalidateSession(origin, certificate); + Certificate certificate = sessionHandlerHandler.validate(authToken); + sessionHandlerHandler.invalidateSession(certificate); logoutResult.setMsg(MessageFormat.format("{0} has been logged out.", certificate.getUsername())); //$NON-NLS-1$ return Response.ok().entity(entity).build(); diff --git a/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/VersionQuery.java b/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/VersionQuery.java index 4779be232..9115633d8 100644 --- a/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/VersionQuery.java +++ b/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/VersionQuery.java @@ -24,7 +24,6 @@ import javax.ws.rs.core.GenericEntity; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; -import li.strolch.agent.api.ComponentContainer; import li.strolch.agent.api.VersionQueryResult; import li.strolch.rest.RestfulStrolchComponent; import li.strolch.rest.StrolchRestfulConstants; @@ -40,12 +39,11 @@ public class VersionQuery { @Produces(MediaType.APPLICATION_JSON) public Response getVersions(@Context HttpServletRequest request) { - ComponentContainer container = RestfulStrolchComponent.getInstance().getContainer(); - Certificate cert = (Certificate) request.getAttribute(StrolchRestfulConstants.STROLCH_CERTIFICATE); - container.getPrivilegeHandler().isCertificateValid(cert); + RestfulStrolchComponent instance = RestfulStrolchComponent.getInstance(); + instance.getSessionHandler().validate(cert); - VersionQueryResult versionQueryResult = container.getAgent().getVersion(); + VersionQueryResult versionQueryResult = instance.getContainer().getAgent().getVersion(); GenericEntity entity = new GenericEntity(versionQueryResult, VersionQueryResult.class) { // diff --git a/li.strolch.rest/src/main/java/li/strolch/rest/filters/AuthenicationRequestFilter.java b/li.strolch.rest/src/main/java/li/strolch/rest/filters/AuthenicationRequestFilter.java index d20b09655..54ee13587 100644 --- a/li.strolch.rest/src/main/java/li/strolch/rest/filters/AuthenicationRequestFilter.java +++ b/li.strolch.rest/src/main/java/li/strolch/rest/filters/AuthenicationRequestFilter.java @@ -7,10 +7,8 @@ import static li.strolch.rest.StrolchRestfulConstants.STROLCH_CERTIFICATE; import java.io.IOException; -import javax.servlet.http.HttpServletRequest; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerRequestFilter; -import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Response; import javax.ws.rs.ext.Provider; @@ -26,18 +24,14 @@ import ch.eitchnet.privilege.model.Certificate; @Provider public class AuthenicationRequestFilter implements ContainerRequestFilter { - @Context - HttpServletRequest request; - @Override public void filter(ContainerRequestContext requestContext) throws IOException { String sessionId = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION); if (sessionId != null) { try { - String origin = this.request == null ? "test" : this.request.getRemoteAddr(); //$NON-NLS-1$ StrolchSessionHandler sessionHandler = RestfulStrolchComponent.getInstance().getComponent( StrolchSessionHandler.class); - Certificate certificate = sessionHandler.validate(origin, sessionId); + Certificate certificate = sessionHandler.validate(sessionId); requestContext.setProperty(STROLCH_CERTIFICATE, certificate); } catch (Exception e) { requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED) diff --git a/li.strolch.rest/src/test/resources/withPrivilegeRuntime/config/StrolchConfiguration.xml b/li.strolch.rest/src/test/resources/withPrivilegeRuntime/config/StrolchConfiguration.xml index 3a04698cf..d89aec3aa 100644 --- a/li.strolch.rest/src/test/resources/withPrivilegeRuntime/config/StrolchConfiguration.xml +++ b/li.strolch.rest/src/test/resources/withPrivilegeRuntime/config/StrolchConfiguration.xml @@ -45,7 +45,7 @@ li.strolch.rest.StrolchSessionHandler li.strolch.rest.DefaultStrolchSessionHandler - true + 1 diff --git a/li.strolch.tutorialwebapp/src/main/webapp/WEB-INF/config/StrolchConfiguration.xml b/li.strolch.tutorialwebapp/src/main/webapp/WEB-INF/config/StrolchConfiguration.xml index bc7109569..c3dbc5bf4 100644 --- a/li.strolch.tutorialwebapp/src/main/webapp/WEB-INF/config/StrolchConfiguration.xml +++ b/li.strolch.tutorialwebapp/src/main/webapp/WEB-INF/config/StrolchConfiguration.xml @@ -80,7 +80,7 @@ li.strolch.rest.DefaultStrolchSessionHandler PrivilegeHandler - true + 1