使用Java连接需要身份验证的远程URL

137

我该如何在Java中连接到需要认证的远程URL? 我正在尝试修改以下代码,以便能够以编程方式提供用户名/密码,以便它不会抛出401错误。

URL url = new URL(String.format("http://%s/manager/list", _host + ":8080"));
HttpURLConnection connection = (HttpURLConnection)url.openConnection();

请查看此处:https://dev59.com/IHM_5IYBdhLWcg3wfTI0 - WesternGun
12个回答

148

有一种本地化且不那么侵入性的替代方法,只适用于您的调用。

URL url = new URL(“location address”);
URLConnection uc = url.openConnection();
String userpass = username + ":" + password;
String basicAuth = "Basic " + new String(Base64.getEncoder().encode(userpass.getBytes()));
uc.setRequestProperty ("Authorization", basicAuth);
InputStream in = uc.getInputStream();

6
Base64类可以由Apache Commons Codec提供。 - Matthew Buckett
因为Grails和许多其他Java框架都使用Apache Commons Libs,所以它在这些框架上是“本地的”。 - Wanderson Santos
1
通常情况下不起作用,除非你使用.trim()来处理结果,或者调用一个不会产生分块输出的方法变体。javax.xml.bind.DatatypeConverter似乎更安全。 - Jesse Glick
你不应该将密码存储在 string 中,而应该使用 char[] 数组。https://dev59.com/rGox5IYBdhLWcg3w74q2 - Brian McCutchon
本地的Base64编码在Java 8之前存在缺陷,因此这可能解释了为什么在某些配置上它无法正常工作。 - Matthieu
@BrianMcCutchon 如果您在内部使用它进行其他用途,则是的。但是,由于它可能以明文形式存储在某个地方... 您可以将其作为 byte[] 使用,并将用户名和密码一起传递到Base64编码中,以使其更加“安全”。 - Matthieu

142

你可以像下面这样设置 HTTP 请求的默认认证器:

Authenticator.setDefault (new Authenticator() {
    protected PasswordAuthentication getPasswordAuthentication() {
        return new PasswordAuthentication ("username", "password".toCharArray());
    }
});

此外,如果您需要更灵活的选项,可以查看Apache HttpClient,它将为您提供更多身份验证选项(以及会话支持等)。


2
以上代码可以运行,但是对于正在发生的事情相当隐晦。 如果您想知道正在发生什么,请深入文档中了解子类和方法覆盖。 这里的代码更加明确 javacodegeeks - Fuchida
13
可以针对特定的URL.openConnection()调用设置特定的Authenticator而不是全局设置吗? - Yuriy Nakonechnyy
@Yura:不行,它必须是全局的。但是,你可以做一些邪恶的事情,比如设置一个全局认证器,从线程本地变量中提取凭据,并在进行HTTP连接之前为每个线程设置凭据。 - David Given
你可以这样做:connection.setAuthenticator(...) - Punyapat
4
回应@YuriyNakonechnyy...如果您使用的是Java 9或更高版本,则可以调用HttpURLConnection.setAuthenticator()。不幸的是,在Java 8及更早版本中,Authenticator实例是一个JVM范围的全局变量。请参见:https://docs.oracle.com/javase/9/docs/api/java/net/HttpURLConnection.html#setAuthenticator-java.net.Authenticator- - Neil Bartlett

81

您也可以使用以下方法,无需使用外部软件包:

URL url = new URL(“location address”);
URLConnection uc = url.openConnection();

String userpass = username + ":" + password;
String basicAuth = "Basic " + javax.xml.bind.DatatypeConverter.printBase64Binary(userpass.getBytes());

uc.setRequestProperty ("Authorization", basicAuth);
InputStream in = uc.getInputStream();

请注意,对于Java9+,您可能需要使用--add-modules javax.xml.bind,因为他们从默认类路径中删除了该包。而在Java11+中,它被完全删除,您需要再次使用外部依赖项。这就是进步的代价! - Ed Randall

48

如果你在使用普通登录时,在协议和域名之间输入用户名和密码就可以了,这样更简单。这种方式可以带登录信息也可以不带。

示例URL:http://user:pass@example.com/url

URL url = new URL("http://user:pass@example.com/url");
URLConnection urlConnection = url.openConnection();

if (url.getUserInfo() != null) {
    String basicAuth = "Basic " + new String(new Base64().encode(url.getUserInfo().getBytes()));
    urlConnection.setRequestProperty("Authorization", basicAuth);
}

InputStream inputStream = urlConnection.getInputStream();

请注意下面来自valerybodak的评论,说明如何在Android开发环境中完成此操作。


如果您的用户名/密码包含特殊字符,我相信您需要对其进行urlencode。然后,在上面的代码片段中,您将首先通过URLDecoder.decode()传递url.getUserInfo()(@Peter Rader)。 - m01
谢谢,但对于Android应该使用以下Base64:String basicAuth = "Basic " + Base64.encodeToString(url.getUserInfo().getBytes(), Base64.DEFAULT); httpConn.setRequestProperty("Authorization", basicAuth); - valerybodak

8

我来这里寻找一个Android-Java答案,现在我简单总结一下:

  1. 使用 java.net.Authenticator,可以参考 James van Huis 的解答
  2. 使用 Apache Commons HTTP Client,可以参考这个解答
  3. 使用基本的 java.net.URLConnection 并像这里所示手动设置认证头。

如果你想要在Android中使用带有基本认证的 java.net.URLConnection,可以尝试以下代码:

URL url = new URL("http://www.example.com/resource");
URLConnection urlConnection = url.openConnection();
String header = "Basic " + new String(android.util.Base64.encode("user:pass".getBytes(), android.util.Base64.NO_WRAP));
urlConnection.addRequestProperty("Authorization", header);
// go on setting more request headers, reading the response, etc

7

使用HttpsURLConnection设置认证成功。

           URL myUrl = new URL(httpsURL);
            HttpsURLConnection conn = (HttpsURLConnection)myUrl.openConnection();
            String userpass = username + ":" + password;
            String basicAuth = "Basic " + new String(Base64.getEncoder().encode(userpass.getBytes()));
            //httpsurlconnection
            conn.setRequestProperty("Authorization", basicAuth);

以下是本文提取的部分变更内容。Base64来自java.util包。


4
自从Java 9版本以后,您可以这样做。
URL url = new URL("http://www.example.com");
HttpURLConnection connection = (HttpURLConnection)url.openConnection();
connection.setAuthenticator(new Authenticator() {
    protected PasswordAuthentication getPasswordAuthentication() {
        return new PasswordAuthentication ("USER", "PASS".toCharArray());
    }
});

2
没有看到 setAuthenticator() 方法。 - WesternGun
我也不行...我希望这是可能的,因为代码非常整洁。 - bigbadmouse
我也不会……我希望这是可能的,因为代码非常整洁。 - undefined
可以。干净利落。我喜欢。 - Paul Cuddihy

4

在使用"Base64().encode()"方法时一定要非常小心,我的团队和我因为它在生成的字符串末尾添加了\r\n而遇到了400个Apache错误请求问题。

我们通过使用Wireshark嗅探数据包来发现这个问题。

以下是我们的解决方案:

import org.apache.commons.codec.binary.Base64;

HttpGet getRequest = new HttpGet(endpoint);
getRequest.addHeader("Authorization", "Basic " + getBasicAuthenticationEncoding());

private String getBasicAuthenticationEncoding() {

        String userPassword = username + ":" + password;
        return new String(Base64.encodeBase64(userPassword.getBytes()));
    }

希望这能有所帮助!

3

使用此代码进行基本身份验证。

URL url = new URL(path);
String userPass = "username:password";
String basicAuth = "Basic " + Base64.encodeToString(userPass.getBytes(), Base64.DEFAULT);//or
//String basicAuth = "Basic " + new String(Base64.encode(userPass.getBytes(), Base64.No_WRAP));
HttpURLConnection urlConnection = (HttpURLConnection)url.openConnection();
urlConnection.setRequestProperty("Authorization", basicAuth);
urlConnection.connect();


3
我希望能够为您提供一种解决方案,来应对您无法控制打开连接的代码的情况。就像我使用URLClassLoader从受密码保护的服务器加载jar文件时所做的那样。 Authenticator的解决方案可行,但它的缺点是,它首先尝试在没有密码的情况下访问服务器,只有当服务器要求密码时才提供密码。如果您已经知道服务器需要密码,那么这是一个不必要的往返。
public class MyStreamHandlerFactory implements URLStreamHandlerFactory {

    private final ServerInfo serverInfo;

    public MyStreamHandlerFactory(ServerInfo serverInfo) {
        this.serverInfo = serverInfo;
    }

    @Override
    public URLStreamHandler createURLStreamHandler(String protocol) {
        switch (protocol) {
            case "my":
                return new MyStreamHandler(serverInfo);
            default:
                return null;
        }
    }

}

public class MyStreamHandler extends URLStreamHandler {

    private final String encodedCredentials;

    public MyStreamHandler(ServerInfo serverInfo) {
        String strCredentials = serverInfo.getUsername() + ":" + serverInfo.getPassword();
        this.encodedCredentials = Base64.getEncoder().encodeToString(strCredentials.getBytes());
    }

    @Override
    protected URLConnection openConnection(URL url) throws IOException {
        String authority = url.getAuthority();
        String protocol = "http";
        URL directUrl = new URL(protocol, url.getHost(), url.getPort(), url.getFile());

        HttpURLConnection connection = (HttpURLConnection) directUrl.openConnection();
        connection.setRequestProperty("Authorization", "Basic " + encodedCredentials);

        return connection;
    }

}

这会注册一个新的协议 my,当添加凭据时,它会被 http 替换。所以在创建新的 URLClassLoader 时,只需将 http 替换为 my 即可。我知道 URLClassLoader 提供了一个接受 URLStreamHandlerFactory 的构造函数,但如果 URL 指向 jar 文件,则不会使用此工厂。

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