CSRF攻击防御,JAVA程序利用token前后端验证每一次请求进行防御

  • Post author:
  • Post category:java


整体思路是:访问页面就生成token,保存到session,并且前端将token放进header或者追加到请求地址最后面。后端拿到header或者请求中的token后比对session与herder或者请求中的token是否一致,一致测是同一次请求。有效的防止了,攻击者不从规定页面请求接口进行篡改利用。

每次请求,地址或者header携带TOKEN到服务端验证,防止CSRF攻击

下面是JAVA后端工具类

package com.invoice.util;
import java.io.IOException;
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.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

/**
 * @version 1.0
 */
public class CsrfDefInterceptorUtil implements Filter {
	/**
	 * 判断session中有没有csrftoken,如果没有则认为是第一次访问,session是新建立的,
	 * 这时生成一个新的token,放于session之中,并继续执行请求。如果session中已经有csrftoken,
	 * 则说明用户已经与服务器之间建立了一个活跃的session,这时遥看这个请求中有没有同时附带这个token,
	 * 由于请求可能来自常规的访问或者XMLHttpRequest异步访问,
	 * 我们分别尝试从请求中获取csrftoken参数以及从HTTP头中获取csrftoken自定义属性并与session中的值进行比较,
	 * 只要有一个地方带有有效token,就判定请求合法,可以继续执行,否则就转到错误页面
	 */
	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest req = (HttpServletRequest) request;
		
		HttpSession s = req.getSession();
		// 从 session 中得到 csrftoken 属性
		String sToken = (String) s.getAttribute("csrftoken");
		System.out.println("sToken:" + sToken);
		if (sToken == null) {
			// 产生新的 token 放入 session 中
			sToken = generateToken();
			s.setAttribute("csrftoken", sToken);
			chain.doFilter(request, response);
		} else {
			// 从 HTTP 头中取得 csrftoken
			String xhrToken = req.getHeader("Token");
			// 从请求参数中取得 csrftoken
			System.out.println("xhrToken:" + xhrToken);
			String pToken = req.getParameter("csrftoken");
			System.out.println("pToken:" + pToken);
			System.out.println("sToken.equals(xhrToken):" + sToken.equals(xhrToken));
			System.out.println("==sToken.equals(pToken)==:" + sToken.equals(pToken));
			if (sToken != null && xhrToken != null && sToken.equals(xhrToken)) {
				chain.doFilter(request, response);
			} else if (sToken != null && pToken != null && sToken.equals(pToken)) {
				chain.doFilter(request, response);
			} else {
				request.getRequestDispatcher("404").forward(request, response);
			}
		}
	}
	
	

	public static String generateToken() {
		String uuid = UUID.randomUUID().toString().replaceAll("-", "");
		System.out.println("生成==csrftoken:" + SM4.getMD5(uuid));
		return SM4.getMD5(uuid);
	}

	@Override
	public void destroy() {
		// TODO Auto-generated method stub

	}

	@Override
	public void init(FilterConfig arg0) throws ServletException {
		// TODO Auto-generated method stub

	}

}

可以再web.xml中配置过滤条件,下面只拦截了接口,也可以设置拦截页面

<filter>
     <filter-name>CsrfDefInterceptorUtil</filter-name>
    <filter-class>com.invoice.util.CsrfDefInterceptorUtil</filter-class>  
</filter>  
<filter-mapping>
     <filter-name>CsrfDefInterceptorUtil</filter-name>
    <url-pattern>*.action</url-pattern>   
</filter-mapping>

下面是Referer里获取原始地址,进行比对请求地址是否是同一域名地址

	// 从 HTTP 头中取得 Referer 值
   String referer=req.getHeader("Referer");
    // 判断 Referer 是否以 bank.example 开头
    if((referer!=null) &&(referer.trim().startsWith("https://localhost:8443"))){
    	System.out.println("referer不为空并且路径符合系统要求");
      chain.doFilter(request, response);
    }else{
   	System.out.println("referer可能为空,或者系统遭受到CSRF攻击!");
   	request.getRequestDispatcher("404.jsp").forward(request,response);
    }

前端JS,其中sToken是生成的Token,拿到token后选择页面中的from表单,还有a标签,在href后面追加参数csrftoken携带token到请求地址

<script type="text/javascript">
var token = '<%=sToken%>';
window.onload=appendToken;
function appendToken(){
    updateForms();
    updateTags();
 }
 function updateForms() {
    // 得到页面中所有的 form 元素
    var forms = document.getElementsByTagName('form');
    for(i=0; i<forms.length; i++) {
        var url = forms[i].action;
        // 如果这个 form 的 action 值为空,则不附加 csrftoken
        if(url == null || url == "" ) continue;
        // 动态生成 input 元素,加入到 form 之后
        var e = document.createElement("input");
        e.name = "csrftoken";
        e.value = token;
        e.type="hidden";
        forms[i].appendChild(e);
    }
 }
 function updateTags() {
    var all = document.getElementsByTagName('a');
    var len = all.length;
    // 遍历所有 a 元素
    for(var i=0; i<len; i++) {
        var e = all[i];
        updateTag(e, 'href', token);
    }
 }
 function updateTag(element, attr, token) {
	    var location = element.getAttribute(attr);
	    if(location != null && location != "" ) {
	        var fragmentIndex = location.indexOf('#');
	        var fragment = null;
	        if(fragmentIndex != -1){
	            //url 中含有只相当页的锚标记
	            fragment = location.substring(fragmentIndex);
	            location = location.substring(0,fragmentIndex);
	        }
	        var index = location.indexOf('?');
	        if(index != -1) {
	            //url 中已含有其他参数
	            location = location + '&csrftoken=' + token;
	        } else {
	            //url 中没有其他参数
	            location = location + '?csrftoken=' + token;
	        }
	        if(fragment != null){
	            location += fragment;
	        }
	        element.setAttribute(attr, location);
	    }
 }
</script> 

给heaeder里加token参数

$(function(){
	 $.ajaxSetup({
		    headers: { // 默认添加请求头
		        "Token": "<%=sToken%>"		
		    } 
	 	});   
})

上面是配置拦截器,做整站拦截的。。如果个别情况不需要配置全站,则可以针对指定接口进行验证。后端从session获取和header里的不匹配或者session和请求参数中携带的token不匹配,则是非法请求。

		String sToken = (String)Session.getAttribute("csrftoken");
		String xhrToken = Request.getHeader("Token");
		if(xhrToken == null) {
			map.put("msg", "非法访问!");
			Request.setAttribute("data", JSON.toJSONString(map));
			return "json";
		}else if(sToken == null){
			map.put("msg", "session not token...");
			Request.setAttribute("data", JSON.toJSONString(map));
			return "json";
		}else if(!sToken.equals(xhrToken)) {
			map.put("msg", "非法请求...");
			Request.setAttribute("data", JSON.toJSONString(map));
			return "json";
		}
		System.out.println("xhrToken:"+xhrToken+" sToken:"+sToken + "token是否一致:"+sToken.equals(xhrToken));



版权声明:本文为tengchengbaba原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。