一)最近项目中要求实现Web应用的SSO(Single Sign On),即对于已经登录到Windows Domain中的用户,不需要输入用户名、密码而直接使用当前登录的Domain用户信息进行验证,如果验证成功则进入,否则拒绝进入。
参考了一下其他朋友的[url=http://www.cnblogs.com/adylee/articles/975213.html]博客[/url],大致了解了一下NTLM协议。
二) 我设计一个Interceptor,通过这个Interceptor的请求的session就会自然被设置上域名和用户名。
当然,不一定要用interceptor,servlet-filter一样可以实现同样的功能。spring-mvc用习惯了,还是interceptor顺手一些。
其中被注入的requestPredicate,是一个谓词,用来过滤一些请求,不满足的谓词要求的request会被忽略掉。如下面这个谓词用来判断Client是否已经在公司的域里了
三) 完成登录功能,登录还是由安全框架负责,如spring-security或apache-shiro
本文不赘述!
四) 开源软件jcifs的maven坐标
参考了一下其他朋友的[url=http://www.cnblogs.com/adylee/articles/975213.html]博客[/url],大致了解了一下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>