[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
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);

View File

@ -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;
}

View File

@ -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<String, Certificate> 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<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.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);
}
}

View File

@ -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);
}

View File

@ -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<LoginResult> entity = new GenericEntity<LoginResult>(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();

View File

@ -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<VersionQueryResult> entity = new GenericEntity<VersionQueryResult>(versionQueryResult,
VersionQueryResult.class) {
//

View File

@ -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)

View File

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

View File

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