OWASP(CsrfGuard)源码解析04----CsrfGuardFilter分析

一、介绍

前面两章 已经对 Listener 进行了分析, 本章主要 分析一下CsrfGuardFilter
在这里插入图片描述

二、CsrfGuardFilter 分析

CsrfGuardFilter 主要 就是对 请求进行拦截, 然后根据配置 设置要不要校验, 校验时 根据 ajax , page, session 等
不同情况分别处理 ,最后再更新 token.


public final class CsrfGuardFilter implements Filter {

	private FilterConfig filterConfig = null;
    
    // 初始化时
	@Override
	public void init(@SuppressWarnings("hiding") FilterConfig filterConfig) throws ServletException {
		this.filterConfig = filterConfig;
	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {

		// 首先判断 配置文件里面 org.owasp.csrfguard.Enabled 是否 开启, 默认是开启的
		// 如果为 false, 那就直接跳过
		if (!CsrfGuard.getInstance().isEnabled()) {
			filterChain.doFilter(request, response);
			return;
		}
		
		/** 仅适用于HttpServletRequest对象  **/
		if (request instanceof HttpServletRequest && response instanceof HttpServletResponse) {
			
			HttpServletRequest httpRequest = (HttpServletRequest) request;
			// 获取session
			HttpSession session = httpRequest.getSession(false);
			
			/**如果session 为null 需要直接跳过,可以将 配置文件里面 org.owasp.csrfguard.ValidateWhenNoSessionExists 设置为 false(默认为 true) ,即可跳过
			**/
			if (session == null && !CsrfGuard.getInstance().isValidateWhenNoSessionExists()) {
				// If there is no session, no harm can be done
				filterChain.doFilter(httpRequest, (HttpServletResponse) response);
				return;
			}

			CsrfGuard csrfGuard = CsrfGuard.getInstance();
			// 打印log 下面有对log 详细介绍,
			csrfGuard.getLogger().log(String.format("CsrfGuard analyzing request %s", httpRequest.getRequestURI()));
           // 转换成InterceptRedirectResponse 类型
			InterceptRedirectResponse httpResponse = new InterceptRedirectResponse((HttpServletResponse) response, httpRequest, csrfGuard);

//			 if(MultipartHttpServletRequest.isMultipartRequest(httpRequest)) {
//				 httpRequest = new MultipartHttpServletRequest(httpRequest);
//			 }

            // 如果session 不为null ,但是是新的,并且 使用新Token 登录页面 ,默认是false,这里的if 逻辑走不到
			if ((session != null && session.isNew()) && csrfGuard.isUseNewTokenLandingPage()) {
			    // 创建新Token 并 建 页面
				csrfGuard.writeLandingPage(httpRequest, httpResponse);
				// 进行验证
			} else if (csrfGuard.isValidRequest(httpRequest, httpResponse)) {
				filterChain.doFilter(httpRequest, httpResponse);
			} else {
				/** invalid request - nothing to do - actions already executed **/
			}

			/** 最后 再更新 token **/
			csrfGuard.updateTokens(httpRequest);

		} else {
		// 打印log
			filterConfig.getServletContext().log(String.format("[WARNING] CsrfGuard does not know how to work with requests of class %s ", request.getClass().getName()));

			filterChain.doFilter(request, response);
		}
	}

    // 销毁
	@Override
	public void destroy() {
		filterConfig = null;
	}

}

三、 CsrfGuard 类分析

3.1 isValidRequest 分析


	public boolean isValidRequest(HttpServletRequest request, HttpServletResponse response) {
        // 首先判断是否是需要保护的页面和方法
		boolean valid = !isProtectedPageAndMethod(request);
		HttpSession session = request.getSession(true);
		String tokenFromSession = (String) session.getAttribute(getSessionKey());

		/** 需要保护的页面资源, token 不为null, 进行校验Token **/
		if (tokenFromSession != null && !valid) {
			try {
			     // 如果开启了ajax 请求, org.owasp.csrfguard.Ajax 对应的值
			     // 并且是 ajax 请求, request 头部的 X-Requested-With 不为null, 有一个 值为XMLHttpRequest ,这个是在 csrfguard.js 里面 配置的
				if (isAjaxEnabled() && isAjaxRequest(request)) {
					verifyAjaxToken(request);
					// 如果是 page 验证
				} else if (isTokenPerPageEnabled()) {
					verifyPageToken(request);
				} else {
				   // session 验证
					verifySessionToken(request);
				}
			    // 修改flag 标志
				valid = true;
			} catch (CsrfGuardException csrfe) {
			   // 如果验证不通过, 调用对应的 action , action 对应的 操作 在下面有介绍
				callActionsOnError(request, response, csrfe);
			}

			/** 旋转 session 和 page  tokens 
			 如果 不是ajax 请求, 并且开启了 旋转替换token ,
			 那就对 session 和page 对应的tokens 替换掉
			**/
			if (!isAjaxRequest(request) && isRotateEnabled()) {
				rotateTokens(request);
			}
			/** expected token in session - bad state and not valid **/
		} else if (tokenFromSession == null && !valid) {
			try {
				throw new CsrfGuardException("CsrfGuard expects the token to exist in session at this point");
			} catch (CsrfGuardException csrfe) {
				callActionsOnError(request, response, csrfe);
				
			}
		} else {
			/** unprotected page - nothing to do **/
		}

		return valid;
	}

3.2 isProtectedPage 分析

public boolean isProtectedPage(String uri) {

		// 如果请求路径在 javascript  对应的Set<> 集合里面,直接返回false
		if (JavaScriptServlet.getJavascriptUris().contains(uri)) {
			return false;
		}
		
		// 从配置文件里面获取 org.owasp.csrfguard.Protect 对应的配置
		boolean retval = !isProtectEnabled();
        // 对需要保护的页面进行 遍历
		for (String protectedPage : getProtectedPages()) {
		    //精确匹配
			if (isUriExactMatch(protectedPage, uri)) {
				return true;
			} else if (isUriMatch(protectedPage, uri)) {
				retval = true;
			}
		}
        // 对不需要保护的进行 匹配
		for (String unprotectedPage : getUnprotectedPages()) {
			if (isUriExactMatch(unprotectedPage, uri)) {
				return false;
			} else if (isUriMatch(unprotectedPage, uri)) {
				retval = false;
			}
		}

		return retval;
	}

private boolean isUriExactMatch(String testPath, String requestPath) {
		
		// 粗略判断一下 是不是 正则表达式, 以 "^" 开头, 以 "$" 结尾
		// 如果是正则表达式, 直接返回false
		if (isTestPathRegex(testPath)) {
			return false;
		}
		
		boolean retval = false;

		/** 精确匹配 **/
		if (testPath.equals(requestPath)) {
			retval = true;
		}

		return retval;
	}

3.2.1 模糊匹配

这段代码是 拷贝的 tomcat 源码,tomcat 里面ApplicationFilterFactory 类

	private boolean isUriMatch(String testPath, String requestPath) {

		// 如果是正则表达式
		if (isTestPathRegex(testPath)) {
			// 获取对应的 pattern
			Pattern pattern = this.regexPatternCache.get(testPath);
			// 如果为null, 新建并放入缓存
			if (pattern == null) {
				pattern = Pattern.compile(testPath);
				this.regexPatternCache.put(testPath, pattern);
			}
			// 返回是否匹配
			return pattern.matcher(requestPath).matches();
		}
		
		boolean retval = false;

		/** Case 1: 精确匹配  **/
		if (testPath.equals(requestPath)) {
			retval = true;
		}

		/** Case 2 - Path Match ("/.../*") **/
		if (testPath.equals("/*")) {
			retval = true;
		}
		if (testPath.endsWith("/*")) {
			if (testPath
					.regionMatches(0, requestPath, 0, testPath.length() - 2)) {
				if (requestPath.length() == (testPath.length() - 2)) {
					retval = true;
				} else if ('/' == requestPath.charAt(testPath.length() - 2)) {
					retval = true;
				}
			}
		}

		/** Case 3 - Extension Match **/
		if (testPath.startsWith("*.")) {
			int slash = requestPath.lastIndexOf('/');
			int period = requestPath.lastIndexOf('.');

			if ((slash >= 0)
					&& (period > slash)
					&& (period != requestPath.length() - 1)
					&& ((requestPath.length() - period) == (testPath.length() - 1))) {
				retval = testPath.regionMatches(2, requestPath, period + 1,
						testPath.length() - 2);
			}
		}

		return retval;
	}

3.3 isProtectedMethod 分析

判断是否是需要保护的方法

public boolean isProtectedMethod(String method) {
		boolean isProtected = true;
		{
			Set<String> theProtectedMethods = getProtectedMethods();
			if (!theProtectedMethods.isEmpty() && !theProtectedMethods.contains(method)) {
					isProtected = false;
			}
		}
		
		{
			Set<String> theUnprotectedMethods = getUnprotectedMethods();
			if (!theUnprotectedMethods.isEmpty() && theUnprotectedMethods.contains(method)) {
					isProtected = false;
			}
		}
		
		return isProtected;
	}

3.4 verifyAjaxToken、verifyPageToken、verifySessionToken分析

ajax ,page, session 请求校验 , 这三块的逻辑差不多


	private void verifyAjaxToken(HttpServletRequest request) throws CsrfGuardException {
		HttpSession session = request.getSession(true);
		String tokenFromSession = (String) session.getAttribute(getSessionKey());
		String tokenFromRequest = request.getHeader(getTokenName());
        // 如果请求头部没有携带 Token, 抛错
		if (tokenFromRequest == null) {
			/** FAIL: token is missing from the request **/
			throw new CsrfGuardException("required token is missing from the request");
		} else {
			// 如果session 里面的token 和 request 里面携带的token 不相等 ,做一个进一步的判断
			if (!tokenFromSession.equals(tokenFromRequest)) {
			    // 如果request 里面的token  包含 "," , 进行split, 并取第一个
				if (tokenFromRequest.contains(",")) {
					tokenFromRequest = tokenFromRequest.substring(0, tokenFromRequest.indexOf(',')).trim();
				}
				// 如果还不相等,抛错
				if (!tokenFromSession.equals(tokenFromRequest)) {
					/** FAIL: the request token does not match the session token **/
					throw new CsrfGuardException("request token does not match session token");
				}
			}
		}
	}

// 
private void verifyPageToken(HttpServletRequest request) throws CsrfGuardException {
		HttpSession session = request.getSession(true);
		@SuppressWarnings("unchecked")
		Map<String, String> pageTokens = (Map<String, String>) session.getAttribute(CsrfGuard.PAGE_TOKENS_KEY);

		String tokenFromPages = (pageTokens != null ? pageTokens.get(request.getRequestURI()) : null);
		String tokenFromSession = (String) session.getAttribute(getSessionKey());
		String tokenFromRequest = request.getParameter(getTokenName());

		if (tokenFromRequest == null) {
			/** FAIL: token is missing from the request **/
			throw new CsrfGuardException("required token is missing from the request");
		} else if (tokenFromPages != null) {
			if (!tokenFromPages.equals(tokenFromRequest)) {
				/** FAIL: request does not match page token **/
				throw new CsrfGuardException("request token does not match page token");
			}
		} else if (!tokenFromSession.equals(tokenFromRequest)) {
			/** FAIL: the request token does not match the session token **/
			throw new CsrfGuardException("request token does not match session token");
		}
	}


	private void verifySessionToken(HttpServletRequest request) throws CsrfGuardException {
		HttpSession session = request.getSession(true);
		String tokenFromSession = (String) session.getAttribute(getSessionKey());
		String tokenFromRequest = request.getParameter(getTokenName());

		if (tokenFromRequest == null) {
			/** FAIL: token is missing from the request **/
			throw new CsrfGuardException("required token is missing from the request");
		} else if (!tokenFromSession.equals(tokenFromRequest)) {
			/** FAIL: the request token does not match the session token **/
			throw new CsrfGuardException("request token does not match session token");
		}
	}

页面请求校验

四、ILogger 分析

定义了一个 ILogger 接口, 并给出了 两种实现方式: JavaLooger 和 ConsoleLogger
可以通过 配置 文件里面 org.owasp.csrfguard.Logger=org.owasp.csrfguard.log.ConsoleLogger 选择 具体的某一种打印日志的方式,
在这里插入图片描述

4.1 ConsoleLogger

ConsoleLogger : 就是 System.out.println 输出

4.2 JavaLogger

JavaLogger: java.util.logging.Logger 是JDK自带的日志工具,其简单实现了日志的功能,不是很完善,所以在实际应用中使用的比较少。
Logger的默认配置,位置在JRE安装目录下lib中的logging.properties中,大致如下图:
在这里插入图片描述
其实这两种都不太好.

五、InterceptRedirectResponse

InterceptRedirectResponse主要是对 sendRedirect 这个方法重写了, 主要逻辑是更新了 token , 对 url 进行了重新的拼装, 还是调用 原先的response(response.sendRedirect(xx))

六、 IAction

下图为 IAction 的 关系, 在这里插入图片描述

IAction 主要是出现异常之后的 处理和跳转相关, 这里可以配置多个 action进行处理. IAction 接口被 AbstractAction 实现,被 如下方法 继承, 主要看一下 每一个方法对应的 execute() 方法, 这里也可以自定义 action.

  • Empty: do nothing
  • Error : 向客户端发送错误信息, code和message 自己定义
  • Forward : 请求转发到 对应的页面, page 自己定义
  • Invalidate : 将session 置位无效
  • Log : 打印log ,这里 可以打印 一些 ip, host, port , 等等
  • Redirect: 重定向
  • RequestAttribute : 设置 request 属性
  • Rotate : 更新Token
  • SessionAttribute : 设置session 属性

七、小结

本章主要介绍了 CsrfGuardFilter 这块的逻辑 以及设计到的 类,方法,整体都是比较好理解的.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一直打铁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值