一. 什么是HttpSession
- HttpSession 是一个服务端的概念,服务端生成的 HttpSession 都会有一个对应的 sessionid,这个sessionid 会通过 cookie 传递给前端,前端以后发送请求的时候,就带上这个 sessionid 参数,服务端看到这个 sessionid 就会把这个前端请求和服务端的某一个 HttpSession 对应起来,形成“会话”的感觉
2.浏览器关闭并不会导致服务端的 HttpSession 失效,想让服务端的 HttpSession 失效,要么手动调用HttpSession#invalidate 方法;要么等到 session 自动过期;要么重启服务端。
3.之所以说浏览器关闭之后session失效是因为浏览器关闭之后,保存在浏览器里面的sessionid就丢失了,所以当浏览器再次访问服务端的时候,服务端就会给浏览器重新分配一个sessionid,这个sessionid和之前的HttpSession对应不上,所以就导致用户感觉session失效。
4.我们可以通过手动配置让浏览器重启之后sessionid不丢失,但是有隐患。
以spring Boot 为例,服务端生成sessionid后,传给前端的响应头如下
在服务端的响应头中有一个 Set-Cookie 字段,该字段指示浏览器更新 sessionid,同时大家注意还有一个 HttpOnly 属性,这个表示通过 JS 脚本无法读取到 Cookie 信息(这个有大用),这样能有效的防止 XSS 攻击。下一次浏览器再去发送请求的时候,就会自觉的携带上这个 jsessionid 了:
二.会话固定攻击(session fixation attack)
正常来说,只要你不关闭浏览器,并且服务端的HttpSession也没有过期,那么维系服务端和浏览器的sessionid是不会发生变化的,而会话固定攻击,就利用了这一点**,借助用户用相同的会话ID获取认证和授权**,然后利用该会话ID劫持用户的会话一冒充用户,从而造成会话固定攻击用户,造成会话固定攻击。
以淘宝为例:解析会话固定攻击的步骤:
1.攻击者自己可以正常访问淘宝网,在访问过程中,淘宝网给攻击者分配了一个sessionid。
2.攻击者利用拿到的sessionid构造了一个淘宝网站的连接,并且把该连接发送而给受害者。
3.用户使用该仿造的连接登录淘宝网站(该连接中含有攻击者之前拿到的sessionid),用户登录时候,淘宝网站一看你有sessionid那我就不用重新给你生成sessionid了直接登录吧,就这样用户登录成功,一个合法的会话就成功建立。
4.然后攻击者就可以利用自己手里的sessionid去冒充用户。
(补充:如果这个过程中,淘宝网支持URL重写,那么攻击会变得更加容易。)
什么是URL重写?
即,用户在浏览器中禁用了cookie,那么也就禁用了sessionid,所以有的服务端就支持把sessionid放在请求地址中
如下
http://www.taobao.com;jsessionid=xxxxxx
这个让我想到了什么?是不是我们的shiro中,登录时偶尔会产生这样的情况(现在shiro已经优化了这个玩意),这也是spring Security比shiro更加有优势的地方,他自动的做了防止固定会话攻击和csrf攻击的操作。
2.1 如何防止会话固定攻击
这个问题的根源在 sessionid 不变,如果用户在未登录时拿到的是一个 sessionid,登录之后服务端给用户重新换一个 sessionid,就可以防止会话固定攻击了。如果你使用了 Spring Security ,其实是不用担心这个问题的,因为 Spring Security 中默认已经做了防御工作了。
1.给请求地址中带上 ; 请求就会直接被拒绝。
2.是响应的 Set-Cookie 字段中有 HttpOnly 属性,这种方式避免了通过 XSS 攻击来获取
Cookie 中的会话信息进而达成会话固定攻击。
3.让 sessionid 变一下。既然问题是由于 sessionid 不变导致的,那我就让 sessionid 变一
下。
具体配置:
可以看到,在这里,我们有四个选项:
1.migrateSession 表示在登录成功之后,创建一个新的会话,然后讲旧的 session 中的信息复制到新的 session 中,默认即此。
2. none 表示不做任何事情,继续使用旧的 session。
3. changeSessionId 表示 session 不变,但是会修改 sessionid,这实际上用到了 Servlet 容器提供的防御会话固定攻击。
4. newSession 表示登录后创建一个新的 session。
注意:
默认的的 migrateSession ,在用户匿名访问的时候是一个 sessionid,当用户成功登录之后,又是另外一个 sessionid,这样就可以有效避免会话固定攻击。这三种方案,可以让我们有效避免会话固定攻击!
三.Csrf攻击
这是一种非常常见的 Web 攻击方式,其实是很好防御的,但是由于经常被很多开发者忽略,进而导致很多网站实际上都存在 CSRF 攻击的安全隐患。
3.1CSRF 原理
其实这个流程很简单:
1 假设用户打开了招商银行网上银行网站,并且登录。
2. 登录成功后,网上银行会返回 Cookie 给前端,浏览器将 Cookie 保存下来。
3. 用户在没有登出网上银行的情况下,在浏览器里边打开了一个新的选项卡,然后又去访问了一个危
险网站。
4. 这个危险网站上有一个超链接,超链接的地址指向了招商银行网上银行。
5. 用户点击了这个超链接,由于这个超链接会自动携带上浏览器中保存的 Cookie,所以用户不知不
觉中就访问了网上银行,进而可能给自己造成了损失。
3.2 项目配置
application.properties
spring.security.user.name=javaboy
spring.security.user.password=123
service
@RestController public class HelloController { @PostMapping("/transfer")
public void transferMoney(String name, Integer money)
{
System.out.println("name = " + name); System.out.println("money = " + money);
}
@GetMapping("/hello")
public String hello()
{
return "hello";
}
}
假设 /transfer 是一个转账接口(这里是假设,主要是给大家演示 CSRF 攻击,真实的转账接口比这复杂)。
最后我们还需要配置一下 Spring Security,因为 Spring Security 中默认是可以自动防御 CSRF 攻击的,所以我们要把这个关闭掉:
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest()
.authenticated()
.and()
.formLogin()
.and()
.csrf()
.disable();
}
}
配置完成后,我们启动 csrf-1 项目。
接下来,我们再创建一个 csrf-2 项目,这个项目相当于是一个危险网站,为了方便,这里创建时我们只
需要引入 web 依赖即可。
项目创建成功后,首先修改项目端口:
server.port=8081
然后我们在 resources/static 目录下创建一个 hello.html ,内容如下:
<body>
<form action="http://localhost:8080/transfer" method="post">
<input type="hidden" value="javaboy" name="name">
<input type="hidden" value="10000" name="money">
<input type="submit" value="点击查看美女图片">
</form>
</body>
这里有一个超链接,超链接的文本是点击查看美女图片,当你点击了超链接之后,会自动请求
http://localhost:8080/transfer 接口,同时隐藏域还携带了两个参数。
配置完成后,就可以启动 csrf-2 项目了。
接下来,用户首先访问 csrf-1 项目中的接口,在访问的时候需要登录,用户就执行了登录操作,访问完
整后,用户并没有执行登出操作,然后用户访问 csrf-2 中的页面,看到了超链接,好奇这美女到底长啥
样,一点击,结果钱就被人转走了。
3.3 CSRF防御
先来说说防御思路。
CSRF 防御,一个核心思路就是在前端请求中,添加一个随机数。
因为在 CSRF 攻击中,黑客网站其实是不知道用户的 Cookie 具体是什么的,他是让用户自己发送请求
到网上银行这个网站的,因为这个过程会自动携带上 Cookie 中的信息。
所以我们的防御思路是这样:用户在访问网上银行时,除了携带 Cookie 中的信息之外,还需要携带一
个随机数,如果用户没有携带这个随机数,则网上银行网站会拒绝该请求。黑客网站诱导用户点击超链
接时,会自动携带上 Cookie 中的信息,但是却不会自动携带随机数,这样就成功的避免掉 CSRF 攻击
了。
Spring Security 中对此提供了很好的支持,我们一起来看下。
3.4总结
csrf攻击原理:csrf:利用浏览器自动携带上cookie机制(用户登录qq登录界面,登录上去了,自己的cookie存储在了浏览器,然后访问盗号网站,访问了他的接口(带了校验参数的接口),浏览器携带着自己的cookie去他的接口进行判断,然后盗号网站的接口就拿到了你cookie里面的账户信息)
防止攻击:spring security怎么防止csrf攻击,加了一个csrf的临牌参数,当用户进行put,post,insert的时候必须携带上自己的令牌参数csrf,否则没有办法去登录接口(你说你自己的不带csrf都访问不了,别人的盗号接口没你的csrf令牌能访问吗?)
注意:
1.get请求只是做查询的,一般都设置为游客都可访问,进入get请求的时候当然不用携带csrf令牌参数
2.为什么手机app没办法csrf攻击:没有csrf参数令牌
具体代码如下:
application
spring.security.user.name=javaboy
spring.security.user.password=123
在 resources/templates 目录下,新建一个 thymeleaf 模版
<body>
<form action="/hello" method="post">
<input type="hidden" th:value="${_csrf.token}" th:name="${_csrf.parameterName}">
<input type="submit" value="hello"> </form>
</body>
service
@GetMapping("/hello")
public String hello2()
{
return "hello";
}
添加完成后,启动项目,我们访问 hello 页面,在访问时候,需要先登录,登录成功之后,我们可以看
到登录请求中也多了一个参数,如下:
而这个参数就是我们总结上提到的csrf令牌。
四.Spring Security中实现前后端分离方案
这次不是将 _csrf 放在 Model 中返回前端了,而是放在 Cookie 中返回前端
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
@Override
protected void configure(HttpSecurity http) throws Exception
{
http.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.formLogin()
.and() .csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}
}
那把csrf令牌放在Cookied中安全吗,会被攻击者盗取吗
1.黑客网站根本不知道你的 Cookie 里边存的啥,他也不需要知道,因为 CSRF 攻击是浏览器自动携带上 Cookie 中的数据的。
2.我们将服务端生成的随机数放在 Cookie 中,前端需要从 Cookie 中自己提取出来 _csrf 参数,然后拼接成参数传递给后端,单纯的将 Cookie 中的数据传到服务端是没用的。
3.js在这里面做了很多,利用js把csrf临牌从cookie中读取出来,放到你请求的参数里,再发送到服务端,而这个攻击是利用cookie一股脑的发送到盗号接口但是并不会把这一些参数解析,所有这么写也是没有问题的
配置完成后,重启项目,此时我们就发现返回的 Cookie 中多了一项:
接下来,我们通过自定义登录页面,来看看前端要如何操作。
首先我们在 resources/static 目录下新建一个 html 页面叫做 login.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"> <title>Title</title>
<script src="js/jquery.min.js"></script> <script src="js/jquery.cookie.js"></script>
</head>
<body>
<div>
<input type="text" id="username">
<input type="password" id="password"> <input type="button" value="登录" id="loginBtn">
</div>
<script>
$("#loginBtn").click(function () {
let _csrf = $.cookie('XSRF-TOKEN'); $.post('/login.html', {username:$("#username").val(),password:$("#password").val(),_csrf:_csrf},functi on (data) {
alert(data);
})
})
</script>
</body>
</html>
这段html做了什么:
1 首先引入 jquery 和 jquery.cookie ,方便我们一会操作 Cookie。
2. 定义三个 input,前两个是用户名和密码,第三个是登录按钮。
3. 点击登录按钮之后,我们先从 Cookie 中提取出 XSRF-TOKEN,这也就是我们要上传的 csrf 参数。
4. 通过一个 POST 请求执行登录操作,注意携带上 _csrf 参数。
服务端我们也稍作修改,如下:
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/js/**");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login.html") .successHandler((req,resp,authentication)->{
resp.getWriter().write("success");
})
.permitAll()
.and()
.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}
}
一方面这里给 js 文件放行。
另一方面配置一下登录页面,以及登录成功的回调,这里简单期间,登录成功的回调我就给一个字符串就可以了。
如上,我们的 _csrf 配置已经生效了
本篇文章参考: