spring security中的csrf防御原理(跨域請(qǐng)求偽造)
什么是csrf?
csrf又稱跨域請(qǐng)求偽造,攻擊方通過(guò)偽造用戶請(qǐng)求訪問(wèn)受信任站點(diǎn)。CSRF這種攻擊方式在2000年已經(jīng)被國(guó)外的安全人員提出,但在國(guó)內(nèi),直到06年才開(kāi)始被關(guān)注,08年,國(guó)內(nèi)外的多個(gè)大型社區(qū)和交互網(wǎng)站分別爆出CSRF漏洞,如:NYTimes.com(紐約時(shí)報(bào))、Metafilter(一個(gè)大型的BLOG網(wǎng)站),YouTube和百度HI......而現(xiàn)在,互聯(lián)網(wǎng)上的許多站點(diǎn)仍對(duì)此毫無(wú)防備,以至于安全業(yè)界稱CSRF為“沉睡的巨人”。
舉個(gè)例子,用戶通過(guò)表單發(fā)送請(qǐng)求到銀行網(wǎng)站,銀行網(wǎng)站獲取請(qǐng)求參數(shù)后對(duì)用戶賬戶做出更改。在用戶沒(méi)有退出銀行網(wǎng)站情況下,訪問(wèn)了攻擊網(wǎng)站,攻擊網(wǎng)站中有一段跨域訪問(wèn)的代碼,可能自動(dòng)觸發(fā)也可能點(diǎn)擊提交按鈕,訪問(wèn)的url正是銀行網(wǎng)站接受表單的url。因?yàn)槎紒?lái)自于用戶的瀏覽器端,銀行將請(qǐng)求看作是用戶發(fā)起的,所以對(duì)請(qǐng)求進(jìn)行了處理,造成的結(jié)果就是用戶的銀行賬戶被攻擊網(wǎng)站修改。
解決方法基本上都是增加攻擊網(wǎng)站無(wú)法獲取到的一些表單信息,比如增加圖片驗(yàn)證碼,可以杜絕csrf攻擊,但是除了登陸注冊(cè)之外,其他的地方都不適合放驗(yàn)證碼,因?yàn)榻档土司W(wǎng)站易用性
相關(guān)介紹:
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攔截器,用來(lái)生成或去除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攔截器,用來(lái)生成或去除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; // 非控制器請(qǐng)求直接跳出 if (!(handler instanceof HandlerMethod)) { return true; } CsrfToken csrfToken = handlerMethod.getMethodAnnotation(CsrfToken.class); // 判斷是否含有@CsrfToken注解 if (null == csrfToken) { return true; } // create、remove同時(shí)為true時(shí)異常 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!"); } // 創(chuàng)建 if (csrfToken.create()) { CsrfTokenBean token = csrfTokenRepository.generateToken(request); csrfTokenRepository.saveToken(token, request, response); // 緩存一個(gè)表單頁(yè)面地址的url csrfTokenRepository.cacheUrl(request, response); request.setAttribute(token.getParameterName(), token); return true; } // 判斷是否ajax請(qǐng)求 boolean isAjax = WebUtils.isAjax(handlerMethod); // 校驗(yàn),并且清除 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請(qǐng)求直接拋出異常,因?yàn)閧@link ExceptionResolver}會(huì)去處理 if (isAjax) { throw new RuntimeException(message); } // 非ajax CsrfToken校驗(yàn)異常,先清理token csrfTokenRepository.saveToken(null, request, response); logger.info("Csrf[redirectUrl]:\t" + cachedUrl); response.sendRedirect(cachedUrl); return false; } /** * 用于清理@CsrfToken保證只能請(qǐng)求成功一次 */ @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { HandlerMethod handlerMethod = (HandlerMethod) handler; // 非控制器請(qǐng)求直接跳出 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過(guò)濾注解,通過(guò)標(biāo)注來(lái)過(guò)濾對(duì)應(yīng)的請(qǐng)求
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Csrf過(guò)濾注解 * @author L.cm */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface CsrfToken { boolean create() default false; boolean remove() default false; }
建立實(shí)例對(duì)象(操作對(duì)象)
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; } }
過(guò)濾過(guò)程中需要的倉(cāng)庫(kù)
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); /** * 緩存來(lái)源的url * @param request request the {@link HttpServletRequest} to use * @param response the {@link HttpServletResponse} to use */ void cacheUrl(HttpServletRequest request, HttpServletResponse response); /** * 獲取并清理來(lái)源的url * @param request the {@link HttpServletRequest} to use * @param response the {@link HttpServletResponse} to use * @return 來(lái)源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(); // 被攔截前的請(qǐng)求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; } }
總結(jié)
以上所述是小編給大家介紹的spring security中的csrf防御原理(跨域請(qǐng)求偽造),希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
如果你覺(jué)得本文對(duì)你有幫助,歡迎轉(zhuǎn)載,煩請(qǐng)注明出處,謝謝!
相關(guān)文章
通過(guò)實(shí)例了解java TransferQueue
這篇文章主要介紹了TransferQueue實(shí)例,下面小編和大家一起來(lái)學(xué)習(xí)一下2019-05-05Java并發(fā)編程之關(guān)鍵字volatile的深入解析
提高java的并發(fā)編程,就不得不提volatile關(guān)鍵字,不管是在面試還是實(shí)際開(kāi)發(fā)中volatile都是一個(gè)應(yīng)該掌握的技能,這篇文章主要給大家介紹了關(guān)于Java并發(fā)編程之關(guān)鍵字volatile的相關(guān)資料,需要的朋友可以參考下2021-09-09基于Java方式實(shí)現(xiàn)數(shù)據(jù)同步
這篇文章主要為大家詳細(xì)介紹了基于Java方式實(shí)現(xiàn)數(shù)據(jù)同步,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-08-08Java ArrayList.add 的實(shí)現(xiàn)方法
這篇文章主要介紹了Java ArrayList.add 的實(shí)現(xiàn)方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-11-11Java實(shí)現(xiàn)任務(wù)超時(shí)處理方法
任務(wù)超時(shí)處理是比較常見(jiàn)的需求,Java中對(duì)超時(shí)任務(wù)的處理有兩種方式,在文中給大家詳細(xì)介紹,本文重點(diǎn)給大家介紹Java實(shí)現(xiàn)任務(wù)超時(shí)處理方法,需要的朋友可以參考下2019-06-06劍指Offer之Java算法習(xí)題精講二叉樹(shù)與鏈表
跟著思路走,之后從簡(jiǎn)單題入手,反復(fù)去看,做過(guò)之后可能會(huì)忘記,之后再做一次,記不住就反復(fù)做,反復(fù)尋求思路和規(guī)律,慢慢積累就會(huì)發(fā)現(xiàn)質(zhì)的變化2022-03-03SpringBoot實(shí)現(xiàn)XSS攻擊防御的幾種方式
隨著Web應(yīng)用的普及,網(wǎng)絡(luò)安全問(wèn)題也日益凸顯,跨站腳本攻擊(Cross-Site Scripting,簡(jiǎn)稱XSS)是一種常見(jiàn)的Web安全漏洞,本文旨在探討如何在Spring Boot應(yīng)用程序中有效地防御XSS攻擊,我們將介紹兩種主要的防御手段:注解和過(guò)濾器,需要的朋友可以參考下2024-07-07