日前 Chrome 工程师 Mike West 发表了一篇文章提议改造 Cookie 标准,以强化
HTTP 状态管理。

继续这一个系列,基于Token的WEB后台登录认证机制(并讲解cookie和session机制)。每个后端不得不解决的认证问题。

1、简介

  Session对于Web应用无疑是最重要的,也是最复杂的。对于web应用程序来说,加强安全性的第一条原则就是

不要信任来自客户端的数据,一定要进行数据验证以及过滤,才能在程序中使用,进而保存到数据层。
然而,为了维持来自同一个用户的不同请求之间的状态,
客户端必须要给服务器端发送一个唯一的身份标识符(Session ID)。
很显然,这和前面提到的安全原则是矛盾的,但是没有办法,http协议是无状态的,为了维持状态,我们别无选择。
可以看出,web应用程序中最脆弱的环节就是session,因为服务器端是通过来自客户端的一个身份标识来认证用户的,
所以session是web应用程序中最需要加强安全性的环节。 

  基于session的攻击有很多种方式。大部分的手段都是首先通过捕获合法用户的session,
然后冒充该用户来访问系统。也就是说,攻击者至少必须要获取到一个有效的session标识符,用于接下来的身份验证。 

  攻击者至少可以通过以下三种方式来获取一个有效的session标识符:

  1、预测

  2、捕获(劫持)

  3、固定

澳门葡萄京官方网站 1

本系列:

2、会话预测

  预测这种方式,也就是攻击者需要猜测出系统中使用的有效的session标识符(PHP中格式为PHPSESSID=1234),有点类似暴力破解。
php内部session的实现机制虽然不是很安全,但是关于生成session
id的关节还是比较安全的,这个随机的session
id往往是极其复杂的并且难于被预测出来,所以说,这种攻击方式基本上是不太可能成功的。 

Mike 分析了目前 Cookie
存在的几个方面的问题,包括很难安全使用浪费用户资源,以及隐私问题,通过它可以以令人惊讶的方式跟踪用户在网络上的活动。

(一)J2EE项目系列(三)–Spring Data JPA+Spring+SpringMVC+Maven快速开发(1)项目架构

3、会话劫持

关于浪费用户资源,Mike
解释,服务器可以为一个注册域名存储大量 Cookie,并且很多 Cookie 可以通过
HTTP 请求发送。例如 Chrome 允许为每一个域名存储大约 180 个
Cookie,相当于约 724kB 数据。在众多 Cookie 中,其请求头大小的中位数是
409 字节,但是其中却有 90% 有 1589 字节,95% 占了 2549 字节,99%
甚至达到了 4601 字节,另外有约 0.1% 的 Cookie 头非常大,超过了
10kB。如此滥用,效率低下。

(二)J2EE项目系列(三)–Spring Data JPA+Spring+SpringMVC+Maven快速开发(2)多个第三方服务端接入之云旺IM

  3.1、含义

  会话劫持(Session hijacking),这是一种通过获取用户Session
ID后,使用该Session
ID登录目标账号的攻击方法,此时攻击者实际上是使用了目标账户的有效Session。会话劫持的第一步是取得一个合法的会话标识来伪装成合法用户,因此需要保证会话标识不被泄漏。

隐私方面,众所周知 Cookie
可以用于身份验证,但它同时也可以用来悄悄跟踪用户的相关信息。

(三)Java-解决实现JPA的hibernate自动建表的编码问题


  3.2、攻击步骤

  1、 目标用户需要先登录站点;

  2、 登录成功后,该用户会得到站点提供的一个会话标识SessionID;

  3、 攻击者通过某种攻击手段捕获Session ID

      4、 攻击者通过捕获到的Session ID访问站点即可获得目标用户合法会话。

澳门葡萄京官方网站 2

攻击者获取SessionID的方式有多种:

       1、 暴力破解:尝试各种Session ID,直到破解为止;

       2、 预测:如果Session
ID使用非随机的方式产生,那么就有可能计算出来;

       3、 窃取:使用网络嗅探,XSS攻击等方法获得。

   对于PHP来说,其内部Session的实现机制虽然不是很安全,但是关于生成Session
ID的环节还是比较安全的,这个随机的Session
ID往往是极其复杂的并且难于被预测出来,所以,对于第一、第二种攻击方式基本上是不太可能成功的。

  对于第三种方式大多使用网络数据通讯层进行攻击获取,可以使用SSL进行防御。

  在应用层上也可以做出相应的防御措施:

  目前有三种广泛使用的在Web环境中维护会话(传递Session
ID)的方法:URL参数,隐藏域和Cookie。其中每一种都各有利弊,Cookie已经被证明是三种方法中最方便最安全的。从安全的观点,如果不是全部也是绝大多数针对基于Cookie的会话管理机制的攻击对于URL或是隐藏域机制同样适用,但是反过来却不一定,这就让Cookie成为从安全考虑的最佳选择。

而关于安全使用的难处,Mike 列出了几条在开发中安全使用 Cookie
遇到的问题:

文章结构:(1)JWT(一种基于 token 的认证方案)介绍并介绍其他几大认证机制;(2)cookie和session机制;(3)Token机制相对于Cookie机制的好处;(4)JWT的Java实现;


  3.3、防御方法

  1、
更改Session名称。PHP中Session的默认名称是PHPSESSID,此变量会保存在Cookie中,如果攻击者不分析站点,就不能猜到Session名称,阻挡部分攻击。

      2、 关闭透明化Session ID。透明化Session
ID指当浏览器中的Http请求没有使用Cookie来存放Session ID时,Session
ID则使用URL来传递。

      3、
设置HttpOnly。通过设置Cookie的HttpOnly为true,可以防止客户端脚本访问这个Cookie,从而有效的防止XSS攻击。

      4、 关闭所有phpinfo类dump request信息的页面。

      5、验证HTTP头部信息

     在http访问头文件:[Accept-Charset、Accept-Encoding、Accept-Language、User-Agent],浏览器一般发出的头部不会改

  使用User-Agent检测请求的一致性。

 1 GET/HTTP/1.1
 2 host:example.org
 3 User-Agent:Firefox/1.0
 4 Accept:text/html,image/png,image/jpeg,image/gif,*/*
 5 Cookie:PHPSESSID=1234
 6 <?php
 7 session_start();
 8 if(isset($_SESSION['HTTP_USER_AGENT']))
 9 {    
10         if($_SESSION['HTTP_USER_AGENT']!=md5($_SERVER['HTTP_USER_AGENT']))
11         {
12                         /*Promptforpassword*/
13                        exit;
14         }
15 }
16 else
17 {      
18   $_SESSION['HTTP_USER_AGENT']=md5($_SERVER['HTTP_USER_AGENT']);
19 }
20 ?>

  确保User-Agent头部信息一致的确是有效的,如果会话标识通过cookie传递,攻击者能取得会话标识,他同时也能取得其它HTTP头部。由于cookie暴露与浏览器漏洞或跨站脚本漏洞相关,受害者需要访问攻击者的网站并暴露所有头部信息。则攻击者只需重建头部即可进行攻击了

  因此前提需要做好XSS防御!

  注意:

在某些版本的IE浏览器中,用户正常访问一个网页和刷新一个网页时发出的Accept头部信息不同,因此Accept头部不能用来判断一致性。

  有专家警告不要依赖于检查User-Agent的一致性。这是因为服务器群集中的HTTP代理服务器会对User-Agent进行编辑,而本群集中的多个代理服务器在编辑该值时可能会不一致。  

  6、
加入Token校验。同样是用于检测请求的一致性,给攻击者制造一些麻烦,使攻击者即使获取了Session
ID,也无法进行破坏,能够减少对系统造成的损失。但Token需要存放在客户端,如果攻击者有办法获取到Session
ID,那么也同样可以获取到Token。

  • Cookie 对 JavaScript 默认是可用的,这使得一次 XSS
    可以获取持久凭证。虽然十年前引入了 HttpOnly 属性,目前也只有大概
    8.31% 的人使用 Set-Cookie 进行相应设置。

  • 默认情况下,Cookie 会被发送到非安全的源,这会导致凭据被盗。Secure
    属性虽然可以标记安全的 Cookie 源,但目前只有大概 7.85% 的人使用
    Set-Cookie 进行了设置。

  • Cookie 经常在请求发送者毫不知情的情况下被发送。SameSite 属性可以减少
    CSRF 风险,但是目前只有大概 0.06% 的人使用 Set-Cookie 进行了设置。

一、JWT(一种基于 token 的认证方案)介绍:

4、会话固定

Mike 认为,一方面 Cookie 采用的缓解安全问题的属性很差,Cookie
根本不符合我们决定对其它类型的 Web
可访问数据强制执行的安全边界。它们在给定的可注册域中流过源,它们忽略端口和方案,这意味着它们可以被网络攻击者轻易伪造,并且它们可以缩小到特定路径,这些特征使得它们难以推理,并制定激励措施来削弱平台其它部分的同源策略。

(1)概述:JWT就是一种Token的编码算法,服务器端负责根据一个密码和算法生成Token,然后发给客户端,客户端只负责后面每次请求都在HTTP header里面带上这个Token,服务器负责验证这个Token是不是合法的,有没有过期等,并可以解析出subject和claim里面的数据。

  4.1、含义

  会话固定(Session
fixation)是一种诱骗受害者使用攻击者指定的会话标识(SessionID)的攻击手段。这是攻击者获取合法会话标识的最简单的方法。(**让合法用户使用黑客预先设置的sessionID进行登录,从而是Web不再进行生成新的sessionID,从而导致黑客设置的sessionId变成了合法桥梁。)**

  会话固定也可以看成是会话劫持的一种类型,原因是会话固定的攻击的主要目的同样是获得目标用户的合法会话,不过会话固定还可以是强迫受害者使用攻击者设定的一个有效会话,以此来获得用户的敏感信息。

Mike
给出了一套新方案,他解释,用户代理可以通过为用户访问的每个安全源生成唯一的
256 位值来控制它代表用户表示的 HTTP 状态,此 Token 可以作为结构化 HTTP
请求头传递到源:

(2)相关问题:

  4.2、攻击步骤

       1、
攻击者通过某种手段重置目标用户的SessionID,然后监听用户会话状态;

       2、 目标用户携带攻击者设定的Session ID登录站点;

       3、 攻击者通过Session ID获得合法会话

澳门葡萄京官方网站 3

Web接收sessionID机制:

  早期浏览器存贮的sessionID容易暴露、使用URL来传送sessionID

  首先检查携带cookie是否含有sessionID;若没有则再检查get、post数据中是否含有,若有则使用此数据;没有才会使系统生成一个sessionID发给客户端。(经测试,get与post都不能设置sessionID【也许是被浏览器限制或者被代码本身禁止了吧,不过没关系,咱还有其他方法进行固定sessionID!】)

重置sessionID方式:

  • (一)使用客户端脚本来设置Cookie到浏览器。大多数浏览器都支持用客户端脚本来设置Cookie的,例如document.cookie=”sessionid=123″,这种方式可以采用跨站脚本攻击来达到目的。防御方式可以是设置HttpOnly属性,但有少数低版本浏览器存在漏洞,即使设置了HttpOnly,也可以重写Cookie。所以还需要加其他方式的校验,如User-Agent验证,Token校验等同样有效。

测试例子:

html页面(表单),用于跨站脚本攻击

 1 <!DOCTYPE html>
 2 <html>
 3 <head>
 4     <title>重置sessionID(一)</title>
 5     <meta charset="utf-8">
 6 </head>
 7 <body>
 8 <form action="./test2.php" method="post">
 9 name:<input type="text" name="name"><br/>
10 age:<input type="text" name="age"><br/>
11 <input type="submit" name="" value="提交">
12 </form>
13 </body>
14 </html>

接收表单 ,test2.php

 1 <?php
 2 header("content-type:text/html;charset=utf8");
 3 session_start();
 4 if(!isset($_SESSION['count']))  #自增测试
 5 {
 6     $_SESSION['count']=0;
 7 }
 8 else
 9 {
10     $_SESSION['count']++;
11 }
12 echo '$_POST数据:';
13 echo "<pre>";
14 print_r($_POST);        #不处理数据直接接收
15 echo "</pre>";
16 echo '$_SESSION数据:';
17 echo "<pre>";
18 print_r($_SESSION);
19 echo "</pre>";die;
20 ?>    

 测试:

  1、表单中插入数据,并提交。然后不断刷新test2.php

<script type='text/javascript'> document.cookie='PHPSESSID=12345' </script>

  2、接着分别查看sessionID以及count数值

澳门葡萄京官方网站 4

澳门葡萄京官方网站 5

  3、在其他浏览器中,执行相同的步骤1(保证sessionID与之前相同)和2,可以看到count初始值不是0,而是在之前的基础上增加的。

 结论:盗取sessionID成功!

 

  • (二)使用HTML的<META>标签加Set-Cookie属性。服务器可以靠在返回的HTML文档中增加<META>标签来设置Cookie。例如<meta
    http-equiv=’Set-Cookie’
    content=’PHPSESSID=22333′>,与客户端脚本相比,对<META>标签的处理目前还不能被浏览器禁止。【只要让此行代码在服务器中执行即可入侵】

 测试:

  只要让此行代码执行即可,(<meta http-equiv=’Set-Cookie’
content=’PHPSESSID=22333′>)即可。我们将它放入表单中然后提交给PHP;接着不断刷新,再换浏览器执行相同步骤。和上面的测试一样!接着查看结果

澳门葡萄京官方网站 6

 

  • (三)使用Set-Cookie的HTTP响应头部设置Cookie。攻击者可以使用一些方法在Web服务器的响应中加入Set-Cookie的HTTP响应头部。如会话收养,闯入目标服务器所在域的任一主机,或者是攻击用户的DNS服务器。

测试:(伪造浏览器执行http请求)

  1、攻击者控制的服务器(www.test88.com)

  2、www.test88.com/test99.php

  客户访问这个页面,无形之中就简介访问第三方网站,并绑定一个sessionID;攻击者就可以掌握这个sessionID进行相应的攻击了

 1 <?php
 2 header("content-type:text/html;charset=utf8");
 3 $host='www.linuxtest.com';
 4 $port=80;
 5 $a=fsockopen($host,$port);
 6 
 7 //请求行
 8 $request_data="Get /test2.php HTTP/1.1rn";
 9 //请求头
10 $request_data.="Host: www.linuxtest.comrn";
11 $request_data.="User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64; rv:10.0) Gecko/20100101 Firefox/10.0rn";
12 $request_data.="Connection: keep-alivern";
13 $request_data.="Cookie: PHPSESSID=99999rn";   #设置sessionID
14 $request_data.="rn"; //空行表示头结束
15 //发送数据
16 fwrite($a,$request_data);
17 
18 #用于测试
19 //接收数据
20 $inheader=1;
21 while(!feof($a))
22 {
23     //echo fgets($a,1024);
24     //除去请求头,只显示返回数据
25     $data=fgets($a,1024);
26     if($inheader && ($data=="n" || $data=="rn"))
27     {
28         $inheader=0;
29     }
30     if($inheader==0)
31     {
32         echo $data;
33     }
34 }
35 //关闭请求
36 fclose($a);
37 
38 ?> 

  1、用户访问这个页面,并不断刷新,然后查看页面中的count值

澳门葡萄京官方网站 7

  2、接着,模拟黑客进行攻击。

  在表单中插入数据(<script type=’text/javascript’>
document.cookie=’PHPSESSID=99999′
</script>),然后提交,并不断刷新test2.php,并观察count值。【换不换浏览器都可以,本质上讲黑客构造的http访问代码就相当于一个独立的浏览器】

  结果显示:count值是接着上面的4不断增加!此时证明攻击成功!

 

Sec-HTTP-State:token = * J6BRKagRIECKdpbDLxtlNzmjKo8MXTjyMomIwMFMonM *

1.为什么用JWT?

  4.3、防御方法

1、每当用户登陆的时候就进行重置sessionID

2、sessionID闲置过久时,进行重置sessionID

3、
大部分防止会话劫持的方法对会话固定攻击同样有效。如设置HttpOnly,关闭透明化Session
ID,User-Agent验证,Token校验等。

【多个方法结合使用】

 

此标识符或多或少类似于客户端控制的 Cookie,但有一些值得注意的区别:

JWT只通过算法实现对Token合法性的验证,不依赖数据库,Memcached的等存储系统,因此可以做到跨服务器验证,只要密钥和算法相同,不同服务器程序生成的Token可以互相验证。

5、参考文献

1.
《Session攻击手段(会话劫持/固定)及其安全防御措施》

 

(以上是自己的一些见解,若有不足或者错误的地方请各位指出)

 作者:那一叶随风 
 

 原文地址:

 声明:本博客文章为原创,只代表本人在工作学习中某一时间内总结的观点或结论。转载时请在文章页面明显位置给出原文链接

 

  • 客户端控制 Token 的值,而不是服务器。

  • Token 只能用于网络层,而不能用于 JavaScript(包括类似网络的
    JavaScript、例如 Service Workers)。

  • 用户代理每个源只生成一个 256 位 Token,并且只将 Token
    暴露给生成它的源。

  • 不会为非安全源生成或传递 Token。

  • 默认情况下,Token 将与同一站点请求一起提供。

  • Token 一直存在,直到服务器、用户或用户代理重置为止。

2.JWT Token不需要持久化在任何NoSQL中,不然背离其算法验证的初心

在些基础上,将为开发人员提供一些可通过 Sec-HTTP-State-Options HTTP
响应头触发的控制点,有如下选项:

3.在退出登录时怎样实现JWT Token失效呢?

1、某些服务器需要跨站点访问其
Token,其它服务器可能希望将交付范围缩小到同源请求,服务器可以指定任一选项:

退出登录, 只要客户端端把Token丢弃就可以了,服务器端不需要废弃Token。

Sec-HTTP-State-Options: ..., delivery=cross-site, ...

4.怎样保持客户端长时间保持登录状态?

或者:

服务器端提供刷新Token的接口, 客户端负责按一定的逻辑刷新服务器Token。

Sec-HTTP-State-Options: ..., delivery=same-origin, ...

5.服务器端是否应该从JWT中取出userid用于业务查询?

2、某些服务器希望限制 Token 的生命周期,可以允许它们设置
TTL(以秒为单位):

REST API是无状态的,意味着服务器端每次请求都是独立的,即不依赖以前请求的结果,因此也不应该依赖JWT token做业务查询, 应该在请求报文中单独加个userid 字段。

Sec-HTTP-State-Options: ..., ttl=3600, ...

为了做用户水平越权的检查,可以在业务层判断传入的userid和从JWT token中解析出的userid是否一致, 有些业务可能会允许查不同用户的数据。


时间到期后,Token 的值将自动重置。同时服务器也可能希望明确触发 Token
的重置行为(例如,在注销时),这可以通过设置 TTL 为 0 来实现:

(3)其他几大认证机制:

Sec-HTTP-State-Options: ..., ttl=0, ...

1.HTTP Basic Auth

在任何一种情况下,都可以向当前运行的页面通知用户的状态变化,以便执行清理操作。当发生重置时,用户代理可以将消息发送到名为
http-state-reset 的源的 BroadcastChannel(并且可能唤醒源的 Service
Worker 以响应用户驱动的重置):

HTTP Basic Auth简单点说明就是每次请求API时都提供用户的username和password,简言之,Basic Auth是配合RESTful API 使用的最简单的认证方式,只需提供用户名密码即可,但由于有把用户名密码暴露给第三方客户端的风险,在生产环境下被使用的越来越少。因此,在开发对外开放的RESTful API时,尽量避免采用HTTP Basic Auth
let resetChannel = new BroadcastChannel('http-state-reset'));
resetChannel.onmessage = e => { /* Do exciting cleanup here. */ };

2.OAuth(开放授权)

3、对于某些服务器,客户端生成的 Token
足以维持状态,它们可以将其视为不透明的会话标识符,并将用户的状态绑定到服务器端。其它服务器需要额外的保证,他们可以信任
Token
的出处,为此,服务器可以生成唯一密钥,将其与服务器上的会话标识符相关联,并通过
HTTP 响应头将其传递给客户端:

是一个开放的授权标准,允许用户让第三方应用访问该用户在某一web服务上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用。
Sec-HTTP-State-Options: ..., key=*ZH0GxtBMWA...nJudhZ8dtz*, ...
OAuth允许用户提供一个令牌,而不是用户名和密码来访问他们存放在特定服务提供者的数据。每一个令牌授权一个特定的第三方系统(例如,视频编辑网站)在特定的时段(例如,接下来的2小时内)内访问特定的资源(例如仅仅是某一相册中的视频)。这样,OAuth让用户可以授权第三方网站访问他们存储在另外服务提供者的某些特定信息,而非所有内容。

澳门葡萄京官方网站 8

这里写图片描述

客户端将存储该密钥,并使用它来生成某些数据集的签名,从而降低 Token
被捕获的风险:

这种基于OAuth的认证机制适用于个人消费者类的互联网产品,如社交类APP等应用,但是不太适合拥有自有认证权限管理的企业应用;

Sec-HTTP-State: token=*J6BRKa...MonM*, sig=*(HMAC-SHA265(key, token+metadata))*

3.Cookie Auth

Mike 同时也表示,该方案并不是一个完全与 Cookie
不同的新东西,并不是要在目前替换掉 Cookie,虽然弃用 Cookie
是应该的,但是当下该方案只是提出了一种在 Cookie
同时存在的情况下也能发挥作用的补充机制。

Cookie认证机制就是为一次请求认证在服务端创建一个Session对象,同时在客户端的浏览器端创建了一个Cookie对象;通过客户端带上来Cookie对象来与服务器端的session对象匹配来实现状态管理的。默认的,当我们关闭浏览器的时候,cookie会被删除。但可以通过修改cookie 的expire time使cookie在一定时间内有效;

(文/开源中国)    

二、cookie和session机制:

(1)概述:

Cookie和Session是为了在无状态的HTTP协议之上维护会话状态,使得服务器可以知道当前是和哪个客户在打交道。

Session是在服务端保存的一个数据结构,用来跟踪用户的状态,这个数据可以保存在集群、数据库、文件中;

Cookie是客户端保存用户信息的一种机制,用来记录用户的一些信息,也是实现Session的一种方式。

因为HTTP协议是无状态的,即每次用户请求到达服务器时,HTTP服务器并不知道这个用户是谁、是否登录过等。现在的服务器之所以知道我们是否已经登录,是因为服务器在登录时设置了浏览器的Cookie!Session则是借由Cookie而实现的更高层的服务器与浏览器之间的会话。

(2)cookie实现机制:

Cookie是由客户端保存的小型文本文件,其内容为一系列的键值对。 Cookie是由HTTP服务器设置的,保存在浏览器中, 在用户访问其他页面时,会在HTTP请求中附上该服务器之前设置的Cookie。

Cookie的传递流程

1.浏览器向某个URL发起HTTP请求(可以是任何请求,比如GET一个页面、POST一个登录表单等)
2.对应的服务器收到该HTTP请求,并计算应当返回给浏览器的HTTP响应。(HTTP响应包括请求头和请求体两部分)
3.在响应头加入Set-Cookie字段,它的值是要设置的Cookie。
4.浏览器收到来自服务器的HTTP响应。
5.浏览器在响应头中发现Set-Cookie字段,就会将该字段的值保存在内存或者硬盘中。(Set-Cookie字段的值可以是很多项Cookie,每一项都可以指定过期时间Expires。 默认的过期时间是用户关闭浏览器时。)
6.浏览器下次给该服务器发送HTTP请求时, 会将服务器设置的Cookie附加在HTTP请求的头字段Cookie中。(浏览器可以存储多个域名下的Cookie,但只发送当前请求的域名曾经指定的Cookie, 这个域名也可以在Set-Cookie字段中指定)。)
7.服务器收到这个HTTP请求,发现请求头中有Cookie字段, 便知道之前就和这个用户打过交道了.
8.过期的Cookie会被浏览器删除。

总之,服务器通过Set-Cookie响应头字段来指示浏览器保存Cookie, 浏览器通过Cookie请求头字段来告诉服务器之前的状态。 Cookie中包含若干个键值对,每个键值对可以设置过期时间。

Cookie 的安全隐患:

Cookie提供了一种手段使得HTTP请求可以附加当前状态, 现今的网站也是靠Cookie来标识用户的登录状态的:

1.用户提交用户名和密码的表单,这通常是一个POST HTTP请求。
2.服务器验证用户名与密码,如果合法则返回200(OK)并设置Set-Cookie为authed=true。
3.浏览器存储该Cookie。
4.浏览器发送请求时,设置Cookie字段为authed=true。
5.服务器收到第二次请求,从Cookie字段得知该用户已经登录。 按照已登录用户的权限来处理此次请求。

问题是什么??风险是什么??

我们知道可以发送HTTP请求的不只是浏览器,很多HTTP客户端软件(包括curl、Node.js)都可以发送任意的HTTP请求,可以设置任何头字段。 假如我们直接设置Cookie字段为authed=true并发送该HTTP请求, 服务器岂不是被欺骗了?这种攻击非常容易,Cookie是可以被篡改的

Cookie 防篡改机制

服务器可以为每个Cookie项生成签名,由于用户篡改Cookie后无法生成对应的签名, 服务器便可以得知用户对Cookie进行了篡改。

例子:一个简单的校验过程:

1.在服务器中配置一个不为人知的字符串(我们叫它Secret),比如:x$sfz32。
2.当服务器需要设置Cookie时(比如authed=false),不仅设置authed的值为false, 在值的后面进一步设置一个签名,最终设置的Cookie是authed=false|6hTiBl7lVpd1P。
3.签名6hTiBl7lVpd1P是这样生成的:Hash(‘x$sfz32’+’true’)。 要设置的值与Secret相加再取哈希。
4.用户收到HTTP响应并发现头字段Set-Cookie: authed=false|6hTiBl7lVpd1P。
5.用户在发送HTTP请求时,篡改了authed值,设置头字段Cookie: authed=true|???。 因为用户不知道Secret,无法生成签名,只能随便填一个。
6.服务器收到HTTP请求,发现Cookie: authed=true|???。服务器开始进行校验: Hash(‘true’+’x$sfz32’),便会发现用户提供的签名不正确。

通过给Cookie添加签名,使得服务器得以知道Cookie被篡改。但是!!还是有风险!

因为Cookie是明文传输的, 只要服务器设置过一次authed=true|xxxx我不就知道true的签名是xxxx了么, 以后就可以用这个签名来欺骗服务器了。因此Cookie中最好不要放敏感数据。 一般来讲Cookie中只会放一个Session Id,而Session存储在服务器端。

(3)session的实现机制:

1.概述:Session 是存储在服务器端的,避免了在客户端Cookie中存储敏感数据。 Session 可以存储在HTTP服务器的内存中,也可以存在内存数据库(如redis)中, 对于重量级的应用甚至可以存储在数据库中。

例子:存储在redis中的Session为例,考察如何验证用户登录状态的问题。

1.用户提交包含用户名和密码的表单,发送HTTP请求。

2.服务器验证用户发来的用户名密码。

3.如果正确则把当前用户名(通常是用户对象)存储到redis中,并生成它在redis中的ID。

这个ID称为Session ID,通过Session ID可以从Redis中取出对应的用户对象, 敏感数据(比如authed=true)都存储在这个用户对象中。

4.设置Cookie为sessionId=xxxxxx|checksum并发送HTTP响应, 仍然为每一项Cookie都设置签名。

5.用户收到HTTP响应后,便看不到任何敏感数据了。在此后的请求中发送该Cookie给服务器。

6.服务器收到此后的HTTP请求后,发现Cookie中有SessionID,进行放篡改验证。

7.如果通过了验证,根据该ID从Redis中取出对应的用户对象, 查看该对象的状态并继续执行业务逻辑。

实现上述过程,在Web应用中可以直接获得当前用户。 相当于在HTTP协议之上,通过Cookie实现了持久的会话。这个会话便称为Session。


三、Token认证机制相对于Cookie等机制的好处:

1. 支持跨域访问: Cookie是不允许垮域访问的,这一点对Token机制是不存在的,前提是传输的用户认证信息通过HTTP头传输。(垮域访问:两个域名之间不能跨过域名来发送请求或者请求数据)

2.无状态(也称:服务端可扩展行):Token机制在服务端不需要存储session信息,因为Token 自身包含了所有登录用户的信息,只需要在客户端的cookie或本地介质存储状态信息.

3.更适用CDN: 可以通过内容分发网络请求你服务端的所有资料(如:javascript,HTML,图片等),而你的服务端只要提供API即可.

4.去耦: 不需要绑定到一个特定的身份验证方案。Token可以在任何地方生成,只要在你的API被调用的时候,你可以进行Token生成调用即可.

5.更适用于移动应用: 当你的客户端是一个原生平台(iOS, Android,Windows 8等)时,Cookie是不被支持的(你需要通过Cookie容器进行处理),这时采用Token认证机制就会简单得多。

6. CSRF:因为不再依赖于Cookie,所以你就不需要考虑对CSRF(跨站请求伪造)的防范。

7.性能: 一次网络往返时间(通过数据库查询session信息)总比做一次HMACSHA256计算 的Token验证和解析要费时得多.

8.不需要为登录页面做特殊处理: 如果你使用Protractor 做功能测试的时候,不再需要为登录页面做特殊处理.

9.基于标准化:你的API可以采用标准化的 JSON Web Token (JWT). 这个标准已经存在多个后端库(.NET, Ruby, Java,Python, PHP)和多家公司的支持(如:Firebase,Google, Microsoft).


四、JWT的Java实现:

概述:一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。这里我们只使用简单的载荷,并将JSON对象进行base64编码得到token

过程:登录为例子

第一次认证:第一次登录,用户从浏览器输入用户名/密码,提交后到服务器的登录处理的Action层(controller)
Login Action调用认证服务进行用户名密码认证,如果认证通过,Login Action层调用用户信息服务获取用户信息(包括完整的用户信息及对应权限信息);
返回用户信息后,Login Action从配置文件再经过工具类处理获取Token签名生成的秘钥信息,进行Token的生成
生成Token的过程中可以调用第三方的JWT Lib生成签名后的JWT数据;
完成JWT数据签名后,将其设置到COOKIE对象中,并重定向到首页,完成登录过程;

请求认证

使用:基于Token的认证机制会在每一次请求中都带上完成签名的Token信息,这个Token信息可能在COOKIE中,也可能在HTTP的Authorization头中;

注意:

客户端(APP客户端或浏览器)通过GET或POST请求访问资源(页面或调用API);
认证服务作为一个Middleware HOOK 对请求进行拦截,首先在cookie中查找Token信息,如果没有找到,则在HTTP Authorization Head中查找;
如果找到Token信息,则根据配置文件中的签名加密秘钥,调用JWT Lib对Token信息进行解密和解码;
完成解码并验证签名通过后,对Token中的exp、nbf、aud等信息进行验证;
全部通过后,根据获取的用户的角色权限信息,进行对请求的资源的权限逻辑判断;
如果权限逻辑判断通过则通过Response对象返回;否则则返回HTTP 401;

(1)使用JWT的包:maven导入

 <!--JSON WEB TOKEN -->
    <dependency>
      <groupId>io.jsonwebtoken</groupId>
      <artifactId>jjwt</artifactId>
      <version>0.6.0</version>
    </dependency>

(2)一个生成token的工具类:

public class JavaWebToken {

    private static Logger log = Logger.getLogger(JavaWebToken.class);

    private static Key getKeyInstance() {
//        return MacProvider.generateKey();
        //We will sign our JavaWebToken with our ApiKey secret
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary("APP");
        Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());
        return signingKey;
    }

    public static String createJavaWebToken(Map<String, Object> claims) {
        return Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS256, getKeyInstance()).compact();
    }

    public static Map<String, Object> verifyJavaWebToken(String jwt) {
        try {

            Map<String, Object> jwtClaims =
                    Jwts.parser().setSigningKey(getKeyInstance()).parseClaimsJws(jwt).getBody();
            return jwtClaims;
        } catch (Exception e) {
            log.error("json web token verify failed");
            return null;
        }
    }

}

(3)一个从request拿去session,并且解密session得到token得到用户id的类

public class AuthUtil {
    private static Map<String, Object> getClientLoginInfo(HttpServletRequest request) throws Exception {
        Map<String, Object> r = new HashMap<>();
        String sessionId = request.getHeader("sessionId");
        if (sessionId != null) {
            r = decodeSession(sessionId);
            return r;
        }
        throw new Exception("session解析错误");
    }

    public static Long getUserId(HttpServletRequest request) throws Exception {
        return  Long.valueOf((Integer)getClientLoginInfo(request).get("userId"));

    }

    /**
     * session解密
     */
    public static Map<String, Object> decodeSession(String sessionId) {
        try {
            return verifyJavaWebToken(sessionId);
        } catch (Exception e) {
            System.err.println("");
            return null;
        }
    }
}

使用例子:

登录的时候把信息放进session,存到map里,再交由JWT得到token保存起来

澳门葡萄京官方网站 9

这里写图片描述

//登录
    @RequestMapping(value = "/login", method = {RequestMethod.GET, RequestMethod.POST}, produces = "text/html;charset=UTF-8")
    public String login(String account) {
        User user = userService.login(account);

        DTO dto = new DTO();
        if (user == null) {
            dto.code = "-1";
            dto.msg = "Have not registered";
        } else {
            //把用户登录信息放进Session
            Map<String, Object> loginInfo = new HashMap<>();
            loginInfo.put("userId", user.getId());
            String sessionId = JavaWebToken.createJavaWebToken(loginInfo);
            System.out.println("sessionID"+sessionId);
            dto.data = sessionId;
        }
        return JSON.toJSONString(dto);
    }

用户登录以后,其他的用户性知道的操作就可以使用token进行了,安全快捷方便:

澳门葡萄京官方网站 10

这里写图片描述

//修改昵称
    @RequestMapping(value = "/updateName", method = {RequestMethod.GET, RequestMethod.POST})
    public String updateName(HttpServletRequest request, String name) {
        DTO dto = new DTO();
        try {
        //从session拿到token,再解密得到userid
            Long userId = AuthUtil.getUserId(request);
            boolean userIsExist = userService.updateName(userId, name);
            if (userIsExist == false) {
                dto.code = "-1";
                dto.msg = "Have not updateAvatar";
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return JSON.toJSONString(dto);
    }

这就是token机制的登录认证功能简单实现了,安全机制等,以后会有博客补充。


源码下载:WEB后台–基于Token的WEB后台登录认证机制(并讲解其他认证机制以及cookie和session机制)

好了,WEB后台–基于Token的WEB后台登录认证机制(并讲解其他认证机制以及cookie和session机制)讲完了。本博客是这个系列的第四篇,讲的是一个简单的登录机制实现。另外,这个系列还有一些我在外包项目过程中做的优化,虽然核心算法跟安全处理不可以公布,但我会尽快出完给大家,分享经验给大家。欢迎在下面指出错误,共同学习!!你的点赞是对我最好的支持!!

更多内容,可以访问JackFrost的博客