【问题标题】:NTLM Authentication in AndroidAndroid 中的 NTLM 身份验证
【发布时间】:2012-01-07 15:00:29
【问题描述】:

我正在开发一个访问 Sharepoint Web 服务并进行 SOAP 处理的 Android 应用程序。我尝试了各种方法,如 JCIFS 等。没有任何效果。

有人可以帮我吗?我用谷歌搜索了很多天,但遇到这个问题的每个人都很沮丧。

谢谢, 英德拉吉特

【问题讨论】:

  • 您找到合适的解决方案了吗?我遇到了 OkHttp 的类似问题

标签: android ntlm


【解决方案1】:

我不是 NTLM 方面的专家,但我使用 JCIFS 库成功连接到我们的后端,并且对标头进行了一些手动操作。

我也使用 OkHttp 3 库进行网络连接,但您可以将我的代码调整到其他库。

主要思想是你必须与服务器协商才能连接。

第 1 步:

当您第一次尝试连接时,您将失败并在标头中收到一些信息:

WWW-认证:协商

WWW-身份验证:NTLM

第 2 步:

您需要使用 jcifs 库生成类型 1 的密钥(带有可选的域和工作站参数),然后尝试再次连接。 你会再次失败,但会在标题中收到一些有用的信息:

WWW-认证:NTLMvery_long_challenge_key

第 3 步:

您需要使用 jcifs 库,使用该挑战密钥 + 登录 + 密码生成类型 3 的密钥。那么连接就成功了!

现在一些代码,将依赖项添加到应用程序的 build.gradle 文件中的库中:

compile files('libs/jcifs-1.3.18.jar')
compile 'com.squareup.okhttp3:okhttp:3.4.1'

罐子可以在这里找到:https://jcifs.samba.org/src/

然后是 NTLMAuthenticator 类

import android.support.annotation.NonNull;

import java.io.IOException;
import java.util.List;

import jcifs.ntlmssp.NtlmFlags;
import jcifs.ntlmssp.Type1Message;
import jcifs.ntlmssp.Type2Message;
import jcifs.ntlmssp.Type3Message;
import jcifs.util.Base64;
import okhttp3.Authenticator;
import okhttp3.Credentials;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.Route;

/**
 * Created by Arnaud Guyon on 07.02.17.
 */

public class NTLMAuthenticator implements Authenticator {

    private static final int TYPE_1_FLAGS =
            NtlmFlags.NTLMSSP_NEGOTIATE_56 |
                    NtlmFlags.NTLMSSP_NEGOTIATE_128 |
                    NtlmFlags.NTLMSSP_NEGOTIATE_NTLM2 |
                    NtlmFlags.NTLMSSP_NEGOTIATE_ALWAYS_SIGN |
                    NtlmFlags.NTLMSSP_REQUEST_TARGET;

    private String mLogin;
    private String mPassword;
    private String mDomain;
    private String mWorkstation;

    public NTLMAuthenticator(@NonNull String login, @NonNull String password) {
        this(login, password, "", "");
    }

    public NTLMAuthenticator(@NonNull String login, @NonNull String password, @NonNull String domain, @NonNull String workstation) {
        mLogin = login;
        mPassword = password;
        mDomain = domain;
        mWorkstation = workstation;
    }

    @Override
    public Request authenticate(Route route, Response response) throws IOException {

        List<String> authHeaders = response.headers("WWW-Authenticate");
        if (authHeaders != null) {
            boolean negociate = false;
            boolean ntlm = false;
            String ntlmValue = null;
            for (String authHeader : authHeaders) {
                if (authHeader.equalsIgnoreCase("Negotiate")) {
                    negociate = true;
                }
                if (authHeader.equalsIgnoreCase("NTLM")) {
                    ntlm = true;
                }
                if (authHeader.startsWith("NTLM ")) {
                    ntlmValue = authHeader.substring(5);
                }
            }

            if (negociate && ntlm) {
                String type1Msg = generateType1Msg(mDomain, mWorkstation);
                String header = "NTLM " + type1Msg;
                return response.request().newBuilder().header("Authorization", header).build();
            } else if (ntlmValue != null) {
                String type3Msg = generateType3Msg(mLogin, mPassword, mDomain, mWorkstation, ntlmValue);
                String ntlmHeader = "NTLM " + type3Msg;
                return response.request().newBuilder().header("Authorization", ntlmHeader).build();
            }
        }

        if (responseCount(response) <= 3) {
            String credential = Credentials.basic(mLogin, mPassword);
            return response.request().newBuilder().header("Authorization", credential).build();
        }

        return null;
    }

    private String generateType1Msg(@NonNull String domain, @NonNull String workstation) {
        final Type1Message type1Message = new Type1Message(TYPE_1_FLAGS, domain, workstation);
        byte[] source = type1Message.toByteArray();
        return Base64.encode(source);
    }

    private String generateType3Msg(final String login, final String password, final String domain, final String workstation, final String challenge) {
        Type2Message type2Message;
        try {
            byte[] decoded = Base64.decode(challenge);
            type2Message = new Type2Message(decoded);
        } catch (final IOException exception) {
            exception.printStackTrace();
            return null;
        }
        final int type2Flags = type2Message.getFlags();
        final int type3Flags = type2Flags
                & (0xffffffff ^ (NtlmFlags.NTLMSSP_TARGET_TYPE_DOMAIN | NtlmFlags.NTLMSSP_TARGET_TYPE_SERVER));
        final Type3Message type3Message = new Type3Message(type2Message, password, domain,
                login, workstation, type3Flags);
        return Base64.encode(type3Message.toByteArray());
    }

    private int responseCount(Response response) {
        int result = 1;
        while ((response = response.priorResponse()) != null) {
            result++;
        }
        return result;
    }

}

然后,当您创建 OkHttpClient 时,添加此身份验证器:

OkHttpClient okHttpClient = new OkHttpClient.Builder()
        .authenticator(new NTLMAuthenticator(login, password))
        // .some other init here if necessary
        .build();

然后照常执行您的请求。

【讨论】:

  • 您能否详细说明“照常执行您的请求”?我对 OkHttp 不熟悉
  • 这里有OkHttp的简单示例:square.github.io/okhttp/#overview
  • 令人印象深刻。谢谢你的工作。我建议您将其上传到 git。对我来说就像一个魅力。
  • 什么是Type1Message??
  • Getting java.net.ProtocolException: Too many follow-up requests: 21
【解决方案2】:
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-27
相关资源
最近更新 更多