Loading

基于JWT进行API权限控制

什么是JWT

JSON Web Token (JWT) 是一个开放标准 (RFC 7519) , 定义了一种紧凑且自包含的方式, 以JSON对象的格式在多方之间进行信息的安全传递. RFC 7519本身不提供技术实现, 但是有很多开源的不同语言平台的开源实现, 使用的时候自己选择合适的库就行.

JWT其实本质上是一个字符串, 这个字符串的两个特点

1)紧凑:指的是这个串很小,能通过url 参数,http 请求提交的数据以及http header的方式来传递;

2)自包含:这个串可以包含很多信息,比如用户的id、角色等,别人拿到这个串,就能拿到这些关键的业务信息,从而避免再通过数据库查询等方式才能得到它们。

JWT的用途

  • 授权认证: JWT最常见的用途就是用来做授权认证, 用户通过登录获取JWT, 在之后的请求中带上这个JWT. 就可以让用户访问资源, 并且JWT是无状态的, 服务器只需要验证JWT的完整性和获取其中的负载, 不需要像session一样储存额外的信息.
  • 信息交换: JWT是一个很好的在多方之间进行安全传输的方法, 因为可以使用公私钥进行签名, 可以保证数据的发送方不会被伪造, 也可以保证数据不被篡改.

JWT的数据结构

一个典型的JWT字符串看起来是这样的

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTY2MjcxMDkyMiwianRpIjoiNGZjNTVhNDAtOWJhYy00ODE4LTg5MTMtN2IwN2NkNzM4OTIwIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6IjYzMWFhNjhjN2IxYjA4YmI0YTM4ZTNjYyIsIm5iZiI6MTY2MjcxMDkyMiwiZXhwIjoxNjYyNzE4MTIyfQ.DSl_rDt25gpD2zcyuvcSktHNLclg_uMB_-Dz7jlpTiY

JWT字符串有三个部分组成, 中间用 . 来分隔, 结构是 <header>.<payload>.<signature>

三个部分如下所示, 其中前两段是由base64加密的

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.
eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTY2MjcxMDkyMiwianRpIjoiNGZjNTVhNDAtOWJhYy00ODE4LTg5MTMtN2IwN2NkNzM4OTIwIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6IjYzMWFhNjhjN2IxYjA4YmI0YTM4ZTNjYyIsIm5iZiI6MTY2MjcxMDkyMiwiZXhwIjoxNjYyNzE4MTIyfQ.
DSl_rDt25gpD2zcyuvcSktHNLclg_uMB_-Dz7jlpTiY

Header和payload分别解密后是这样的

// header: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
{
	"typ":"JWT",
	"alg":"HS256"
}
// payload: eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTY2MjcxMDkyMiwianRpIjoiNGZjNTVhNDAtOWJhYy00ODE4LTg5MTMtN2IwN2NkNzM4OTIwIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6IjYzMWFhNjhjN2IxYjA4YmI0YTM4ZTNjYyIsIm5iZiI6MTY2MjcxMDkyMiwiZXhwIjoxNjYyNzE4MTIyf
{
	"fresh":false,
	"iat":1662710922,
	"jti":"4fc55a40-9bac-4818-8913-7b07cd738920",
	"type":"access","sub":"631aa68c7b1b08bb4a38e3cc",
	"nbf":1662710922,"exp":1662718122
}

header通常包含了两个部分.

一是token的类型, 一般也就是JWT; 二是签发时使用的签名算法

img

{
  "alg": "HS256",
  "typ": "JWT"
}

一般情况下header只需要包含这两个属性就够了

Payload

payload就是负载, 里面是实际传输的信息, 可以自定义里面的内容.

如下面的这段

{
	"fresh":false,
	"iat":1662710922,
	"jti":"4fc55a40-9bac-4818-8913-7b07cd738920",
	"type":"access",
	"sub":"631aa68c7b1b08bb4a38e3cc",
	"nbf":1662710922,
	"exp":1662718122
}

其中的每一个键值对被称作claim.

这些claim分为三种:

  1. Registered claims(保留): JWT标准中定义好的claim字段

    • iss(Issuer):代表这个JWT的签发主体;
    • sub(Subject):代表这个JWT的主体,即它的所有人;
    • aud(Audience):代表这个JWT的接收对象;
    • exp(Expiration time):一个时间戳,代表这个JWT的过期时间;
    • nbf(Not Before):一个时间戳,代表这个JWT生效的开始时间,意味着在这个时间之前验证JWT是会失败的;
    • iat(Issued at):一个时间戳,代表这个JWT的签发时间;
    • jti(JWT ID):是JWT的唯一标识。
  2. Public claims: 这些claims需要根据IANA JSON Web Token Registry中来定义.

  3. Private claims: 这些就是可以自定义的字段

对于registered claims, 实现库一般会参照标准实现对其中的进行读取和验证, 其他claims就需要自己去读取并验证了.

Signature

Signature是使用编码(上面的例子中是使用base64)后的header和payload, 根据header中指定的加密和摘要算法计算出来的.

以上面的HS256为例, 就是使用了HMAC和SHA-256两种算法, HAMC用于生成摘要, SHA-256进行数字签名, 其中your-256-bit-secret 应该尽可能复杂, 并且防止被他人获取.

img

JWT的签发过程

在使用JWT进行认证时, 用户只要登录成功, 后端便会生成JSON Web Token, 并返回给用户.

用户在发送请求时, 将access_token像这样带在请求头(HEADER)的Authorization中即可:

Authorization: Bearer <access-token>

如图是一个完整的请求头:

img

后端在收到请求时会对请求头中的token进行验证, 根据JWT中header的中的算法对payload进行计算, 如果计算出的signature和JWT中的不同, 则说明payload被篡改. 同时, 后端也会对exp 进行验证, 判断token是否过期.

前端JWT的储存和使用

在web开发中, 前端可以将token很方便地储存在LocalStorage里面, 调用也非常方便, 但是由于所有引入的其他JS脚本都可以访问到, 遭受到XSS攻击的风险很大.

所以如果有安全方面的需求, 也可以使用JS无法访问到的httpOnly Cookie:

Set-Cookie: name=Value; HttpOnly

但是这种方法对跨域又不友好, 而且还有CSRF(跨站请求伪造)的风险.


所以一般比较常用的方案是借鉴OAuth2.0, 后端同时返回access_tokenrefresh_token.

access_token用来访问api的同时设置一个相对较短的过期时间比如1小时, 过期之后使用refresh_token获取新的access_token;

refresh_token则设置一个相对较长的过期时间, 同样也是登录过期的时间, refresh_token 过期就需要重新登陆, 登出操作也就是让refresh_token失效.


使用这种方案, access_token可以放在LocalStorage里, 取用方便, 比较短的过期时间又能防止token泄露之后的持续影响, 而refresh_token 则放在httpOnly Cookie中, 较少的使用次数可以减少token被盗取的几率.

JWT的管理

后端并不会对生成的JWT进行储存和管理, 这意味着分发出去的JWT在过期之前都是有效的, 那对于用户登出 修改密码等操作, 之前分发的JWT仍然会有效, 所以后端仍然需要一个简单的JWT管理.

JWT的管理一般会采用两种方法, 一种是把生成的token存入数据库中, 每次请求过来, 差异下数据库看token在不在库中, 要让某个token失效把它从数据库里面删除就行. 另一种方法是维护一个token黑名单, 要使JWT失效把JWT加入黑名单中, 请求过来的时候判断token在不在黑名单就行了. 虽然这两种办法都违背了JWT的无状态的原则, 但是在实际的应用场景中对JWT的管理总是要用到的.

posted @ 2022-09-14 09:31  マルシル  阅读(605)  评论(1)    收藏  举报