1|什么是JWT ?
JWT,全称Json Web Token,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。
1|与Session的区别
一、Session是在服务器端的,而JWT是在客户端的,这点很重要。
二、流程不同
1|JWT使用场景
- 大量需要进行跨域的站点
- 服务器运算能力较差、存储空间较小
1|JWT的原理
JWT 的原理是,服务器认证以后,生成一个 JSON 对象,发回给用户,就像下面这样。
{ "姓名": "张三", "角色": "管理员", "到期时间": "2018年7月1日0点0分" }
以后,用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名(详见后文)。
服务器就不保存任何 session 数据了,也就是说,服务器变成无状态了,从而比较容易实现扩展。
1|JWT数据的格式
实际的 JWT 大概就像下面这样。
它是一个很长的字符串,中间用点(.
)分隔成三个部分。注意,JWT 内部是没有换行的,这里只是为了便于展示,将它写成了几行。
JWT 的三个部分依次如下。
- Header(头部)
- Payload(负载)
- Signature(签名)
Header 部分是一个 JSON 对象,描述 JWT 的元数据,通常是下面的样子。
{ "alg": "HS256", "typ": "JWT" }
上面代码中,alg属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256);typ属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT。
最后,将上面的 JSON 对象使用 Base64URL 算法(详见后文)转成字符串。
Payload
Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用。
- iss (issuer):签发人
- exp (expiration time):过期时间
- sub (subject):主题
- aud (audience):受众
- nbf (Not Before):生效时间
- iat (Issued At):签发时间
- jti (JWT ID):编号
除了官方字段,你还可以在这个部分定义私有字段,下面就是一个例子。
{ "sub": "1234567890", "name": "John Doe", "admin": true }
注意,JWT 默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分。
这个 JSON 对象也要使用 Base64URL 算法转成字符串。
Signature
Signature 部分是对前两部分的签名,防止数据篡改。
首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用”点”(.)分隔,就可以返回给用户。
Base64URL
前面提到,Header 和 Payload 串型化的算法是 Base64URL。这个算法跟 Base64 算法基本类似,但有一些小的不同。
JWT 作为一个令牌(token),有些场合可能会放到 URL(比如 api.example.com/?token=xxx
)。Base64 有三个字符+、/和=,在 URL 里面有特殊含义,所以要被替换掉:=被省略、+替换成-,/替换成_ 。这就是 Base64URL 算法。
1|JWT的使用方式
客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage。
此后,客户端每次与服务器通信,都要带上这个 JWT。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求的头信息Authorization字段里面。
Authorization: Bearer <token>
另一种做法是,跨域的时候,JWT 就放在 POST 请求的数据体里面。
1|JWT 的几个特点
(1)JWT 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。
(2)JWT 不加密的情况下,不能将秘密数据写入 JWT。
(3)JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数。
(4)JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。
(5)JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。
(6)为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。
1|内容说明
以上主要内容转载于廖雪峰的网络日志
2|0实例:使用Django完成微信小程序的JWT登录及鉴权
网上目前已经有了一些文章来说明了,可是我在查阅的时候发现大多讲得不是很清楚。
2|1基本了解
通过之前的内容铺垫,相信读者对于JWT都有了一定的了解,总的来说,便是JWT是保存在用户端的Token机制。
需要注意的是:JWT默认是无加密的,只是使用了一层Base64编码,所以我们不能将重要信息,如密码等放入header和payload字段中。
2|2开始
对于Django来说,这里我们使用djangorestframework-jwt库
安装命令:
pip install djangorestframework-jwt
注意djangorestframework-jwt库默认将settings里的SECRET_KEY
当中jwt加密秘钥。
首先我们先去我们的project下的settings文件内设置jwt库的一些参数
import datetime # 在末尾添加上 REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework_jwt.authentication.JSONWebTokenAuthentication',# JWT认证,在前面的认证方案优先 'rest_framework.authentication.SessionAuthentication', 'rest_framework.authentication.BasicAuthentication', ), } JWT_AUTH = { 'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1), #JWT_EXPIRATION_DELTA 指明token的有效期 }
登录函数的实现:
''' 登录函数: ''' def get_user_info_func(user_code): api_url = 'https://api.weixin.qq.com/sns/jscode2session?appid={0}&secret={1}&js_code={2}&grant_type=authorization_code' get_url = api_url.format(App_id,App_secret,user_code) r = requests.get(get_url) return r.json() @require_http_methods(['POST']) def user_login_func(request): try: user_code = request.POST.get('user_code') print(user_code) if user_code == None: print(request.body) json_data = json.loads(request.body) user_code = json_data['user_code'] print(user_code) except: return JsonResponse({'status':500,'error':'请输入完整数据'}) try: json_data = get_user_info_func(user_code) #json_data = {'errcode':0,'openid':'111','session_key':'test'} if 'errcode' in json_data: return JsonResponse({'status': 500, 'error': '验证错误:' + json_data['errmsg']}) res = login_or_create_account(json_data) return JsonResponse(res) except: return JsonResponse({'status':500,'error':'无法与微信验证端连接'}) def login_or_create_account(json_data): openid = json_data['openid'] session_key = json_data['session_key'] try: user = User.objects.get(username=openid) except: user = User.objects.create( username=openid, password=openid, ) user.session_key = session_key user.save() try: 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) res = { 'status': 200, 'token': token } except: res = { 'status': 500, 'error': 'jwt验证失败' } return res
视图函数:
''' 视图样例: ''' from django.http import JsonResponse from account.models import * from rest_framework_jwt.views import APIView from rest_framework import authentication from rest_framework.permissions import IsAuthenticated from rest_framework_jwt.authentication import JSONWebTokenAuthentication from rest_framework import permissions class IsOwnerOrReadOnly(permissions.BasePermission): def has_object_permission(self, request, view, obj): if request.method in ['GET','POST']: return True return obj.user == request.user class test_view(APIView): http_method_names = ['post'] #限制api的访问方式 authentication_classes = (authentication.SessionAuthentication,JSONWebTokenAuthentication) permission_classes = (IsAuthenticated,IsOwnerOrReadOnly) #权限管理 def post(self,request): #视图函数 user = request.user.username U = User.objects.get(username=user) json_data = json.loads(request.body) try: test = json_data[''] except: return JsonResponse({'status':500,'errmsg':'参数不全'}) try: U.sex = sex U.weight = weight U.height = height U.save() except: return JsonResponse({'status': 500, 'errmsg': '数据库错误'}) return JsonResponse({'status':200})
urls.py:
urlpatterns = [ re_path('^$', index), re_path('^login$',login), # 登录 re_path('^test$',test_view.as_view()) ]