Search code examples
javaauthenticationsingletonhttpclientpersistence

Java Apache HttpClient - Authentication becomes Stale


I hope that this question will make sense for everyone so here goes. I have a Singleton HttpClient manager class that upon instantiation will Authenticate against Windows Live (Microsoft Auth)

After a while of inactivity (lack of requests), however, this authentication becomes stale and subsequent requests to URL's return a page requiring me to sign-in. My question is essentially, how should I handle re-authenticating to the Server? Should I have another thread that periodically makes get requests and checks to see if the sign-in page is returned, then re-instantiating the HttpClient? Please let me know what best practices there are for this.

Here is a snippet from my connection manager class that performs the authentication:

    public static synchronized HttpConnectionManager getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new HttpConnectionManager();
        }
        return INSTANCE;
    }

    private HttpConnectionManager() {
        final PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
        connectionManager.setMaxTotal(100);
        connectionManager.setDefaultMaxPerRoute(40);

        client = HttpClients.custom()
                .setDefaultRequestConfig(RequestConfig.custom().setCookieSpec(CookieSpecs.STANDARD).build())
                .setConnectionManager(connectionManager)
                .disableRedirectHandling()
                .build();

        try {
            // Prepare HTTPClient by performing Authentication Handshake
            performAuthenticationHandshake();
        } catch (IOException ioException) {
            connectionLogger.log(Level.ERROR, "Unable to Authenticate to https://login.live.com");
        } catch (HttpException httpException) {
            connectionLogger.log(Level.ERROR, httpException.getMessage());
        }
    }

    private void performAuthenticationHandshake() throws IOException, HttpException {
        final HttpGet authenticatedGet = new HttpGet(LIVE_URI);
        final HttpResponse authGetResponse = client.execute(authenticatedGet);

        final String authResponseStr = IOUtils.toString(authGetResponse.getEntity().getContent(), StandardCharsets.UTF_8);

        final HttpPost credentialsPost = getCredentialsPost(authResponseStr);
        final HttpResponse credentialsPostResponse = client.execute(credentialsPost);

        if (credentialsPostResponse.getStatusLine().getStatusCode() != 302) {
            throw new HttpException("An invalid status code was returned in credentialsPostResponse (Updated TOS?)): " + credentialsPostResponse.getStatusLine().getStatusCode());
        }

        final String locationURIStr = Arrays.stream(credentialsPostResponse.getAllHeaders())
                .filter(header -> header.getName().startsWith("Location"))
                .map(Header::getValue)
                .findFirst()
                .orElseThrow(HttpException::new);

        final HttpGet locationGet = new HttpGet(locationURIStr);
        final HttpResponse locationGetResponse = client.execute(locationGet);

        if (locationGetResponse.getStatusLine().getStatusCode() != 302) {
            throw new HttpException("An invalid status code was returned in locationGetResponse: " + locationGetResponse.getStatusLine().getStatusCode());
        }

        storeCookies(locationGetResponse.getAllHeaders());
        auth = cookieStore.stream()
                .filter(cookie -> cookie.getName().startsWith("Auth"))
                .map(NameValuePair::getValue)
                .findFirst()
                .orElseThrow(HttpException::new);

    }


Solution

  • One approach would be to use cookiestore in httpClient and the expiration date fo the cookie can be get from that cookiestore.

    we can assign cookiestore to httpclient like this.

    BasicCookieStore cookieStore = new BasicCookieStore();
    client = HttpClients.custom().setDefaultRequestConfig(RequestConfig.custom().setCookieSpec(CookieSpecs.STANDARD).build())
                    .setConnectionManager(connectionManager)
                    .setDefaultCookieStore(cookieStore)
                    .disableRedirectHandling()
                    .build();
    

    Now the cookiestore object contains all the cookies along with their expiration date. we check the expiration date of the authentication cookie whenever you are trying to execute any URLs in the HTTPClient. If the current Date exceeds the expiration date of the authentication cookie then the HTTPClient must relogin to get the authentication cookie and continue with the actual process.

    The below check must be inserted whenever any URL is executed using HTTPClient.

    // let cookiestore one the one used in httpClient
    Cookie auth = cookieStore.getCookies().stream()
                .filter(cookie -> cookie.getName().startsWith("Auth"))
                .findFirst()
                .orElseThrow(HttpException::new);
    
    Date currentDate = new Date();
    if( auth.isExpire(currentDate) ){
        performAuthenticationHandshake();
    }