面试场景:
以前有个学生去面试,公司问他,如果你们公司的接口文档被你的亲戚看到了,会怎么样?会导致什么问题,为了防止这个问题,需要用什么来解决?这个根据学生回忆的写的。
学生当场懵逼。我的亲戚……,我的亲戚看不懂啊……%¥#?~%¥#?~………………,我的亲戚也不懂程序啊%¥#?~%¥#?~%¥#?~………………
其实,面试官想问的意思是,如果一个公司的接口文档被别人(懂程序,懂前端的人)看见,他如果写个代码发送请求,是不是可以把数据库中的数据拿到,如何防止这种情况。
这个其实就是一个身份,鉴权的问题。现在前后端分离开发后,一般都用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);
//保存token
sessionStorage.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) {
//产生token
let 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) {
// 还需要拿token
let 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 过期时间