我们在我们的产品中使用最新的 JCIFS jars (org.codelibs-jcifs-2.1.19)
进行 NTLM 身份验证。
迁移到较新的 JCIFS 版本的原因是因为旧版本使用 SMB1,该版本已被弃用。
客户不愿意切换到 Kerberos 或类似的高级身份验证方案,并希望坚持使用 NTLM。
在我们使用最新的 JCIFS jar 之后,我们遇到的问题是浏览器没有响应来自服务器的 NTLM Challenge。
以下是 req-res 的年表。
这是wireshark的截图
以下是我的源代码(其中一些仅从 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;
}
}
非常感谢任何帮助。