标签:存储 共享 否则 应该 密钥 前后端分离 帐户 有用 接收
JWT 是一个开放标准(RFC 7519),它定义了一种用于简洁,自包含的用于通信双方之间以 JSON 对象的形式安全传递信息的方法。JWT 可以使用 HMAC 算法或者是 RSA 的公钥密钥对进行签名。它具备两个特点:
简洁(Compact)
可以通过URL, POST 参数或者在 HTTP header 发送,因为数据量小,传输速度快
自包含(Self-contained)
负载中包含了所有用户所需要的信息,避免了多次查询数据库
头部包含了两部分,token 类型和采用的加密算法
{ "alg": "HS256", "typ": "JWT" }
typ
: (Type)类型。在JOSE Header中这是个可选参数,但这里我们需要指明类型是JWT
。alg
: (Algorithm)算法,必须是JWS支持的算法它会使用 base64url编码组成 JWT 结构的第一部分
Base64URL 算法
这个算法跟 Base64 算法基本类似,是一种编码,也就是说,它是可以被翻译回原来的样子来的。它并不是一种加密过程
JWT 作为一个令牌(token),有些场合可能会放到 URL(比如 api.example.com/?token=xxx)。Base64 有三个字符+
、/
和=
,在 URL 里面有特殊含义,所以要被替换掉:=
被省略、+
替换成-
,/
替换成_
。这就是 Base64URL 算法。
这部分就是我们存放信息的地方了,你可以把用户 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
标签:存储 共享 否则 应该 密钥 前后端分离 帐户 有用 接收
原文地址:https://www.cnblogs.com/freely/p/10328011.html