protected void verifyWebflowSessionIsCorrectlyPinned(final SerializedFlowExecutionState state) {
        val currentClientInfo = ClientInfoHolder.getClientInfo();

        val conversationScope = state.getConversationScope();
        val userAgent = (String) conversationScope.get(WEBFLOW_USER_AGENT);
        val clientIpAddress = (String) conversationScope.get(WEBFLOW_CLIENT_IP_ADDRESS);
        Assert.hasText(userAgent, "User-agent cannot be null or empty");
        Assert.hasText(clientIpAddress, "Client IP address cannot be null or empty");

        if (!StringUtils.equals(currentClientInfo.getUserAgent(), userAgent)
            || !StringUtils.equals(currentClientInfo.getClientIpAddress(), clientIpAddress)) {
            LOGGER.error("User-agent attached to the webflow [{}] does not match the current user-agent [{}] or "
                    + "client IP address attached to the webflow [{}] does not match the current client IP address [{}]. "
                    + "The flow execution key is invalid or likely tampered with.",
                userAgent, currentClientInfo.getUserAgent(), clientIpAddress, currentClientInfo.getClientIpAddress());
            throw new ClientFlowExecutionRepositoryException("Webflow execution key is invalid");
        }
    }
Client flow storage has not backing store independent from the flow key, so no locking is required.
core/cas-server-core-webflow-api/src/main/java/org/apereo/cas/web/flow/executor/ClientFlowExecutionRepository.java