JWT实战:使用axios+PHP实现登录认证

文中涉及的重要知识点有:

因此在阅读这边文章之前,请先了解以上知识点以及JWT的基本概念,这样你会很快理解我们这篇文章中的实例代码。

准备

在本站上篇文章《有关JWT(Json Web Token)的那些事》有介绍用户登录鉴权流程:

  • 用户使用用户名密码来请求服务器
  • 服务器进行验证用户的信息
  • 服务器通过验证发送给用户一个token
  • 客户端存储token,并在每次请求时附送上这个token值
  • 服务端验证token值,并返回数据

那么现在我们就按这个流程开始。

HTML

我们的HTML结构是这样的:一个登录表单,供用户输入用户名和密码,以及提交按钮;一个是登录成功后的显示信息。

<div id="showpage" style="display: none">
  <div class="form-group">
    <label for="username">用户名</label>
    <input type="text" class="form-control" id="username" placeholder="请输入用户名">
  </div>
  <div class="form-group">
    <label for="password">密码</label>
    <input type="password" class="form-control" id="password" placeholder="请输入密码">
  </div>
  <button type="submit" id="sub-btn" class="btn btn-default">登录</button>

    <br/>
    <p class="bg-warning" style="padding: 10px;">演示用户名和密码都是<code>demo</code>。</p>
</div>
<div id="user" style="display: none"><p>欢迎<strong id="uname"></strong>,您已登录,<a href="javascript:;" id="logout">退出>></a></p></div>

详细的代码,可以下载demo源码中查看,这里样式我们使用的是Bootstrap3的经典样式。

Javascript

前端Javascript异步请求,我们使用Axios库,当然你也可以使用jQuery的Ajax方法。

首先引入axios库:

<script src="https://cdn.bootcss.com/axios/0.17.1/axios.min.js"></script>

按照流程,1.提交登录表单,发送用户名和密码到PHP后端,2.后端验证成功后,会发送一个token给前端,3.前端再拿这个token去请求需要用户权限访问,4.后端验证toen,鉴权,返回相应结果。下面的js代码实现了1,3两步。

<script>
    document.querySelector('#sub-btn').onclick = function() {
    let username = document.querySelector('#username').value;
    let password = document.querySelector('#password').value;
   
    var params = new URLSearchParams();
    params.append('user', username);
    params.append('pass', password);

    axios.post(
        'user.php?action=login', 
        params
    )
    .then((response) => {
        if (response.data.result === 'success') {
            // 本地存储token
            localStorage.setItem('jwt', response.data.jwt);
            // 把token加入header里
            axios.defaults.headers.common['X-token'] = response.data.jwt;
            axios.get('user.php').then(function(response) {
                if (response.data.result === 'success') {
                    document.querySelector('#showpage').style.display = 'none';
                    document.querySelector('#user').style.display = 'block';
                    document.querySelector('#uname').innerHTML = response.data.info.data.username;
                } else {
                }
            });
        } else {
            console.log(response.data.msg);
        }
    })
    .catch(function (error) {
        console.log(error);
    });
}

</script>

很显然,当登录成功后,立马使用本地存储token,然后把这个token放在请求头header里,再次去请求后端另一个用户信息接口,如果成功了就显示用户信息。

如果要退出登录,我们不需要再次去请求后端接口,直接前端清空本地存储就OK了。

document.querySelector('#logout').onclick = function() {
    localStorage.removeItem('jwt');
    document.querySelector('#showpage').style.display = 'block';
    document.querySelector('#user').style.display = 'none';
}

登录成功后,当我们刷新页面(再次请求需要登录后才能访问的页面),需要进行判断,判断本地存储中是否有token,如果有token,那就拿去给后端接口验证下token是否合法,如果没问题就显示用户相关信息,如果验证失败,那可能是token过去或者伪造的token等原因。

let jwt =  localStorage.getItem('jwt');

if (jwt) {
    axios.defaults.headers.common['X-token'] = jwt;
    axios.get('user.php')
    .then(function (response) {
        if (response.data.result === 'success') {
            document.querySelector('#showpage').style.display = 'none';
            document.querySelector('#user').style.display = 'block';
            document.querySelector('#uname').innerHTML = response.data.info.data.username;
        } else {
            document.querySelector('#showpage').style.display = 'block';
            console.log(response.data.msg);
        }
    })
    .catch(function (error) {
        console.log(error);
    });
} else {
    document.querySelector('#showpage').style.display = 'block';
}

PHP

后端我们使用了一个专门的JWT库:php-jwt

使用composer安装php-jwt,接收到登录用户名和密码后,PHP验证用户名和密码是否正确(实际开发中应该结合数据库,从数据库里拿用户名和密码比对,本实例为了演示只做简单验证),如果用户名和密码准确无误,那么就签发token,在token中,我们可以定义token的签发者、过期时间等等,并返回给前端。注意在签发token时,我们需要定义一个密钥,这个密钥是一个私钥,实际应用中是保密的不可告诉别人。

require 'vendor/autoload.php';

use FirebaseJWTJWT;

define('KEY', '1gHuiop975cdashyex9Ud23ldsvm2Xq'); //密钥

$res['result'] = 'failed';

$action = isset($_GET['action']) ? $_GET['action'] : '';

if ($action == 'login') {
    if ($_SERVER['REQUEST_METHOD'] == 'POST') {
        $username = htmlentities($_POST['user']);
        $password = htmlentities($_POST['pass']);

        if ($username == 'demo' && $password == 'demo') { //用户名和密码正确,则签发tokon
            $nowtime = time();
            $token = [
                'iss' => 'http://www.helloweba.net', //签发者
                'aud' => 'http://www.helloweba.net', //jwt所面向的用户
                'iat' => $nowtime, //签发时间
                'nbf' => $nowtime + 10, //在什么时间之后该jwt才可用
                'exp' => $nowtime + 600, //过期时间-10min
                'data' => [
                    'userid' => 1,
                    'username' => $username
                ]
            ];
            $jwt = JWT::encode($token, KEY);
            $res['result'] = 'success';
            $res['jwt'] = $jwt;
        } else {
            $res['msg'] = '用户名或密码错误!';
        }
    }
    echo json_encode($res);

} else {
    $jwt = isset($_SERVER['HTTP_X_TOKEN']) ? $_SERVER['HTTP_X_TOKEN'] : '';
    if (empty($jwt)) {
        $res['msg'] = 'You do not have permission to access.';
        echo json_encode($res);
        exit;
    }

    try {
        JWT::$leeway = 60;
        $decoded = JWT::decode($jwt, KEY, ['HS256']);
        $arr = (array)$decoded;
        if ($arr['exp'] < time()) {
            $res['msg'] = '请重新登录';
        } else {
            $res['result'] = 'success';
            $res['info'] = $arr;
        }
    } catch(Exception $e) {
        $res['msg'] = 'Token验证失败,请重新登录';
    }

    echo json_encode($res);
}

用户每次请求都要带上后端签发的token,后端获取请求中的token,PHP中使用$_SERVER['HTTP_X_TOKEN']就可以获取token值。这个X_TOKEN就是在我们前端的请求header头信息中。

然后PHP拿到这个token后,解密分析token值,返回给前端即可。

结束语

以上就是整个JWT的实战应用,我们可以看到,在用户鉴权的过程中并没有使用Session或者Cookie,服务端无需存储用户会话信息。只用了一个Token串,建立前后端的验证的数据传递,实现了有效的登录鉴权过程。

声明:本文为原创文章,helloweba.net和作者拥有版权,如需转载,请注明来源于helloweba.net并保留原文链接:https://www.helloweba.net/javascript/521.html

https://www.helloweba.net/javascript/521.html

------------------------------------------------------------------------------------------------------------------

demo

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> 
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>演示:JWT实战:使用axios+PHP实现登录认证</title>
    <link rel="stylesheet" href="//cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap.min.css">
    <link rel="stylesheet" type="text/css" href="css/demo.css">
    <style>
        #user{text-align: center;}
    </style>
</head>
<body>
<div class="container">
    <header>
        <div class="row">
            <div class="col-md-3 col-xs-12"><h1 class="logo"><a href="http://www.helloweba.net" title="返回helloweba首页">helloweba</a></h1></div>
        </div>
    </header>
    <div class="row main">
        <div class="col-md-12">
            <h2 class="top_title"><span class="glyphicon glyphicon-menu-left" aria-hidden="true"></span><a href="http://www.helloweba.net/javascript/521.html">JWT实战:使用axios+PHP实现登录认证</a></h2>
            <div class="row" style="margin-top: 30px;">
                <div class="col-md-4 col-sm-12 col-md-offset-3">
                    <div id="showpage" style="display: none">
                      <div class="form-group">
                        <label for="username">用户名</label>
                        <input type="text" class="form-control" id="username" placeholder="请输入用户名">
                      </div>
                      <div class="form-group">
                        <label for="password">密码</label>
                        <input type="password" class="form-control" id="password" placeholder="请输入密码">
                      </div>
                      <button type="submit" id="sub-btn" class="btn btn-default">登录</button>
                    
                        <br/>
                        <p class="bg-warning" style="padding: 10px;">演示用户名和密码都是<code>demo</code></p>
                    </div>
                    <div id="user" style="display: none"><p>欢迎<strong id="uname"></strong>,您已登录,<a href="javascript:;" id="logout">退出&gt;&gt;</a></p></div>
                </div>
                
            </div>
            

        </div>
    </div>
    <footer>
        <p>Powered by helloweba.net  允许转载、修改和使用本站的DEMO,但请注明出处:<a href="http://www.helloweba.net">www.helloweba.net</a></p>
    </footer>
</div>
<script src="https://cdn.bootcss.com/axios/0.17.1/axios.min.js"></script>
<script>
let jwt =  localStorage.getItem('jwt');

if (jwt) {
    axios.defaults.headers.common['X-token'] = jwt;
    axios.get('user.php')
    .then(function (response) {
        if (response.data.result === 'success') {
            document.querySelector('#showpage').style.display = 'none';
            document.querySelector('#user').style.display = 'block';
            document.querySelector('#uname').innerHTML = response.data.info.data.username;
        } else {
            document.querySelector('#showpage').style.display = 'block';
            console.log(response.data.msg);
        }
    })
    .catch(function (error) {
        console.log(error);
    });
} else {
    document.querySelector('#showpage').style.display = 'block';
}

document.querySelector('#sub-btn').onclick = function() {
    let username = document.querySelector('#username').value;
    let password = document.querySelector('#password').value;
   
    var params = new URLSearchParams();
    params.append('user', username);
    params.append('pass', password);

    axios.post(
        'user.php?action=login', 
        params
    )
    .then((response) => {
        if (response.data.result === 'success') {
            // 本地存储token
            localStorage.setItem('jwt', response.data.jwt);
            axios.defaults.headers.common['X-token'] = response.data.jwt;
            axios.get('user.php').then(function(response) {
                if (response.data.result === 'success') {
                    document.querySelector('#showpage').style.display = 'none';
                    document.querySelector('#user').style.display = 'block';
                    document.querySelector('#uname').innerHTML = response.data.info.data.username;
                } else {
                }
            });
        } else {
            console.log(response.data.msg);
        }
    })
    .catch(function (error) {
        console.log(error);
    });
}

document.querySelector('#logout').onclick = function() {
    localStorage.removeItem('jwt');
    document.querySelector('#showpage').style.display = 'block';
    document.querySelector('#user').style.display = 'none';
}

</script>
</body>
</html>
demo.css 如下图
@charset "utf-8";
/* CSS Document */
html,body,div,span,h1,h2,h3,h4,h5,h6,p,pre,a,code,em,img,small,strong,sub,sup,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label{margin:0;padding:0;border:0;outline:0;font-size:100%;vertical-align:baseline;background:transparent}
a{color:#007bc4/*#424242*/; text-decoration:none;}
a:hover{text-decoration:underline}
a:focus {outline:none; -moz-outline:none;}
ol,ul{list-style:none}
table{border-collapse:collapse;border-spacing:0}
html{background:url(../../images/bg.png)}
body{height:100%; font:14px/18px "Microsoft Yahei", Tahoma, Helvetica, Arial, Verdana, "5b8b4f53", sans-serif; color:#51555C; background: url(../../images/body_bg.gif) repeat-x}
img{border:none}

header{box-sizing: border-box;width: 100%;padding:10px;overflow: hidden;}
.logo{width:240px; height:70px; background:url(../../images/logo.png) no-repeat 0 10px;text-indent:-999em;}
.logo a{display:block; width:240px; height:70px}
.main{border:1px solid #d3d3d3; border-radius:5px;background:#fff;}

h2.top_title{margin:4px 10px; padding-top:15px; padding-bottom:10px; border-bottom:1px solid #d3d3d3; font-size:18px; color:#007bc4;font-weight:bold}
.optlist{margin:50px 10px;text-align:center;}
.optlist a{margin:6px;}

footer{height:60px;}
footer p{ padding:10px 2px; line-height:24px; text-align:center}
footer p a:hover{color:#51555C}

.google_ad{width:728px; height:90px; margin:50px auto}
.ad_76090,.ad_demo{width:760px; height:90px; margin:40px auto}
.demo_topad{position:absolute; top:15px; right:0px; width:472px; height:60px;}


@media screen and (min- 320px) and (max-width : 480px) {
 html{-webkit-text-size-adjust: none;}
.header{width:100%}
.logo{width:60px; height:60px; margin: 10px auto; background:url(../../images/logo.png) no-repeat 0 10px;text-indent:-999em;}
.logo a{display:block; width:60px; height:60px}
.main{width:100%; margin:10px auto;  -moz-border-radius:0px;-khtml-border-radius: 0px;-webkit-border-radius: 0px; border-radius:0px;}
 .demo_topad{display:none}
 .google_ad{width:100%; margin:40px auto; text-align:center}
.ad_76090,.ad_demo{width:100%; height:auto; margin:40px auto;text-align:center}
.demo{width:98%; margin:10px auto}
}

user.php

<?php 
date_default_timezone_set("PRC");   //系统使用北京时间

require 'vendor/autoload.php';

use FirebaseJWTJWT;

define('KEY', '1gHuiop975cdashyex9Ud23ldsvm2Xq');

// header('Access-Control-Allow-Origin:*');  
$res['result'] = 'failed';

$action = isset($_GET['action']) ? $_GET['action'] : '';

if ($action == 'login') {
    if ($_SERVER['REQUEST_METHOD'] == 'POST') {
        $username = htmlentities($_POST['user']);
        $password = htmlentities($_POST['pass']);

        if ($username == 'demo' && $password == 'demo') { //用户名和密码正确,则签发tokon
            $nowtime = time();
            $token = [
                'iss' => 'http://www.helloweba.net', //签发者
                'aud' => 'http://www.helloweba.net', //jwt所面向的用户
                'iat' => $nowtime, //签发时间
                'nbf' => $nowtime + 10, //在什么时间之后该jwt才可用
                'exp' => $nowtime + 600, //过期时间-10min
                'data' => [
                    'userid' => 1,
                    'username' => $username
                ]
            ];
            $jwt = JWT::encode($token, KEY);
            $res['result'] = 'success';
            $res['jwt'] = $jwt;
        } else {
            $res['msg'] = '用户名或密码错误!';
        }
    }
    echo json_encode($res);

} else {
    $jwt = isset($_SERVER['HTTP_X_TOKEN']) ? $_SERVER['HTTP_X_TOKEN'] : '';
    if (empty($jwt)) {
        $res['msg'] = 'You do not have permission to access.';
        echo json_encode($res);
        exit;
    }

    try {
        JWT::$leeway = 60;
        $decoded = JWT::decode($jwt, KEY, ['HS256']);
        $arr = (array)$decoded;
        if ($arr['exp'] < time()) {
            $res['msg'] = '请重新登录';
        } else {
            $res['result'] = 'success';
            $res['info'] = $arr;
        }
    } catch(Exception $e) {
        $res['msg'] = 'Token验证失败,请重新登录';
    }

    echo json_encode($res);
}
原文地址:https://www.cnblogs.com/yehuisir/p/11114536.html