详解 Json Web Token (如何为Flutter开发一个简单的JWT解析库)

什么是JWT

JSON Web Token (JWT) is a compact URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is digitally signed using JSON Web Signature (JWS).

简单解释即:JWT是一个紧凑的、URL安全的、用于在双方之间传输claims的这样一个方法。而这个claims是用JSON格式编码的,并且进行了数字签名。

claim - 声明;宣称;断言;(尤指对财产、土地等要求拥有的)所有权;(尤指向公司、政府等)索款,索赔。让我们看一看具体的JWT长什么样子,就知道这个单词用的其实是相当贴切的。

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJpZDEyMyIsIm5hbWUiOiJKb2huIERvZSIsImlhdCI6MTUxNjIzOTAyMiwiZXhwIjoxNTE2MjM5MDk5fQ.8HLeWIBn5d87r-XItgQJOnwqYGjJYrpKmz-2eC9fb8A

这个就是一个JWT的串,它分为3个部分,分别由两个.隔开,这三个部分分别是:

Header


eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

// 是下面这个JSON object的base64编码,它是JWT的头信息

// 表明了这个JWT用的是哪种签名算法,以及类型(JWT)

{

  "alg": "HS256",

  "typ": "JWT"

}

Payload


eyJpc3MiOiJpZDEyMyIsIm5hbWUiOiJKb2huIERvZSIsImlhdCI6MTUxNjIzOTAyMiwiZXhwIjoxNTE2MjM5MDk5fQ

// 是羡慕这个JSON object的base64编码

{

  "iss": "id123",

  "name": "John Doe",

  "iat": 1516239022,

  "exp": 1516239099

}

Payload里放的其实就是claims(声明),在使用中。JWT一般都是由服务器返回给客户端的,而客户端每次请求时都会带上这个JWT串,服务端通过解析这个Payload里的内容,就知道这个请求来自于哪个用户以及一些其他用户基本信息。

上面的这四个字段iss,name, iat, exp,除了name都是RFC7519定义的一些 Registered Claim Names, 可以认为标准的claim名字。iss表示这个token是由谁签发的, iat token签发的时间, exptoken过期时间。我们也可以像name一样,加一些自身逻辑需要的字段。

那么为什么说claim这个单词用的很贴切呢?首先,payload里往往会是一些和身份认证、资源访问权限相关的内容;其次,payload字段经过base64解码之后,完全都是明文,任何人都可以修改,甚至伪造。也就是说任何人都可以通过伪造payload的内容来声称自己是谁、或者具有何种权限,这都只是单方面的声明,并不一定是有效/合法的。而要想知道是否合法,则需要第三个部分:

Signature


8HLeWIBn5d87r-XItgQJOnwqYGjJYrpKmz-2eC9fb8A

// 计算HMAC SHA-256签名值, 之后进行base64编码

base64urlsafeencode(

(HMACSHA256(

  base64UrlEncode(header) + "." +

  base64UrlEncode(payload),

  secret

)

)

有了签名,只需要对收到的JWT再进行一次签名校验就能知道这个JWT是不是合法的了。HS256(HMACSHA256)是jwt支持的签名算法之一,其他的详见RFC7519

JSON in Flutter

接上文的Payload:


{

  "iss": "id123",

  "name": "John Doe",

  "iat": 1516239022,

  "exp": 1516239099

}

要解析这条数据,我们需要dart:convert为提供的jsonDecode函数:


import 'dart:convert';

Map<String, dynamic> payload = jsonDecode(jsonString);

print('${payload["iss"]} and ${payload["name"]}');

jsonDecode函数签名返回值是一个dynamic类型,dynamic可以是任意类型,字符串,数字,列表等。很明显,jsonDecode需要能解析所有的合法JSON类型,它的返回值可以是多种不同的类型。而在我们的例子里,payload是一个Map<String, dynamic>,因为我们知道jsonString是一个JSON的对象类型。

json编码也是同样简单:


var data = jsonEncode(payload);

// data == '{"iss":"id123","name":"John Doe","iat":1516239022,"exp":1516239099}'

除了以上手动方法之外,我们还可以用json_serializable来自动生成这些解析代码。

自动生成解析

首先你的pubspec.yaml需要一些额外的库


dependencies:

  json_annotation: ^2.0.0

dev_dependencies:

  build_runner: ^1.0.0

  json_serializable: ^2.0.0

定义Claim数据类(claim.dart)


import 'package:json_annotation/json_annotation.dart';

// 文件内容将会由工具自动生成

part 'claim.g.dart';

@JsonSerializable()

class Claim {

  Claim(this.iss, this.name, this.iat, this.exp);

  String iss;

  String name;

  int iat;

  int exp;

  // 从json创建类对象的工厂函数,_$ClaimFromJson将会被定义在 claim.g.dart 文件中

  factory Claim.fromJson(Map<String, dynamic> json) => _$ClaimFromJson(json);

  // 将对象转成json,_$ClaimToJson将会被定义在 claim.g.dart中

  Map<String, dynamic> toJson() => _$ClaimToJson(this);

}

代码生成

运行 flutter packages pub run build_runner build。 Done,你现在可以像这样使用Claim与json之间的转换了:


// decode

Map map = jsonDecode(jsonString);

var claim = Claim.fromJson(map);

// encode

String json = jsonEncode(claim);

生成JWT串


import 'dart:convert';

import 'package:crypto/crypto.dart';

var header = jsonEncode(<String, dynamic>{

"alg": "HS256",

"typ": "JWT"

});

Claim claim = Claim("id123", "John Doe",1516239022,1516239099);

String payload = jsonEncode(claim);

var headerBase64 = base64Url.encode(utf8.encode(header));

var claimBase64 = base64Url.encode(utf8.encode(payload));

var key = utf8.encode('secret');

var bytes = utf8.encode(headerBase64 + "." + claimBase64);

var hmacSha256 = new Hmac(sha256, key); // HMAC-SHA256

var digest = hmacSha256.convert(bytes);

var signature = base64Url.encode(digest.bytes);

print("Just get a fresh new jwt: $headerBase64.$claimBase64.$signature");

我们把这写功能稍微封装一下,就是一个简单的jwt编码函数,可以处理任何@JsonSerializable()注解的类实例。


String encodeJWT(dynamic obj) {

  var header = jsonEncode(<String, dynamic>{"alg": "HS256", "typ": "JWT"});

  String payload = jsonEncode(obj);

  var headerBase64 = base64Url.encode(utf8.encode(header));

  var claimBase64 = base64Url.encode(utf8.encode(payload));

  var key = utf8.encode('secret');

  var bytes = utf8.encode(headerBase64 + "." + claimBase64);

  var hmacSha256 = new Hmac(sha256, key); // HMAC-SHA256

  var digest = hmacSha256.convert(bytes);

  var signature = base64Url.encode(digest.bytes);

  return "$headerBase64.$claimBase64.$signature";

}

尾声

离真正的库还差一段距离,至少还缺少jwt验证(应该是超级简单的)、支持其他的签名算法(HS384/HS512/RS/ES)。就在我正在想应该怎么去写的时候,我突然想到,前端APP好像并没有处理/解析JWT的场景......

那我就写到这里吧,希望有人能觉得有用╮(╯▽╰)╭

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342

推荐阅读更多精彩内容