be.agiv.security.AGIVSecurity.java Source code

Java tutorial

Introduction

Here is the source code for be.agiv.security.AGIVSecurity.java

Source

/*
 * AGIV Java Security Project.
 * Copyright (C) 2011-2012 AGIV.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License version
 * 3.0 as published by the Free Software Foundation.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, see 
 * http://www.gnu.org/licenses/.
 */

package be.agiv.security;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.net.Proxy;
import java.net.ProxySelector;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.Security;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

import javax.xml.ws.Binding;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.handler.Handler;
import javax.xml.ws.soap.SOAPFaultException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.joda.time.DateTime;
import org.joda.time.Duration;

import be.agiv.security.client.ClientProxySelector;
import be.agiv.security.client.IPSTSClient;
import be.agiv.security.client.RSTSClient;
import be.agiv.security.client.SecureConversationClient;
import be.agiv.security.handler.AGIVSOAPHandler;
import be.agiv.security.handler.AuthenticationHandler;
import be.agiv.security.handler.LoggingHandler;
import be.agiv.security.handler.SecureConversationHandler;
import be.agiv.security.handler.SecurityTokenConsumer;
import be.agiv.security.handler.SecurityTokenProvider;
import be.agiv.security.handler.WSSecurityHandler;

/**
 * The main AGIV security framework component.
 * <p/>
 * Basically you use the {@link AGIVSecurity#enable(BindingProvider)} method (or
 * one of its variants) to enable the AGIV Security framework on the JAX-WS
 * stubs.
 * <p/>
 * An AGIV security component holds all information to be able to connect to
 * AGIV secured web services. Although JAX-WS itself is not multi-threaded, this
 * component can be shared between different JAX-WS clients. Each AGIV security
 * component also caches the different obtained security tokens for as long as
 * they are valid, or explicitly cancelled. The AGIV security component also
 * manages the network proxy settings.
 * 
 * @author Frank Cornelis
 * 
 */
public class AGIVSecurity implements SecurityTokenProvider {

    private static final Log LOG = LogFactory.getLog(AGIVSecurity.class);

    public static final long DEFAULT_TOKEN_RETIREMENT_DURATION = 1000 * 60 * 5;

    /**
     * R-STS Beta Realm.
     */
    public static final String BETA_REALM = "urn:agiv.be/salvador";

    /**
     * R-STS Production Realm.
     */
    public static final String PRODUCTION_REALM = "urn:agiv.be/sts";

    private final String ipStsLocation;

    private final ExternalIPSTSClient externalIpStsClient;

    private final String rStsLocation;

    private final String username;

    private final String password;

    private final X509Certificate certificate;

    private final PrivateKey privateKey;

    // key = service location
    private final Map<String, SecurityToken> secureConversationTokens;

    private SecurityToken ipStsSecurityToken;

    // key = service realm
    private final Map<String, SecurityToken> rStsSecurityTokens;

    private static ClientProxySelector clientProxySelector;

    private String proxyHost;
    private int proxyPort;
    private Proxy.Type proxyType;

    private long tokenRetirementDuration = DEFAULT_TOKEN_RETIREMENT_DURATION;

    private final List<STSListener> stsListeners;

    private final String rStsRealm;

    static {
        ProxySelector defaultProxySelector = ProxySelector.getDefault();
        AGIVSecurity.clientProxySelector = new ClientProxySelector(defaultProxySelector);
        ProxySelector.setDefault(AGIVSecurity.clientProxySelector);
    }

    /**
     * Main constructor. This constructor assumes the usage of username/password
     * credentials.
     * 
     * @param ipStsLocation
     *            the location of the IP-STS WS-Trust web service.
     * @param rStsLocation
     *            the location of the R-STS WS-Trust web service.
     * @param rStsRealm
     *            the AGIV R-STS realm.
     * @param username
     *            the username client credential
     * @param password
     *            the password client credential.
     */
    public AGIVSecurity(String ipStsLocation, String rStsLocation, String rStsRealm, String username,
            String password) {
        this(ipStsLocation, rStsLocation, rStsRealm, username, password, null, null, null);
    }

    /**
     * Constructor for X509 credentials.
     * 
     * @param ipStsLocation
     *            the location of the IP-STS WS-Trust web service.
     * @param rStsLocation
     *            the location of the R-STS WS-Trust web service.
     * @param rStsRealm
     *            the AGIV R-STS realm.
     * @param certificate
     *            the X509 certificate credential.
     * @param privateKey
     *            the corresponding private RSA key.
     * @see AGIVSecurity#AGIVSecurity(String, String, File, String)
     */
    public AGIVSecurity(String ipStsLocation, String rStsLocation, String rStsRealm, X509Certificate certificate,
            PrivateKey privateKey) {
        this(ipStsLocation, rStsLocation, rStsRealm, null, null, null, certificate, privateKey);
    }

    /**
     * Constructor for X509 credentials. The certificate and corresponding
     * private key are loaded from a PKCS#12 keystore file.
     * 
     * @param ipStsLocation
     *            the location of the IP-STS WS-Trust web service.
     * @param rStsLocation
     *            the location of the R-STS WS-Trust web service.
     * @param rStsRealm
     *            the AGIV R-STS realm.
     * @param pkcs12File
     *            the PKCS#12 keystore file.
     * @param pkcs12Password
     *            the PKCS#12 keystore password.
     * @throws SecurityException
     *             gets thrown in case of a PKCS#12 keystore error.
     * @see AGIVSecurity#AGIVSecurity(String, String, X509Certificate,
     *      PrivateKey)
     */
    public AGIVSecurity(String ipStsLocation, String rStsLocation, String rStsRealm, File pkcs12File,
            String pkcs12Password) throws SecurityException {
        this.ipStsLocation = ipStsLocation;
        this.rStsLocation = rStsLocation;
        this.rStsRealm = rStsRealm;
        this.username = null;
        this.password = null;

        InputStream pkcs12InputStream;
        try {
            pkcs12InputStream = new FileInputStream(pkcs12File);
        } catch (FileNotFoundException e) {
            throw new SecurityException("PKCS#12 file does not exist: " + pkcs12File.getAbsolutePath());
        }
        Provider sunJSSEProvider = Security.getProvider("SunJSSE");
        try {
            KeyStore keyStore;
            if (null != sunJSSEProvider) {
                // avoid older BouncyCastle implementations
                keyStore = KeyStore.getInstance("PKCS12", sunJSSEProvider);
            } else {
                keyStore = KeyStore.getInstance("PKCS12");
            }
            keyStore.load(pkcs12InputStream, pkcs12Password.toCharArray());
            Enumeration<String> aliases = keyStore.aliases();
            String alias = aliases.nextElement();
            this.certificate = (X509Certificate) keyStore.getCertificate(alias);
            this.privateKey = (PrivateKey) keyStore.getKey(alias, pkcs12Password.toCharArray());
        } catch (Exception e) {
            LOG.error("error loading PKCS#12 keystore: " + e.getMessage(), e);
            throw new SecurityException("error loading PKCS#12 certificate: " + e.getMessage(), e);
        }
        this.externalIpStsClient = null;
        this.secureConversationTokens = new ConcurrentHashMap<String, SecurityToken>();
        this.rStsSecurityTokens = new ConcurrentHashMap<String, SecurityToken>();
        this.stsListeners = new CopyOnWriteArrayList<STSListener>();
    }

    /**
     * Constructor for external IP-STS services. Use this constructor for
     * external IP-STS services that do not behave exactly like the AGIV IP-STS
     * service.
     * 
     * @param externalIpStsClient
     *            the external IP-STS service client to be used.
     * @param rStsLocation
     *            the location of the R-STS WS-Trust web service.
     */
    public AGIVSecurity(ExternalIPSTSClient externalIpStsClient, String rStsLocation) {
        this(null, rStsLocation, null, null, null, externalIpStsClient, null, null);
    }

    private AGIVSecurity(String ipStsLocation, String rStsLocation, String rStsRealm, String username,
            String password, ExternalIPSTSClient externalIpStsClient, X509Certificate certificate,
            PrivateKey privateKey) {
        this.ipStsLocation = ipStsLocation;
        this.rStsLocation = rStsLocation;
        this.rStsRealm = rStsRealm;
        this.username = username;
        this.password = password;
        this.certificate = certificate;
        this.privateKey = privateKey;
        this.externalIpStsClient = externalIpStsClient;
        this.secureConversationTokens = new ConcurrentHashMap<String, SecurityToken>();
        this.rStsSecurityTokens = new ConcurrentHashMap<String, SecurityToken>();
        this.stsListeners = new CopyOnWriteArrayList<STSListener>();
    }

    /**
     * Adds an STS Listener to this AGIV security instance. An STS Listener can
     * be used to monitor the activity of the AGIVSecurity component related to
     * the different STS services.
     * 
     * @param stsListener
     *            the STS listener to be added.
     * @see STSListener
     */
    public void addSTSListener(STSListener stsListener) {
        this.stsListeners.add(stsListener);
    }

    /**
     * Sets the proxy configuration to be used by this AGIV Security component.
     * <p/>
     * Even the JAX-WS enabled stubs will use the proxy settings.
     * 
     * @param proxyHost
     *            the host of the proxy.
     * @param proxyPort
     *            the port of the proxy.
     * @param proxyType
     *            the type of the proxy.
     */
    public synchronized void setProxy(String proxyHost, int proxyPort, Proxy.Type proxyType) {
        AGIVSecurity.clientProxySelector.setProxy(this.ipStsLocation, proxyHost, proxyPort, proxyType);
        AGIVSecurity.clientProxySelector.setProxy(this.rStsLocation, proxyHost, proxyPort, proxyType);
        this.proxyHost = proxyHost;
        this.proxyPort = proxyPort;
        this.proxyType = proxyType;
    }

    /**
     * Enable the AGIV security on the given JAX-WS binding provider. Each
     * JAX-WS port can be casted to a JAX-WS binding provider.
     * <p/>
     * It is no problem to call the enable method multiple times for a certain
     * JAX-WS stub. This method will only decorate the AGIV Security framework
     * once on the given JAX-WS stub. If the JAX-WS stub is already decorated by
     * another AGIVSecurity instance a {@link SecurityException} will be thrown.
     * <p/>
     * JAX-WS stubs enabled via this method will not use WS-SecureConversation.
     * 
     * @param bindingProvider
     *            the JAX-WS binding provider on which to enable the AGIV
     *            security framework.
     * @see AGIVSecurity#enable(BindingProvider, String)
     * @see AGIVSecurity#enable(BindingProvider, String, String)
     * @see AGIVSecurity#enable(BindingProvider, String, boolean)
     * @see AGIVSecurity#enable(BindingProvider, String, boolean, String)
     * @see AGIVSecurity#enable(BindingProvider, boolean)
     * @see AGIVSecurity#disable(BindingProvider)
     */
    public void enable(BindingProvider bindingProvider) {
        enable(bindingProvider, false, null);
    }

    /**
     * Enable the AGIV security on the given JAX-WS binding provider. Each
     * JAX-WS port can be casted to a JAX-WS binding provider.
     * <p/>
     * It is no problem to call the enable method multiple times for a certain
     * JAX-WS stub. This method will only decorate the AGIV Security framework
     * once on the given JAX-WS stub. If the JAX-WS stub is already decorated by
     * another AGIVSecurity instance a {@link SecurityException} will be thrown.
     * 
     * @param bindingProvider
     *            the JAX-WS binding provider on which to enable the AGIV
     *            security framework.
     * @param useWsSecureConversation
     *            set to <code>true</code> if WS-SecureConversation should be
     *            used.
     * @param serviceRealm
     *            the optional service realm.
     * @see AGIVSecurity#enable(BindingProvider, String)
     * @see AGIVSecurity#enable(BindingProvider, String, boolean)
     * @see AGIVSecurity#disable(BindingProvider)
     */
    public void enable(BindingProvider bindingProvider, boolean useWsSecureConversation, String serviceRealm) {
        Binding binding = bindingProvider.getBinding();
        List<Handler> handlerChain = binding.getHandlerChain();
        for (Handler handler : handlerChain) {
            if (handler instanceof SecureConversationHandler || handler instanceof AuthenticationHandler) {
                LOG.warn("security already enabled");
                SecurityTokenConsumer securityTokenConsumer = (SecurityTokenConsumer) handler;
                if (this != securityTokenConsumer.getSecurityTokenProvider()) {
                    throw new SecurityException(
                            "security on JAX-WS stub already enabled by another AGIVSecurity instance");
                }
                return;
            }
        }
        WSSecurityHandler wsSecurityHandler = new WSSecurityHandler();
        if (useWsSecureConversation) {
            handlerChain.add(new SecureConversationHandler(this, wsSecurityHandler, serviceRealm));
        } else {
            handlerChain.add(new AuthenticationHandler(this, wsSecurityHandler, serviceRealm));
        }
        handlerChain.add(wsSecurityHandler);
        handlerChain.add(new LoggingHandler());
        binding.setHandlerChain(handlerChain);
    }

    /**
     * Disable the AGIV Security framework on the given JAX-WS port.
     * <p/>
     * Can be used when the JAX-WS stubs are managed by some container and the
     * container strategy is to pool JAX-WS stub instances.
     * 
     * @param bindingProvider
     *            the JAX-WS port.
     * @see AGIVSecurity#enable(BindingProvider)
     * @see AGIVSecurity#enable(BindingProvider, String)
     */
    public void disable(BindingProvider bindingProvider) {
        LOG.debug("disabling AGIV security...");
        Binding binding = bindingProvider.getBinding();
        List<Handler> handlerChain = binding.getHandlerChain();
        Iterator<Handler> handlerIterator = handlerChain.iterator();
        while (handlerIterator.hasNext()) {
            Handler handler = handlerIterator.next();
            if (handler instanceof AGIVSOAPHandler) {
                handlerIterator.remove();
                continue;
            }
        }
        binding.setHandlerChain(handlerChain);
    }

    /**
     * Enable the AGIV security on the given JAX-WS binding provider. Each
     * JAX-WS port can be casted to a JAX-WS binding provider. The JAX-WS port
     * will also be configured to use the service at the given service location.
     * 
     * @param bindingProvider
     *            the JAX-WS binding provider on which to enable the AGIV
     *            security framework.
     * @param serviceLocation
     *            the location of the web service.
     * @param useWsSecureConversation
     *            set to <code>true</code> if WS-SecureConversation should be
     *            used.
     * @param serviceRealm
     *            the optional service realm.
     * @see AGIVSecurity#enable(BindingProvider)
     * @see AGIVSecurity#disable(BindingProvider)
     */
    public void enable(BindingProvider bindingProvider, String serviceLocation, boolean useWsSecureConversation,
            String serviceRealm) {
        bindingProvider.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, serviceLocation);
        enable(bindingProvider, useWsSecureConversation, serviceRealm);
    }

    /**
     * Enable the AGIV security on the given JAX-WS binding provider. Each
     * JAX-WS port can be casted to a JAX-WS binding provider. The JAX-WS port
     * will also be configured to use the service at the given service location.
     * <p/>
     * JAX-WS stubs enabled via this method will not use WS-SecureConversation.
     * 
     * @param bindingProvider
     *            the JAX-WS binding provider on which to enable the AGIV
     *            security framework.
     * @param serviceLocation
     *            the location of the web service.
     * @param serviceRealm
     *            the optional service realm.
     * @see AGIVSecurity#enable(BindingProvider)
     * @see AGIVSecurity#enable(BindingProvider, boolean)
     * @see AGIVSecurity#enable(BindingProvider, String, boolean)
     * @see AGIVSecurity#disable(BindingProvider)
     */
    public void enable(BindingProvider bindingProvider, String serviceLocation, String serviceRealm) {
        enable(bindingProvider, serviceLocation, false, serviceRealm);
    }

    /**
     * Gives back the location of the IP-STS WS-Trust web service.
     * 
     * @return IP-STS location.
     */
    public String getIpStsLocation() {
        return this.ipStsLocation;
    }

    /**
     * Gives back the location of the R-STS WS-Trust web service.
     * 
     * @return R-STS location.
     */
    public String getRStsLocation() {
        return this.rStsLocation;
    }

    /**
     * Gives back the username credential. Can be <code>null</code> in case of
     * an external IP-STS configuration, or on case of certificate based
     * credentials.
     * 
     * @return the username credential.
     */
    public String getUsername() {
        return this.username;
    }

    /**
     * Gives back the X509 certificate credential. Can be <code>null</code> in
     * case of an external IP-STS configuration, or in case of username/password
     * credentials.
     * 
     * @return the X509 certificate.
     */
    public X509Certificate getCertificate() {
        return this.certificate;
    }

    /**
     * Gives back a map of secure conversation tokens, indexed via the location
     * of the web service for which the tokens apply.
     * <p/>
     * The map can be empty if no WS-SecureConversation is used.
     * 
     * @return a map of secure conversation tokens indexed per web service
     *         location.
     */
    public Map<String, SecurityToken> getSecureConversationTokens() {
        return this.secureConversationTokens;
    }

    /**
     * Prefetch all security tokens (IP-STS, R-STS and secure conversation
     * token) for the given web service location. This method could be used
     * within applications to improve the end user experience.
     * 
     * @param location
     *            the web service location for which to prefetch tokens.
     * @param the
     *            service realm.
     * @param useWsSecureConversation
     *            set to <code>true</code> if the WS-SecureConversation token
     *            should also be fetched.
     * @see AGIVSecurity#prefetchTokens(String)
     */
    public void prefetchTokens(String location, String serviceRealm, boolean useWsSecureConversation) {
        if (useWsSecureConversation) {
            getSecureConversationToken(location, serviceRealm);
        } else {
            getSecurityToken(serviceRealm);
        }
    }

    /**
     * Prefetch all security tokens (IP-STS, R-STS) for the given web service
     * location. This method could be used within applications to improve the
     * end user experience.
     * 
     * @param location
     *            the web service location for which to prefetch tokens.
     * @param serviceRealm
     *            the service realm.
     * @see AGIVSecurity#prefetchTokens(String, boolean)
     */
    public void prefetchTokens(String location, String serviceRealm) {
        prefetchTokens(location, serviceRealm, false);
    }

    /**
     * Prefetch all security tokens (IP-STS, R-STS) for the given web service
     * location. The location will be used as service realm.
     * 
     * @param location
     *            the web service location for which to prefetch tokens.
     */
    public void prefetchTokens(String location) {
        prefetchTokens(location, location, false);
    }

    /**
     * Prefetch all security tokens (IP-STS, R-STS) for the given web service
     * location. The location will be used as service realm.
     * 
     * @param location
     *            the web service location for which to prefetch tokens.
     * @param useWsSecureConversation
     *            set to <code>true</code> if the WS-SecureConversation token
     *            should also be fetched.
     */
    public void prefetchTokens(String location, boolean useWsSecureConversation) {
        prefetchTokens(location, location, useWsSecureConversation);
    }

    private void notifyIPSTSListeners() {
        for (STSListener stsListener : this.stsListeners) {
            try {
                stsListener.requestingIPSTSToken();
            } catch (Exception e) {
                LOG.error("error calling STS listener: " + e.getMessage(), e);
            }
        }
    }

    private void notifyRSTSListeners() {
        for (STSListener stsListener : this.stsListeners) {
            try {
                stsListener.requestingRSTSToken();
            } catch (Exception e) {
                LOG.error("error calling STS listener: " + e.getMessage(), e);
            }
        }
    }

    private void notifySecureConversationListeners() {
        for (STSListener stsListener : this.stsListeners) {
            try {
                stsListener.requestingSecureConversationToken();
            } catch (Exception e) {
                LOG.error("error calling STS listener: " + e.getMessage(), e);
            }
        }
    }

    /**
     * Gives back the secure conversation token for the given web service
     * location. In case the token cache does not yet hold tokens for the given
     * web service location, this method will fetch new tokens from IP-STS,
     * R-STS, and the WS-SecureConversation enabled web service. This method
     * might also notify the registered STS listeners in case of STS activity.
     * 
     * @param location
     *            the location of the web service for which the token should
     *            apply.
     * @param the
     *            service realm.
     * @return the secure conversation token
     */
    public SecurityToken getSecureConversationToken(String location, String serviceRealm) {
        SecurityToken secureConversationToken = this.secureConversationTokens.get(location);
        if (requireNewToken(secureConversationToken)) {
            secureConversationToken = refreshSecureConversationToken(location, serviceRealm);
        }
        return secureConversationToken;
    }

    private SecurityToken refreshSecureConversationToken(String location, String serviceRealm) {
        AGIVSecurity.clientProxySelector.setProxy(location, this.proxyHost, this.proxyPort, this.proxyType);
        /*
         * New clients here since JAX-WS is not thread safe.
         */
        SecurityToken rStsSecurityToken = getSecurityToken(serviceRealm);

        notifySecureConversationListeners();
        SecureConversationClient secureConversationClient = new SecureConversationClient(location);
        SecurityToken secureConversationToken = secureConversationClient
                .getSecureConversationToken(rStsSecurityToken);

        this.secureConversationTokens.put(location, secureConversationToken);
        return secureConversationToken;
    }

    public SecurityToken getSecurityToken(String serviceRealm) {
        SecurityToken rStsSecurityToken = this.rStsSecurityTokens.get(serviceRealm);
        if (false == requireNewToken(rStsSecurityToken)) {
            return rStsSecurityToken;
        }
        if (requireNewToken(this.ipStsSecurityToken)) {
            refreshIPSTSSecurityToken();
        }
        rStsSecurityToken = refreshRSTSSecurityToken(serviceRealm);
        return rStsSecurityToken;
    }

    /**
     * Refreshes all cached security tokens. Can be used by a background process
     * in order to minimize the possible delay on clients because of expired
     * security tokens. The returned expiry date can be used to program a timer
     * at which the method should be called again. Don't forget to take the
     * token retirement duration into account when programming such a timer.
     * 
     * @return the expiry date of the token that expires next in line.
     */
    public Date refreshSecurityTokens() {
        refreshIPSTSSecurityToken();
        Date expiryDate = this.ipStsSecurityToken.getExpires();
        Set<String> serviceRealms = this.rStsSecurityTokens.keySet();
        if (serviceRealms.isEmpty()) {
            return expiryDate;
        }
        for (String serviceRealm : serviceRealms) {
            SecurityToken rStsSecurityToken = refreshRSTSSecurityToken(serviceRealm);
            Date rStsExpiryDate = rStsSecurityToken.getExpires();
            if (rStsExpiryDate.before(expiryDate)) {
                expiryDate = rStsExpiryDate;
            }
        }
        Set<String> serviceLocations = this.secureConversationTokens.keySet();
        if (serviceLocations.isEmpty()) {
            return expiryDate;
        }
        for (String serviceLocation : serviceLocations) {
            SecurityToken secureConversationToken = this.secureConversationTokens.get(serviceLocation);
            SecurityToken rStsSecurityToken = secureConversationToken.getParentSecurityToken();
            String serviceRealm = rStsSecurityToken.getRealm();
            secureConversationToken = refreshSecureConversationToken(serviceLocation, serviceRealm);
            Date sctExpiryDate = secureConversationToken.getExpires();
            if (sctExpiryDate.before(expiryDate)) {
                expiryDate = sctExpiryDate;
            }
        }
        return expiryDate;
    }

    private SecurityToken refreshRSTSSecurityToken(String serviceRealm) {
        SecurityToken rStsSecurityToken;
        notifyRSTSListeners();
        AGIVSecurity.clientProxySelector.setProxy(this.rStsLocation, this.proxyHost, this.proxyPort,
                this.proxyType);
        RSTSClient rStsClient = new RSTSClient(this.rStsLocation);
        rStsSecurityToken = rStsClient.getSecurityToken(this.ipStsSecurityToken, serviceRealm);
        this.rStsSecurityTokens.put(serviceRealm, rStsSecurityToken);
        return rStsSecurityToken;
    }

    private void refreshIPSTSSecurityToken() {
        notifyIPSTSListeners();
        if (null != this.externalIpStsClient) {
            this.ipStsSecurityToken = this.externalIpStsClient.getSecurityToken();
        } else {
            AGIVSecurity.clientProxySelector.setProxy(this.ipStsLocation, this.proxyHost, this.proxyPort,
                    this.proxyType);
            IPSTSClient ipStsClient = new IPSTSClient(this.ipStsLocation, this.rStsRealm);
            if (null != this.certificate) {
                this.ipStsSecurityToken = ipStsClient.getSecuritytoken(this.certificate, this.privateKey);
            } else {
                this.ipStsSecurityToken = ipStsClient.getSecurityToken(this.username, this.password);
            }
        }
    }

    private boolean requireNewToken(SecurityToken securityToken) {
        if (null == securityToken) {
            return true;
        }
        DateTime now = new DateTime();
        DateTime expires = new DateTime(securityToken.getExpires());
        Duration duration = new Duration(now, expires);
        LOG.debug("token validity: " + duration);
        if (duration.isLongerThan(new Duration(this.tokenRetirementDuration))) {
            LOG.debug("reusing security token: " + securityToken.getAttachedReference());
            return false;
        }
        return true;
    }

    /**
     * Cancels all secure conversation tokens currently present within the token
     * cache. Cancelling tokens on the server-side reduces load on the AGIV web
     * services.
     */
    public void cancelSecureConversationTokens() {
        for (Map.Entry<String, SecurityToken> secureConversationTokenEntry : this.secureConversationTokens
                .entrySet()) {
            String location = secureConversationTokenEntry.getKey();
            SecurityToken secureConversationToken = secureConversationTokenEntry.getValue();
            this.secureConversationTokens.remove(location);
            LOG.debug("cancelling secure conversation token: " + secureConversationToken.getAttachedReference());
            SecureConversationClient secureConversationClient = new SecureConversationClient(location);
            try {
                secureConversationClient.cancelSecureConversationToken(secureConversationToken);
            } catch (SOAPFaultException e) {
                // in case token is expired
                LOG.warn("SOAP fault: " + e.getMessage());
            }
        }
    }

    /**
     * Gives back the duration in milliseconds that a token will be refreshed
     * before its expiration. Default is 5 minutes (5 * 60 * 1000).
     * 
     * @return duration in milliseconds.
     */
    public long getTokenRetirementDuration() {
        return this.tokenRetirementDuration;
    }

    /**
     * Sets the duration in milliseconds that a token will be refreshed before
     * its expiration.
     * 
     * @param tokenRetirementDuration
     *            duration in milliseconds.
     */
    public void setTokenRetirementDuration(long tokenRetirementDuration) {
        this.tokenRetirementDuration = tokenRetirementDuration;
    }

    /**
     * Enable the AGIV security on the given JAX-WS binding provider. Each
     * JAX-WS port can be casted to a JAX-WS binding provider.
     * 
     * @param bindingProvider
     *            the JAX-WS binding provider on which to enable the AGIV
     *            security framework.
     * @param useWsSecureConversation
     *            set to <code>true</code> if WS-SecureConversation should be
     *            used.
     * @see AGIVSecurity#enable(BindingProvider, boolean, String)
     */
    public void enable(BindingProvider bindingProvider, boolean useWsSecureConversation) {
        enable(bindingProvider, useWsSecureConversation, null);
    }

    /**
     * Enable the AGIV security on the given JAX-WS binding provider. Each
     * JAX-WS port can be casted to a JAX-WS binding provider.
     * 
     * @param bindingProvider
     *            the JAX-WS binding provider on which to enable the AGIV
     *            security framework.
     * @param serviceLocation
     *            the service location.
     * @see AGIVSecurity#enable(BindingProvider, String, String)
     */
    public void enable(BindingProvider bindingProvider, String serviceLocation) {
        enable(bindingProvider, serviceLocation, null);
    }

    /**
     * Enable the AGIV security on the given JAX-WS binding provider. Each
     * JAX-WS port can be casted to a JAX-WS binding provider.
     * 
     * @param bindingProvider
     *            the JAX-WS binding provider on which to enable the AGIV
     *            security framework.
     * @param serviceLocation
     *            the service location.
     * @param useWsSecureConversation
     *            set to <code>true</code> if WS-SecureConversation should be
     *            used.
     * @see AGIVSecurity#enable(BindingProvider, String, String)
     */
    public void enable(BindingProvider bindingProvider, String serviceLocation, boolean useWsSecureConversation) {
        enable(bindingProvider, serviceLocation, useWsSecureConversation, null);
    }
}