以前做登录授权的时候,流程是:
- 用户在登录页面输入用户名和密码
- 服务器验证提交过来的信息,通过后,在session中保存相关数据,比如用户ID
- 服务器返回一个
session_id
,写入用户的Cookie
。 - 用户的每一次请求,都会通过
Cookie
将session_id
传递给服务器。 - 服务器收到
session_id
,找到前面保存的数据,得知用户的身份。
这次正好也用midway.js
来做到登录授权了,换种方式,采用JWT(JSON Web Token)。
使用egg-jwt
插件,插件地址:okoale/egg。
JWT原理
jwt的原理是,在服务器认证通过后,将数据保存在一个JSON对象中,然后生成一个字符串TOKEN,发回给用户。用户在请求的时候,头部带着这个TOKEN,服务器就可以解析出前面保存的JSON对象,然后识别用户身份。
JWT的数据结构由三部分组成:
- Header 头部
- Payload 负载
- Signature 签名
Header部分是用来描述JWT的元数据,Paylod 部分是用来存放实际需要传递的数据,我们可以在这个里面定义私有字段。Signature是对前面两部分的签名,防止数据篡改。
服务器中会设定一个密钥,然后使用Header里面指定的签名算法(默认是HMAC SHA256),按照下面的公式产生签名:
1 | HMACSHA256( |
算出签名后,把Header、Payload、Signature三个部分使用.
拼成一个字符串,返回给用户。
客户端在收到JWT后,可以存储在Cookie
里面,也可以存储在localStorage
里面,以后每次与服务器通信时,都需携带JWT,为了解决跨域问题,更好的做法是将JWT放在HTTP请求头信息的Authorization
字段里面:
1 | Authorization: Bearer 你的JWT |
也就是说,这个Header的名字叫做Authorization
,值为Bearer
加一个空格再加上你的JWT。
配置插件
文件src/config/plugin.ts
:
1 | import {EggPlugin} from 'egg'; |
然后修改配置文件src/config/config.default.ts
:
1 |
|
使用
该插件提供了app.jwt
对象,而@plugin
属性装饰器,则是类似于直接从app对象上拿属性。所以要使用app['jwt']
,可以使用@plugin
:
1 | export class testController { |
生成token
1 | @get('/') |
解token获得数据
插件会自动解出数据,并放在ctx.state.user
上,可以直接访问。
携带token访问
需要在header里面加上参数:
Authorization
,然后值为:Bearea You-token
,使用curl示例:
1 | curl -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE0OTAwMTU0MTN9.ehQ38YsRlM8hDpUMKYq1rHt-YjBPSU11dFm0NOroPEg" 127.0.0.1:7001/test |
很好,这时候,没有特别设置的路由进来的请求的话会抛出401的异常,当携带了token并有效的时候才会返回正常的接口结果。
但是,万一没有jwt或者jwt过期了,不能直接抛出异常啊,我们需要手动处理异常,然后以json的形式返回。
错误处理
我们需要添加一个中间件来处理这个错误,error_handle.ts
:
1 | module.exports = () => { |
尝试把中间件加入到config.default.ts
中:
1 | config.middleware = [ |
但是感觉没起作用,没有进入中间件就抛出了异常。
搜了好久,才看到eggjs的官网文档上有解释:
应用层定义的中间件(
app.config.appMiddleware
)和框架默认中间件(app.config.coreMiddleware
)都会被加载器加载,并挂载到app.middleware
上。
所以,需要把这个错误拦截的处理中间件挂载在光甲默认中间件上, 修改app.ts
:
1 | module.exports = (app) => { |
再次请求尝试,发现UnauthorizedError
被拦截了,当没有token或token过期的时候就会返回我们的errcode。
再谈插件的ignore
我在打通公众号的时候,将接口的域名配置到了公众号那边,但是公众号那边发起的验证请求是不带token的,这要求我在配置的时候忽略不带路径的请求,我尝试了下面两种方式:
1 | ignore: [ |
尝试的结果是,这两种方式都是错的,这导致我的jwt插件会忽略所有的请求。
我以为做这个操作的是插件,我查看了egg-jwt
插件的源码,以及内部使用的koa-jwt2
甚至连node-jsonwebtoken
都看了,没有我想要的配置,后续一查,原来配置这个中间件是否起作用是中间件的配置。有match
和igonre
两个, 他们的参数都一样:
- 字符串:配置是一个url的路径前缀。也可以是字符串数组
- 正则:直接匹配满足正则验证的url的路径。
- 函数:将请求上下文传递给这个函数,最终由函数的返回结果
true
或false
来判断是否匹配。
我们可以使用函数和数组结合:
1 | ignore: [ |
OK,满足需求。
参考文章:JSON Web Token 入门教程