package com.iamberry.wechat.filters;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.Date;
import java.util.UUID;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;

import com.iamberry.app.tool.des.MD5;
import com.iamberry.wechat.tools.DateTimeUtil;
import com.iamberry.wechat.tools.IpAddressUtil;
/**
 * @company	深圳爱贝源科技有限公司
 * @website www.iamberry.com
 * @author 	献
 * @tel		18271840547
 * @date	2016年10月10日
 * @explain	CSRF基本防范，如果还存在xss，那么此防范本身就没有意义；<br>
 * 			使用注意：如果多次重复启动，可能导致系统拒绝服务，这只是因为tomcat默认最多接受200个cookie的缘故,关闭浏览器重新打开即可。
 * 			另外系统本身已经做了优化处理，保证99%情况下不会出现问题。
 */
//@WebFilter(value = {"/admin/*", "/loginUI"})
public class CSRFTokenFilter implements Filter {
	
	/**
	 * DES对称加密
	 */
	private static MD5 md5 = new MD5();
	
	/**
	 * 后台登录地址
	 */
	private static String ADMIN_LOGIN_URL;
	
	/**
	 * 签名格式
	 */
	public static final String SIGNATURE_SIMPLE = "userAgent=%s&userIp=%s&token=%s&timestamp=%s&sessionId=%s";

	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
		// 获取ContextPath,如果为了程序优化，可以写死，但是降低扩展性
		if (ADMIN_LOGIN_URL == null) {
			ADMIN_LOGIN_URL = request.getServletContext().getContextPath() + "/loginUI";
		}
		
		HttpServletRequest req = (HttpServletRequest) request;
		HttpServletResponse resp = (HttpServletResponse) response;
		
		// 当前请求uri
		String uri = req.getRequestURI();
		// 是否请求登录页面，如果是，那么创建数据，放行。
		if (isLoginPage(uri)) {
			// 如果是后台页面请求登录，那么自动创建数据，但是登录后不允许invalidate session
			savesSignatureToToken(req, resp);
			chain.doFilter(req, resp);
			return;
		}
		
		// 是否请求后台页面数据，如果是不放行,继续判断，如果是请求后台的静态资源，那么放行
		if (!isAdminPage(uri)) {
			chain.doFilter(req, resp);return;
		}
		// 如果请求为后台页面数据，那么做出处理和判断，降低发生csrf概率
		if (!handlerAdminPage(req, resp)) {
			// 校验失败，需要重新登录
			req.getSession().invalidate();
			resp.sendRedirect(ADMIN_LOGIN_URL);return;
		}
		chain.doFilter(req, resp);
	}
	
	/**
	 * 是否为后台页面请求
	 * @param uri
	 */
	public boolean isAdminPage(String uri) {
		
		if (StringUtils.lastIndexOfAny(uri, "png", "jpg", "jpeg", "js", "css", "ttf", "wttf") != -1) return false;
		
		if (uri.contains("admin")) return true;
		return false;
	}

	/**
	 * 判断是否为后台登陆页面
	 * @param uri
	 */
	public boolean isLoginPage(String uri) {
		if (uri.contains(ADMIN_LOGIN_URL)) return true;
		return false;
	}
	
	/**
	 * 处理后台页面请求
	 * @param request
	 * @return true:继续过滤链的，false:停止当前过滤链
	 */
	public boolean handlerAdminPage(HttpServletRequest request, HttpServletResponse response) {
		// 遍历用户数据:token、ip、userAgent、timestamp、sessionId、Signature
		String TOKEN_KEY = (String) request.getSession().getAttribute("1");
		String IP_KEY = (String) request.getSession().getAttribute("2");
		String USER_AGENT_KEY = (String) request.getSession().getAttribute("3");
		String TIME_KEY = (String) request.getSession().getAttribute("4");
		String SESSION_KEY = (String) request.getSession().getAttribute("5");
		String SIGN_KEY = (String) request.getSession().getAttribute("6");
		
		String token = null, ip = null, userAgent = null, timestamp = null, sessionId = null, signature = null;
		Cookie[] cookies = request.getCookies();
		for(Cookie cookie : cookies){
			if (cookie.getName().equals(TOKEN_KEY)) {
				token = cookie.getValue();
			} else if (cookie.getName().equals(IP_KEY)) {
				ip = cookie.getValue();
			} else if (cookie.getName().equals(USER_AGENT_KEY)) {
				userAgent = cookie.getValue();
			} else if (cookie.getName().equals(TIME_KEY)) {
				timestamp = cookie.getValue();
			} else if (cookie.getName().equals(SESSION_KEY)) {
				sessionId = cookie.getValue();
			} else if (cookie.getName().equals(SIGN_KEY)) {
				signature = cookie.getValue();
			}
		}
		
		if (StringUtils.isEmpty(token) || StringUtils.isEmpty(ip) || StringUtils.isEmpty(userAgent) || StringUtils.isEmpty(timestamp) || StringUtils.isEmpty(sessionId) || StringUtils.isEmpty(signature)) {
			return false;
		}
		if (!StringUtils.equals(signature(userAgent, ip, SIGNATURE_SIMPLE, token, timestamp, sessionId), signature)) {
			return false;
		}
		String userAgentTemp = StringUtils.replace(StringUtils.replace(StringUtils.trimToEmpty(request.getHeader("user-agent")), " ", ""), "\0", "");
		try {
			userAgentTemp = md5.encrypt(userAgentTemp);
			if (!StringUtils.endsWithAny(userAgent, userAgentTemp)) {
				return false;
			}
		} catch (Exception e) {e.printStackTrace();}
		
		String ipTemp = IpAddressUtil.getIpAddr(request);
		ipTemp = StringUtils.isEmpty(ipTemp) ? "proxyClientIp" : StringUtils.replace(ipTemp, ".", "");
		if (!StringUtils.equals(ip, ipTemp)) {
			return false;
		}
		
		// 是否超时
		try {
			Long date = Long.parseLong(timestamp);
			Long temp = date - System.currentTimeMillis();
			if (temp < 0) {
				savesSignatureToToken(request, response);
			}
			return true;
		} catch (Exception e) {
			return false;
		}
	}
	
	/**
	 * 保存用户校验所需的数据
	 * @param request
	 * @throws Exception 
	 */
	public static void savesSignatureToToken(HttpServletRequest request, HttpServletResponse response) {
		
		// 如果已经存在值，不要变更名称保存，否则最后会导致cookie过多，tomcat直接返回400; 如果不存在，那么随机产生，防止被猜测
		String TOKEN_KEY = (String) request.getSession().getAttribute("1");
		String IP_KEY = (String) request.getSession().getAttribute("2");
		String USER_AGENT_KEY = (String) request.getSession().getAttribute("3");
		String TIME_KEY = (String) request.getSession().getAttribute("4");
		String SESSION_KEY = (String) request.getSession().getAttribute("5");
		String SIGN_KEY = (String) request.getSession().getAttribute("6");
		if (TOKEN_KEY == null) {
			TOKEN_KEY = getSecureRandomString();
		}
		if (IP_KEY == null) {
			IP_KEY =  getSecureRandomString();
		}
		if (USER_AGENT_KEY == null) {
			USER_AGENT_KEY = getSecureRandomString();
		}
		if (TIME_KEY == null) {
			TIME_KEY = getSecureRandomString();
		}
		if (SESSION_KEY == null) {
			SESSION_KEY = getSecureRandomString();
		}
		if (SIGN_KEY == null) {
			SIGN_KEY = getSecureRandomString();
		}
		// 随机
		request.getSession().setAttribute("1", TOKEN_KEY);
		request.getSession().setAttribute("2", IP_KEY);
		request.getSession().setAttribute("3", USER_AGENT_KEY);
		request.getSession().setAttribute("4", TIME_KEY);
		request.getSession().setAttribute("5", SESSION_KEY);
		request.getSession().setAttribute("6", SIGN_KEY);
		// token
		String token = getSecureRandomString();
		createCookie(response, TOKEN_KEY, token);
		request.getSession().setAttribute(TOKEN_KEY, token);
		// IP
		String ipTemp = IpAddressUtil.getIpAddr(request);
		String ip = StringUtils.isEmpty(ipTemp) ? "proxyClientIp" : StringUtils.replace(ipTemp, ".", "");
		createCookie(response, IP_KEY, ip);
		// userAgent
		String userAgent = StringUtils.replace(StringUtils.replace(StringUtils.trimToEmpty(request.getHeader("user-agent")), " ", ""), "\0", "");
		try {userAgent = md5.encrypt(userAgent);} catch (Exception e) {e.printStackTrace();}
		createCookie(response, USER_AGENT_KEY, userAgent);
		// timestamp
		Long timestamp = DateTimeUtil.addMinute(5).getTime();
		createCookie(response, TIME_KEY, timestamp.toString());
		// sessionId
		String sessionId = request.getSession().getId();
		createCookie(response, SESSION_KEY, sessionId);
		// Signature
		String signature = signature(userAgent, ip, SIGNATURE_SIMPLE, token, timestamp.toString(), sessionId);
		createCookie(response, SIGN_KEY, signature);
	}

	/**
	 * 创建cookie，并保存到response中，写入客户端
	 * @param response
	 * @param name
	 * @param value
	 */
	public static void createCookie(HttpServletResponse response, String name, String value) {
		Cookie cookie = new Cookie(name,value);
		cookie.setMaxAge(-1);		// 设置为session级cookie，防止撑爆浏览器
		cookie.setPath("/"); 		// 一旦正式的环境，使用此，保证多个应用共享
		cookie.setHttpOnly(true);	// 不允许页面读取cookie,此方法不安全，最终保证还是取决浏览器，某些浏览器不支持。
		response.addCookie(cookie);
	}
	
	/**
	 * 根据name,解析token中的数据
	 * @param cookieName
	 * @param request
	 * @return
	 */
	public static String parseCookieByName(String cookieName, HttpServletRequest request) {
		Cookie[] cookies = request.getCookies();
		for(Cookie cookie : cookies){
			if (cookie.getName().equals(cookieName)) {return cookie.getValue();}
		}
		return null;
	}

	/**
	 * 签名认证，根据userAgent、用户IP、token、有效时间、sessionId签名
	 * @param userAgent
	 * @param userIp
	 * @param format
	 * @param token
	 * @param timestamp
	 * @param sessionId
	 * @return
	 */
	public static String signature(String userAgent, String userIp,
			String format, String token, String timestamp, String sessionId) {
		// 准备签名模版
		String signatureTemp = String.format(SIGNATURE_SIMPLE, userAgent, userIp, token, timestamp, sessionId);
		// md5签名
		try {
			String signature = md5.encrypt(signatureTemp);
			return signature;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 创建一个安全的随机数组byte[],SHA1
	 * @return
	 */
	public static byte[] createRandomByte() {
		try {
			SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
			byte[] bytes = new byte[1024 / 8];
			random.nextBytes(bytes);
			int count = 10;
			byte[] seed = random.generateSeed(count);
			random = SecureRandom.getInstance("SHA1PRNG");
			random.setSeed(seed);
			SecureRandom random2 = SecureRandom.getInstance("SHA1PRNG");
			random2.setSeed(seed);
			return seed;
		} catch (Exception e) {
			return UUID.randomUUID().toString().getBytes();
		}
	}
	
	/**
	 * 获取一个安全的随机数
	 * @return
	 */
	public static String getSecureRandomString() {
		return byte2hex(createRandomByte());
	}
	public static String byte2hex(byte[] b) {
		String hs = "";
		String stmp = "";
		for (int n = 0; n < b.length; n++) {
			stmp = (java.lang.Integer.toHexString(b[n] & 0XFF));
			if (stmp.length() == 1)
				hs = hs + "0" + stmp;
			else
				hs = hs + stmp;
		}
		return hs.toUpperCase();
	}

    public CSRFTokenFilter() {}
	public void destroy() {}
	public void init(FilterConfig fConfig) throws ServletException {}
}
