简体   繁体   English

如何处理多个Authenticator

[英]How to handle multiple Authenticator

To authenticate a request, I use Authenticator.setDefault which is VM wide... 要验证请求,我使用的是Authenticator.setDefault ,它是VM范围的......
What If I want to separate different webservices and each one are aware of their authentication credentials. 如果我想分隔不同的Web服务,每个人都知道他们的身份验证凭据。
Do I need to Authenticator.setDefault for each request ? 我是否需要为每个请求使用Authenticator.setDefault
This may not work if there are concurrent connection with mixed webservices... 如果与混合Web服务并发连接,这可能无效...

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). 如果JDK通过java.net.Authenticator中未传递URL的两个静态请求方法之一请求身份验证(然后getRequestingURL()将返回null),Mike的解决方案将抛出NullPointerException。
  • It requires you to pass in an external regex pattern that deconstructs the URL. 它要求您传入解构URL的外部正则表达式模式。 This is (very) easy to get wrong, and the URL class in the JDK implements this parsing, so I prefer to use that. 这(非常)容易出错,JDK中的URL类实现了这种解析,所以我更喜欢使用它。
  • It requires that some external class builds the map of PasswordAuthentication objects, and then sets it. 它要求一些外部类构建PasswordAuthentication对象的映射,然后设置它。 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. 更多的风格事物:我不建议复制类名(Authenticator),所以我将其重命名为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). 当我在这时,让我给你我的单元测试(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. 我一直想知道同样的事情,我认为答案是它无法通过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. 我认为您需要一次限制对一个服务器的http访问,或者查看其他软件包,例如org.apache.http.client。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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