Spring CSRF+Ajax前端安全策略 - 純Java Config版

Spring CSRF Token Java Config。

spring-security-config-4.2.13.jar、spring-security-core-4.2.13.jar、spring-security-web-4.2.13.jar。

CSRF(Cross Site Request Forgery),跨站請求偽造,想看詳細的說明可參考下列網址。

https://blog.techbridge.cc/2017/02/25/csrf-introduction/

簡單來說就是在你使用瀏覽器登入一些正常的網站後,又點了其他不明連結。

而這些連結背後會利用你瀏覽器所存的cookie去對你剛剛登入的網站做一些不好的事(例如竄改資料)。

也因此CSRF攻擊又稱one-click attack。

CSRF Token的防禦方法就是由Server端產生一個Token,Client端在送出POST時必須帶這個Token給Server檢驗,檢驗過了才允許POST要求。

其實丟Google關鍵字查詢,Spring CSRF Token的範例非常的多,但幾乎都還是以XML設定為主,為求程式碼簡潔易控制,本人整合了Java Config的版本。

1.先分別各建立一個類別,分別繼承WebSecurityConfigurerAdapter與AbstractSecurityWebApplicationInitializer。

@EnableWebSecurity
@Configuration
public class SecurityJavaConfig extends WebSecurityConfigurerAdapter {
	
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		
	}
}
public class SecurityWebInitializer extends AbstractSecurityWebApplicationInitializer {

}

2.接著撰寫JSP與對應送出POST的javascript。

JSP上須預留<meta name="_csrf" content="${_csrf.token}"/>、<meta name="_csrf_header" content="${_csrf.headerName}"/>這兩個tag。

以利送出POST時,server端將Token生成並塞入內容。

Ajax再送出POST前,需增加CSRF Token的標頭資訊讓Server端檢驗。

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<script src="${pageContext.request.contextPath}/static/js/jquery-ui/external/jquery/jquery.js"></script>
<script src="${pageContext.request.contextPath}/static/js/jquery-ui/jquery-ui.js"></script>
<script src="${pageContext.request.contextPath}/static/js/csrfSample.js"></script>
<meta name="_csrf" content="${_csrf.token}"/>
<meta name="_csrf_header" content="${_csrf.headerName}"/>
</head>
<body onload="onReady()">
準備送出POST
</body>
</html>
function onReady() {
	
	alert("準備送出含CSRF Token");
	
	$.ajax({
		url:'/spring_ryuichi/csrfTokenPosting',
		type:"post",
		//dataType (default: Intelligent Guess (xml, json, script, or html))
		processData:false,
		contentType:false,
		success:function(data) {
			alert("success " + data.result);
		},
		error:function(data) {
			alert("error " + JSON.stringify(data));
		},
		beforeSend: function(xhr) {
			var token = $("meta[name='_csrf']").attr("content");
			var header = $("meta[name='_csrf_header']").attr("content");
			if(header && token) {
				xhr.setRequestHeader(header, token);
			}
		}
	});
}

3.撰寫Ajax呼叫使用的Controller:

@RequestMapping(value = "/csrfTokenPosting", method = RequestMethod.POST)
@ResponseBody
public Map<String,Object> csrfTokenPosting() throws Exception {
	
	//準備一個Map供回傳Ajax讀取結果使用
	Map<String,Object> result = new HashMap<String,Object>();
	result.put("result", "呼叫成功");

	return result;
}

 

4.結果1-帶正確的Token:

4.結果2-帶錯誤的Token:

修改beforeSend段的token,隨便帶。

function onReady() {
	
	alert("準備送出含CSRF Token");
	
	$.ajax({
		url:'/spring_ryuichi/csrfTokenPosting',
		type:"post",
		//dataType (default: Intelligent Guess (xml, json, script, or html))
		processData:false,
		contentType:false,
		success:function(data) {
			alert("success " + data.result);
		},
		error:function(data) {
			alert("error " + JSON.stringify(data));
		},
		beforeSend: function(xhr) {
			var token = "123456789";
			var header = $("meta[name='_csrf_header']").attr("content");
			if(header && token) {
				xhr.setRequestHeader(header, token);
			}
		}
	});
}