CSRF 攻擊簡單來講,是多Tab頁面瀏覽器的一個安全漏洞,好比你正在訪問A網站,此時若是瀏覽器有你的cookie,而且session沒有過時,此時你去訪問B網站,那麼B網站能夠直接調用A網站的接口,而A網站則認爲是你本人進行的操做。如下是圖示:java
對CSRF進行防護,能夠經過加Token.也就是當你訪問A網站的時候,A會給你一個token,而後,接下去的post請求,你須要把token帶上,否則服務器則拒絕接收這個請求。
- 1. token的產生:spring-security 4.0以後默認開啓csrf,能夠直接產生csrf token。
- 2. token的存儲:這裏存儲是指服務端的存儲,token是存儲在session中。
- 3. token的傳送:token能夠經過cookie,也能夠放在header中自定義的屬性中。
- 4. token的接收和返回:前段收到http respon 以後,須要把相應的token返回回來。
- 5. token校驗:服務器端對本身持有的token和客戶端反饋回來的token進行校驗,決定是否拒絕服務(拒絕服務能夠自定義)。web
通常寫REST服務(也就是直接@ResponseBody)返回json字符串,則能夠把token加在header裏頭的自定義屬性中,爲何不能直接加在header中的cooike裏,spring-sercurity官方給出的答案:spring
One might ask why the expected CsrfToken isn’t stored in a cookie by default. This is because there are known exploits in which headers (i.e. specify the cookies) can be set by another domain. This is the same reason Ruby on Rails no longer skips CSRF checks when the header X-Requested-With is present. See this webappsec.org thread for details on how to perform the exploit. Another disadvantage is that by removing the state (i.e. the timeout) you lose the ability to forcibly terminate the token if it is compromised.json
翻譯一下:
- 1.cookie能夠被其餘域設置
- 2.cookie是沒有狀態的,可是若是是session(含有過時時間),則可使session過時,從而使token失效。(若有出入,歡迎拍磚)promise
既然如此,那麼須要在header中加入token,咱們只要註冊一個Filter,就能夠完成這個功能:
- STEP 1 建立Filter瀏覽器
/** * * "將CSRF TOKEN加入到header中" * * Created by hzlaojiaqi on 2016/9/13. */ public class CsrfTokenResponseHeaderBindingFilter extends OncePerRequestFilter { protected static final String REQUEST_ATTRIBUTE_NAME = "_csrf"; protected static final String RESPONSE_HEADER_NAME = "X-CSRF-HEADER"; protected static final String RESPONSE_PARAM_NAME = "X-CSRF-PARAM"; protected static final String RESPONSE_TOKEN_NAME = "X-CSRF-TOKEN"; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, javax.servlet.FilterChain filterChain) throws ServletException, IOException { CsrfToken token = (CsrfToken) request.getAttribute(REQUEST_ATTRIBUTE_NAME); if (token != null) { response.setHeader(RESPONSE_HEADER_NAME, token.getHeaderName()); response.setHeader(RESPONSE_PARAM_NAME, token.getParameterName()); response.setHeader(RESPONSE_TOKEN_NAME , token.getToken()); } filterChain.doFilter(request, response); } }
STEP 2 加入到過濾器中安全
@Configuration @EnableWebSecurity public class SecurityConfigure extends WebSecurityConfigurerAdapter { private static final Logger THIRDPARTY_LOG = LoggerFactory.getLogger("THIRDPARTY_LOGGER"); @Autowired UserService userService; protected void configure(HttpSecurity httpSecurity) throws Exception { CsrfTokenResponseHeaderBindingFilter csrfTokenFilter = new CsrfTokenResponseHeaderBindingFilter(); CustomAccessDeniedHandler accessDeniedHandler=new CustomAccessDeniedHandler(); httpSecurity.addFilterAfter(csrfTokenFilter,CsrfFilter.class); } }
狀況2 帶header發起token服務器
能夠看到,服務器端正確返回數據。cookie
狀況3 不帶header發起tokensession
能夠看到,服務器端 拒絕了咱們的請求。