前言: 仔细想想我们现在的服务器应用是否不太安全?只要你的请求路径被得知,你的接口就会被任何来源的用户所使用,这显然不是我们所希望的。接下来就到解决这个问题。
JWT 的原理
JWT 的原理是,服务器认证以后,生成一个 JSON 对象,发回给用户,就像下面这样。
{
“姓名”: “张三”,
“角色”: “管理员”,
“到期时间”: “2018年7月1日0点0分”
}
以后,用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名(详见后文)。
服务器就不保存任何 session 数据了,也就是说,服务器变成无状态了,从而比较容易实现扩展。
上面摘自http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html。
其中的JWT全名JsonWebToken,是实现token技术的一种解决方案。
JWT 的使用方式
客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage。此后,客户端每次与服务器通信,都要带上这个 JWT。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求的头信息Authorization字段里面。
通过这种方法,我们就可以实现只有登录的用户(在token未泄露的情况下),才能调用我们服务器的接口,去请求数据。好了,接下来就是去实现将这个方案运用到我们的项目中。
npm install jsonwebtoken --save OR
cnpm install jsonwebtoken --save
在config配置目录下添加token.js文件,我们暂时只需在user模块登录的时候将这个模块引入。
jwt在服务器使用这个密钥注册一个token对象,expiresIn有过cookie使用的应该很容易想到,这就是过期时间的设置。载荷是存放有效信息的地方,它可以接受三种声明,标准中注册的声明,公共的声明,私有的声明。这里我们使用的是公共的声明,可以添加任何的信息。通过这个唯一的id我们可以生成唯一的token。
为了保证我们的用户可以使用token,我们应该在登录的地方再做一些处理,将token返回到前端,同时前端负责把他持久的存储进客户端。
引入token模块,在确认登录前生成一个token让他返回前端。同时你可能注意到我这加了个字段user_imgurl,别嫌麻烦,回去dao目录下再添加一个返回字段把。
先测试一下我们的token是否能正常返回了。
到这已经完成了一半了,还有一半就是客户端发送请求时携带上服务器端返回的token,同时服务器对请求进行token验证。
先进行服务器端的token验证,我们限定客户端发起请求的时候请求头要加上’Authorization’字段,他的值是token
如果当前被请求的地址是登录或者注册的接口我们直接放行,如果不是这两个地址,我们就要进行token验证。那么我们试试这段代码是否已经跑通了。
成功了,我们已经能做到不使用token请求的话就无法访问服务器的资源。客户端发送请求时需要带上token,那这个token首先得保存在客户端中,然后在请求的时候请求头中发送出去。
首先在前端将token存储进去,那这个时候问题就来了,我们肯定不希望每次写请求的时候都得在原生的方法的header中添加token字段,我们应该希望每一次请求都自动的携带上我们保存好的token。这个时候我们就得封装一下原生的请求方法了。
在前端的common\js下新建文件http.js
const sendRequest = (url,method = 'GET',data = {},contentType) => {
url = 'http://localhost:3000'+ url;
let token = uni.getStorageSync('token') || '';
console.log(url);
return new Promise(function(resolve,reject){
uni.request({
url,
data,
method,
header: {
'Content-Type': 'application/json',
'Accept': 'application/json, text/javascript, */*; q=0.01',
'Authorization': token
},
success: function(res){
if(res.header.authorization){
uni.setStorageSync('token',res.header.authorization);
}else{
var code = res.statusCode;
switch (code) {
case 403:
uni.showModal({
title: '登录提示',
content: '身份已过期,请重新登录后再来操作!',
success:res => {
if (res.confirm) {
uni.clearStorageSync('token');
uni.redirectTo({
url:'/pages/login/login.vue'
})
}
}
})
break;
default:
resolve(res);
break;
}
}
}
})
})
}
我们做下测试,先把服务端的signup 加以限制,不允许他直接越过token检测。在main.js里把他挂载到原型实例上。
测试下。发现结果并不如我们的预期
我尽可能的描述下这个现象产生的原因吧。他的意思是我们不能在客户端里访问一个服务器端未被返回的字段。好,那我们根据它的要求修改一下服务器的配置,在’Access-Control-Allow-Headers’: 'X-Requested-With,Content-Type‘ 里加上authorization
为什么这么做?其实我们完全可以把上面封装的http方法的 res.header.authorization)去掉,因为我们知道我们写的这个服务器端对于token的传送方式是直接通过登录的时候返回,而不是把他放在请求头中给客户端。你可以纯当我就额外的添加了一个多余的知识点吧。
再测试。这次到后端报错了,那这个错误我们就好理解了,jwt没有提供,那我们就给他提供呗。
在’Access-Control-Request-Headers’: ’ Origin, X-Requested-With, content-Type, Accept,Authorization’,添加Authorization认证字段(这里稍微解释下为什么上面的获取req.headers.authorization,是因为发送请求的时候,请求头的字符会被默认转换为小写),前端的http方法请求的时候已经带上了token,那这次我们再测试一下。
成功了!至此基于token的安全验证已经实现了,我们客户端请求的时候只要使用我们封装好的方法,使它自己带上token进行请求。最后别忘了在app.js中把我们的放行路径给添加上。