浏览器未响应服务器发送的 NTLM 质询

时间:2021-02-01 14:47:51

标签: java single-sign-on ntlm jcifs

我们在我们的产品中使用最新的 JCIFS jars (org.codelibs-jcifs-2.1.19) 进行 NTLM 身份验证。
迁移到较新的 JCIFS 版本的原因是因为旧版本使用 SMB1,该版本已被弃用。
客户不愿意切换到 Kerberos 或类似的高级身份验证方案,并希望坚持使用 NTLM。

在我们使用最新的 JCIFS jar 之后,我们遇到的问题是浏览器没有响应来自服务器的 NTLM Challenge。
以下是 req-res 的年表。

  1. 浏览器 - 资源请求 /hello
  2. 服务器 - 401 WWW 验证 NTLM
  3. 浏览器 - 授权 NTLM 协商
  4. 服务器 - 401 WWW 验证 NTLM 挑战
  5. 浏览器 - 没有进一步的请求发送到服务器。

这是wireshark的截图

NTLM Request Response
以下是我的源代码(其中一些仅从 stackoverflow 复制)-

public class NTLMAuthenticator {
    private CIFSContext cifsContext;
    private Properties properties;
    private String domainName;
    private String clientUserName;
    private String clientPassword;
    private NtlmPasswordAuthenticator ntlmPasswordAuthenticator;
    private SSPContext context;

    public NTLMAuthenticator(String domainName, String clientUserName, String clientPassword) throws CIFSException {
        this.domainName = domainName;
        this.clientUserName = clientUserName;
        this.clientPassword = clientPassword;
        initProperties();
        ntlmPasswordAuthenticator = new NtlmPasswordAuthenticator(domainName, clientUserName, clientPassword);
        cifsContext = new BaseContext(new PropertyConfiguration(properties));
    }

    private byte[] extractTokenFromRequest(HttpServletRequest request) throws UnsupportedEncodingException, Base64DecodingException {
        String header = request.getHeader("Authorization");

        if ((header != null)) {
            System.out.println("Received Negotiate Header for request " + request.getRequestURL() + ": " + header);
            byte[] base64Token = header.replace("NTLM", "").trim().getBytes("US-ASCII");
            byte[] decodedToken = Base64.decode(base64Token);
            return decodedToken;
        } else return null;
    }

    private void negotiate(HttpServletResponse response) throws UnsupportedEncodingException {
        String redirectHeader = "NTLM";
        response.setHeader("Connection", "Keep-Alive");
        response.setHeader("WWW-Authenticate", redirectHeader);
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
    }

    private void sendType2Response(HttpServletResponse response, byte[] tokenFromRequest) throws Exception {
        Type2Message type2Message = new Type2Message(cifsContext, (Type1Message) constructNTLMMessage(tokenFromRequest), domainName.getBytes("US-ASCII"), null);
        
        String msg = new String(Base64.encode(type2Message.toByteArray()));
        response.setHeader("Connection", "Keep-Alive");
        response.setHeader("WWW-Authenticate", "NTLM " + msg);
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        response.setContentLength(0);
        response.flushBuffer();
        return;
    }

    public void authenticate(HttpServletRequest request, HttpServletResponse response) throws Exception {
        byte[] tokenFromRequest = extractTokenFromRequest(request);

        // step 0 - Send Negotiate header
        if (tokenFromRequest == null || tokenFromRequest.length == 0) {
            negotiate(response);
            return;
        }

        // step 1 - client sends negotiation token, which is called as Type1Token
        NtlmMessage message = constructNTLMMessage(tokenFromRequest);
        if (message instanceof Type1Message) {
            // create new context here - LOL not thread safe of course !
            context = ntlmPasswordAuthenticator.createContext(cifsContext, domainName, null, null, false);
            byte[] type1TokenBytes = context.initSecContext(tokenFromRequest, 0, tokenFromRequest.length);  //type2 message is created and returned as byte array

            sendType2Response(response, tokenFromRequest);
            return;
        }

        // step 2 - client sends type3 message, which contains actual information
        byte[] type1TokenBytes = context.initSecContext(tokenFromRequest, 0, tokenFromRequest.length);
        context.dispose(); // auth is completed

        System.out.println(context);
    }


    // The Client will only ever send a Type1 or Type3 message ... try 'em both
    protected NtlmMessage constructNTLMMessage(byte[] token) {
        NtlmMessage message = null;
        try {
            message = new Type1Message(token);
            return message;
        } catch (IOException e) {
            if ("Not an NTLMSSP message.".equals(e.getMessage())) {
                return null;
            }
        }

        try {
            message = new Type3Message(token);
            return message;
        } catch (IOException e) {
            if ("Not an NTLMSSP message.".equals(e.getMessage())) {
                return null;
            }
        }
        return message;
    }
}

非常感谢任何帮助。

0 个答案:

没有答案