赵永娜 發表於 2019-9-15 09:24:00

php firebase/php-jwt token验证

<p>一:JWT介绍:全称JSON Web Token,基于JSON的开放标准((RFC 7519)&nbsp;,以token的方式代替传统的Cookie-Session模式,用于各服务器、客户端传递信息签名验证。</p>
<p>&nbsp;</p>
<p>二:JWT优点:</p>
<p>1:服务端不需要保存传统会话信息,没有跨域传输问题,减小服务器开销。</p>
<p>2:jwt构成简单,占用很少的字节,便于传输。</p>
<p>3:json格式通用,不同语言之间都可以使用。</p>
<p>&nbsp;</p>
<p>三:JWT组成</p>
<p>1:jwt由三部分组成:</p>
<p>&nbsp; &nbsp; &nbsp;头部(header)<br>&nbsp; &nbsp; &nbsp;载荷(payload) 包含一些定义信息和自定义信息<br>&nbsp; &nbsp; &nbsp;签证(signature)</p>
<p>2:具体构成:</p>
<p>header:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">{
"typ": "JWT", //声明类型为jwt
"alg": "HS256" //声明签名算法为SHA256
}</span></pre>
</div>
<p>载荷(payload)</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">{
"iss": "http://www.helloweba.net",
"aud": "http://www.helloweba.net",
"iat": 1525317601,
"nbf": 1525318201,
"exp": 1525318201,
"data": {
    "userid": 1,
    "username": "李小龙"
}
}</span></pre>
</div>
<p>载荷包括两部分:标准声明和其他声明。</p>
<p>标准声明:JWT标准规定的声明,但不是必须填写的;</p>
<p>标准声明字段:</p>
<p>接收该JWT的一方</p>
<p>iss:&nbsp;jwt签发者</p>
<p>sub:&nbsp;jwt所面向的用户</p>
<p>aud:&nbsp;接收jwt的一方</p>
<p>exp:&nbsp;jwt的过期时间,过期时间必须要大于签发时间</p>
<p>nbf:&nbsp;定义在什么时间之前,某个时间点后才能访问</p>
<p>iat:&nbsp;jwt的签发时间</p>
<p>jti:&nbsp;jwt的唯一身份标识,主要用来作为一次性token。</p>
<p>其他声明:自己定义的字段,因为这部分是可以解开的,建议不要加入敏感信息,这里的data就是我自己定义的声明</p>
<p>&nbsp;</p>
<p>最终是这样的:<br><img src="https://img2018.cnblogs.com/blog/1251386/201909/1251386-20190915091903750-751010611.png"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>三部分分别以逗号隔开</p>
<p>四:使用:PHP有很多jwt包,包括比如:lcobucci/jwt,我这里使用firebase。可以从&nbsp;git地址&nbsp;下载</p>
<p>1:把代码拉取下来</p>
<div class="cnblogs_code">
<pre>composer <span style="color: rgba(0, 0, 255, 1)">require</span> firebase/php-jwt</pre>
</div>
<p>2:服务端签发Token</p>
<div class="cnblogs_code">
<pre>?<span style="color: rgba(0, 0, 0, 1)">php
</span><span style="color: rgba(0, 0, 255, 1)">use</span> \Firebase\JWT\JWT; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">导入JWT</span>
<span style="color: rgba(0, 0, 255, 1)">class</span> MainController <span style="color: rgba(0, 0, 255, 1)">extends</span><span style="color: rgba(0, 0, 0, 1)"> Controller
{

    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">签发Token</span>
    <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> lssue()
    {
      </span><span style="color: rgba(128, 0, 128, 1)">$key</span> = '344'; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">key</span>
      <span style="color: rgba(128, 0, 128, 1)">$time</span> = <span style="color: rgba(0, 128, 128, 1)">time</span>(); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">当前时间</span>
               <span style="color: rgba(128, 0, 128, 1)">$token</span> =<span style="color: rgba(0, 0, 0, 1)"> [
            </span>'iss' =&gt; 'http://www.helloweba.net', <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">签发者 可选</span>
               'aud' =&gt; 'http://www.helloweba.net', <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">接收该JWT的一方,可选</span>
               'iat' =&gt; <span style="color: rgba(128, 0, 128, 1)">$time</span>, <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">签发时间</span>
               'nbf' =&gt; <span style="color: rgba(128, 0, 128, 1)">$time</span> , <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">(Not Before):某个时间点后才能访问,比如设置time+30,表示当前时间30秒后才能使用</span>
               'exp' =&gt; <span style="color: rgba(128, 0, 128, 1)">$time</span>+7200, <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">过期时间,这里设置2个小时</span>
                'data' =&gt; [ <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">自定义信息,不要定义敏感信息</span>
                     'userid' =&gt; 1,
                     'username' =&gt; '李小龙'<span style="color: rgba(0, 0, 0, 1)">
            ]
      ];
      </span><span style="color: rgba(0, 0, 255, 1)">echo</span> JWT::encode(<span style="color: rgba(128, 0, 128, 1)">$token</span>, <span style="color: rgba(128, 0, 128, 1)">$key</span>); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">输出Token</span>
<span style="color: rgba(0, 0, 0, 1)">    }
}
————————————————</span></pre>
</div>
<p>3:解析Token,为了演示,这里直接写</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">&lt;?php
use \Firebase\JWT\JWT; //导入JWT
class MainController extends Controller
{

    public function verification()
    {
      $key = '344'; //key要和签发的时候一样

      $jwt = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC93d3cuaGVsbG93ZWJhLm5ldCIsImF1ZCI6Imh0dHA6XC9cL3d3dy5oZWxsb3dlYmEubmV0IiwiaWF0IjoxNTI1MzQwMzE3LCJuYmYiOjE1MjUzNDAzMTcsImV4cCI6MTUyNTM0NzUxNywiZGF0YSI6eyJ1c2VyaWQiOjEsInVzZXJuYW1lIjoiXHU2NzRlXHU1YzBmXHU5Zjk5In19.Ukd7trwYMoQmahOAtvNynSA511mseA2ihejoZs7dxt0"; //签发的Token
      try {
                   JWT::$leeway = 60;//当前时间减去60,把时间留点余地
                   $decoded = JWT::decode($jwt, $key, ['HS256']); //HS256方式,这里要和签发的时候对应
                   $arr = (array)$decoded;
                   print_r($arr);
            } catch(\Firebase\JWT\SignatureInvalidException $e) {//签名不正确
                echo $e-&gt;getMessage();
            }catch(\Firebase\JWT\BeforeValidException $e) {// 签名在某个时间点之后才能用
                echo $e-&gt;getMessage();
            }catch(\Firebase\JWT\ExpiredException $e) {// token过期
                echo $e-&gt;getMessage();
         }catch(Exception $e) {//其他错误
                echo $e-&gt;getMessage();
            }
      //Firebase定义了多个 throw new,我们可以捕获多个catch来定义问题,catch加入自己的业务,比如token过期可以用当前Token刷新一个新Token
    }
}</span></pre>
</div>
<p>在src目录下的JWT.php中的方法&nbsp;decode中,可以看到作者是通过多个 自定义&nbsp;throw new 抛出异常的</p>
<p><img src="https://img2018.cnblogs.com/blog/1251386/201909/1251386-20190915092035347-1865060644.png"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>所以我们可以根据不同的throw new设置多个catch来捕获。</p>
<p>输出的数据:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">Array
(
    =&gt; http://www.helloweba.net
    =&gt; http://www.helloweba.net
    =&gt; 1525340317
    =&gt; 1525340317
    =&gt; 1525347517
    =&gt; stdClass Object
      (
             =&gt; 1
             =&gt; 李小龙
      )

)</span></pre>
</div>
<p>五:模拟实战</p>
<p>1:方案:客户端通过用户名密码登录以后,服务端返回给客户端两个token:access_token和refresh_token。</p>
<p>access_token:请求接口的token</p>
<p>refresh_token:刷新access_token</p>
<p>举个例子:比如access_token设置2个小时过期,refresh_token设置7天过期,2小时候后,access_token过期,但是refresh_token还在7天以内,那么客户端通过refresh_token来服务端刷新,服务端重新生成一个access_token;如果refresh_token也超过了7天,那么客户端需要重新登录获取access_token和refresh_token。</p>
<p>为了区分两个token,我们在载荷(payload)加一个字段&nbsp;scopes :作用域。</p>
<p>access_token中设置:scopes:role_access</p>
<p>refresh_token中设置:scopes:role_refresh<span style="color: rgba(255, 0, 0, 1)"><br></span></p>
<p><span style="color: rgba(255, 0, 0, 1)">有些人是用一个token,要么设置很长时间过期;要么设置几个小时过期,如果过期,用过期的token去换取新的token,这种其实是有问题的,有些人说token有两个时间,一个是过期时间,一个是刷新时间,只要在刷新时间内就可以换取新token。假如别人拿到了你这token,如果也在过期时间之内,不是同样可以刷新token?除非两个token都拿到,相对来说第二种更安全。</span><br><br></p>
<p><span style="color: rgba(0, 0, 0, 1)">直接上代码:</span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">&lt;?php
use \Firebase\JWT\JWT; //导入JWT
class MainController extends Controller
{

    public function authorizations()
    {
      $key = 'ffdsfsd@4_45'; //key
      $time = time(); //当前时间

      //公用信息
      $token = [
            'iss' =&gt; 'http://www.helloweba.net', //签发者 可选
            'iat' =&gt; $time, //签发时间
            'data' =&gt; [ //自定义信息,不要定义敏感信息
               'userid' =&gt; 1,
            ]
      ];

      $access_token = $token;
      $access_token['scopes'] = 'role_access'; //token标识,请求接口的token
      $access_token['exp'] = $time+7200; //access_token过期时间,这里设置2个小时

      $refresh_token = $token;
      $refresh_token['scopes'] = 'role_refresh'; //token标识,刷新access_token
      $refresh_token['exp'] = $time+(86400 * 30); //access_token过期时间,这里设置30天

      $jsonList = [
            'access_token'=&gt;JWT::encode($access_token,$key),
            'refresh_token'=&gt;JWT::encode($refresh_token,$key),
            'token_type'=&gt;'bearer' //token_type:表示令牌类型,该值大小写不敏感,这里用bearer
      ];
                Header("HTTP/1.1 201 Created");
      echo json_encode($jsonList); //返回给客户端token信息
    }
}</span></pre>
</div>
<p>看起来输出是这样的:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">{
    "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC93d3cuaGVsbG93ZWJhLm5ldCIsImlhdCI6MTUyNTQxNzg5NywiZGF0YSI6eyJ1c2VyaWQiOjF9LCJzY29wZXMiOiJyb2xlX2FjY2VzcyIsImV4cCI6MTUyNTQyNTA5N30.Nxp1yutwt8Fxj5XEzes4j-X4tCBQwE0htEV3Msm2D8s",
    "refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC93d3cuaGVsbG93ZWJhLm5ldCIsImlhdCI6MTUyNTQxNzg5NywiZGF0YSI6eyJ1c2VyaWQiOjF9LCJzY29wZXMiOiJyb2xlX3JlZnJlc2giLCJleHAiOjE1MjgwMDk4OTd9.YY8Lid3nk3bnV-ZnAYneKJxGiaD73waqTpC3bHz3wsY",
    "token_type": "bearer"
}</span></pre>
</div>
<p>http头部响应是这样的:两个token客户端保存,一个用来取接口,一个用来刷新接口。</p>
<p><img src="https://img2018.cnblogs.com/blog/1251386/201909/1251386-20190915092300990-555368323.png"></p>
<p>&nbsp;</p>
<p>&nbsp;客户端请求的时候在http头部携带&nbsp;Authorization: bearer token,注意bearer后面有个空格,我们用PHP模拟一下</p>
<p><span style="color: rgba(255, 0, 0, 1)"><img src="https://img2018.cnblogs.com/blog/1251386/201909/1251386-20190915092313489-1904605800.png"></span></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>请求信息是这样的,PHP获取Authorization信息判断。</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>这个只是一种思路,大家可以按照自己的思路去弄,这种思路服务端不用保存token,但如果涉及到token的注销就必须保存,比如token没过期,但是要注销token,这时候可以上redis。</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>最后请务必使用HTTPS</p>
<p><img src="https://img2018.cnblogs.com/blog/1251386/201909/1251386-20190915092405580-1064446505.png"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>------------------------------------------------------------------------------------------------------------------------------------------</p>
<p>1 安装</p>
<div class="cnblogs_code">
<pre>composer <span style="color: rgba(0, 0, 255, 1)">require</span> firebase/php-jwt</pre>
</div>
<p>2 用法</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">se \Firebase\JWT\JWT;
</span><span style="color: rgba(128, 0, 128, 1)">$key</span> = "example_key"<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(128, 0, 128, 1)">$token</span> = <span style="color: rgba(0, 0, 255, 1)">array</span><span style="color: rgba(0, 0, 0, 1)">(
    </span>"iss" =&gt; "http://example.org",
    "aud" =&gt; "http://example.com",
    "iat" =&gt; 1356999524,
    "nbf" =&gt; 1357000000<span style="color: rgba(0, 0, 0, 1)">
);
</span><span style="color: rgba(128, 0, 128, 1)">$jwt</span> = JWT::encode(<span style="color: rgba(128, 0, 128, 1)">$token</span>, <span style="color: rgba(128, 0, 128, 1)">$key</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(128, 0, 128, 1)">$decoded</span> = JWT::decode(<span style="color: rgba(128, 0, 128, 1)">$jwt</span>, <span style="color: rgba(128, 0, 128, 1)">$key</span>, <span style="color: rgba(0, 0, 255, 1)">array</span>('HS256'<span style="color: rgba(0, 0, 0, 1)">));
</span><span style="color: rgba(0, 128, 128, 1)">print_r</span>(<span style="color: rgba(128, 0, 128, 1)">$decoded</span><span style="color: rgba(0, 0, 0, 1)">);

</span><span style="color: rgba(128, 0, 128, 1)">$decoded_array</span> = (<span style="color: rgba(0, 0, 255, 1)">array</span>) <span style="color: rgba(128, 0, 128, 1)">$decoded</span><span style="color: rgba(0, 0, 0, 1)">;

JWT</span>::<span style="color: rgba(128, 0, 128, 1)">$leeway</span> = 60; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> $leeway in seconds</span>
<span style="color: rgba(128, 0, 128, 1)">$decoded</span> = JWT::decode(<span style="color: rgba(128, 0, 128, 1)">$jwt</span>, <span style="color: rgba(128, 0, 128, 1)">$key</span>, <span style="color: rgba(0, 0, 255, 1)">array</span>('HS256'));</pre>
</div>
<p>3 补充捕获异常</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
            </span><span style="color: rgba(128, 0, 128, 1)">$arr</span> = <span style="color: rgba(128, 0, 128, 1)">$jwt</span>::decode(<span style="color: rgba(128, 0, 128, 1)">$token</span>, <span style="color: rgba(128, 0, 128, 1)">$key</span>, <span style="color: rgba(0, 0, 255, 1)">array</span>('HS256'<span style="color: rgba(0, 0, 0, 1)">));
      } </span><span style="color: rgba(0, 0, 255, 1)">catch</span> (\<span style="color: rgba(0, 0, 255, 1)">Exception</span> <span style="color: rgba(128, 0, 128, 1)">$e</span>) { <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> token验证失败</span>
            <span style="color: rgba(0, 0, 255, 1)">return</span> json(['code' =&gt; 0, 'msg' =&gt; <span style="color: rgba(128, 0, 128, 1)">$e</span>-&gt;getMessage()],400)-&gt;<span style="color: rgba(0, 0, 0, 1)">send();
      }</span><span style="color: rgba(0, 0, 255, 1)">catch</span> (ExpiredException <span style="color: rgba(128, 0, 128, 1)">$e</span>){ <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 过期</span>
            <span style="color: rgba(0, 0, 255, 1)">return</span> json(['code' =&gt; 0, 'msg' =&gt; <span style="color: rgba(128, 0, 128, 1)">$e</span>-&gt;getMessage()],400)-&gt;<span style="color: rgba(0, 0, 0, 1)">send();

      }</span></pre>
</div>
<p>&nbsp;转载:https://www.kancloud.cn/jiangguowu/kfjsdkfjskd/1080723</p><br><br>
来源:https://www.cnblogs.com/yehuisir/p/11521165.html
頁: [1]
查看完整版本: php firebase/php-jwt token验证