最近了解下基于 Token的身份验证,跟大伙分享下。很多大型网站也都在用,比如 Facebook,Twitter,Google+,Github等等,比起传统的身份验证方法,Token 扩展性更强,也更安全点,非常适合用在 Web 应用或者移动应用上。Token 的中文有人翻译成“令牌”,我觉得挺好,意思就是,你拿着这个令牌,才能过一些关卡。
传统身份验证的方法
HTTP 是一种没有状态的协议,也就是它并不知道是谁是访问应用。这里我们把用户看成是客户端,客户端使用用户名还有密码通过了身份验证,不过下回这个客户端再发送请求时候,还得再验证一下。
解决的方法就是,当用户请求登录的时候,如果没有问题,我们在服务端生成一条记录,这个记录里可以说明一下登录的用户是谁,然后把这条记录的 ID号发送给客户端,客户端收到以后把这个 ID 号存储在 Cookie 里,下次这个用户再向服务端发送请求的时候,可以带着这个 Cookie,这样服务端会验证一个这个 Cookie里的信息,看看能不能在服务端这里找到对应的记录,如果可以,说明用户已经通过了身份验证,就把用户请求的数据返回给客户端。
上面说的就是 Session,我们需要在服务端存储为登录的用户生成的 Session ,这些 Session 可能会存储在内存,磁盘,或者数据库里。我们可能需要在服务端定期的去清理过期的 Session。
基于Token的身份验证方法
使用基于 Token 的身份验证方法,在服务端不需要存储用户的登录记录。大概的流程是这样的:
客户端使用用户名跟密码请求登录
服务端收到请求,去验证用户名与密码
验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端
客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者 Local Storage 里
客户端每次向服务端请求资源的时候需要带着服务端签发的 Token
服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据
JWT
实施 Token 验证的方法挺多的,还有一些标准方法,比如 JWT,读作:jot ,表示:JSON Web Tokens 。JWT 标准的 Token 有三个部分:
header
payload
signature
中间用点分隔开,并且都会使用 Base64 编码,所以真正的 Token 看起来像这样:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJuaW5naGFvLm5ldCIsImV4cCI6IjE0Mzg5NTU0NDUiLCJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlfQ.SwyHTEx_RQppr97g4J5lKXtabJecpejuef8AqKYMAJc
Header
header 部分主要是两部分内容,一个是 Token 的类型,另一个是使用的算法,比如下面类型就是 JWT,使用的算法是 HS256。
{
"typ": "JWT",
"alg": "HS256"
}
上面的内容要用 Base64 的形式编码一下,所以就变成这样:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
Payload
Payload 里面是 Token 的具体内容,这些内容里面有一些是标准字段,你也可以添加其它需要的内容。下面是标准字段:
iss:Issuer,发行者
sub:Subject,主题
aud:Audience,观众
exp:Expiration time,过期时间
nbf:Not before
iat:Issued at,发行时间
jti:JWT ID
比如下面这个 Payload ,用到了 iss 发行人,还有 exp 过期时间。另外还有两个自定义的字段,一个是 name ,还有一个是 admin 。
{
"iss": "ninghao.net",
"exp": "1438955445",
"name": "wanghao",
"admin": true
}
使用 Base64 编码以后就变成了这个样子:
eyJpc3MiOiJuaW5naGFvLm5ldCIsImV4cCI6IjE0Mzg5NTU0NDUiLCJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlfQ
Signature
JWT 的最后一部分是 Signature ,这部分内容有三个部分,先是用 Base64 编码的 header.payload ,再用加密算法加密一下,加密的时候要放进去一个 Secret ,这个相当于是一个密码,这个密码秘密地存储在服务端。
header
payload
secret
var encodedString = base64UrlEncode(header) + "." + base64UrlEncode(payload);
HMACSHA256(encodedString, 'secret');
处理完成以后看起来像这样:
SwyHTEx_RQppr97g4J5lKXtabJecpejuef8AqKYMAJc
最后这个在服务端生成并且要发送给客户端的 Token 看起来像这样:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJuaW5naGFvLm5ldCIsImV4cCI6IjE0Mzg5NTU0NDUiLCJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlfQ.SwyHTEx_RQppr97g4J5lKXtabJecpejuef8AqKYMAJc
客户端收到这个 Token 以后把它存储下来,下回向服务端发送请求的时候就带着这个Token 。服务端收到这个 Token ,然后进行验证,通过以后就会返回给客户端想要的资源。
Demo:在胎心判读医生端中的应用
服务端
胎心判读医生端采用了codeigniter(CI)框架,通过框架自带的类库扩展功能加入jwt类。大家有兴趣可以看一下。
类库github地址:https://github.com/gree/jose
CI框架中的jwt已经被做过修改,删去了部分冗余(对我们项目没有意义)的代码。在使用中可以直接使用CI框架的lib应用方式加载,具体代码如下:
$params = array('phone' => $this->input->post('phone'),
'pwd' => md5($this->input->post('pwd')));
$this->load->library("JWT");
$key = "hKZPLE1ATbJnfkWc6MIH2B9UOVXgR";
$token = $params;
$jwt = $this->jwt->encode($token, $key);
通过此段代码,即可实现参数的加密,接口会将加密后的字符串返回给客户端,客户端存在本地,在需要时取用。
好的,上面是在CI框架中如何使用加密,下面是解密的内容。
在客户端中,我们可以将返回的加密字符串通过一定的方式插入至其请求的header中,在每次发送http请求时都会携带此字符串。服务端的验证方式就是获取此加密字符串,并加以验证。
$this->load->library("JWT");
$token = $_SERVER['HTTP_X_TOKEN_HEADER'];
$decoded = $this->jwt->decode($token, 'hKZPLE1ATbJnfkWc6MIH2B9UOVXgR', array('HS256'));
if($decoded !== 400)
{
//验证成功
}
else
{
//验证失败
}
服务端的内容就是辣么多啦!
客户端
胎心医生端我们主要应用了jQuery的Ajax,所以加密以后我们还是采用此种方式。下面是只要的示范代码:
$("button").click(function() {
$.post("http://fhr.yunqitixing.com/index.php/Doctor/login_test", {
phone: $('#docPhone').val(),
pwd: $('#docPwd').val()
},
function(data, status) {
window.localStorage.setItem('token', data.token);
});
});
在登录时访问login_test接口,获取加密后的字符串(token)并保存在本地的localStorage中,以便在下次使用是取用。
$("#test").click(function() {
console.log('click')
$.ajax({
type: "GET",
async: true,
global: false,
url: "http://fhr.yunqitixing.com/index.php/Doctor/get?id=6&" + Math.random(),
success: function(result) {
console.log(result);
},
error:function(result){
console.log(result)
},
beforeSend: function(request) {
request.setRequestHeader("X-Token-Header", window.localStorage.getItem('token'));
}
});
});
这是使用加密的代码,我们在这里使用了ajax的beforesend参数,通过
request.setRequestHeader("X-Token-Header", window.localStorage.getItem('token'));
加入header加密信息。
ok~一次加密的访问就完成了。~
VUE.js的加密方式
this.$http.get(
'https://app.asana.com/api/1.0/projects?opt_fields=name,notes',
function (projects) {
// $set sets a property even if it's not declared
},
{
headers: {
"X-Token-Header": window.localStorage.getItem('token')
}
}
);
也可以采用vue的全局设置:
Vue.http.options.root = '/root';
Vue.http.headers.common['Authorization'] = 'Basic YXBpOnBhc3N3b3Jk';
但是考虑到我们的设置应在登陆(login)以后生效,那么此方法可能并不适用。
本文由 admin 创作,采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为: Nov 23, 2017 at 11:13 am