Token到底是什么?!

随着Web应用的发展,为了保证API通信的安全性,很多项目在进行设计时会采用JSON Web TokenJWT)的解决方案。

JWT是一种开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间安全地传输信息作为JSON对象。这种信息可以被验证和信任,因为它是数字签名的。

那么JWT中的Token到底是什么?接下来,我们将以登录功能为例进行Token的分析。

登录流程

很多小伙伴对登录的流程已经很熟悉了,我们来看一个最基本的后台系统的登录流程

登录流程图.png

流程图很清楚了,接下来我们使用 V2Koa 实现一个登录过程,来看看Token到底是什么

Vue2 + Koa 实现登录

前端代码

1. 前端点击事件

数据的校验就忽略掉,感兴趣的同学可自行书写或者找我要源码,直接看点击事件

handleLogin() {
  this.$refs.loginForm.validate((valid) => {
    if (valid) {
      this.loading = true;
      // 这里使用了VueX
      this.$store
        .dispatch("user/login", this.loginForm)
        .then(() => {
        this.$router.push({ path: this.redirect || "/" });
        this.loading = false;
      })
        .catch(() => {
        this.loading = false;
      });
    } else {
      return false;
    }
  });
}

2. Vuex中的action

校验通过后触发VueXUser模块的Login方法:

async login(context, userInfo) {
  const users = {
    username: userInfo.mobile,
    password: userInfo.password
  }
  const token = await login(users)
  // 在这里大家可以对返回的数据进行更详细的逻辑处理
  context.commit('SET_TOKEN', token)
  setToken(token)
}

3. 封装的接口

export function login(data) {
  return request({
    url: '/login',
    method: 'post',
    data
  })
}

以上三步,是我们从前端向后端发送了请求并携带着用户名和密码,接下来,我们来看看Koa中是如何处理前端的请求的

Koa 处理请求

首先介绍一下Koa

Koa 基于Node.js平台,由 Express 幕后的原班人马打造,是一款新的服务端 web 框架

Koa的使用极其简单,感兴趣的小伙伴可以参考官方文档尝试用一下

Koa官网:https://koa.bootcss.com/index.html#introduction

1. 技术说明

在当前案例的koa中,使用到了jsonwebtoken的依赖包帮助我们去加密生成和解密Token

2. 接口处理

const { login } = require("../app/controller/user")
const jwt = require("jsonwebtoken")
const SECRET = 'test_';
router.post('/login', async (ctx, next) => {
    const { username, password } = ctx.request.body
    // 这里是调用Controller中的login方法来跟数据库中的数据作对比,可忽略
    const userList = await login(username, password)
    
    if (!userList) {
        // 这里的errorModel是自己封装的处理错误的模块
        ctx.body = new errorModel('用户名或密码错误', '1001')
        return
    }
  
    // ↓↓↓↓↓↓↓↓↓↓↓↓↓↓ ※ 重点看这里 ※ ↓↓↓↓↓↓↓↓↓↓↓↓↓↓
    const token = jwt.sign({ userList }, SECRET, { expiresIn: "1h" })
    
    ctx.body = {
        success: true,
        state: 200,
        message: 'login success',
        data: token
    };
    return;
})

关于 JWT

上面的重点代码大家看到了,接下来具体给大家解释下JWT

Jwt由三部分组成:headerpayloadsignature

export interface Jwt {
    header: JwtHeader;
    payload: JwtPayload | string;
    signature: string;
}

header头部

里面的包含的内容有很多,比如用于指定加密算法的alg、指定加密类型的typ,全部参数如下所示:

export interface JwtHeader {
    alg: string | Algorithm;
    typ?: string | undefined;
    cty?: string | undefined;
    crit?: Array<string | Exclude<keyof JwtHeader, 'crit'>> | undefined;
    kid?: string | undefined;
    jku?: string | undefined;
    x5u?: string | string[] | undefined;
    'x5t#S256'?: string | undefined;
    x5t?: string | undefined;
    x5c?: string | string[] | undefined;
}

payload负载

payload使我们存放信息的地方,里面包含了签发者过期时间签发时间等信息

export interface JwtPayload {
    [key: string]: any;
    iss?: string | undefined;
    sub?: string | undefined;
    aud?: string | string[] | undefined;
    exp?: number | undefined;
    nbf?: number | undefined;
    iat?: number | undefined;
    jti?: string | undefined;
}

signature签名

signature需要使用编码后的headerpayload以及我们提供的一个密钥(SECRET),然后使用header 中指定的签名算法进行签名

关于 jwt.sign()

jwt.sign()方法,需要三个基本参数和一个可选参数:payloadsecretOrPrivateKeyoptions和一个callback

export function sign(
    payload: string | Buffer | object,
    secretOrPrivateKey: Secret,
    options: SignOptions,
    callback: SignCallback,
): void;

payload是我们需要加密的一些信息,这个参数对应上面koa代码中的{ userList },而userList则是我从数据库中查询得到的数据结果

secretOrPrivateKey则是我们自己定义的秘钥,用来后续验证Token时所用

options选项中有很多内容,例如加密算法algorithm、有效期expiresIn等等

export interface SignOptions {
    /**
     * Signature algorithm. Could be one of these values :
     * - HS256:    HMAC using SHA-256 hash algorithm (default)
     * - HS384:    HMAC using SHA-384 hash algorithm
     * - HS512:    HMAC using SHA-512 hash algorithm
     * - RS256:    RSASSA using SHA-256 hash algorithm
     * - RS384:    RSASSA using SHA-384 hash algorithm
     * - RS512:    RSASSA using SHA-512 hash algorithm
     * - ES256:    ECDSA using P-256 curve and SHA-256 hash algorithm
     * - ES384:    ECDSA using P-384 curve and SHA-384 hash algorithm
     * - ES512:    ECDSA using P-521 curve and SHA-512 hash algorithm
     * - none:     No digital signature or MAC value included
     */
    algorithm?: Algorithm | undefined;
    keyid?: string | undefined;
    /** expressed in seconds or a string describing a time span [zeit/ms](https://github.com/zeit/ms.js).  Eg: 60, "2 days", "10h", "7d" */
    expiresIn?: string | number | undefined;
    /** expressed in seconds or a string describing a time span [zeit/ms](https://github.com/zeit/ms.js).  Eg: 60, "2 days", "10h", "7d" */
    notBefore?: string | number | undefined;
    audience?: string | string[] | undefined;
    subject?: string | undefined;
    issuer?: string | undefined;
    jwtid?: string | undefined;
    mutatePayload?: boolean | undefined;
    noTimestamp?: boolean | undefined;
    header?: JwtHeader | undefined;
    encoding?: string | undefined;
    allowInsecureKeySizes?: boolean | undefined;
    allowInvalidAsymmetricKeyTypes?: boolean | undefined;
}

callback则是一个回调函数,有两个参数,默认返回Token

export type SignCallback = (
    error: Error | null,
    encoded: string | undefined,
) => void;

通过以上方法加密之后的结果就是一个Token

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjU3ZmVmMTY0ZTU0YWY2NGZmYzUzZGJkNSIsInhzcmYiOiI0ZWE1YzUwOGE2NTY2ZTc2MjQwNTQzZjhmZWIwNmZkNDU3Nzc3YmUzOTU0OWM0MDE2NDM2YWZkYTY1ZDIzMzBlIiwiaWF0IjoxNDc2NDI3OTMzfQ.PA3QjeyZSUh7H0GfE0vJaKW4LjKJuC3dVLQiY4hii8s

总结

在整个的Koa中,用到了jsonwebtoken这个依赖包,里面有sign()方法

而我们前端所得到的数据通过sign()加密出来的包含自定义秘钥的一份用户信息而已

至于用户信息中有什么内容,可以随便处理,比如用户的ID、用户名、昵称、头像等等

那么这个Token后续有什么用呢?

后续我们可以在前端的拦截器中配置这个Token,让每一次的请求都携带这个Token,因为Koa后续需要对每一次请求进行Token的验证

比如登录成功后请求用户的信息,获取动态路由,再通过前端的router.addRoutes()将动态路由添加到路由对象中去即可

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

推荐阅读更多精彩内容