[Major] Added Session timeout handling

SessionHandler now has a timer checking to make sure that sessions which haven’t been accessed for a time defined by ‘session.ttl.minutes’ are invalidated
This commit is contained in:
Robert von Burg 2014-09-27 12:30:35 +02:00
parent 9ccfdd7244
commit 32c17858e1
11 changed files with 122 additions and 92 deletions

@ -1 +1 @@
Subproject commit aa16887d674876a160bb8002a8d7899c85b2f7b1 Subproject commit 67271d611ee66d67477843ca7fc388e5fb50750c

View File

@ -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 @Override
public Certificate authenticate(String username, byte[] password) { public Certificate authenticate(String username, byte[] password) {
assertContainerStarted(); assertContainerStarted();
@ -123,46 +116,29 @@ public class DefaultStrolchPrivilegeHandler extends StrolchComponent implements
} }
} }
/**
* @param certificate
* @throws PrivilegeException
* @see ch.eitchnet.privilege.handler.PrivilegeHandler#isCertificateValid(Certificate)
*/
@Override @Override
public void isCertificateValid(Certificate certificate) throws PrivilegeException { public void isCertificateValid(Certificate certificate) throws PrivilegeException {
assertContainerStarted(); assertContainerStarted();
this.privilegeHandler.isCertificateValid(certificate); this.privilegeHandler.isCertificateValid(certificate);
} }
/** @Override
* @param certificate public void checkPassword(Certificate certificate, byte[] password) throws PrivilegeException {
* @return assertContainerStarted();
* @see ch.eitchnet.privilege.handler.PrivilegeHandler#invalidateSession(ch.eitchnet.privilege.model.Certificate) this.privilegeHandler.checkPassword(certificate, password);
*/ }
@Override @Override
public boolean invalidateSession(Certificate certificate) { public boolean invalidateSession(Certificate certificate) {
assertContainerStarted(); assertContainerStarted();
return this.privilegeHandler.invalidateSession(certificate); return this.privilegeHandler.invalidateSession(certificate);
} }
/**
* @param certificate
* @return
* @throws PrivilegeException
* @see ch.eitchnet.privilege.handler.PrivilegeHandler#getPrivilegeContext(ch.eitchnet.privilege.model.Certificate)
*/
@Override @Override
public PrivilegeContext getPrivilegeContext(Certificate certificate) throws PrivilegeException { public PrivilegeContext getPrivilegeContext(Certificate certificate) throws PrivilegeException {
return this.privilegeHandler.getPrivilegeContext(certificate); 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 @Override
public void runAsSystem(String systemUsername, SystemUserAction action) throws PrivilegeException { public void runAsSystem(String systemUsername, SystemUserAction action) throws PrivilegeException {
this.privilegeHandler.runAsSystem(systemUsername, action); this.privilegeHandler.runAsSystem(systemUsername, action);

View File

@ -73,4 +73,11 @@ public interface PrivilegeHandler {
public abstract ch.eitchnet.privilege.handler.PrivilegeHandler getPrivilegeHandler(Certificate certificate) public abstract ch.eitchnet.privilege.handler.PrivilegeHandler getPrivilegeHandler(Certificate certificate)
throws PrivilegeException; 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;
} }

View File

@ -18,12 +18,19 @@ package li.strolch.rest;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; 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.ComponentContainer;
import li.strolch.agent.api.StrolchComponent; import li.strolch.agent.api.StrolchComponent;
import li.strolch.exception.StrolchException; import li.strolch.exception.StrolchException;
import li.strolch.runtime.configuration.ComponentConfiguration; import li.strolch.runtime.configuration.ComponentConfiguration;
import li.strolch.runtime.privilege.PrivilegeHandler; import li.strolch.runtime.privilege.PrivilegeHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.Certificate;
import ch.eitchnet.utils.dbc.DBC; import ch.eitchnet.utils.dbc.DBC;
@ -32,11 +39,12 @@ import ch.eitchnet.utils.dbc.DBC;
*/ */
public class DefaultStrolchSessionHandler extends StrolchComponent implements StrolchSessionHandler { public class DefaultStrolchSessionHandler extends StrolchComponent implements StrolchSessionHandler {
private static final String PARAM_SESSION_ORIGIN = "session.origin"; //$NON-NLS-1$ private static final Logger logger = LoggerFactory.getLogger(DefaultStrolchSessionHandler.class);
private static final String PARAM_VALIDATE_ORIGIN = "validateOrigin"; //$NON-NLS-1$ private static final String PARAM_SESSION_TTL_MINUTES = "session.ttl.minutes"; //$NON-NLS-1$
private PrivilegeHandler privilegeHandler; private PrivilegeHandler privilegeHandler;
private Map<String, Certificate> certificateMap; private Map<String, Certificate> certificateMap;
private boolean validateOrigin; private long sessionTtl;
private Timer sessionTimeoutTimer;
/** /**
* @param container * @param container
@ -48,7 +56,7 @@ public class DefaultStrolchSessionHandler extends StrolchComponent implements St
@Override @Override
public void initialize(ComponentConfiguration configuration) { 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); super.initialize(configuration);
} }
@ -56,17 +64,30 @@ public class DefaultStrolchSessionHandler extends StrolchComponent implements St
public void start() { public void start() {
this.privilegeHandler = getContainer().getComponent(PrivilegeHandler.class); this.privilegeHandler = getContainer().getComponent(PrivilegeHandler.class);
this.certificateMap = new HashMap<>(); this.certificateMap = new HashMap<>();
this.sessionTimeoutTimer = new Timer("SessionTimeoutTimer");
long checkInterval = TimeUnit.MINUTES.toMillis(1);
this.sessionTimeoutTimer.schedule(new SessionTimeoutTask(), checkInterval, checkInterval);
super.start(); super.start();
} }
@Override @Override
public void stop() { public void stop() {
if (this.certificateMap != null) { if (this.certificateMap != null) {
for (Certificate certificate : this.certificateMap.values()) { synchronized (this.certificateMap) {
this.privilegeHandler.invalidateSession(certificate); 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; this.privilegeHandler = null;
super.stop(); super.stop();
} }
@ -78,52 +99,89 @@ public class DefaultStrolchSessionHandler extends StrolchComponent implements St
} }
@Override @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("Origin must be set!", username); //$NON-NLS-1$
DBC.PRE.assertNotEmpty("Username 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$ DBC.PRE.assertNotNull("Passwort must be set", password); //$NON-NLS-1$
Certificate certificate = this.privilegeHandler.authenticate(username, password); synchronized (this.certificateMap) {
certificate.getSessionDataMap().put(PARAM_SESSION_ORIGIN, origin); Certificate certificate = this.privilegeHandler.authenticate(username, password);
this.certificateMap.put(certificate.getAuthToken(), certificate); this.certificateMap.put(certificate.getAuthToken(), certificate);
return certificate; logger.info(this.certificateMap.size() + " sessions currently active.");
return certificate;
}
} }
@Override @Override
public Certificate validate(String origin, String authToken) { public Certificate validate(String authToken) {
DBC.PRE.assertNotEmpty("Origin must be set!", origin); //$NON-NLS-1$
DBC.PRE.assertNotEmpty("SessionId must be set!", authToken); //$NON-NLS-1$ 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) if (certificate == null)
throw new StrolchException(MessageFormat.format("No certificate exists for sessionId {0}", authToken)); //$NON-NLS-1$ 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); this.privilegeHandler.isCertificateValid(certificate);
certificate.setLastAccess(System.currentTimeMillis());
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);
}
return certificate; return certificate;
} }
@Override @Override
public void invalidateSession(String origin, Certificate certificate) { public void invalidateSession(Certificate certificate) {
DBC.PRE.assertNotEmpty("Origin must be set!", origin); //$NON-NLS-1$
DBC.PRE.assertNotNull("Certificate must bet given!", certificate); //$NON-NLS-1$ DBC.PRE.assertNotNull("Certificate must bet given!", certificate); //$NON-NLS-1$
if (this.validateOrigin && !origin.equals(certificate.getSessionDataMap().get(PARAM_SESSION_ORIGIN))) { Certificate removedCert;
String msg = MessageFormat.format("Illegal request for origin {0} and sessionId {1}", origin, //$NON-NLS-1$ synchronized (this.certificateMap) {
certificate.getAuthToken()); removedCert = this.certificateMap.remove(certificate.getAuthToken());
throw new StrolchException(msg);
} }
Certificate removedCert = this.certificateMap.remove(certificate.getAuthToken());
if (removedCert == null) if (removedCert == null)
logger.error(MessageFormat.format("No session was registered with token {0}", certificate.getAuthToken())); //$NON-NLS-1$ logger.error(MessageFormat.format("No session was registered with token {0}", certificate.getAuthToken())); //$NON-NLS-1$
this.privilegeHandler.invalidateSession(certificate); this.privilegeHandler.invalidateSession(certificate);
} }
/**
* @return the certificateMap
*/
protected Map<String, Certificate> 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 <eitch@eitchnet.ch>
*/
private class SessionTimeoutTask extends TimerTask {
@Override
public void run() {
Map<String, Certificate> map = getCertificateMap();
Map<String, Certificate> 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);
}
}
}
}
} }

View File

@ -19,7 +19,6 @@ import li.strolch.agent.api.ComponentContainer;
import li.strolch.agent.api.StrolchComponent; import li.strolch.agent.api.StrolchComponent;
import li.strolch.rest.filters.AccessControlResponseFilter; import li.strolch.rest.filters.AccessControlResponseFilter;
import li.strolch.runtime.configuration.ComponentConfiguration; import li.strolch.runtime.configuration.ComponentConfiguration;
import li.strolch.runtime.privilege.PrivilegeHandler;
import ch.eitchnet.utils.dbc.DBC; import ch.eitchnet.utils.dbc.DBC;
/** /**
@ -82,7 +81,7 @@ public class RestfulStrolchComponent extends StrolchComponent {
return getContainer().getComponent(clazz); return getContainer().getComponent(clazz);
} }
public PrivilegeHandler getPrivilegeHandler() { public StrolchSessionHandler getSessionHandler() {
return getContainer().getPrivilegeHandler(); return getContainer().getComponent(StrolchSessionHandler.class);
} }
} }

View File

@ -22,9 +22,11 @@ import ch.eitchnet.privilege.model.Certificate;
*/ */
public interface StrolchSessionHandler { 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);
} }

View File

@ -20,7 +20,6 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes; import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE; import javax.ws.rs.DELETE;
import javax.ws.rs.POST; import javax.ws.rs.POST;
@ -62,7 +61,7 @@ public class AuthenticationService {
@POST @POST
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
@Produces(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(); LoginResult loginResult = new LoginResult();
GenericEntity<LoginResult> entity = new GenericEntity<LoginResult>(loginResult, LoginResult.class) { GenericEntity<LoginResult> entity = new GenericEntity<LoginResult>(loginResult, LoginResult.class) {
@ -84,16 +83,14 @@ public class AuthenticationService {
return Response.status(Status.UNAUTHORIZED).entity(loginResult).build(); return Response.status(Status.UNAUTHORIZED).entity(loginResult).build();
} }
StrolchSessionHandler sessionHandler = RestfulStrolchComponent.getInstance().getComponent( RestfulStrolchComponent restfulStrolchComponent = RestfulStrolchComponent.getInstance();
StrolchSessionHandler.class); StrolchSessionHandler sessionHandler = restfulStrolchComponent.getComponent(StrolchSessionHandler.class);
String origin = request == null ? "test" : request.getRemoteAddr(); //$NON-NLS-1$ Certificate certificate = sessionHandler.authenticate(login.getUsername(), login.getPassword().getBytes());
Certificate certificate = sessionHandler.authenticate(origin, login.getUsername(), login.getPassword()
.getBytes());
Locale locale = RestfulHelper.getLocale(headers); Locale locale = RestfulHelper.getLocale(headers);
certificate.setLocale(locale); certificate.setLocale(locale);
PrivilegeHandler privilegeHandler = RestfulStrolchComponent.getInstance().getPrivilegeHandler(); PrivilegeHandler privilegeHandler = restfulStrolchComponent.getContainer().getPrivilegeHandler();
PrivilegeContext privilegeContext = privilegeHandler.getPrivilegeContext(certificate); PrivilegeContext privilegeContext = privilegeHandler.getPrivilegeContext(certificate);
loginResult.setSessionId(certificate.getAuthToken()); loginResult.setSessionId(certificate.getAuthToken());
loginResult.setUsername(certificate.getUsername()); loginResult.setUsername(certificate.getUsername());
@ -124,7 +121,7 @@ public class AuthenticationService {
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
@Path("{authToken}") @Path("{authToken}")
public Response logout(@PathParam("authToken") String authToken, @Context HttpServletRequest request) { public Response logout(@PathParam("authToken") String authToken) {
LogoutResult logoutResult = new LogoutResult(); LogoutResult logoutResult = new LogoutResult();
@ -135,9 +132,8 @@ public class AuthenticationService {
StrolchSessionHandler sessionHandlerHandler = RestfulStrolchComponent.getInstance().getComponent( StrolchSessionHandler sessionHandlerHandler = RestfulStrolchComponent.getInstance().getComponent(
StrolchSessionHandler.class); StrolchSessionHandler.class);
String origin = request == null ? "test" : request.getRemoteAddr(); //$NON-NLS-1$ Certificate certificate = sessionHandlerHandler.validate(authToken);
Certificate certificate = sessionHandlerHandler.validate(origin, authToken); sessionHandlerHandler.invalidateSession(certificate);
sessionHandlerHandler.invalidateSession(origin, certificate);
logoutResult.setMsg(MessageFormat.format("{0} has been logged out.", certificate.getUsername())); //$NON-NLS-1$ logoutResult.setMsg(MessageFormat.format("{0} has been logged out.", certificate.getUsername())); //$NON-NLS-1$
return Response.ok().entity(entity).build(); return Response.ok().entity(entity).build();

View File

@ -24,7 +24,6 @@ import javax.ws.rs.core.GenericEntity;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import li.strolch.agent.api.ComponentContainer;
import li.strolch.agent.api.VersionQueryResult; import li.strolch.agent.api.VersionQueryResult;
import li.strolch.rest.RestfulStrolchComponent; import li.strolch.rest.RestfulStrolchComponent;
import li.strolch.rest.StrolchRestfulConstants; import li.strolch.rest.StrolchRestfulConstants;
@ -40,12 +39,11 @@ public class VersionQuery {
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
public Response getVersions(@Context HttpServletRequest request) { public Response getVersions(@Context HttpServletRequest request) {
ComponentContainer container = RestfulStrolchComponent.getInstance().getContainer();
Certificate cert = (Certificate) request.getAttribute(StrolchRestfulConstants.STROLCH_CERTIFICATE); 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<VersionQueryResult> entity = new GenericEntity<VersionQueryResult>(versionQueryResult, GenericEntity<VersionQueryResult> entity = new GenericEntity<VersionQueryResult>(versionQueryResult,
VersionQueryResult.class) { VersionQueryResult.class) {
// //

View File

@ -7,10 +7,8 @@ import static li.strolch.rest.StrolchRestfulConstants.STROLCH_CERTIFICATE;
import java.io.IOException; import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter; import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import javax.ws.rs.ext.Provider; import javax.ws.rs.ext.Provider;
@ -26,18 +24,14 @@ import ch.eitchnet.privilege.model.Certificate;
@Provider @Provider
public class AuthenicationRequestFilter implements ContainerRequestFilter { public class AuthenicationRequestFilter implements ContainerRequestFilter {
@Context
HttpServletRequest request;
@Override @Override
public void filter(ContainerRequestContext requestContext) throws IOException { public void filter(ContainerRequestContext requestContext) throws IOException {
String sessionId = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION); String sessionId = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
if (sessionId != null) { if (sessionId != null) {
try { try {
String origin = this.request == null ? "test" : this.request.getRemoteAddr(); //$NON-NLS-1$
StrolchSessionHandler sessionHandler = RestfulStrolchComponent.getInstance().getComponent( StrolchSessionHandler sessionHandler = RestfulStrolchComponent.getInstance().getComponent(
StrolchSessionHandler.class); StrolchSessionHandler.class);
Certificate certificate = sessionHandler.validate(origin, sessionId); Certificate certificate = sessionHandler.validate(sessionId);
requestContext.setProperty(STROLCH_CERTIFICATE, certificate); requestContext.setProperty(STROLCH_CERTIFICATE, certificate);
} catch (Exception e) { } catch (Exception e) {
requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED) requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED)

View File

@ -45,7 +45,7 @@
<api>li.strolch.rest.StrolchSessionHandler</api> <api>li.strolch.rest.StrolchSessionHandler</api>
<impl>li.strolch.rest.DefaultStrolchSessionHandler</impl> <impl>li.strolch.rest.DefaultStrolchSessionHandler</impl>
<Properties> <Properties>
<rememberUser>true</rememberUser> <session.ttl.minutes>1</session.ttl.minutes>
</Properties> </Properties>
</Component> </Component>
<Component> <Component>

View File

@ -80,7 +80,7 @@
<impl>li.strolch.rest.DefaultStrolchSessionHandler</impl> <impl>li.strolch.rest.DefaultStrolchSessionHandler</impl>
<depends>PrivilegeHandler</depends> <depends>PrivilegeHandler</depends>
<Properties> <Properties>
<rememberUser>true</rememberUser> <session.ttl.minutes>1</session.ttl.minutes>
</Properties> </Properties>
</Component> </Component>
</env> </env>