JWT 是一个开放标准(RFC 7519),它定义了一种用于简洁,自包含的用于通讯双方之间以 JSON 对象的形式安全传递信息的方法。JWT 可使用 HMAC 算法或者是 RSA 的公钥密钥对进行签名。它具有两个特色:html
简洁(Compact)前端
能够经过URL, POST 参数或者在 HTTP header 发送,由于数据量小,传输速度快web
自包含(Self-contained)算法
负载中包含了全部用户所须要的信息,避免了屡次查询数据库shell
头部包含了两部分,token 类型和采用的加密算法数据库
{ "alg": "HS256", "typ": "JWT" }
typ
: (Type)类型。在JOSE Header中这是个可选参数,但这里咱们须要指明类型是JWT
。alg
: (Algorithm)算法,必须是JWS支持的算法它会使用 base64url编码组成 JWT 结构的第一部分django
Base64URL 算法json
这个算法跟 Base64 算法基本相似,是一种编码,也就是说,它是能够被翻译回原来的样子来的。它并非一种加密过程后端
JWT 做为一个令牌(token),有些场合可能会放到 URL(好比 api.example.com/?token=xxx)。Base64 有三个字符+
、/
和=
,在 URL 里面有特殊含义,因此要被替换掉:=
被省略、+
替换成-
,/
替换成_
。这就是 Base64URL 算法。api
这部分就是咱们存放信息的地方了,你能够把用户 ID 等信息放在这里,JWT 规范里面对这部分有进行了比较详细的介绍,JWT 规定了7个官方字段,供选用
iss (issuer):签发人
exp (expiration time):过时时间
sub (subject):主题
aud (audience):受众
nbf (Not Before):生效时间
iat (Issued At):签发时间
jti (JWT ID):编号
经常使用的有,
{ "iss": "lee JWT", "iat": 1441593502, "exp": 1441594722, "aud": "www.example.com", "sub": "6465644@163.com" }
一样的,它会使用 base64url 编码组成 JWT 结构的第二部分
签名的做用是保证 JWT 没有被篡改过
前面两部分都是使用 base64url 进行编码的,即前端能够解开知道里面的信息。Signature 须要使用编码后的 header 和 payload 以及咱们提供的一个密钥,这个密钥只有服务器才知道,不能泄露给用户,而后使用 header 中指定的签名算法(HS256)进行签名。
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
算出签名之后,把 Header、Payload、Signature 三个部分拼成一个字符串,每一个部分之间用"点"(.
)分隔,就能够返回给用户。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjU3ZmVmMTY0ZTU0YWY2NGZmYzUzZGJkNSIsInhzcmYiOiI0ZWE1YzUwOGE2NTY2ZTc2MjQwNTQzZjhmZWIwNmZkNDU3Nzc3YmUzOTU0OWM0MDE2NDM2YWZkYTY1ZDIzMzBlIiwiaWF0IjoxNDc2NDI3OTMzfQ.PA3QjeyZSUh7H0GfE0vJaKW4LjKJuC3dVLQiY4hii8s
最后一步签名的过程,其实是对头部以及负载内容进行签名,防止内容被窜改。若是有人对头部以及负载的内容解码以后进行修改,再进行编码,最后加上以前的签名组合造成新的JWT的话,那么服务器端会判断出新的头部和负载造成的签名和JWT附带上的签名是不同的。若是要对新的头部和负载进行签名,在不知道服务器加密时用的密钥的话,得出来的签名也是不同的。
客户端收到服务器返回的 JWT,能够储存在 Cookie 里面,也能够储存在 localStorage。
此后,客户端每次与服务器通讯,都要带上这个 JWT。你能够把它放在 Cookie 里面自动发送,可是这样不能跨域,因此更好的作法是放在 HTTP 请求的头信息Authorization
字段里面。
首先,前端经过Web表单将本身的用户名和密码发送到后端的接口。这一过程通常是一个HTTP POST请求。建议的方式是经过SSL加密的传输(https协议),从而避免敏感信息被嗅探。
后端核对用户名和密码成功后,将用户的id等其余信息做为JWT Payload(负载),将其与头部分别进行Base64编码拼接后签名,造成一个JWT。造成的JWT就是一个形同lll.zzz.xxx的字符串。
后端将JWT字符串做为登陆成功的返回结果返回给前端。前端能够将返回的结果保存在localStorage或sessionStorage上,退出登陆时前端删除保存的JWT便可。
前端在每次请求时将JWT放入HTTP Header中的Authorization位。(解决XSS和XSRF问题)
5.后端检查是否存在,如存在验证JWT的有效性。例如,检查签名是否正确;检查Token是否过时;检查Token的接收方是不是本身(可选)。
(1)JWT 默认是不加密,但也是能够加密的。生成原始 Token 之后,能够用密钥再加密一次。
(2)JWT 不加密的状况下,不能将秘密数据写入 JWT。
(3)JWT 不只能够用于认证,也能够用于交换信息。有效使用 JWT,能够下降服务器查询数据库的次数。
(4)JWT 的最大缺点是,因为服务器不保存 session 状态,所以没法在使用过程当中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期以前就会始终有效,除非服务器部署额外的逻辑。
(5)JWT 自己包含了认证信息,一旦泄露,任何人均可以得到该令牌的全部权限。为了减小盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。
(6)为了减小盗用,JWT 不该该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。
使用django-rest-framework-jwt这个库来帮助咱们简单的使用jwt进行身份验证
并解决一些先后端分离而产生的跨域问题
直接使用pip安装便可,目前支持Python、Django、DRF主流版本
pip install djangorestframework-jwt
在settings.py文件中,将JSONWebTokenAuthentication 添加到REST framework框架的DEFAULT_AUTHENTICATION_CLASSES.
REST_FRAMEWORK = { 'DEFAULT_PERMISSION_CLASSES': ( 'rest_framework.permissions.IsAuthenticated', ), 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework_jwt.authentication.JSONWebTokenAuthentication', 'rest_framework.authentication.SessionAuthentication', 'rest_framework.authentication.BasicAuthentication', ),
一样,你还可使用基于APIView
类的视图,在每一个视图或每一个视图集的基础上设置身份验证方案。与 Token 认证同样,尽量使用基于APIView
类的视图认证方式。
但使用基于APIView
类的视图认证方式时,不要忘记导入类。
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
在你的urls.py
文件中添加如下URL路由,以便经过POST包含用户名和密码的令牌获取。
from rest_framework_jwt.views import obtain_jwt_token urlpatterns += [ url(r'^api-token-auth/', obtain_jwt_token) ]
若是你使用用户名admin和密码admin123456建立了用户,则能够经过在终端中执行如下操做来测试JWT是否正常工做。
$ curl -X POST -d "username=admin&password=admin123456" http://127.0.0.1:8000/api-token-auth/
或者,你可使用Django REST framework支持的全部内容类型来获取身份验证令牌。例如:
$ curl -X POST -H "Content-Type: application/json" -d '{"username":"admin","password":"admin123456"}' http://127.0.0.1:8000/api-token-auth/
如今访问须要认证的API时,就必需要包含Authorization: JWT <your_token> 头信息了:
$ curl -H "Authorization: JWT <your_token>" http://127.0.0.1:8000/virtual/
若是JWT_ALLOW_REFRESH
为True,能够“刷新”未过时的令牌以得到具备更新到期时间的全新令牌。像以下这样添加一个URL模式:
from rest_framework_jwt.views import refresh_jwt_token urlpatterns += [ url(r'^api-token-refresh/', refresh_jwt_token) ]
使用方式就是将现有令牌传递到刷新API,以下所示: {"token": EXISTING_TOKEN}
。请注意,只有非过时的令牌才有效。另外,响应JSON看起来与正常获取令牌端点{"token": NEW_TOKEN}
相同。
$ curl -X POST -H "Content-Type: application/json" -d '{"token":"<EXISTING_TOKEN>"}' http://localhost:8000/api-token-refresh/
能够重复使用令牌刷新(token1 -> token2 -> token3),但此令牌链存储原始令牌(使用用户名/密码凭据获取)的时间。做为orig_iat,你只能将刷新令牌保留至JWT_REFRESH_EXPIRATION_DELTA。
刷新token以得到新的token的做用在于,持续保持活跃用户登陆状态。好比经过用户密码得到的token有效时间为1小时,那么也就意味着1小时后此token失效,用户必须得从新登陆,这对于活跃用户来讲实际上是多余的。若是这个用户在这1小时内都在浏览网站,咱们不该该让用户从新登陆,就是在token没有失效以前调用刷新接口为用户得到新的token。
在一些微服务架构中,身份验证由单个服务处理。此服务负责其余服务委派确认用户已登陆此身份验证服务的责任。这一般意味着其余服务将从用户接收JWT传递给身份验证服务,并在将受保护资源返回给用户以前等待JWT有效的确认。添加如下URL模式:
from rest_framework_jwt.views import verify_jwt_token urlpatterns += [ url(r'^api-token-verify/', verify_jwt_token) ]
将Token传递给验证API,若是令牌有效,则返回令牌,返回状态码为200。不然,它将返回400 Bad Request以及识别令牌无效的错误。
有时候你可能但愿手动生成令牌,例如在建立账户后当即将令牌返回给用户。或者,你须要返回的信息不止是Token,可能还有用户权限相关值。你能够这样作:
from rest_framework_jwt.settings import api_settings jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER payload = jwt_payload_handler(user) token = jwt_encode_handler(payload)
你能够覆盖一些其余设置,好比变动Token过时时间,如下是全部可用设置的默认值。在settings.py
文件中设置。
JWT_AUTH = { 'JWT_ENCODE_HANDLER': 'rest_framework_jwt.utils.jwt_encode_handler', 'JWT_DECODE_HANDLER': 'rest_framework_jwt.utils.jwt_decode_handler', 'JWT_PAYLOAD_HANDLER': 'rest_framework_jwt.utils.jwt_payload_handler', 'JWT_PAYLOAD_GET_USER_ID_HANDLER': 'rest_framework_jwt.utils.jwt_get_user_id_from_payload_handler', 'JWT_RESPONSE_PAYLOAD_HANDLER': 'rest_framework_jwt.utils.jwt_response_payload_handler', // 这是用于签署JWT的密钥,确保这是安全的,不共享不公开的 'JWT_SECRET_KEY': settings.SECRET_KEY, 'JWT_GET_USER_SECRET_KEY': None, 'JWT_PUBLIC_KEY': None, 'JWT_PRIVATE_KEY': None, 'JWT_ALGORITHM': 'HS256', // 若是秘钥是错误的,它会引起一个jwt.DecodeError 'JWT_VERIFY': True, 'JWT_VERIFY_EXPIRATION': True, 'JWT_LEEWAY': 0, // Token过时时间设置 'JWT_EXPIRATION_DELTA': datetime.timedelta(seconds=300), 'JWT_AUDIENCE': None, 'JWT_ISSUER': None, // 是否开启容许Token刷新服务,及限制Token刷新间隔时间,从原始Token获取开始计算 'JWT_ALLOW_REFRESH': False, 'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=7), // 定义与令牌一块儿发送的Authorization标头值前缀 'JWT_AUTH_HEADER_PREFIX': 'JWT', 'JWT_AUTH_COOKIE': None,
通常除了过时时间外,其余配置参数不多改变。具体参数意义当用到时能够查询官网。
参考:http://www.ruanyifeng.com/blog/2018/0
7/json_web_token-tutorial.html
参考:https://juejin.im/entry/5979a9355188253de4272ff4
参考:http://www.ywnds.com/?p=14967