简体   繁体   中英

How to handle multiple Authenticator

To authenticate a request, I use Authenticator.setDefault which is VM wide...
What If I want to separate different webservices and each one are aware of their authentication credentials.
Do I need to Authenticator.setDefault for each request ?
This may not work if there are concurrent connection with mixed webservices...

Building on Mike's response above I have the following solution, because while I much appreciate the general idea (that's why I've copied it ;-) , I see a few problems with it:

  • Mike's solution will throw a NullPointerException if the JDK requests the authentication via one of the two static request methods in java.net.Authenticator that do not pass the URL (then getRequestingURL() will return null).
  • It requires you to pass in an external regex pattern that deconstructs the URL. This is (very) easy to get wrong, and the URL class in the JDK implements this parsing, so I prefer to use that.
  • It requires that some external class builds the map of PasswordAuthentication objects, and then sets it. It does not implement a registration mechanism that other components in your system can use. I've also turned it into a singleton.
  • More of a style thing: I don't recommend duplicating class names (Authenticator), so I've renamed it DefaultAuthenticator.

Below solution I think solves these issues.

    /**
     * Authenticator which keeps credentials to be passed to the requestor based on authority of the requesting URL. The
     * authority is <pre>user:password@host:port</pre>, where all parts are optional except the host.
     * <p>
     * If the configured credentials are not found, the Authenticator will use the credentials embedded in the URL, if
     * present. Embedded credentials are in the form of <pre>user:password@host:port</pre>
     *   
     * @author Michael Fortin 2011-09-23
     */
    public final class DefaultAuthenticator extends Authenticator {

        private static final Logger LOG = Logger.getLogger(DefaultAuthenticator.class.getName());
        private static DefaultAuthenticator instance;

        private Map<String, PasswordAuthentication> authInfo = new HashMap<String, PasswordAuthentication>();

        private DefaultAuthenticator() {
        }

        public static synchronized DefaultAuthenticator getInstance() {
            if (instance == null) {
                instance = new DefaultAuthenticator();
                Authenticator.setDefault(instance);
            }
            return instance;
        }

        // unit testing
        static void reset() {
            instance = null;
            Authenticator.setDefault(null);        
        }

        @Override
        protected PasswordAuthentication getPasswordAuthentication() {

            String requestorInfo = getRequestorInfo();
            LOG.info(getRequestorType() + " at \"" + getRequestingPrompt() + "\" is requesting " + getRequestingScheme()
                    + " password authentication for \"" + requestorInfo + "\"");

            if (authInfo.containsKey(requestorInfo)) {
                return authInfo.get(requestorInfo);
            } else {
                PasswordAuthentication pa = getEmbeddedCredentials(getRequestingURL());
                if (pa == null) {
                    LOG.warning("No authentication information");
                }
                return pa;
            }

        }

        /**
         * Register the authentication information for a given URL.
         * 
         * @param url - the URL that will request authorization
         * @param auth - the {@link PasswordAuthentication} for this URL providing the credentials
         */
        public void register(URL url, PasswordAuthentication auth) {
            String requestorInfo = getRequestorInfo(url.getHost(), url.getPort()); 
            authInfo.put(requestorInfo, auth);
        }

        /**
         * Get the requestor info based on info provided.
         * 
         * @param host - hostname of requestor
         * @param port - TCP/IP port
         * @return requestor info string
         */
        private String getRequestorInfo(String host, int port) {

            String fullHostname;
            try {
                InetAddress addr = InetAddress.getByName(host);
                fullHostname = addr.getCanonicalHostName();
            } catch (UnknownHostException e) {
                fullHostname = host;
            }

            if (port == -1) {
                return fullHostname;
            } else {
                return fullHostname + ":" + port;
            }
        }

        /**
         * Get the requestor info for the request currently being processed by this Authenticator.
         * 
         * @return requestor info string for current request
         */
        private String getRequestorInfo() {

            String host;
            InetAddress addr = getRequestingSite();
            if (addr == null) {
                host = getRequestingHost();
            } else {
                host = addr.getCanonicalHostName();
            }
            return getRequestorInfo(host, getRequestingPort());
        }

        /**
         * Get the credentials from the requesting URL.
         * 
         * @param url - URL to get the credentials from (can be null, method will return null)
         * @return PasswordAuthentication with credentials from URL or null if URL contains no credentials or if URL is
         * null itself
         */
        PasswordAuthentication getEmbeddedCredentials(URL url) {

            if (url == null) {
                return null;
            }

            String userInfo = url.getUserInfo();
            int colon = userInfo == null ? -1 : userInfo.indexOf(":");
            if (colon == -1) {
                return null;
            } else {
                String userName = userInfo.substring(0, colon);
                String pass = userInfo.substring(colon + 1);
                return new PasswordAuthentication(userName, pass.toCharArray());
            }
        }
    }

While I'm at it, let me give you my unit tests (JUnit 4).

    /**
     * @author Paul Balm - May 10 2012
     */
    public class DefaultAuthenticatorTest {

        private static final Logger LOG = Logger.getLogger(DefaultAuthenticatorTest.class.getName());

        @Before
        public void setUp() throws Exception {
            DefaultAuthenticator.reset();
            DefaultAuthenticator.getInstance();
        }

        @After
        public void tearDown() {
            DefaultAuthenticator.reset();
        }

        @Test
        public void testRequestAuthenticationFromURL() throws MalformedURLException, UnknownHostException {

            Map<String, String[]> urls = generateURLs();

            for (String urlStr : urls.keySet()) {
                String[] userInfo = urls.get(urlStr);
                LOG.info("Testing: " + urlStr);
                URL url = new URL(urlStr);
                request(userInfo[1], userInfo[2], url, true);
            }

        }

        @Test
        public void testRequestAuthenticationRegistered() throws UnknownHostException, MalformedURLException {

            Map<String, String[]> urls = generateURLs();

            for (String urlStr : urls.keySet()) {
                String[] userInfo = urls.get(urlStr);
                LOG.info("Testing: " + urlStr);
                URL url = new URL(urlStr);

                DefaultAuthenticator.reset();
                DefaultAuthenticator auth = DefaultAuthenticator.getInstance();

                String userName = userInfo[1];
                String password = userInfo[2];

                if (password != null) {
                    // You can't register a null password
                    auth.register(url, new PasswordAuthentication(userName, password.toCharArray()));
                }

                request(userName, password, url, false);
            }

        }

        /**
         *  Generate a bunch of URLs mapped to String array. The String array has the following elements:
         *  - user info part of URL, 
         *  - expected user, 
         *  - expected password
         *  
         *  Note that the keys of the maps must be strings and not URL objects, because of the way URL.equals is
         *  implemented. This method does not consider the credentials.
         *  
         * @throws MalformedURLException 
         */
        Map<String, String[]> generateURLs() {
            String[] hosts = new String[]{ "127.0.0.1", "localhost.localdomain"};

            List<String[]> userData = new ArrayList<String[]>();

            // normal cases
            userData.add(new String[] { "user:pass@", "user", "pass" }); // results in: http://user:pass@[host]
            userData.add(new String[] { "", null, null });
            // unexpected cases
            userData.add(new String[] { "@", null, null });
            userData.add(new String[] { ":@", "", "" });
            userData.add(new String[] { "user:@", "user", "" });
            userData.add(new String[] { ":pass@", "", "pass" });

            Map<String, String[]> urls = new HashMap<String, String[]>();

            for (String[] userInfo : userData) {
                for (String host : hosts) {
                    String s = "http://" + userInfo[0] + host;
                    urls.put(s, userInfo);
                }
            }

            LOG.info("" + urls.size() + " URLs prepared");

            return urls;
        }

        private void request(String expectedUser, String expectedPass, URL url, boolean inURL)
        throws UnknownHostException {        

            String host = url.getHost();
            InetAddress addr = InetAddress.getAllByName(host)[0];
            int port = url.getPort();
            String protocol = url.getProtocol();
            String prompt = ""; // prompt for the user when asking for the credentials
            String scheme = "basic"; // or digest
            RequestorType reqType = RequestorType.SERVER;

            PasswordAuthentication credentials =
                Authenticator.requestPasswordAuthentication(addr, port, protocol, prompt, scheme);
            // If the credentials are in the URL, you can't find them using this method because we're not passing the URL
            checkCredentials(url, inURL ? null : expectedUser, inURL ? null : expectedPass, credentials); 

            credentials = Authenticator.requestPasswordAuthentication(host, addr, port, protocol, prompt, scheme);
            // If the credentials are in the URL, you can't find them using this method because we're not passing the URL
            checkCredentials(url, inURL ? null : expectedUser, inURL ? null : expectedPass, credentials); 

            credentials = Authenticator.requestPasswordAuthentication(host, addr, port, protocol, prompt, scheme, url, reqType);
            checkCredentials(url, expectedUser, expectedPass, credentials);
        }

        private void checkCredentials(URL url, String expectedUser, String expectedPass, PasswordAuthentication credentials) {
            if (expectedUser == null) {
                Assert.assertNull(url.toString(), credentials);
            } else {
                Assert.assertNotNull(url.toString(), credentials);
                Assert.assertEquals(url.toString(), expectedUser, credentials.getUserName());

                if (expectedPass == null) {
                    Assert.assertNull(url.toString(), credentials.getPassword());
                } else {
                    Assert.assertArrayEquals(url.toString(), expectedPass.toCharArray(), credentials.getPassword());
                }
            }
        }

    }

Here's the solution I've implemented and it works like a charm!

import java.net.*;
import java.util.*;
import java.util.logging.*;
import java.util.regex.*;

/**
 * Authenticator which keeps credentials to be passed to the requester
 * based on the concatenation of the authority and the URL that requires
 * authentication.
 * 
 * If the configured credentials are not found, the Authenticator will
 * use the embedded credentials if present.
 * 
 * Embedded credentials are in the form of <pre><b>user</b>:<b>password</b><i>@host:port/&lt;url-path&gt;</i></pre>
 *   
 * @author Michael Fortin 2011-09-23
 */
public class Authenticator extends java.net.Authenticator {

    private Logger log = Logger.getLogger(this.getClass().getName());
    private Map<String, PasswordAuthentication> authInfos;
    private Pattern embeddedAuthInfoPattern;

    @Override
    protected PasswordAuthentication getPasswordAuthentication() {

        String requesterInfo = String.format("%s%s", getRequestingURL().getAuthority(), getRequestingURL().getPath());
        log.fine(String.format("%s at \"%s\" is requesting %s password authentication for \"%s\"", getRequestorType(), getRequestingPrompt(), getRequestingScheme(), requesterInfo));
        PasswordAuthentication pa = null;

        if ((pa = authInfos.get(requesterInfo)) == null && (pa = getEmbeddedPA(getRequestingURL().getAuthority())) == null) {
            log.warning(String.format("No authentication information for \"%s\"", requesterInfo));
        }

        return pa;
    }

    public void setAuthInfos(Map<String, PasswordAuthentication> authInfos) {
        this.authInfos = authInfos;
    }

    public void setEmbeddedAuthInfoPattern(String pattern) {
        this.embeddedAuthInfoPattern = Pattern.compile(pattern);
    }

    private PasswordAuthentication getEmbeddedPA(String authInfo) {
        if (authInfo != null) {
            Matcher matcher = embeddedAuthInfoPattern.matcher(authInfo);
            if (matcher.find()) {
                return new PasswordAuthentication(matcher.group(1), matcher.group(2).toCharArray());
            }
        }
        return null;
    }
}

Lack of answers tends to mean that nobody knows, which tells me there isn't an answer.

I've been wondering the same thing, and I think the answer is that it can't be done through java.net. I think you either need to limit your http accesses to one server at a time, or look into other packages such as org.apache.http.client.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM