介绍一种适用于restful+json的API认证方法,这个方法是基于jwt,而且加入了一些从oauth2.0借鉴的改良。css
首先要明白,认证和鉴权是不一样的。认证是断定用户的合法性,鉴权是断定用户的权限级别是否可执行后续操做。这里所讲的仅含认证。认证有几种方法:html
这是http协议中所带带基本认证,是一种简单为上的认证方式。原理是在每一个请求的header中添加用户名和密码的字符串(格式为“username:password”,用base64编码)。前端
这种方式至关于将“用户名:密码”绑定为一个开放式证书,这会有几个问题:python
整体来讲,这种方法的特色就是,简单但不安全。web
将认证的结果存在客户端的cookie中,经过检查cookie中的身份信息来做为认证结果。
这种方式的特色是便捷,且只须要一次认证,屡次可用;也能够注销登陆状态和设置过时时间;甚至也有办法(好比设置httpOnly)来避免XSS攻击。算法
但它的缺点十分明显,使用cookie那即是有状态的服务了。数据库
JWT协议彷佛已经应用十分普遍,JSON Web Token——一种基于token的json格式web认证方法。json
基本的原理是,第一次认证经过用户名密码,服务端签发一个json格式的token。后续客户端的请求都携带这个token,服务端仅须要解析这个token,来判别客户端的身份和合法性。flask
而JWT协议仅仅规定了这个协议的格式(<a href=”https://tools.ietf.org/heml/rfc7519”>RFC7519</a>),它的序列生成方法在JWS协议中描述(https://tools.ietf.org/html/rfc7515),分为三个部分:后端
1.3.1 header头部:
声明类型,这里是jwt
声明加密的算法 一般直接使用 HMAC SHA256
一种常见的头部是这样的:
再将其进行base64编码。
1.3.2 payload载荷:
payload是放置实际有效使用信息的地方。JWT定义了几种内容,包括:
一个常见的payload是这样的:
事实上,payload中的内容是自由的,按照本身开发的须要加入。
Ps. 有个小问题。使用itsdangerous包的TimedJSONWebSignatureSerializer进行token序列生成的结果,exp是在头部里的。这里彷佛违背了jwt的协议规则。
1.3.3 signature
存储了序列化的secreate key和salt key。这个部分须要base64加密后的header和base64加密后的payload使用.链接组成的字符串,而后经过header中声明的加密方式进行加盐secret组合加密,而后就构成了jwt的第三部分。
目标场景是一个先后端分离的后端系统,用于运维工做,虽在内网使用,也有必定的保密性要求。
选择JWT。
这里使用python模块itsdangerous,这个模块能作不少编码工做,其中一个是实现JWS的token序列。
genTokenSeq这个函数用于生成token。其中使用的是TimedJSONWebSignatureSerializer进行序列的生成,这里secret_key密钥、salt盐值从配置文件中读取,固然也能够直接写死在这里。expires_in是超时时间间隔,这个间隔以秒记,能够直接在这里设置,我选择将其设为方法的形参(由于这个函数也用在了解决下提到的问题2)。
使用这个Serializer能够帮咱们处理好header、signature的问题。咱们只须要用s.dumps将payload的内容写进来。这里我准备在每一个token中写入三个值:用户id、用户角色id和当前时间(‘iat’是JWT标准注册声明中的一项)。
假设我所写入的信息是
采用以上的方法所生成的token为
eyJhbGciOiJIUzI1NiIsImV4cCI6MTQ2NzM0MTQ3NCwiaWF0IjoxNDY3MzM3ODc0fQ.eyJpYXQiOjE0NjczMzc4NzQuNzE3MDYzLCJ1c2VyX2lkIjoiNDY1MDEyMjgzNDNiMTFlNmFhYTZhNDVlNjBlZDVlZDVmOTczYmEwZmNmNzgzYmI4YWRlMzRjN2I0OTJkOWU1NSIsInVzZXJfcm9sZSI6M30.23QD0OwLjdioKu5BgbaH2gHT2GoMz90n8VZcpvdyp7U
它是由“header.payload.signature”构成的。
解析须要使用到一样的serializer,配置同样的secret key和salt,使用loads方法来解析token。itsdangerous提供了各类异常处理类,用起来也很方便:
若是是SignatureExpired,则能够直接返回过时;
若是是BadSignature,则表明了全部其余签名错误的状况,因而又分为:
以上内容写成一个函数,用于验证用户token。若是实如今python flask,能够考虑将此函数改成一个decorator修饰漆,将修饰器@到全部须要验证token的方法前面,则代码能够更加优雅。
检查和断定的机制以下:
- 使用加密的类,再用来解密(用上以前的密钥和盐值),获得结果存入data;
上述的方法能够作到基本的JWT认证,但在实际开发过程当中还有其余问题:
token在生成以后,是靠expire使其过时失效的。签发以后的token,是没法收回修改的,所以涉及token的有效期的更改是个难题,它体如今如下两个问题:
如何解决更改token有效期的问题,网上看到不少讨论,主要集中在如下内容:
这里,笔者借鉴了第三方认证协议Oauth2.0(<a href=”https://tools.ietf.org/html/rfc6749”>RFC6749</a>),它采起了另外一种方法:refresh token,一个用于更新令牌的令牌。在用户首次认证后,签发两个token:
由此能够给两类不一样token设置不一样的有效期,例如给access token仅1小时的有效时间,而refresh token则能够是一个月。api的登出经过access token的过时来实现(前端则可直接抛弃此token实现登出),在refresh token的存续期内,访问api时可执refresh token申请新的access token(前端可存此refresh token,access token过其实进行更新,达到自动延期的效果)。
refresh token不可再延期,过时需从新使用用户名密码登陆。
这种方式的理念在于,将证书分为三种级别:
- access token 短时间证书,用于最终鉴权
经过这种方式,使证书功效和证书时效结合考虑。
ps.前面提到建立token的时候将expire_in(jwt的推荐字段,超时时间间隔)做为函数的形参,是为了将此函数用于生成access token和refresh token,而二者的expire_in时间是不一样的。
咱们作了一个JWT的认证模块:
(access token在如下代码中为’token’,refresh token在代码中为’rftoken’)
client —–用户名密码———–> server
client <——token、rftoken—– server
client ——请求(携带token)—-> server
client <—–结果—————– server
client ——请求(携带token)—-> server
client <—–msg:token expired— server
client -请求新token(携带rftoken)-> server
client <—–新token————– server
client -请求新token(携带rftoken)-> server
client <—-msg:rftoken expired— server
若是设计一个针对此认证的前端,须要:
存储access token、refresh token
访问时携带access token,自动检查access token超时,超时则使用refresh token更新access token;状态延期用户无感知
用户登出直接抛弃access token与refresh token
其余参考文章: