CSRF(Cross-site request forgery跨站請求偽造,也被稱為“One Click Attack”或者Session Riding,通常縮寫為CSRF或者XSRF,是一種對網站的惡意利用。盡管聽起來像跨站腳本(XSS),但它與XSS非常不同,並且攻擊方式幾乎相左。XSS利用站點內的信任用戶,而CSRF則通過偽裝來自受信任用戶的請求來利用受信任的網站。與XSS攻擊相比,CSRF攻擊往往不大流行(因此對其進行防范的資源也相當稀少)和難以防范,所以被認為比XSS更具危險性。
具體思路:
1、跳轉頁面前生成隨機token,並存放在session中
2、form中將token放在隱藏域中,保存時將token放頭部一起提交
3、獲取頭部token,與session中的token比較,一致則通過,否則不予提交
4、生成新的token,並傳給前端
1、攔截器配置
<mvc:interceptors> <!-- csrf攻擊防御 --> <mvc:interceptor> <!-- 需攔截的地址 --> <mvc:mapping path="/**" /> <!-- 需排除攔截的地址 --> <mvc:exclude-mapping path="/resources/**" /> <bean class="com.cnpc.framework.interceptor.CSRFInterceptor" /> </mvc:interceptor> </mvc:interceptors>
2攔截器實現 CSRFInterceptor.java
package com.cnpc.framework.interceptor;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import com.alibaba.fastjson.JSONObject;
import com.cnpc.framework.annotation.RefreshCSRFToken;
import com.cnpc.framework.annotation.VerifyCSRFToken;
import com.cnpc.framework.base.pojo.ResultCode;
import com.cnpc.framework.constant.CodeConstant;
import com.cnpc.framework.utils.CSRFTokenUtil;
import com.cnpc.framework.utils.StrUtil;
/**
* CSRFInterceptor 防止跨站請求偽造攔截器
*
* @author billJiang 2016年10月6日 下午8:14:40
*/
public class CSRFInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("---------->" + request.getRequestURI());
System.out.println(request.getHeader("X-Requested-With"));
// 提交表單token 校驗
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
VerifyCSRFToken verifyCSRFToken = method.getAnnotation(VerifyCSRFToken.class);
// 如果配置了校驗csrf token校驗,則校驗
if (verifyCSRFToken != null) {
// 是否為Ajax標志
String xrq = request.getHeader("X-Requested-With");
// 非法的跨站請求校驗
if (verifyCSRFToken.verify() && !verifyCSRFToken(request)) {
if (StrUtil.isEmpty(xrq)) {
// form表單提交,url get方式,刷新csrftoken並跳轉提示頁面
String csrftoken = CSRFTokenUtil.generate(request);
request.getSession().setAttribute("CSRFToken", csrftoken);
response.setContentType("application/json;charset=UTF-8");
PrintWriter out = response.getWriter();
out.print("非法請求");
response.flushBuffer();
return false;
} else {
// 刷新CSRFToken,返回錯誤碼,用於ajax處理,可自定義
String csrftoken = CSRFTokenUtil.generate(request);
request.getSession().setAttribute("CSRFToken", csrftoken);
ResultCode rc = CodeConstant.CSRF_ERROR;
response.setContentType("application/json;charset=UTF-8");
PrintWriter out = response.getWriter();
out.print(JSONObject.toJSONString(rc));
response.flushBuffer();
return false;
}
}
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception {
// 第一次生成token
if (modelAndView != null) {
if (request.getSession(false) == null || StrUtil.isEmpty((String) request.getSession(false).getAttribute("CSRFToken"))) {
request.getSession().setAttribute("CSRFToken", CSRFTokenUtil.generate(request));
return;
}
}
// 刷新token
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
RefreshCSRFToken refreshAnnotation = method.getAnnotation(RefreshCSRFToken.class);
// 跳轉到一個新頁面 刷新token
String xrq = request.getHeader("X-Requested-With");
if (refreshAnnotation != null && refreshAnnotation.refresh() && StrUtil.isEmpty(xrq)) {
request.getSession().setAttribute("CSRFToken", CSRFTokenUtil.generate(request));
return;
}
// 校驗成功 刷新token 可以防止重復提交
VerifyCSRFToken verifyAnnotation = method.getAnnotation(VerifyCSRFToken.class);
if (verifyAnnotation != null) {
if (verifyAnnotation.verify()) {
if (StrUtil.isEmpty(xrq)) {
request.getSession().setAttribute("CSRFToken", CSRFTokenUtil.generate(request));
} else {
Map<String, String> map = new HashMap<String, String>();
map.put("CSRFToken", CSRFTokenUtil.generate(request));
response.setContentType("application/json;charset=UTF-8");
OutputStream out = response.getOutputStream();
out.write((",'csrf':" + JSONObject.toJSONString(map) + "}").getBytes("UTF-8"));
}
}
}
}
/**
* 處理跨站請求偽造 針對需要登錄后才能處理的請求,驗證CSRFToken校驗
*
* @param request
*/
protected boolean verifyCSRFToken(HttpServletRequest request) {
// 請求中的CSRFToken
String requstCSRFToken = request.getHeader("CSRFToken");
if (StrUtil.isEmpty(requstCSRFToken)) {
return false;
}
String sessionCSRFToken = (String) request.getSession().getAttribute("CSRFToken");
if (StrUtil.isEmpty(sessionCSRFToken)) {
return false;
}
return requstCSRFToken.equals(sessionCSRFToken);
}
}
3.CSRFTokenUtil.java 生成隨機系列號
package com.cnpc.framework.utils;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
public class CSRFTokenUtil {
public static String generate(HttpServletRequest request) {
return UUID.randomUUID().toString();
}
}
4、ResultCode
package com.cnpc.framework.base.pojo;
public class ResultCode {
private String code;
private String message;
public ResultCode(String code, String message) {
this.code = code;
this.message = message;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
5、CodeConstant.java
package com.cnpc.framework.constant;
import com.cnpc.framework.base.pojo.ResultCode;
public class CodeConstant {
public final static ResultCode CSRF_ERROR = new ResultCode("101", "CSRF ERROR:無效的token,或者token過期");
}
6、ajax提交方法
function ajaxPost(url, params, callback) {
var result = null;
var headers={};
headers['CSRFToken']=$("#csrftoken").val();
$.ajax({
type : 'post',
async : false,
url : url,
data : params,
dataType : 'json',
headers:headers,
success : function(data, status) {
result = data;
if(data&&data.code&&data.code=='101'){
modals.error("操作失敗,請刷新重試,具體錯誤:"+data.message);
return false;
}
if (callback) {
callback.call(this, data, status);
}
},
error : function(err, err1, err2) {
if(err && err.readyState && err.readyState == '4'){
var responseBody = err.responseText;
if(responseBody){
responseBody = "{'retData':"+responseBody;
var resJson = eval('(' + responseBody + ')');
$("#csrftoken").val(resJson.csrf.CSRFToken);
this.success(resJson.retData, 200);
}
return ;
}
modals.error({
text : JSON.stringify(err) + '<br/>err1:' + JSON.stringify(err1) + '<br/>err2:' + JSON.stringify(err2),
large : true
});
}
});
return result;
}
7、form表單配置
<input type='hidden' value='${CSRFToken}' id='csrftoken'>
8、注解定義
package com.cnpc.framework.annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 跨站請求仿照注解 刷新CSRFToken
*
*/
@Target({ java.lang.annotation.ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface RefreshCSRFToken {
/**
* 刷新token
*
* @return
*/
public abstract boolean refresh() default true;
}
9、注解配置
@RefreshCSRFToken
@RequestMapping(method = RequestMethod.GET, value = "/edit")
private String edit(String id, HttpServletRequest request) {
request.setAttribute("id", id);
return "base/user/user_edit";
}
@RequestMapping(method = RequestMethod.POST, value = "/get")
@ResponseBody
private User getUser(String id) {
return userService.get(User.class, id);
}
