CSRF 全称:Cross Site Request Forgery
,译:跨站请求伪造html
点击一个连接以后发现:帐号被盗,钱被转走,或者莫名发表某些评论等一切本身不知情的操做。前端
csrf 是一个能够发送http请求的脚本。能够假装受害者向网站发送请求,达到修改网站数据的目的。chrome
当你在浏览器上登陆某网站后,cookie会保存登陆的信息,这样在继续访问的时候不用每次都登陆了,这个你们都知道。而CSRF就利用这个登录态去发送恶意请求给后端。json
为何脚本能够得到目标网站的cookie呢?
只要是请求目标网站,浏览器会自动带上该网站域名下面的cookie,看下面的脚本,能够证实恶意脚本能够得到CSDN网站的登陆信息。
前提是你已经在浏览器上登陆了CSND网站。后端
<!doctype html> <html> <head> <meta charset="utf-8"/> <title>csrf demo</title> </head> <body> 您在CSDN上的 粉丝数:<span id="fans_num"></span> 关注数:<span id="follow_num"></span> <script> fetch('https://me.csdn.net/api/relation/get', { credentials: 'include' }).then(res => res.json()) .then( res => { document.getElementById('fans_num').innerText = res.data.fans_num; document.getElementById('follow_num').innerText = res.data.follow_num; }) </script> </body> </html>
保证CSDN的登陆状态,用浏览器打开这个html文件,能够看到这个脚本已经得到了我在csdn 上的用户信息。以及寒酸的粉丝数量!
F12打开选择应用程序一栏左边Cookie 还有来自csdn网站关于当前用户的一些信息。api
这个脚本让每一个不一样的登陆用户打开,都会根据当前用户来展现关注数和粉丝数,
这就足以说明能够得到目标网站的当前用户的信息,并可以表明用户发送请求。
这只是个无害的get请求,若是是post请求呢?浏览器
知道了原理,攻击就变得好理解了,接着上面的例子,
我把请求地址改为评论本篇文章的url,参数为 “这篇文章写得6
”,
在没有CSRF防护的状况下,我发表一个评论如:脱单秘笈:
,后面附上这个脚本的连接,只要有用户点了连接,就会以他的名义给本篇文章发评论“这篇文章写得6
”。cookie
CSDN 确定是作了防护了哈,我就不白费力气了。session
三种防护方式:并发
禁止第三方网站使用本站Cookie。
这是后端在设置Cookie时候给SameSite
的值设置为Strict
或者Lax
。
当设置Strict
的时候表明第三方网站全部请求都不能使用本站的Cookie。
当设置Lax
的时候表明只容许第三方网站的GET
表单、<a>
标签和<link>
标签携带Cookie。
当设置None
的时候表明和没设同样。
@Bean public CookieSerializer httpSessionIdResolver(){ DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer(); cookieSerializer.setCookieName("JESSIONID"); cookieSerializer.setUseHttpOnlyCookie(true); cookieSerializer.setSameSite("Lax"); cookieSerializer.setUseSecureCookie(true); return cookieSerializer; }
缺点:
目前只有chrome浏览器支持........
referer
表明着请求的来源,不能够伪造。
后端写个过滤器检查请求的headers
中的referer
,检验是否是本网站的请求。
题外话:referer
和origin
的区别,只有post请求会携带origin请求头,而referer不论何种状况下都带。referer
正确的拼写 应该是 referrer
,HTTP的标准制定者们将错就错,不打算改了
缺点:
浏览器能够关闭referer..........
最广泛的一种防护方法,后端生成一个token放在session中并发给前端,前端发送请求时携带这个token,后端经过校验这个token和session中的token是否一致判断是不是本网站的请求。
具体实现:
用户登陆输入帐号密码,请求登陆接口,后端在用户登陆信息正确的状况下将token放到session中,并返回token给前端,前端把token 存放在localstory中,以后再发送请求都会将token放到header中。
后端写个过滤器,拦截POST请求,注意忽略掉不须要token的请求,好比登陆接口,获取token的接口,省得尚未获取token就检验token。
校验原则: session中的token和前端header中的token一致的post ,放行。
/** * @author mashu * Date 2020/6/22 9:37 */ @Slf4j @Component @WebFilter(urlPatterns = "/*", filterName = "verificationTokenFilter", description = "用于校验token") public class VerificationTokenFilter implements Filter { List<String> ignorePathList = ImmutableList.of("/demo/login","/demo/getToken"); @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse; //忽略不须要token的请求 String serviceUrl = httpServletRequest.getServletPath(); for (final String ignorePath : ignorePathList) { if (serviceUrl.contains(ignorePath)) { filterChain.doFilter(servletRequest, servletResponse); return; } } String method = httpServletRequest.getMethod(); if ("POST".equals(method)) { String tokenSession = (String)httpServletRequest.getSession().getAttribute("token"); String token = httpServletRequest.getHeader("token"); if (null != token && null != tokenSession && tokenSession.equals(token)) { filterChain.doFilter(servletRequest, servletResponse); return; } else { log.error("验证token失败!" + tokenSession + "!=" + token); httpServletResponse.sendError(403); return; } } filterChain.doFilter(servletRequest, servletResponse); } @Override public void destroy() { } }