JWT验证机制【刘新宇】【Django REST framework中使用JWT】
JWT在用户注册或登录后,我们想记录用户的登录状态,或者为用户创建身份认证的凭证。我们不再使用Session认证机制,而使用Json Web Token认证机制。 什么是JWT
起源说起JWT,我们应该来谈一谈基于token的认证和传统的session认证的区别。 传统的session认证我们知道,http协议本身是一种无状态的协议,而这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证,那么下一次请求时,用户还要再一次进行用户认证才行,因为根据http协议,我们并不能知道是哪个用户发出的请求,所以为了让我们的应用能识别是哪个用户发出的请求,我们只能在服务器存储一份用户登录的信息,这份登录信息会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给我们的应用,这样我们的应用就能识别请求来自哪个用户了,这就是传统的基于session认证。 但是这种基于session的认证使应用本身很难得到扩展,随着不同客户端用户的增加,独立的服务器已无法承载更多的用户,而这时候基于session认证应用的问题就会暴露出来. 基于session认证所显露的问题Session: 每个用户经过我们的应用认证之后,我们的应用都要在服务端做一次记录,以方便用户下次请求的鉴别,通常而言session都是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大。 扩展性: 用户认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源,这样在分布式的应用上,相应的限制了负载均衡器的能力。这也意味着限制了应用的扩展能力。 CSRF: 因为是基于cookie来进行用户识别的,cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。 基于token的鉴权机制基于token的鉴权机制类似于http协议也是无状态的,它不需要在服务端去保留用户的认证信息或者会话信息。这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录了,这就为应用的扩展提供了便利。 流程上是这样的:
这个token必须要在每次请求时传递给服务端,它应该保存在请求头里, 另外,服务端要支持 那么我们现在回到JWT的主题上。 JWT长什么样?JWT是由三段信息构成的,将这三段信息文本用 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ JWT的构成第一部分我们称它为头部(header),第二部分我们称其为载荷(payload,类似于飞机上承载的物品),第三部分是签证(signature). headerjwt的头部承载两部分信息:
完整的头部就像下面这样的JSON: { ‘typ‘: ‘JWT‘,‘alg‘: ‘HS256‘ } 然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分. eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9 payload载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分
标准中注册的声明?(建议但不强制使用) :
公共的声明?: 公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密. 私有的声明?: 私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。 定义一个payload: { "sub": "1234567890","name": "John Doe","admin": true } 然后将其进行base64加密,得到JWT的第二部分。 eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9 signatureJWT的第三部分是一个签证信息,这个签证信息由三部分组成:
这个部分需要base64加密后的header和base64加密后的payload使用 // javascript var encodedString = base64UrlEncode(header) + ‘.‘ + base64UrlEncode(payload); var signature = HMACSHA256(encodedString,‘secret‘); // TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ 将这三部分用 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ 注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret,那就意味着客户端是可以自我签发jwt了。 如何应用一般是在请求头里加入 fetch(‘api/user/1‘,{ headers: { ‘Authorization‘: ‘Bearer ‘ + token } }) 服务端会验证token,如果验证通过就会返回相应的资源。整个流程就是这样的: ? ? ?
?
优点?
? Django REST framework JWT我们在验证完用户的身份后(检验用户名和密码),需要向用户签发JWT,在需要用到用户身份信息的时候,还需核验用户的JWT。 关于签发和核验JWT,我们可以使用Django REST framework JWT扩展来完成。 文档网站http://getblimp.github.io/django-rest-framework-jwt/ ?
?
安装配置? 安装 pip install djangorestframework-jwt 配置 REST_FRAMEWORK = { ‘DEFAULT_AUTHENTICATION_CLASSES‘: ( ‘rest_framework_jwt.authentication.JSONWebTokenAuthentication‘,‘rest_framework.authentication.SessionAuthentication‘,‘rest_framework.authentication.BasicAuthentication‘,),} JWT_AUTH = { ‘JWT_EXPIRATION_DELTA‘: datetime.timedelta(days=1),}
? ?
?
后端实现Django REST framework JWT提供了登录签发JWT的视图,可以直接使用 from rest_framework_jwt.views import obtain_jwt_token urlpatterns = [ url(r‘^authorizations/$‘,obtain_jwt_token),] 但是默认的返回值仅有token,我们还需在返回值中增加username和user_id。 通过修改该视图的返回值可以完成我们的需求。 在users/utils.py 中,创建 def jwt_response_payload_handler(token,user=None,request=None): """ 自定义jwt认证成功返回数据 """ return { ‘token‘: token,‘id‘: user.id,‘username‘: user.username } 修改配置文件 # JWT配置 JWT_AUTH = { ‘JWT_EXPIRATION_DELTA‘: datetime.timedelta(days=1),‘JWT_RESPONSE_PAYLOAD_HANDLER‘: ‘meiduo_admin.utils.jwt_response.jwt_response_payload_handler‘,} 4. 增加支持管理员用户登录账号JWT扩展的登录视图,在收到用户名与密码时,也是调用Django的认证系统中提供的authenticate()来检查用户名与密码是否正确。 我们可以通过修改Django认证系统的认证后端(主要是authenticate方法)来支持登录账号既可以是用户名也可以是手机号。 修改Django认证系统的认证后端需要继承
我们想要让管理员用户才能登录我们的admin后台,这时我们就要修改django原有的用户验证方法。 重写authenticate方法的思路:
在meiduo_mall/utils/authenticate.py中编写: from django.contrib.auth.backends import ModelBackend import re from users.models import User class MeiduoModelBackend(ModelBackend): def authenticate(self,**kwargs): # 判断是否通过vue组件发送请求 if request is None: try: user = User.objects.get(username=username,is_staff=True) except: return None # 判断密码 if user.check_password(password): return user else: # 变量username的值,可以是用户名,也可以是手机号,需要判断,再查询 try: # if re.match(r‘^1[3-9]d{9}$‘,username): # user = User.objects.get(mobile=username) # else: # user = User.objects.get(username=username) user = User.objects.get(username=username) except: # 如果未查到数据,则返回None,用于后续判断 try: user = User.objects.get(mobile=username) except: return None # return None # 判断密码 if user.check_password(password): return user else: return None 在配置文件中告知Django使用我们自定义的认证后端 前端保存token我们可以将JWT保存在cookie中,也可以保存在浏览器的本地存储里,我们保存在浏览器本地存储中 浏览器的本地存储提供了sessionStorage 和 localStorage 两种:
使用方法 sessionStorage.变量名 = 变量值 // 保存数据 sessionStorage.变量名 // 读取数据 sessionStorage.clear() // 清除所有sessionStorage保存的数据 localStorage.变量名 = 变量值 // 保存数据 localStorage.变量名 // 读取数据 localStorage.clear() // 清除所有localStorage保存的数据 var vm = new Vue({ ... methods: { ... on_submit: function(){ axios.post(...) .then(response => { // 记录用户的登录状态 sessionStorage.clear(); localStorage.clear(); localStorage.token = response.data.token; localStorage.username = response.data.username; localStorage.user_id = response.data.id; location.href = ‘/index.html‘; }) .catch(...) } } }) (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |