這篇文章給大家介紹利用spring security防御csrf攻擊的原理是什么,內容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。
什么是csrf?
csrf又稱跨域請求偽造,攻擊方通過偽造用戶請求訪問受信任站點。CSRF這種攻擊方式在2000年已經被國外的安全人員提出,但在國內,直到06年才開始被關注,08年,國內外的多個大型社區和交互網站分別爆出CSRF漏洞,如:NYTimes.com(紐約時報)、Metafilter(一個大型的BLOG網站),YouTube和百度HI......而現在,互聯網上的許多站點仍對此毫無防備,以至于安全業界稱CSRF為“沉睡的巨人”。
舉個例子,用戶通過表單發送請求到銀行網站,銀行網站獲取請求參數后對用戶賬戶做出更改。在用戶沒有退出銀行網站情況下,訪問了攻擊網站,攻擊網站中有一段跨域訪問的代碼,可能自動觸發也可能點擊提交按鈕,訪問的url正是銀行網站接受表單的url。因為都來自于用戶的瀏覽器端,銀行將請求看作是用戶發起的,所以對請求進行了處理,造成的結果就是用戶的銀行賬戶被攻擊網站修改。
解決方法基本上都是增加攻擊網站無法獲取到的一些表單信息,比如增加圖片驗證碼,可以杜絕csrf攻擊,但是除了登陸注冊之外,其他的地方都不適合放驗證碼,因為降低了網站易用性
相關介紹:
http://baike.baidu.com/view/1609487.htm?fr=aladdin
spring-servlet中配置csrf
<!-- Spring csrf 攔截器 --> <mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/login" /> <bean class="com.wangzhixuan.commons.csrf.CsrfInterceptor" /> </mvc:interceptor> </mvc:interceptors>
在類中聲明Csrf攔截器,用來生成或去除CsrfToken
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import com.wangzhixuan.commons.scan.ExceptionResolver;
import com.wangzhixuan.commons.utils.WebUtils;
/**
* Csrf攔截器,用來生成或去除CsrfToken
*
* @author L.cm
*/
public class CsrfInterceptor extends HandlerInterceptorAdapter {
private static final Logger logger = LogManager.getLogger(ExceptionResolver.class);
@Autowired
private CsrfTokenRepository csrfTokenRepository;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HandlerMethod handlerMethod = (HandlerMethod) handler;
// 非控制器請求直接跳出
if (!(handler instanceof HandlerMethod)) {
return true;
}
CsrfToken csrfToken = handlerMethod.getMethodAnnotation(CsrfToken.class);
// 判斷是否含有@CsrfToken注解
if (null == csrfToken) {
return true;
}
// create、remove同時為true時異常
if (csrfToken.create() && csrfToken.remove()) {
logger.error("CsrfToken attr create and remove can Not at the same time to true!");
return renderError(request, response, Boolean.FALSE, "CsrfToken attr create and remove can Not at the same time to true!");
}
// 創建
if (csrfToken.create()) {
CsrfTokenBean token = csrfTokenRepository.generateToken(request);
csrfTokenRepository.saveToken(token, request, response);
// 緩存一個表單頁面地址的url
csrfTokenRepository.cacheUrl(request, response);
request.setAttribute(token.getParameterName(), token);
return true;
}
// 判斷是否ajax請求
boolean isAjax = WebUtils.isAjax(handlerMethod);
// 校驗,并且清除
CsrfTokenBean tokenBean = csrfTokenRepository.loadToken(request);
if (tokenBean == null) {
return renderError(request, response, isAjax, "CsrfToken is null!");
}
String actualToken = request.getHeader(tokenBean.getHeaderName());
if (actualToken == null) {
actualToken = request.getParameter(tokenBean.getParameterName());
}
if (!tokenBean.getToken().equals(actualToken)) {
return renderError(request, response, isAjax, "CsrfToken not eq!");
}
return true;
}
private boolean renderError(HttpServletRequest request, HttpServletResponse response,
boolean isAjax, String message) throws IOException {
// 獲取緩存的cacheUrl
String cachedUrl = csrfTokenRepository.getRemoveCacheUrl(request, response);
// ajax請求直接拋出異常,因為{@link ExceptionResolver}會去處理
if (isAjax) {
throw new RuntimeException(message);
}
// 非ajax CsrfToken校驗異常,先清理token
csrfTokenRepository.saveToken(null, request, response);
logger.info("Csrf[redirectUrl]:\t" + cachedUrl);
response.sendRedirect(cachedUrl);
return false;
}
/**
* 用于清理@CsrfToken保證只能請求成功一次
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
HandlerMethod handlerMethod = (HandlerMethod) handler;
// 非控制器請求直接跳出
if (!(handler instanceof HandlerMethod)) {
return;
}
CsrfToken csrfToken = handlerMethod.getMethodAnnotation(CsrfToken.class);
if (csrfToken == null || !csrfToken.remove()) {
return;
}
csrfTokenRepository.getRemoveCacheUrl(request, response);
csrfTokenRepository.saveToken(null, request, response);
}
}聲明Csrf過濾注解,通過標注來過濾對應的請求
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Csrf過濾注解
* @author L.cm
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CsrfToken {
boolean create() default false;
boolean remove() default false;
}建立實例對象(操作對象)
import java.io.Serializable;
import org.springframework.util.Assert;
public class CsrfTokenBean implements Serializable {
private static final long serialVersionUID = -6865031901744243607L;
private final String token;
private final String parameterName;
private final String headerName;
/**
* Creates a new instance
* @param headerName the HTTP header name to use
* @param parameterName the HTTP parameter name to use
* @param token the value of the token (i.e. expected value of the HTTP parameter of
* parametername).
*/
public CsrfTokenBean(String headerName, String parameterName, String token) {
Assert.hasLength(headerName, "headerName cannot be null or empty");
Assert.hasLength(parameterName, "parameterName cannot be null or empty");
Assert.hasLength(token, "token cannot be null or empty");
this.headerName = headerName;
this.parameterName = parameterName;
this.token = token;
}
public String getHeaderName() {
return this.headerName;
}
public String getParameterName() {
return this.parameterName;
}
public String getToken() {
return this.token;
}
}過濾過程中需要的倉庫
package com.wangzhixuan.commons.csrf;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public interface CsrfTokenRepository {
/**
* Generates a {@link CsrfTokenBean}
*
* @param request the {@link HttpServletRequest} to use
* @return the {@link CsrfTokenBean} that was generated. Cannot be null.
*/
CsrfTokenBean generateToken(HttpServletRequest request);
/**
* Saves the {@link CsrfTokenBean} using the {@link HttpServletRequest} and
* {@link HttpServletResponse}. If the {@link CsrfTokenBean} is null, it is the same as
* deleting it.
*
* @param token the {@link CsrfTokenBean} to save or null to delete
* @param request the {@link HttpServletRequest} to use
* @param response the {@link HttpServletResponse} to use
*/
void saveToken(CsrfTokenBean token, HttpServletRequest request,
HttpServletResponse response);
/**
* Loads the expected {@link CsrfTokenBean} from the {@link HttpServletRequest}
*
* @param request the {@link HttpServletRequest} to use
* @return the {@link CsrfTokenBean} or null if none exists
*/
CsrfTokenBean loadToken(HttpServletRequest request);
/**
* 緩存來源的url
* @param request request the {@link HttpServletRequest} to use
* @param response the {@link HttpServletResponse} to use
*/
void cacheUrl(HttpServletRequest request, HttpServletResponse response);
/**
* 獲取并清理來源的url
* @param request the {@link HttpServletRequest} to use
* @param response the {@link HttpServletResponse} to use
* @return 來源url
*/
String getRemoveCacheUrl(HttpServletRequest request, HttpServletResponse response);
}HttpSessionCsrfTokenRepository
package com.wangzhixuan.commons.csrf;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import com.wangzhixuan.commons.utils.StringUtils;
public final class HttpSessionCsrfTokenRepository implements CsrfTokenRepository {
private static final String DEFAULT_CSRF_PARAMETER_NAME = "_csrf";
private static final String DEFAULT_CSRF_HEADER_NAME = "X-CSRF-TOKEN";
private static final String DEFAULT_CSRF_TOKEN_ATTR_NAME = HttpSessionCsrfTokenRepository.class
.getName().concat(".CSRF_TOKEN");
private static final String DEFAULT_CACHE_URL_ATTR_NAME = HttpSessionCsrfTokenRepository.class
.getName().concat(".CACHE_URL");
private String parameterName = DEFAULT_CSRF_PARAMETER_NAME;
private String headerName = DEFAULT_CSRF_HEADER_NAME;
private String sessionAttributeName = DEFAULT_CSRF_TOKEN_ATTR_NAME;
private String cacheUrlAttributeName = DEFAULT_CACHE_URL_ATTR_NAME;
/*
* (non-Javadoc)
*
* @see org.springframework.security.web.csrf.CsrfTokenRepository#saveToken(org.
* springframework .security.web.csrf.CsrfToken,
* javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
public void saveToken(CsrfTokenBean token, HttpServletRequest request,
HttpServletResponse response) {
if (token == null) {
HttpSession session = request.getSession(false);
if (session != null) {
session.removeAttribute(this.sessionAttributeName);
}
}
else {
HttpSession session = request.getSession();
session.setAttribute(this.sessionAttributeName, token);
}
}
/*
* (non-Javadoc)
*
* @see
* org.springframework.security.web.csrf.CsrfTokenRepository#loadToken(javax.servlet
* .http.HttpServletRequest)
*/
public CsrfTokenBean loadToken(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session == null) {
return null;
}
return (CsrfTokenBean) session.getAttribute(this.sessionAttributeName);
}
/*
* (non-Javadoc)
*
* @see org.springframework.security.web.csrf.CsrfTokenRepository#generateToken(javax.
* servlet .http.HttpServletRequest)
*/
public CsrfTokenBean generateToken(HttpServletRequest request) {
return new CsrfTokenBean(this.headerName, this.parameterName,
createNewToken());
}
private String createNewToken() {
return UUID.randomUUID().toString();
}
@Override
public void cacheUrl(HttpServletRequest request, HttpServletResponse response) {
String queryString = request.getQueryString();
// 被攔截前的請求URL
String redirectUrl = request.getRequestURI();
if (StringUtils.isNotBlank(queryString)) {
redirectUrl = redirectUrl.concat("?").concat(queryString);
}
HttpSession session = request.getSession();
session.setAttribute(this.cacheUrlAttributeName, redirectUrl);
}
@Override
public String getRemoveCacheUrl(HttpServletRequest request, HttpServletResponse response) {
HttpSession session = request.getSession(false);
if (session == null) {
return null;
}
String redirectUrl = (String) session.getAttribute(this.cacheUrlAttributeName);
if (StringUtils.isBlank(redirectUrl)) {
return null;
}
session.removeAttribute(this.cacheUrlAttributeName);
return redirectUrl;
}
}關于利用spring security防御csrf攻擊的原理是什么就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。