`

SpringMVC杂记(十二) 自定义Interceptor从Active Directory得到域信息

 
阅读更多
一)最近项目中要求实现Web应用的SSO(Single Sign On),即对于已经登录到Windows Domain中的用户,不需要输入用户名、密码而直接使用当前登录的Domain用户信息进行验证,如果验证成功则进入,否则拒绝进入。
参考了一下其他朋友的博客,大致了解了一下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>
分享到:
评论
1 楼 3108493554 2016-03-18  
你好 ,有些问题想请教下,加下我qq310849354,你这上面的代码是ntlm验证成功就跳转到项目的主页,验证失败就跳到项目的登录页面是吗?

相关推荐

Global site tag (gtag.js) - Google Analytics