如何处理多个身份验证器

6
为了进行身份验证,我使用了“Authenticator.setDefault”函数,该函数是VM范围内生效的...
如果我想分离不同的Web服务,并且每个服务都知道它们自己的身份验证凭据。
我需要为每个请求都调用“Authenticator.setDefault”函数吗?
但如果有混合不同Web服务的并发连接,这种方法可能无法正常工作...
3个回答

5

基于Mike的回答,我有以下解决方案。虽然我很欣赏这个一般想法(这就是为什么我复制它的原因;-),但我发现它有几个问题:

  • Mike的解决方案会抛出NullPointerException,如果JDK通过java.net.Authenticator中两个不传递URL的静态请求方法之一请求认证(那么getRequestingURL()将返回null)。
  • 它要求你传入一个外部正则表达式模式,以解构URL。这非常容易出错,并且JDK中的URL类实现了这种解析,所以我更喜欢使用它。
  • 它需要某个外部类构建PasswordAuthentication对象的映射,然后设置它。它没有实现其他组件在您的系统中可以使用的注册机制。我也将其转换为单例。
  • 更多的是一种风格:我不建议重复类名(Authenticator),所以我将其重命名为DefaultAuthenticator。

下面的解决方案解决了这些问题。

    /**
     * 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());
            }
        }
    }

顺便说一下,让我给你我的单元测试(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());
                }
            }
        }

    }

1

这是我实现的解决方案,它非常有效!

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;
    }
}

0
缺乏答案往往意味着没有人知道,这告诉我没有一个答案。
我一直在思考同样的问题,我认为答案是不能通过java.net来实现。我认为你要么需要限制一次只能访问一个服务器,要么研究其他包,比如org.apache.http.client。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接