面试场景:
以前有个学生去面试,公司问他,如果你们公司的接口文档被你的亲戚看到了,会怎么样?会导致什么问题,为了防止这个问题,需要用什么来解决?这个根据学生回忆的写的。
学生当场懵逼。我的亲戚……,我的亲戚看不懂啊……%¥#?~%¥#?~………………,我的亲戚也不懂程序啊%¥#?~%¥#?~%¥#?~………………
其实,面试官想问的意思是,如果一个公司的接口文档被别人(懂程序,懂前端的人)看见,他如果写个代码发送请求,是不是可以把数据库中的数据拿到,如何防止这种情况。
这个其实就是一个身份,鉴权的问题。现在前后端分离开发后,一般都用token解决。
本文章后端使用node+mySQL,和 JWT(JSON WEB TOKEN)。
一、token的思路
1、客户端使用用户名和密码请求登录接口(前端做)
2、服务端收到请求,去验证用户名与密码 (后端做)
3、验证成功后,服务端会签发(产生)一个 Token(加了密的字符串),再把这个 Token 发送给客户端(后端做)
4、客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者 Local Storage (sessionStorage)里(前端)
5、客户端每次向服务端请求资源的时候需要带着服务端签发的 Token(前端)
6、服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据
二、代码:
1、登录时,产生token。
1)、前端代码:
输入用户名和密码,发送请求。登录成功后,把后端响应的token保存起来。
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>登录页面</title>
</head>
<body><div><h1>登录页面</h1><p>手机号:*<input type="text" id="userphone"></p><p>密码:<input type="password" id="userpass"></p><p><input type="button" value="登录" id="btnLogin"><span id="errMsg"></span></p></div>
</body>
</html>
<script src="./js/jquery.js"></script>
<script src="./js/ajaxTools.js"></script>//这个是自己封装ajax库
<script src="./js/cookieTools.js"></script>//这个是自己封装的cookie库。
<script>
function loginCheck(){// 1、非空判断
// 2、登录的前后端交互(调用的自己封装的ajax库的函数)ajaxUsePromise02({url:"http://10.12.156.16:9000/login",method:"post",data:`loginname=${$("#userphone").val()}&password=${$("#userpass").val()}`}).then(res=>{if(res.code=="200"){//保存用户名(调用的自己封装的cookie库的函数)saveCookie("userphone",$("#userphone").val(),7);//保存tokensessionStorage.setItem("token",res.data.token);location.href="index.html";}else{$("#errMsg").html("亲,账户号或者密码不对").css({color:"red"});}})
}
window.onload = function(){$("#btnLogin").on("click",loginCheck)// document.getElementById("btnLogin").onclick = loginCheck;
}
</script>
2)、后端代码:
接收前端传来的用户名和密码。去数据库中验证,验证通过后,产生token,响应给前端。
var express = require("express");
var router = express.Router();
var mysql = require("mysql");
const jwt = require("jsonwebtoken");
router.post("/", function (req, res, next) {// 1、接收前端的数据let loginname = req.body.loginname;let password = req.body.password;
// 2、后端逻辑// 1)、连接数据库var connection = mysql.createConnection({host: "localhost", //主机名user: "root",password: "root",database: "db2308", //库名});
connection.connect();
// 2)、执行sql语句let sqlStr = `select * from users where tel='${loginname}' and password='${password}'`;connection.query(sqlStr, function (error, results, fields) {if (error) {res.json({code: "0",msg: "登录失败,连接数据库出错!",});return;}// 3、响应if (results.length == 1) {//产生tokenlet token = jwt.sign({username:loginname,userpass:password}, "who are you?");res.json({code: "200",msg: "登录成功!",data:{loginname:loginname,password:password,token:token//把token响应给前端}})}else{res.json({code: "10011",msg: "登录失败,用户名或者密码不对!",})}});// 3)、关闭数据库connection.end();
});
module.exports = router;
2、再次发送请求时,携带token
1)、前端代码:
token一般都携带在请求头里。
// 发送请求,从后端获取商品数据
ajaxUsePromise02({url: `${baseurl}/getGoodslist`,data: data,headers: {token: sessionStorage.getItem("token");// 把token携带在头部}
})
2)、后端代码:
先从请求头里拿到token。然后,验证token是否正确,如果正确,才给前端响应数据。
var express = require("express");
const connection = require("../db/conn");
var router = express.Router();
//专门定义一个验证token的函数。
const tokenVerify= function (req, res, success, fail) {// 还需要拿tokenlet token = req.query.token;console.log("token", token);
try {//这句话是验证token的。let decoded = jwt.verify(token, "who are you?");console.log("decoded", decoded);
// 1)、连接数据库var connection = mysql.createConnection({host: "localhost", //主机名user: "root",password: "root",database: "db2308", //库名port: 3306,});connection.connect();
// 2)、执行sql语句let sqlStr = `select * from users where tel='${decoded.username}' and password='${decoded.userpass}'`;console.log("sqlStr", sqlStr);connection.query(sqlStr, function (error, results, fields) {if (error) {// throw error;console.log("数据库出错了", error);}console.log("执行成功", results);// 3、响应if (results.length == 1) {success();} else {fail();}});// 3)、关闭数据库connection.end();} catch (error) {res.json({code: "-1",msg: "token无效",});}
};
// 获取所有的商品数据
router.get("/", function (req, res, next) {// 1、接收前端的数据let goodsname = req.query.goodsname;let goodsid = req.query.goodsid;
//2、验证token,如果成功获取数据。tokenVerify(req,res,function () {// 2、后端逻辑let sqlstr = "select * from goods where 1=1 ";if (goodsid != undefined) {sqlstr += ` and goodsid='${goodsid}'`;}
if (goodsname != undefined) {sqlstr += ` and goodsname='${goodsname}'`;}
console.log("sqlstr", sqlstr);
// res.setHeader( "access-control-allow-origin","*");
connection.query(sqlstr, function (error, results, fields) {if (error) {// 3、响应res.json({code: "0",msg: "数据库服务器出问题",});} else {console.log("获取到了商品列表数据");// 3、响应res.json({code: "200",data: results,});}});},function () {res.json({code: "-1",msg: "token不对",});});
});
module.exports = router;
三、JWT(JSON WEB TOKEN)的两个函数
jsonwebtoken的安装引入
let jwt = require('jsonwebtoken')
生成签名(token)
let token = jwt.sign(payload, secretOrPrivateKey, [options, callback])
[payload] 使用用户输入的信息(如:用户名),加密的原始字符串
[secretOrPrivateKey] 加密规则,字符串,或者私钥path模块,其实就是一串字符串(越乱越好)
[options] 可选配置项 ,如:expiresIn表示 过期时间(单位是秒)
[callback] 成功回调, 可选 返回制作后的token,也可同步返回;如果写了该回调函数就用的回调函数的异步操作,如果不写,那么同步返回
校验token
jwt.verify(token, secretOrPublicKey, [options, callback])
[token] 制作后的token
[secretOrPublicKey] 解密规则,字符串,或者公钥
[callback:] 回调 err 错误信息 decode 成功后的信息
[options] expiresIn 过期时间