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,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,"CsrfToken not eq!"); } return true; } private boolean renderError(HttpServletRequest request,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,response); logger.info("Csrf[redirectUrl]:\t" + cachedUrl); response.sendRedirect(cachedUrl); return false; } /** * 用於清理@CsrfToken保證只能請求成功一次 */ @Override public void postHandle(HttpServletRequest request,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,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,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防禦原理(跨域請求偽造),希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回覆大家的。在此也非常感謝大家對我們網站的支援!
如果你覺得本文對你有幫助,歡迎轉載,煩請註明出處,謝謝!