dispose client connection token and connection token caching

This commit is contained in:
anhefti 2022-06-22 08:51:13 +02:00
parent 4ab317d763
commit 1c896c827d
8 changed files with 78 additions and 54 deletions

View file

@ -11,10 +11,11 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.dao;
import java.util.Collection; import java.util.Collection;
import java.util.Set; import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Cacheable;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam; import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Result;
@ -26,6 +27,8 @@ public interface ClientConnectionDAO extends
EntityDAO<ClientConnection, ClientConnection>, EntityDAO<ClientConnection, ClientConnection>,
BulkActionSupportDAO<ClientConnection> { BulkActionSupportDAO<ClientConnection> {
Logger log = LoggerFactory.getLogger(ClientConnectionDAO.class);
String CONNECTION_TOKENS_CACHE = "CONNECTION_TOKENS_CACHE"; String CONNECTION_TOKENS_CACHE = "CONNECTION_TOKENS_CACHE";
/** Get a list of all connection tokens of all connections (no matter what state) /** Get a list of all connection tokens of all connections (no matter what state)
@ -43,9 +46,13 @@ public interface ClientConnectionDAO extends
unless = "#result.hasError()") unless = "#result.hasError()")
Result<Collection<String>> getConnectionTokens(Long examId); Result<Collection<String>> getConnectionTokens(Long examId);
@CacheEvict(cacheNames = CONNECTION_TOKENS_CACHE, key = "#examId") @CacheEvict(
cacheNames = CONNECTION_TOKENS_CACHE,
key = "#examId")
default void evictConnectionTokenCache(final Long examId) { default void evictConnectionTokenCache(final Long examId) {
if (log.isDebugEnabled()) {
log.debug("Evict SEB connection tokens for exam: {}", examId);
}
} }
/** Get a list of all connection tokens of all connections of an exam /** Get a list of all connection tokens of all connections of an exam
@ -101,22 +108,6 @@ public interface ClientConnectionDAO extends
* @return Result refer to a collection of all ClientConnection of the room or to an error if happened */ * @return Result refer to a collection of all ClientConnection of the room or to an error if happened */
Result<Collection<ClientConnection>> getCollectingRoomConnections(final Long examId, final String roomName); Result<Collection<ClientConnection>> getCollectingRoomConnections(final Long examId, final String roomName);
/** Creates new ClientConnection from the given ClientConnection data.
*
* This evicts all entries from the CONNECTION_TOKENS_CACHE.
*
* TODO improvement: Use the examId to evict only the relevant cache entry
*
* @param data ClientConnection instance
* @return Result refer to the newly created ClientConnection data or to an error if happened */
@Override
@CacheEvict(cacheNames = CONNECTION_TOKENS_CACHE, allEntries = true)
Result<ClientConnection> createNew(ClientConnection data);
@Override
@CacheEvict(cacheNames = CONNECTION_TOKENS_CACHE, allEntries = true)
Result<ClientConnection> save(ClientConnection data);
@CacheEvict( @CacheEvict(
cacheNames = ExamSessionCacheService.CACHE_NAME_ACTIVE_CLIENT_CONNECTION, cacheNames = ExamSessionCacheService.CACHE_NAME_ACTIVE_CLIENT_CONNECTION,
key = "#connectionToken") key = "#connectionToken")
@ -130,19 +121,6 @@ public interface ClientConnectionDAO extends
/** Used to re-mark a client connection record for room update in error case. */ /** Used to re-mark a client connection record for room update in error case. */
Result<Void> markForProctoringUpdate(Long id); Result<Void> markForProctoringUpdate(Long id);
/** Deletes the given ClientConnection data.
*
* This evicts all entries from the CONNECTION_TOKENS_CACHE.
*
* TODO improvement: Use the examId to evict only the relevant cache entry
*
* @param all Set of EntityKey for entities to delete
* @return Result refer to a collection of deleted entities or to an error if happened */
@Override
@CacheEvict(cacheNames = CONNECTION_TOKENS_CACHE, allEntries = true)
// TODO this probably is nor working when called from BulkActionSupportDAO
Result<Collection<EntityKey>> delete(Set<EntityKey> all);
/** Get a ClientConnection by connection token. /** Get a ClientConnection by connection token.
* *
* @param connectionToken the connection token * @param connectionToken the connection token

View file

@ -24,6 +24,7 @@ import org.apache.commons.lang3.BooleanUtils;
import org.mybatis.dynamic.sql.SqlBuilder; import org.mybatis.dynamic.sql.SqlBuilder;
import org.mybatis.dynamic.sql.select.MyBatis3SelectModelAdapter; import org.mybatis.dynamic.sql.select.MyBatis3SelectModelAdapter;
import org.mybatis.dynamic.sql.select.QueryExpressionDSL; import org.mybatis.dynamic.sql.select.QueryExpressionDSL;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -72,6 +73,7 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
private final ClientIndicatorRecordMapper clientIndicatorRecordMapper; private final ClientIndicatorRecordMapper clientIndicatorRecordMapper;
private final ClientNotificationRecordMapper clientNotificationRecordMapper; private final ClientNotificationRecordMapper clientNotificationRecordMapper;
private final ClientConnectionTokenMapper clientConnectionMinMapper; private final ClientConnectionTokenMapper clientConnectionMinMapper;
private final CacheManager cacheManager;
protected ClientConnectionDAOImpl( protected ClientConnectionDAOImpl(
final ClientConnectionRecordMapper clientConnectionRecordMapper, final ClientConnectionRecordMapper clientConnectionRecordMapper,
@ -79,7 +81,8 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
final ClientInstructionRecordMapper clientInstructionRecordMapper, final ClientInstructionRecordMapper clientInstructionRecordMapper,
final ClientIndicatorRecordMapper clientIndicatorRecordMapper, final ClientIndicatorRecordMapper clientIndicatorRecordMapper,
final ClientNotificationRecordMapper clientNotificationRecordMapper, final ClientNotificationRecordMapper clientNotificationRecordMapper,
final ClientConnectionTokenMapper clientConnectionMinMapper) { final ClientConnectionTokenMapper clientConnectionMinMapper,
final CacheManager cacheManager) {
this.clientConnectionRecordMapper = clientConnectionRecordMapper; this.clientConnectionRecordMapper = clientConnectionRecordMapper;
this.clientEventRecordMapper = clientEventRecordMapper; this.clientEventRecordMapper = clientEventRecordMapper;
@ -87,6 +90,7 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
this.clientIndicatorRecordMapper = clientIndicatorRecordMapper; this.clientIndicatorRecordMapper = clientIndicatorRecordMapper;
this.clientNotificationRecordMapper = clientNotificationRecordMapper; this.clientNotificationRecordMapper = clientNotificationRecordMapper;
this.clientConnectionMinMapper = clientConnectionMinMapper; this.clientConnectionMinMapper = clientConnectionMinMapper;
this.cacheManager = cacheManager;
} }
@Override @Override
@ -529,6 +533,8 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
return Collections.emptyList(); return Collections.emptyList();
} }
ids.stream().forEach(this::clearConnecionTokenCache);
// delete all related client indicators // delete all related client indicators
this.clientIndicatorRecordMapper.deleteByExample() this.clientIndicatorRecordMapper.deleteByExample()
.where( .where(
@ -879,4 +885,22 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
return record.getConnectionToken(); return record.getConnectionToken();
} }
private Long clearConnecionTokenCache(final Long id) {
try {
final ClientConnectionRecord rec = recordById(id)
.getOrThrow();
this.cacheManager
.getCache(CONNECTION_TOKENS_CACHE)
.evictIfPresent(rec.getExamId());
} catch (final Exception e) {
log.error("Failed to clear connection token cache: ", e);
}
return id;
}
} }

View file

@ -215,6 +215,9 @@ public class SEBClientConfigDAOImpl implements SEBClientConfigDAO {
return Collections.emptyList(); return Collections.emptyList();
} }
// clear caches and revoke tokens first
ids.stream().forEach(this::disposeSEBClientConfig);
final SebClientConfigRecord record = new SebClientConfigRecord( final SebClientConfigRecord record = new SebClientConfigRecord(
null, null, null, null, null, null, null, null, null, null, null, null, null, null,
BooleanUtils.toIntegerObject(active), BooleanUtils.toIntegerObject(active),
@ -228,7 +231,6 @@ public class SEBClientConfigDAOImpl implements SEBClientConfigDAO {
return ids.stream() return ids.stream()
.map(id -> new EntityKey(id, EntityType.SEB_CLIENT_CONFIGURATION)) .map(id -> new EntityKey(id, EntityType.SEB_CLIENT_CONFIGURATION))
.map(this::revokeClientConnection)
.collect(Collectors.toList()); .collect(Collectors.toList());
}); });
} }
@ -308,6 +310,9 @@ public class SEBClientConfigDAOImpl implements SEBClientConfigDAO {
return Collections.emptyList(); return Collections.emptyList();
} }
// clear caches and revoke tokens first
ids.stream().forEach(this::disposeSEBClientConfig);
this.sebClientConfigRecordMapper.deleteByExample() this.sebClientConfigRecordMapper.deleteByExample()
.where(SebClientConfigRecordDynamicSqlSupport.id, isIn(ids)) .where(SebClientConfigRecordDynamicSqlSupport.id, isIn(ids))
.build() .build()
@ -315,7 +320,6 @@ public class SEBClientConfigDAOImpl implements SEBClientConfigDAO {
return ids.stream() return ids.stream()
.map(id -> new EntityKey(id, EntityType.SEB_CLIENT_CONFIGURATION)) .map(id -> new EntityKey(id, EntityType.SEB_CLIENT_CONFIGURATION))
.map(this::revokeClientConnection)
.collect(Collectors.toList()); .collect(Collectors.toList());
}); });
} }
@ -670,10 +674,10 @@ public class SEBClientConfigDAOImpl implements SEBClientConfigDAO {
} }
} }
private EntityKey revokeClientConnection(final EntityKey key) { private Long disposeSEBClientConfig(final Long pk) {
try { try {
final SebClientConfigRecord rec = recordById(Long.parseLong(key.modelId)) final SebClientConfigRecord rec = recordById(pk)
.getOrThrow(); .getOrThrow();
// revoke token // revoke token
@ -681,17 +685,18 @@ public class SEBClientConfigDAOImpl implements SEBClientConfigDAO {
this.applicationEventPublisher this.applicationEventPublisher
.publishEvent(new RevokeExamTokenEvent(rec.getClientName())); .publishEvent(new RevokeExamTokenEvent(rec.getClientName()));
} catch (final Exception e) { } catch (final Exception e) {
log.error("Failed to revoke token for SEB client connection. Connection Configuration: {}", key, e); log.error("Failed to revoke token for SEB client connection. Connection Configuration: {}", pk, e);
} }
// clear cache // clear cache
this.cacheManager.getCache(ClientConfigService.EXAM_CLIENT_DETAILS_CACHE) this.cacheManager
.getCache(ClientConfigService.EXAM_CLIENT_DETAILS_CACHE)
.evictIfPresent(rec.getClientName()); .evictIfPresent(rec.getClientName());
} catch (final Exception e) { } catch (final Exception e) {
log.error("Failed to revoke SEB client connection. Connection Configuration: {}", key, e); log.error("Failed to revoke SEB client connection. Connection Configuration: {}", pk, e);
} }
return key; return pk;
} }
} }

View file

@ -180,6 +180,11 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
this.clientIndicatorFactory.initializeDistributedCaches(clientConnection); this.clientIndicatorFactory.initializeDistributedCaches(clientConnection);
} }
// flash connection token cache for exam if available
if (examId != null) {
this.clientConnectionDAO.evictConnectionTokenCache(examId);
}
// load client connection data into cache // load client connection data into cache
final ClientConnectionDataInternal activeClientConnection = this.examSessionService final ClientConnectionDataInternal activeClientConnection = this.examSessionService
.getConnectionDataInternal(connectionToken); .getConnectionDataInternal(connectionToken);
@ -286,8 +291,9 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
this.clientIndicatorFactory.initializeDistributedCaches(clientConnection); this.clientIndicatorFactory.initializeDistributedCaches(clientConnection);
} }
final ClientConnectionDataInternal activeClientConnection = final ClientConnectionDataInternal activeClientConnection = reloadConnectionCache(
reloadConnectionCache(connectionToken); connectionToken,
examId);
if (activeClientConnection == null) { if (activeClientConnection == null) {
log.warn("Failed to load ClientConnectionDataInternal into cache on update"); log.warn("Failed to load ClientConnectionDataInternal into cache on update");
@ -441,8 +447,9 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
} }
// flush and reload caches to work with actual connection data // flush and reload caches to work with actual connection data
final ClientConnectionDataInternal activeClientConnection = final ClientConnectionDataInternal activeClientConnection = reloadConnectionCache(
reloadConnectionCache(connectionToken); connectionToken,
examId);
if (activeClientConnection == null) { if (activeClientConnection == null) {
log.warn("Failed to load ClientConnectionDataInternal into cache on update"); log.warn("Failed to load ClientConnectionDataInternal into cache on update");
@ -496,13 +503,14 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
establishedClientConnection.remoteProctoringRoomUpdate); establishedClientConnection.remoteProctoringRoomUpdate);
// Update other connection with token and exam id // Update other connection with token and exam id
this.clientConnectionDAO final ClientConnection connection = this.clientConnectionDAO
.save(new ClientConnection( .save(new ClientConnection(
vdiPairCompanion.getId(), null, vdiPairCompanion.getId(), null,
vdiExamId, null, null, null, null, null, null, vdiExamId, null, null, null, null, null, null,
establishedClientConnection.connectionToken, null, null, null, null, null, null, null)) establishedClientConnection.connectionToken, null, null, null, null, null, null, null))
.getOrThrow(); .getOrThrow();
reloadConnectionCache(vdiPairCompanion.getConnectionToken());
reloadConnectionCache(vdiPairCompanion.getConnectionToken(), connection.examId);
return updatedConnection; return updatedConnection;
} }
@ -557,7 +565,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
.deleteIndicatorValues(updatedClientConnection.id); .deleteIndicatorValues(updatedClientConnection.id);
} }
reloadConnectionCache(connectionToken); reloadConnectionCache(connectionToken, clientConnection.examId);
return updatedClientConnection; return updatedClientConnection;
}); });
} }
@ -619,7 +627,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
.deleteIndicatorValues(updatedClientConnection.id); .deleteIndicatorValues(updatedClientConnection.id);
} }
reloadConnectionCache(connectionToken); reloadConnectionCache(connectionToken, clientConnection.examId);
return updatedClientConnection; return updatedClientConnection;
}); });
} }
@ -886,7 +894,14 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
.getOrThrow(); .getOrThrow();
} }
private ClientConnectionDataInternal reloadConnectionCache(final String connectionToken) { private ClientConnectionDataInternal reloadConnectionCache(
final String connectionToken,
final Long examId) {
if (examId != null) {
// evict connection tokens for exam
this.clientConnectionDAO.evictConnectionTokenCache(examId);
}
// evict cached ClientConnection // evict cached ClientConnection
this.examSessionCacheService.evictClientConnection(connectionToken); this.examSessionCacheService.evictClientConnection(connectionToken);
// and load updated ClientConnection into cache // and load updated ClientConnection into cache

View file

@ -79,8 +79,7 @@ public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdap
response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
log.warn( log.warn(
"Unauthorized Request: {}, {}", "Unauthorized Request: {}",
request,
exception != null ? exception.getMessage() : Constants.EMPTY_NOTE); exception != null ? exception.getMessage() : Constants.EMPTY_NOTE);
response.getOutputStream().println("{ \"error\": \"" + exception.getMessage() + "\" }"); response.getOutputStream().println("{ \"error\": \"" + exception.getMessage() + "\" }");
}); });

View file

@ -66,7 +66,9 @@ public class WebClientDetailsService implements ClientDetailsService {
return getForExamClientAPI(clientId) return getForExamClientAPI(clientId)
.get(t -> { .get(t -> {
log.error("Active ClientConfig not found: {} cause: {}", clientId, t.getMessage()); if (log.isDebugEnabled()) {
log.warn("Active ClientConfig not found: {} cause: {}", clientId, t.getMessage());
}
throw new UsernameNotFoundException(t.getMessage()); throw new UsernameNotFoundException(t.getMessage());
}); });
} }

View file

@ -25,7 +25,7 @@ sebserver.webservice.clean-db-on-startup=false
# webservice configuration # webservice configuration
sebserver.init.adminaccount.gen-on-init=false sebserver.init.adminaccount.gen-on-init=false
sebserver.webservice.distributed=true sebserver.webservice.distributed=false
#sebserver.webservice.master.delay.threshold=10000 #sebserver.webservice.master.delay.threshold=10000
sebserver.webservice.http.external.scheme=http sebserver.webservice.http.external.scheme=http
sebserver.webservice.http.external.servername=localhost sebserver.webservice.http.external.servername=localhost

View file

@ -22,6 +22,7 @@ logging.level.ch.ethz.seb.sebserver.webservice.weblayer.oauth=DEBUG
#logging.level.ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamRecordMapper=DEBUG #logging.level.ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamRecordMapper=DEBUG
#logging.level.ch.ethz.seb.sebserver.webservice.weblayer.api.ExamAPI_V1_Controller=TRACE #logging.level.ch.ethz.seb.sebserver.webservice.weblayer.api.ExamAPI_V1_Controller=TRACE
logging.level.com.zaxxer.hikari=INFO logging.level.com.zaxxer.hikari=INFO
#logging.level.ch.ethz.seb.sebserver.webservice.servicelayer.dao=DEBUG
sebserver.http.client.connect-timeout=15000 sebserver.http.client.connect-timeout=15000
sebserver.http.client.connection-request-timeout=10000 sebserver.http.client.connection-request-timeout=10000