如何让Android DownloadManager支持NTLM认证以下载校园网文件?
嗨,这个问题我之前帮朋友解决过!Android自带的DownloadManager确实有个硬伤——它原生完全不支持NTLM或者Negotiate这种校园网常用的域认证方式,只认Basic、Digest这类简单的HTTP基础认证。所以你直接改DownloadManager.Request是没用的,得换个思路,自己实现带NTLM认证的下载逻辑,我给你两个靠谱的方案:
方案一:用OkHttp实现(推荐,配置简单还灵活)
OkHttp这个第三方库内置了对NTLM认证的支持,而且还能轻松模拟DownloadManager的通知栏进度提示,步骤也不复杂:
第一步:先加依赖
在你项目的Module级build.gradle里添加上OkHttp的依赖(版本可以用最新的,我这里给的是稳定版):
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
第二步:写下载工具类
我给你写了个完整的工具类,包含NTLM认证配置、文件保存、通知栏进度更新,直接复制就能用:
import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; import android.content.Context; import android.os.Build; import android.os.Environment; import androidx.core.app.NotificationCompat; import okhttp3.Authenticator; import okhttp3.Credentials; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import okhttp3.Route; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; public class NTLMDownloader { private Context mContext; private NotificationManager mNotificationManager; private NotificationCompat.Builder mNotificationBuilder; private static final int NOTIFICATION_ID = 1001; private static final String CHANNEL_ID = "ntlm_download_channel"; public NTLMDownloader(Context context) { mContext = context; createNotificationChannel(); } private void createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel( CHANNEL_ID, "下载通知", NotificationManager.IMPORTANCE_DEFAULT ); mNotificationManager = mContext.getSystemService(NotificationManager.class); mNotificationManager.createNotificationChannel(channel); } else { mNotificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); } } public void startDownload(String url, String username, String password, String domain) { // 配置OkHttpClient的NTLM认证,这里要注意凭证格式是domain\\username OkHttpClient client = new OkHttpClient.Builder() .authenticator(new Authenticator() { @Override public Request authenticate(Route route, Response response) throws IOException { String credential = Credentials.ntlm(username, password, domain); return response.request().newBuilder() .header("Authorization", credential) .build(); } }) .build(); Request request = new Request.Builder() .url(url) .build(); // 下载要放子线程,别卡主线程 new Thread(() -> { try (Response response = client.newCall(request).execute()) { if (!response.isSuccessful()) { throw new IOException("请求失败: " + response.code()); } // 把文件存到系统Downloads目录,你也可以改其他路径 File downloadDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); String fileName = url.substring(url.lastIndexOf("/") + 1); File file = new File(downloadDir, fileName); try (InputStream inputStream = response.body().byteStream(); OutputStream outputStream = new FileOutputStream(file)) { byte[] buffer = new byte[4096]; long totalBytes = response.body().contentLength(); long downloadedBytes = 0; int bytesRead; // 初始化通知栏 mNotificationBuilder = new NotificationCompat.Builder(mContext, CHANNEL_ID) .setSmallIcon(android.R.drawable.stat_sys_download) .setContentTitle("正在下载") .setContentText(fileName) .setProgress((int) totalBytes, 0, false) .setOngoing(true); mNotificationManager.notify(NOTIFICATION_ID, mNotificationBuilder.build()); // 边读边写,更新进度 while ((bytesRead = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); downloadedBytes += bytesRead; mNotificationBuilder.setProgress((int) totalBytes, (int) downloadedBytes, false); mNotificationManager.notify(NOTIFICATION_ID, mNotificationBuilder.build()); } // 下载完成更新通知 mNotificationBuilder.setContentTitle("下载完成") .setProgress(0, 0, false) .setOngoing(false); mNotificationManager.notify(NOTIFICATION_ID, mNotificationBuilder.build()); } catch (IOException e) { e.printStackTrace(); // 下载失败提示 mNotificationBuilder.setContentTitle("下载失败") .setProgress(0, 0, false) .setOngoing(false); mNotificationManager.notify(NOTIFICATION_ID, mNotificationBuilder.build()); } } catch (IOException e) { e.printStackTrace(); } }).start(); } }
第三步:调用下载
在你的Activity或者Fragment里,直接实例化这个工具类调用就行:
// 替换成你的校园网文件URL、用户名、密码,域如果没有的话传空字符串就行 NTLMDownloader downloader = new NTLMDownloader(this); downloader.startDownload("https://你的校园网文件地址", "学号/用户名", "密码", "校园网域");
方案二:用原生HttpURLConnection实现(不用第三方库)
如果你不想加第三方依赖,也可以用Android原生的HttpURLConnection,它也支持NTLM,不过是全局配置,可能会影响其他请求:
// 全局设置NTLM认证,注意这里的用户名格式是"域\\用户名" Authenticator.setDefault(new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication( "校园网域\\用户名", "密码".toCharArray() ); } }); // 然后就是常规的HttpURLConnection下载逻辑,和普通下载一样 // 这里就不写完整代码了,核心就是设置完认证后,正常发起请求、读写文件就行
⚠️ 注意:这个全局认证会影响整个App的所有HTTP请求,如果你的App还有其他不需要NTLM认证的接口,可能会出问题,所以还是推荐用OkHttp的局部配置更稳妥。
为啥DownloadManager搞不定?
说句实在的,DownloadManager是系统级的服务,它的认证逻辑是写死在系统源码里的,只支持Basic和Digest,完全没做NTLM/Negotiate的适配,所以不管怎么改DownloadManager.Request都没用,只能绕开它自己实现。
内容的提问来源于stack exchange,提问作者Ahmed Ali

