Android NTLM Authentication

本文介绍了一种在Android应用中实现NTLM认证的方法。通过使用JCIF库和自定义认证方案工厂,可以成功地与使用NTLM协议的服务器进行交互。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

http://stackoverflow.com/questions/15762237/negotiate-using-one-session-with-ntlm-authentication-at-android

 

http://www.tekritisoftware.com/android-ntlm-authentication

 

 

With the increasing usage of smart phone in our daily life, this usage is getting more quantitative as well as qualitative by each passing day. It started with basic telephony then gaming and now it has graduated to Apps which helps in managing and exchanging important data like mails, financial details, payrolls and many more. Such heavy exchanges from or to the outer world is done by interacting with different server through legacy communication protocols and that involves different types of authentication handshakes.

Recently we came across a requirement of communicating to a server which is using NTLM authentication protocol. Now some of you would think “What is NTLM” so here is the answer::

NTLM is a suite of Microsoft security protocol and is successor to the authentication protocol in Microsoft LAN Manager. It is used for the authentication and negotiation of secure DCE purpose.

NTLM Security Service Provider (NTLMSSP) implements some core operations and these are::

1) Authentication :: Clients would be able to prove their respective identities
2) Signing :: It provides digital “signature” security.
3) Sealing :: It keeps the data confidential by providing symmetric-Key encryption.

More about NTLM and its authentication mechanism can be read in details at

http://www.innovation.ch/personal/ronald/ntlm.html
http://davenport.sourceforge.net/ntlm.html#whatIsNtlm

Here is the solution for successfully authenticating and communicating with a server using NTLM authentication protocol:

Step 1: We need to have the JCIF library. 
JCIFS can be downloaded from: http://jcifs.samba.org/

 

Step 2: Create a class which is the AuthSchemeFactory interface:

             NTLMSchemeFactory.java:

 

package com.movit.util;

 

import org.apache.http.auth.AuthScheme;

import org.apache.http.auth.AuthSchemeFactory;

import org.apache.http.impl.auth.NTLMScheme;

import org.apache.http.params.HttpParams;

 

public class NTLMSchemeFactory implements AuthSchemeFactory {

    public AuthScheme newInstance(HttpParams params) {

        return new NTLMScheme(new JCIFSEngine());

    }

}

 

Step 3Create a class for acting as a NTLM engine interface, which does all the type message validation and verification.

JCIFSEngine.java

package com.movit.util;

 

import java.io.IOException;

 

import jcifs.ntlmssp.NtlmFlags;

import jcifs.ntlmssp.Type1Message;

import jcifs.ntlmssp.Type2Message;

import jcifs.ntlmssp.Type3Message;

 

import org.apache.http.impl.auth.NTLMEngine;

import org.apache.http.impl.auth.NTLMEngineException;

 

public class JCIFSEngine implements NTLMEngine {

    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;

 

    public String generateType1Msg(String domain, String workstation) throws NTLMEngineException {

        final Type1Message type1Message = new Type1Message(TYPE_1_FLAGS, domain, workstation);

        return Base64.encode(type1Message.toByteArray());

    }

 

    public String generateType3Msg(String username, String password, String domain, String workstation, String challenge)

        throws NTLMEngineException {

        Type2Message type2Message;

 

        try {

            type2Message = new Type2Message(Base64.decode(challenge));

        } catch (final IOException exception) {

            throw new NTLMEngineException("Error in type2 message", exception);

        }

 

        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, username, workstation, type3Flags);

        return Base64.encode(type3Message.toByteArray());

    }

}

 

step 4. Register the NTLM Scheme Factory with HttpClient instance and other domain credentials to do the handshake. It has been observed that deviceIP and domainName is not required on a mandatory basis. These can be set as null also.

 

 

    /*

     * authentication method 7

     * webserviceUrl, 

     * url of the web service. 

     * webserviceIP, 

     * IP of the server.

     * username, Domain username 

     * password, Domain password

     */

    public int getFileSize(String webserviceUrl, String username, String password) {

        int fileSize = 0;

        try {

            String deviceIP = null;// getLocalIpAddress(); Device IP

            String domainName = FileUtils.DOMAIN_NAME;

 

            DefaultHttpClient httpclient = new DefaultHttpClient();

            httpclient.getAuthSchemes().register("ntlm", new NTLMSchemeFactory());

            AuthScope authScope = new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT);

            AuthScope authScope1 = new AuthScope(null, -1);

            httpclient.getCredentialsProvider().setCredentials(authScope,

                                                               new NTCredentials(username, password, deviceIP, domainName));

 

            HttpGet httpGet = new HttpGet(webserviceUrl);

            httpGet.getParams().setBooleanParameter(CoreProtocolPNames.USE_EXPECT_CONTINUE, false);

 

            HttpResponse response = httpclient.execute(httpGet);

            // String responseXML = EntityUtils.toString(response.getEntity());

 

            if (HttpStatus.SC_OK == response.getStatusLine().getStatusCode()) {

                HttpEntity entity = response.getEntity();

                //fileSize = (int) entity.getContentLength();

                fileSize = getfileSizeByInputStream(entity.getContent());

                entity.consumeContent();

                return fileSize;

            }

 

        } catch (Exception e) {

            e.printStackTrace();

        }

        return fileSize;

    }

 

    public int getfileSizeByInputStream(InputStream input) {

        BufferedInputStream bis = new BufferedInputStream(input);

        int progress = 0;

        try {

            int length;

            byte buffer[] = new byte[6 * 1024];

            while (-1 != (length = bis.read(buffer))) {

                progress += length;

            }

        } catch (Exception e) {

            e.printStackTrace();

        } finally {

            try {

                if (null != bis) {

                    bis.close();

                    bis = null;

                }

            } catch (Exception e) {

                e.printStackTrace();

            }

        }

        return progress;

    }

 

    public String getLocalIpAddress() {

 

        String deviceIp = null;

        boolean keepLookupOn = true;

 

        try {

            Enumeration availableNetwork = NetworkInterface.getNetworkInterfaces();

 

            while (availableNetwork.hasMoreElements() && keepLookupOn) {

                NetworkInterface intf = (NetworkInterface) availableNetwork.nextElement();

                Enumeration enumIpAddr = intf.getInetAddresses();

 

                while (enumIpAddr.hasMoreElements()) {

                    InetAddress inetAddress = (InetAddress) enumIpAddr.nextElement();

 

                    deviceIp = inetAddress.getHostAddress().toString();

 

                    if (!inetAddress.isLoopbackAddress() && InetAddressUtils.isIPv4Address(deviceIp)) {

                        keepLookupOn = false;

                        break;

                    }

                }

            }

        } catch (SocketException ex) {

            ex.printStackTrace();

        }

 

        return deviceIp;

 

    }

 

<think>我们正在讨论Kerberos V5认证在浏览器环境中的重定向问题,特别是当浏览器不支持Kerberos和NTLM时的情况。 根据用户的问题,我们需要理解Kerberos V5认证在浏览器中重定向的机制,以及当浏览器不支持Kerberos和NTLM时会出现什么问题。 首先,Kerberos V5是一种网络认证协议,它使用票据(ticket)来证明用户身份。在Web环境中,Kerberos认证通常通过SPNEGO(Simple and Protected GSS-API Negotiation Mechanism)来实现,而NTLM是另一种认证协议,主要用于Windows环境。 当浏览器支持Kerberos时,它可以在接收到服务器的401 Unauthorized响应(包含WWW-Authenticate: Negotiate头部)后,自动获取Kerberos票据并发送给服务器。但是,如果浏览器不支持Kerberos和NTLM,那么它就无法完成这种认证。 用户提到“Kerberos V5 authentication redirection browser does not support Kerberos and NTLM”,这可能是描述了一个问题:当浏览器不支持Kerberos和NTLM时,认证重定向会失败。 我们可以从以下几个方面进行解释: 1. 认证流程:在Kerberos认证流程中,浏览器需要能够处理服务器的重定向(例如,当需要认证时,服务器返回401并期望浏览器发送认证令牌)。 2. 浏览器支持:现代浏览器(如Chrome、Firefox、Edge等)通常支持Kerberos认证(需要配置),但某些浏览器或特殊环境(如移动浏览器)可能不支持。 3. 问题表现:如果浏览器不支持Kerberos和NTLM,那么当服务器要求进行协商认证(Negotiate)时,浏览器无法提供有效的令牌,导致认证失败,用户可能会看到重复的登录提示或者403 Forbidden错误。 解决方案建议: - 确保浏览器支持Kerberos认证,并已正确配置(例如,在Chrome中需要将目标站点添加到受信任站点列表)。 - 如果浏览器不支持Kerberos,可以考虑使用其他认证方式(如基于表单的认证、OAuth等)。 - 在服务器端配置回退机制,例如当协商认证失败时,回退到基本认证(Basic)或其他认证方式(但基本认证不够安全,需谨慎使用)。 引用说明: 在引用[3]中提到了SSL配置,虽然与Kerberos没有直接关系,但安全连接(HTTPS)通常是Kerberos认证的前提条件,因为浏览器在非安全上下文中可能不会发送认证令牌。因此,确保使用HTTPS是重要的。 根据上述分析,我们可以生成以下相关问题: §§相关问题§§ 1. 如何配置浏览器以支持Kerberos认证? 2. Kerberos认证失败时有哪些常见的调试步骤? 3. 在浏览器不支持Kerberos的情况下,有哪些替代的认证方案? 4. Kerberos V5认证中,SPNEGO协议的作用是什么? 5. 为什么在Web环境中使用Kerberos认证需要HTTPS? 下面我们开始正式回答用户的问题。</think>### Kerberos V5认证重定向与浏览器兼容性问题分析 Kerberos V5认证在浏览器环境中的重定向流程涉及以下关键环节: 1. **认证初始化**:当用户访问受保护资源时,服务器返回`401 Unauthorized`并携带`WWW-Authenticate: Negotiate`头部,要求SPNEGO协商认证[^1] 2. **浏览器响应**: - 支持Kerberos的浏览器(如Chrome/Edge)会尝试获取Kerberos票据 - 不支持Kerberos/NTLM的浏览器会忽略该头部或显示登录框 3. **重定向问题**:当浏览器既**不支持Kerberos也不支持NTLM**时: - 无法生成有效的`Authorization`头部 - 服务器持续返回401状态形成循环重定向 - 最终导致`ERR_TOO_MANY_REDIRECTS`错误或空白页面 #### 根本原因分析 ```mermaid graph LR A[访问资源] --> B{浏览器支持认证?} B -->|是| C[发送Kerberos/NTLM令牌] B -->|否| D[忽略WWW-Authenticate] D --> E[服务器持续重定向] E --> F[认证循环失败] ``` #### 解决方案 1. **强制启用浏览器支持**(需管理员权限): - Chrome:配置注册表项`AuthNegotiateDelegateWhitelist`和`AuthServerWhitelist`[^2] - Firefox:设置`network.negotiate-auth.trusted-uris` 2. **服务端降级机制**: ```nginx # 示例:Nginx配置认证回退 location /secure { auth_gss on; auth_gss_allow_basic_fallback on; # 允许BASIC认证回退 } ``` 3. **替代认证方案**: - 实现OAuth 2.0/OIDC联合认证 - 使用客户端证书认证(需配置SSL引擎)[^3] #### 调试步骤 1. 通过浏览器开发者工具检查网络请求中的`WWW-Authenticate`和`Authorization`头部 2. 使用Wireshark捕获SPNEGO协商包(过滤条件:`kerberos or ntlmssp`) 3. 验证服务端SPN配置: ```powershell setspn -Q HTTP/your-server.domain.com ``` > **关键点**:Kerberos浏览器认证要求严格的服务SPN注册、正确的时间同步(KDC偏差<5分钟)和双向信任关系。在公有云环境中还需注意NAT地址转换导致的SPN注册问题[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值