参考了一下其他朋友的 博客,大致了解了一下NTLM协议。
二) 我设计一个Interceptor,通过这个Interceptor的请求的session就会自然被设置上域名和用户名。
<mvc:interceptor> <mvc:mapping path="/security/login"/> <bean class="ying.interceptor.ADUserInfoSetInterceptor"> <property name="domain" value="*.*.com" /> <property name="domainController" value="192.168.**" /> <!-- 域服务器ip --> <property name="requestPredicate"> <bean class="ying.function.InDomainPredicate" /> </property> </bean> </mvc:interceptor>
当然,不一定要用interceptor,servlet-filter一样可以实现同样的功能。spring-mvc用习惯了,还是interceptor顺手一些。
其中被注入的requestPredicate,是一个谓词,用来过滤一些请求,不满足的谓词要求的request会被忽略掉。如下面这个谓词用来判断Client是否已经在公司的域里了
package ying.function; import java.io.IOException; import java.io.InputStreamReader; import java.io.LineNumberReader; import javax.servlet.http.HttpServletRequest; import org.apache.commons.collections.Predicate; import org.apache.commons.lang.StringUtils; public class InDomainPredicate implements Predicate { @Override public boolean evaluate(Object object) { if (! (object instanceof HttpServletRequest)) { return false; } HttpServletRequest request = (HttpServletRequest) object; String ip = request.getRemoteHost(); if ("127.0.0.1".equals(ip) || "localhost".equals(ip)) { return false; } try { Process p = Runtime.getRuntime().exec(String.format("nbtstat -A %s", ip)); InputStreamReader ir = new InputStreamReader(p.getInputStream()); LineNumberReader input = new LineNumberReader(ir); String workstation = null; String domain = null; int k = 0; for (int i = 1; i < 20; i++) { String str = StringUtils.defaultIfEmpty((input.readLine()), ""); if (str.indexOf("<00>") != -1) { if (StringUtils.isEmpty(workstation)) { workstation = str.substring(0, str.indexOf("<00>")).trim(); k = i; } if (StringUtils.isEmpty(domain) && (k > 0 && (i == k + 1))) { domain = str.substring(0, str.indexOf("<00>")).trim(); } } } return (workstation.startsWith("ZT") && workstation.contains("-") && "ZTGAME".equals(domain)); } catch (IOException e) { return false; } } }
package ying.interceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import jcifs.Config; import jcifs.UniAddress; import jcifs.ntlmssp.Type1Message; import jcifs.ntlmssp.Type2Message; import jcifs.ntlmssp.Type3Message; import jcifs.smb.SmbSession; import jcifs.util.Base64; import org.apache.commons.collections.Predicate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.util.Assert; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; public class ADUserInfoSetInterceptor extends HandlerInterceptorAdapter implements InitializingBean { private static final Logger LOGGER = LoggerFactory.getLogger(ADUserInfoSetInterceptor.class); private String domainController = null; private String domain = null; private String useExtendedSecurity = "false"; private String lmCompatibility = "0"; private String cachePolicy = "1200"; private String soTimeout = "1800000"; private Predicate requestPredicate; private String domainAttributeName = "domainAttributeName"; private String nameAttributeName = "nameAttributeName"; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (requestPredicate != null && ! requestPredicate.evaluate(request)) { LOGGER.debug("requestPredicate.evaluate(request) == false"); return true; } UniAddress dc = null; String msg = request.getHeader("Authorization"); if (msg != null && msg.startsWith("NTLM ")) { byte[] src = Base64.decode(msg.substring(5)); dc = UniAddress.getByName(domainController, true); byte[] challenge = SmbSession.getChallenge(dc); if (src[8] == 1) { Type1Message type1 = new Type1Message(src); Type2Message type2 = new Type2Message(type1, challenge, null); msg = Base64.encode(type2.toByteArray()); if (response != null) { response.setHeader("WWW-Authenticate", "NTLM " + msg); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.setContentLength(0); response.flushBuffer(); LOGGER.debug("C <-- S 401 Unauthorized WWW-Authenticate: NTLM <base64-encoded type-2-message>"); return false; } } else if (src[8] == 3) { Type3Message type3 = new Type3Message(src); byte[] lmResponse = type3.getLMResponse(); if (lmResponse == null) lmResponse = new byte[0]; byte[] ntResponse = type3.getNTResponse(); if (ntResponse == null) ntResponse = new byte[0]; request.getSession(true).setAttribute(this.nameAttributeName, type3.getUser()); request.getSession(true).setAttribute(this.domainAttributeName, type3.getDomain()); LOGGER.debug("C --> S GET ... Authorization: NTLM <base64-encoded type-3-message>"); } } else { response.setHeader("WWW-Authenticate", "NTLM"); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.setContentLength(0); response.flushBuffer(); LOGGER.debug("C <-- S 401 Unauthorized"); return false; } return true; } private void init() { Config.setProperty("jcifs.smb.client.soTimeout", getSoTimeout()); Config.setProperty("jcifs.netbios.cachePolicy", getCachePolicy()); Config.setProperty("jcifs.smb.lmCompatibility", getLmCompatibility()); Config.setProperty("jcifs.smb.client.useExtendedSecurity", getUseExtendedSecurity()); Config.setProperty("jcifs.http.domainController", getDomainController()); Config.setProperty("jcifs.smb.client.domain", getDomain()); } @Override public void afterPropertiesSet() throws Exception { Assert.notNull(domain); Assert.notNull(domainController); init(); } // --------------------------------------------------------------------------------------------- // getter and setter // 为了节约版面不写了 }
三) 完成登录功能,登录还是由安全框架负责,如spring-security或apache-shiro
本文不赘述!
四) 开源软件jcifs的maven坐标
<dependency> <groupId>jcifs</groupId> <artifactId>jcifs</artifactId> <version>1.3.17</version> </dependency>