基于HTTP 协议认证介绍与实现

原创
2013/06/15 10:29
阅读数 95

<h2 id="menuIndex0">导言</h2> <p>一直对http 的头认证有兴趣,就是路由器的那种弹出对话框输入账号密码怎么实现一直不明白,最近,翻了一下http 协议,发现这是一个RFC 2617的实现,所以写篇文章介绍一下吧. <a name="more"></a></p> <h3 id="menuIndex1">Http基本认证</h3> <p>这是一个用于web浏览器或其他客户端在请求时提供用户名和密码的登录认证,要实现这个认证很简单:</p> <p>我们先来看下协议里面怎么定义这个认证的. 1. 编码: 将用户名 追加一个 冒号(':')接上密码,把得出的结果字符串在用Base64算法编码.</p> <ol> <li>请求头: Authorization: 认证类型 编码字符串 </li> </ol> <p>来看一下客户端如何发起请求例如,有一个用户名为:tom, 密码为:123456 怎么认证呢?</p> <p>步骤如下 1. 编码 </p> <blockquote> <p>Base64('tom:123456') == dG9tOjEyMzQ1Ng==;</p> </blockquote> <ol> <li>把编码结果放到请求头当中 <blockquote> <p>Authorization: Basic dG9tOjEyMzQ1Ng==</p> </blockquote> </li> </ol> <p>请求样例客户端</p> <p></p> <figure class="highlight lang-shell"> <table><tbody> <tr> <td class="gutter"> <pre>1 2 3</pre> </td>

  <td class="code">
    <pre><span class="request">GET <span class="string">/</span> HTTP/1.1</span>

<span class="attribute">Host</span>: <span class="string">localhost</span> <span class="attribute">Authorization</span>: <span class="string">Basic dG9tOjEyMzQ1Ng</span></pre> </td> </tr>

</tbody></table> </figure>服务端应答

<p></p>

<p></p> <figure class="highlight lang-shell">

<table><tbody> <tr> <td class="gutter"> <pre>1 2 3 4</pre> </td>

  <td class="code">
    <pre><span class="status">HTTP/1.1 <span class="number">200</span> OK</span>

<span class="attribute">Date</span>: <span class="string">Thu, 13 Jun 2013 20:25:37 GMT</span> <span class="attribute">Content-Type</span>: <span class="string">application/json; charset=utf-8</span> <span class="attribute">Content-Length</span>: <span class="string">53</span></pre> </td> </tr>

</tbody></table> </figure>如果没有认证信息

<p></p>

<p></p> <figure class="highlight lang-shell">

<table><tbody> <tr> <td class="gutter"> <pre>1 2 3</pre> </td>

  <td class="code">
    <pre><span class="status">HTTP/1.1 <span class="number">401</span> Authorization Required</span>

<span class="attribute">Date</span>: <span class="string">Thu, 13 Jun 2013 20:25:37 GMT</span> <span class="attribute">WWW-Authenticate</span>: <span class="string">Basic realm="Users"</span></pre> </td> </tr>

</tbody></table> </figure>验证失败的时候,响应头加上WWW-Authenticate: Basic realm=&quot;请求域&quot;.

<p></p>

<p>这种http 基本实现,几乎目前所有浏览器都支持.不过,大家可以发现,直接把用户名和密码只是进行一次base64 编码实际上是很不安全的,因为对base64进行反编码十分容易,所以这种验证虽然简便,但是很少会在公开访问的互联网使用,一般多用在小的私有系统,例如,你们家里头的路由器,多用这种认证方式.</p>

<h3 id="menuIndex2">Http 摘要认证</h3>

<p>这个认证可以看做是基本认证的增强版本,使用随机数+密码进行md5,防止通过直接的分析密码MD5防止破解. 摘要访问认证最初由 RFC 2069 (HTTP的一个扩展:摘要访问认证)中被定义加密步骤:</p>

<ol> <li> <p><a href="http://static.oschina.net/uploads/img/201306/15102904_EDym.jpg"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="ha1" border="0" alt="ha1" src="http://static.oschina.net/uploads/img/201306/15102904_g1mS.jpg" width="244" height="22" /></a> </p> </li>

<li> <p><a href="http://static.oschina.net/uploads/img/201306/15102904_F4zj.jpg"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="ha2" border="0" alt="ha2" src="http://static.oschina.net/uploads/img/201306/15102904_m5IG.jpg" width="244" height="26" /></a>&#160; </p> </li>

<li> <p><a href="http://static.oschina.net/uploads/img/201306/15102905_rbIJ.jpg"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="res" border="0" alt="res" src="http://static.oschina.net/uploads/img/201306/15102905_QxGb.jpg" width="244" height="32" /></a> </p> </li> </ol>

<p>后来发现,就算这样还是不安全(md5 可以用彩虹表进行攻击),所以在RFC 2617入了一系列安全增强的选项;“保护质量”(qop)、随机数计数器由客户端增加、以及客户生成的随机数。这些增强为了防止如选择明文攻击的密码分析。</p>

<blockquote> <p><a href="http://static.oschina.net/uploads/img/201306/15102905_hvLh.jpg"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="d1" border="0" alt="d1" src="http://static.oschina.net/uploads/img/201306/15102905_cz17.jpg" width="244" height="22" /></a> </p> </blockquote>

<ol> <li> <p>如果 qop 值为“auth”或未指定,那么 HA2 为</p>

<blockquote>
  <p><a href="http://static.oschina.net/uploads/img/201306/15102905_wgGH.jpg"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="d3" border="0" alt="d3" src="http://static.oschina.net/uploads/img/201306/15102905_zSeD.jpg" width="244" height="19" /></a> </p>
</blockquote>

</li>

<li> <p>如果 qop 值为“auth-int”,那么 HA2 为</p>

<blockquote>
  <p><a href="http://static.oschina.net/uploads/img/201306/15102905_B5bd.jpg"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="d3" border="0" alt="d3" src="http://static.oschina.net/uploads/img/201306/15102905_yrIu.jpg" width="244" height="19" /></a> </p>
</blockquote>

</li>

<li> <p>如果 qop 值为“auth”或“auth-int”,那么如下计算 response:</p>

<blockquote>
  <p><a href="http://static.oschina.net/uploads/img/201306/15102905_ck2m.jpg"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="d4" border="0" alt="d4" src="http://static.oschina.net/uploads/img/201306/15102905_9v3K.jpg" width="244" height="19" /></a> </p>
</blockquote>

</li>

<li> <p>如果 qop 未指定,那么如下计算 response:</p>

<blockquote>
  <p><a href="http://static.oschina.net/uploads/img/201306/15102905_zuGO.jpg"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="d5" border="0" alt="d5" src="http://static.oschina.net/uploads/img/201306/15102905_ZxIp.jpg" width="244" height="32" /></a> </p>
</blockquote>

</li> </ol>

<p>好了,知道加密步骤,下面我们用文字来描述一下;</p>

<p>最后,我们的response 由三步计算所得. 1. 对用户名、认证域(realm)以及密码的合并值计算 MD5 哈希值,结果称为 HA1。</p>

<blockquote> <p>HA1 = MD5( &quot;tom:Hi!:123456&quot; ) = d8ae91c6c50fabdac442ef8d6a68ae8c</p> </blockquote>

<ol> <li> <p>对HTTP方法以及URI的摘要的合并值计算 MD5 哈希值,例如,&quot;GET&quot; 和 &quot;/index.html&quot;,结果称为 HA2。</p>

<blockquote>
  <p>HA2 = MD5( &quot;GET:/&quot; ) = 71998c64aea37ae77020c49c00f73fa8</p>
</blockquote>

</li>

<li> <p>最后生成的响应码</p>

<blockquote>
  <p>Response = MD5(&quot;d8ae91c6c50fabdac442ef8d6a68ae8c:L4qfzASytyQJAC2B1Lvy2llPpj9R8Jd3:00000001:c2dc5b32ad69187a 
    <br />:auth:71998c64aea37ae77020c49c00f73fa8&quot;) = 2f22e6d56dabb168702b8bb2d4e72453;</p>
</blockquote>

</li> </ol>

<p>RFC2617 的安全增强的主要方式:</p>

<p>发起请求的时候,服务器会生成一个密码随机数(nonce)(而这个随机数只有每次&quot;401&quot;相应后才会更新),为了防止攻击者可以简单的使用同样的认证信息发起老的请求,于是,在后续的请求中就有一个随机数计数器(cnonce),而且每次请求必须必前一次使用的打.这样,服务器每次生成新的随机数都会记录下来,计数器增加.在RESPONSE 码中我们可以看出计数器的值会导致不同的值,这样就可以拒绝掉任何错误的请求.</p>

<p>请求样例(服务端 qop 设置为&quot;auth&quot;)</p>

<p>客户端 无认证</p>

<p></p> <figure class="highlight lang-shell">

<table><tbody> <tr> <td class="gutter"> <pre>1 2</pre> </td>

  <td class="code">
    <pre><span class="request">GET <span class="string">/</span> HTTP/1.1</span>

<span class="attribute">Host</span>: <span class="string">localhost</span></pre> </td> </tr>

</tbody></table> </figure>服务器响应(qop 为 'auth')

<p></p>

<p></p> <figure class="highlight lang-shell">

<table><tbody> <tr> <td class="gutter"> <pre>1 2 3</pre> </td>

  <td class="code">
    <pre><span class="status">HTTP/1.1 <span class="number">401</span> Authorization Required</span>

<span class="attribute">Date</span>: <span class="string">Thu, 13 Jun 2013 20:25:37 GMT</span> <span class="attribute">WWW-Authenticate</span>: <span class="string">Digest realm="Hi!", nonce="HSfb5dy15hKejXAbZ2VXjVbgNC8sC1Gq", qop="auth"</span></pre> </td> </tr>

</tbody></table> </figure>客户端请求(用户名: &quot;tom&quot;, 密码 &quot;123456&quot;)

<p></p>

<p></p> <figure class="highlight lang-shell">

<table><tbody> <tr> <td class="gutter"> <pre>1 2 3 4 5 6 7 8 9</pre> </td>

  <td class="code">
    <pre>GET / HTTP/<span class="number">1.1</span>

<span class="label">Host:</span> localhost <span class="label">Authorization:</span> Digest username=<span class="string">"tom"</span>, realm=<span class="string">"Hi!"</span>, nonce=<span class="string">"L4qfzASytyQJAC2B1Lvy2llPpj9R8Jd3"</span>, uri=<span class="string">"/"</span>, qop=auth, nc=<span class="number">00000001</span>, cnonce=<span class="string">"c2dc5b32ad69187a"</span>, response=<span class="string">"2f22e6d56dabb168702b8bb2d4e72453"</span></pre> </td> </tr>

</tbody></table> </figure>服务端应答

<p></p>

<p></p> <figure class="highlight lang-shell">

<table><tbody> <tr> <td class="gutter"> <pre>1 2 3 4</pre> </td>

  <td class="code">
    <pre><span class="status">HTTP/1.1 <span class="number">200</span> OK</span>

<span class="attribute">Date</span>: <span class="string">Thu, 13 Jun 2013 20:25:37 GMT</span> <span class="attribute">Content-Type</span>: <span class="string">application/json; charset=utf-8</span> <span class="attribute">Content-Length</span>: <span class="string">53</span></pre> </td> </tr>

</tbody></table> </figure>注意qop 设置的时候慎用:auth-int,因为一些常用浏览器和服务端并没有实现这个协议.

展开阅读全文
打赏
0
1 收藏
分享
加载中
更多评论
打赏
0 评论
1 收藏
0
分享
返回顶部
顶部