概述
使用spring security后,get请求可以正常访问资源,但提交post请求时,会报错,这是因为security默认开启防csrf攻击
csrf攻击
CSRF(Cross-site request forgery)跨站请求伪造,也被称为One Click Attack或者Session Riding,通常缩写为CSRF或XSRF,是一种对网站的恶意利用。尽管听起来像跨站脚本(XSS),但它与XSS非常不同,XSS利用站点内的信任用户,而CSRF则通过伪装成受信任用户的请求来利用受信任的网站。与XSS攻击相比,CSRF攻击往往不大流行(因此对其进行防范的资源也相当稀少)和难以防范,所以被认为比XSS更具危险性。
CSRF是一种依赖web浏览器的、被混淆过的代理人攻击(deputy attack)。
spring security
springSecurity在2.0之后就会 默认开启防csrf攻击
GET,HEAD,TRACE,OPTIONS请求放行,其他请求(如post、delete、put),都需要带上 csrf token,不带tocken,服务器会认为该请求非法,请求就会被拒绝
CsrfFilter
CsrfFilter处理CSRF校验,详见:
https://www.malaoshi.top/show_1EF5zQbtQP3U.html
如何能够正常发post请求
- 禁用csrf
- 前台页面获取csrf token,然后发post请求时,将token一并发出
- HttpSessionCsrfTokenRepository,适用 jsp、Thymeleaf
- CookieCsrfTokenRepository ,适用前后台分离式开发
禁用csrf
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();//关闭打开的csrf保护
}
}
HttpSessionCsrfTokenRepository
在默认情况下,Spring Security加载的是一个HttpSessionCsrfTokenRepository
HttpSessionCsrfTokenRepository 将 CsrfToken 值存储在 HttpSession 中,并指定前端把CsrfToken 值放在名为_csrf的请求参数或名为X-CSRF-TOKEN的请求头字段里(可以调用相应的设置方法来重新设定)。校验时,通过对比HttpSession内存储的CsrfToken值与前端携带的CsrfToken值是否一致,便能断定本次请求是否为CSRF攻击。
jsp获取token
<input type='hidden' name='${_csrf.parameterName}' value='${_csrf.token}'>
应用场景
这种方式适合 jsp、Thymeleaf等,可以从session中取出token
在前后台分离式开发 会出现问题,因为html、js无法从session中取出token
CookieCsrfTokenRepository
Spring Security还提供了另一种方式,即CookieCsrfTokenRepositoryCookieCsrfTokenRepository 是一种更加灵活可行的方案,它将 CsrfToken 值存储在用户的cookie内。
减少了服务器HttpSession存储的内存消耗,并且当用cookie存储CsrfToken值时,前端可以用JavaScript读取(需要设置该cookie的httpOnly属性为false),而不需要服务器注入参数,在使用方式上更加灵活。
存储在cookie中是不可以被CSRF利用的,cookie 只有在同域的情况下才能被读取,所以杜绝了第三方站点跨域获取 CsrfToken 值的可能。CSRF攻击本身是不知道cookie内容的,只是利用了当请求自动携带cookie时可以通过身份验证的漏洞。
注意: 服务器对 CsrfToken 值的校验 不是取自 cookie,而是需要前端 手动将CsrfToken值作为参数携带在请求里
启用cookie方式
关键代码:
http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
完整代码:
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) //使用cookie
.and()
.authorizeRequests()//url权限配置
// .antMatchers("/index.html").permitAll() // 匹配这些路径可以所有人访问
.antMatchers("/user/**").hasRole("admin")
.anyRequest().authenticated()
.and()
//login部分
.formLogin()
.loginPage("/login.html")//指定登录页面。所有url前面必须写/,否则报错 'xxx' is not a valid forward URL
.loginProcessingUrl("/login")//security提供的处理登录url,默认是/login
// .successForwardUrl("/index.html")//登录成功后跳转的页面,需要额外处理才能使用,否则页面会提示错误
.defaultSuccessUrl("/index.html")//登录成功后重定向的页面
.failureUrl("/login_fails.html")//登录失败跳转的页面
// .failureForwardUrl("/login_fails.html")//登录失败不会转发
.permitAll()//让login部分的url可以任意访问
//
// //logout部分
.and()
.logout()
.logoutSuccessUrl("/login.html")//退出成功访问的页面
.invalidateHttpSession(true)//清空session
.permitAll();//让logout部分的url可以任意访问
}
}
访问 http://localhost:8080/ ,在cookie中可以看到 xsrf-token:
修改登录页面
由于登录要发送post请求,所以必须带上token,否则会被过滤器拦截,无法正常提交
从cookie中获取token,并放入表单隐藏域中,一并发出
<script src="/js/js.cookie.min.js"></script>
<form method="post" action="/doLogin">
用户名:<input name="username"><br>
密码:<input name="password"><br>
<input id="_csrf" type="" name="_csrf" value="">
<input type="submit">
</form>
<script>
alert(Cookies.get('XSRF-TOKEN'));
document.getElementById("_csrf").value=Cookies.get('XSRF-TOKEN');
</script>
在表单中添加隐藏域<input id="_csrf" type="" name="_csrf" value="">,提交表单时连带token一起提交
感谢:
https://www.cnblogs.com/dalianpai/p/12393133.html
