JWT身份认证原理
0x01 认证原理
JWT,Json Web Token。用户登陆成功后,服务端返回一个 jwt 的 token,服务端不储存,客户端储存。此后客户端每次请求,都带上 token。服务端会 jwt校验 token 的有效性。优点显而易见,是不需要在服务端储存 token,服务端只需要做校验有效性的工作即可。
JWT 格式:
点分三段的字符串:
第一段:HEADER
base64url 加密的密文,可解密。
jwt 头部,包含说明第三段不可解密的编码算法名 HS256、字段数据类型标识 JWT。
第二段:PAYLOAD
base64url 加密的密文,可解密。
jwt 数据,包含自定义的数据。
第三段:VERIFY SIGNATURE
HS256 加密的密文,无法解密
校验签名,第一段密文.第二段密文“盐”进行 HS256 加密,生成密文,再做base64url 加密。
0x02 代码实例
为了实现用户登录之后的身份认证和授权管理,采用 JWT 认证。
pip install pyjwt
颁发 token
release.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import jwtimport datetimefrom django.conf import settingsdef create_token (payload,timeout=30 ): """ 创建token的部分 payload 外部传入payload,即用户信息在外部写,传入这里输出token即可 timeout 超时时间,默认30s 返回值:token """ headers = { "alg" :"HS256" , "typ" :"jwt" } payload['exp' ] = datetime.datetime.utcnow() + datetime.timedelta(seconds=timeout) token = jwt.encode(payload=payload,key=settings.SECRET_KEY,algorithm="HS256" ,headers=headers) return token
认证 token
auth.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 from django.conf import settingsfrom rest_framework.authentication import BaseAuthenticationfrom rest_framework.exceptions import AuthenticationFailedimport jwtfrom jwt import exceptionsclass JwtAuthentication (BaseAuthentication ): def authenticate (self, request ): token = request.META.get("HTTP_AAMS_TOKEN" ) try : payload = jwt.decode(token, settings.SECRET_KEY, algorithms="HS256" ) except exceptions.ExpiredSignatureError: raise AuthenticationFailed({"code" : 400 , "msg" : "token已失效" }) except jwt.DecodeError: raise AuthenticationFailed({"code" : 400 , "msg" : "token认证失败" }) except jwt.InvalidTokenError: raise AuthenticationFailed({"code" : 400 , "msg" : "token无效" }) return (payload, token)
配置在全局
若是要哪个请求需要认证才能进行后续业务,那么:
views.py
1 2 3 4 5 from myserver.mytoken.auth import JwtAuthenticationclass aView (...): authentication_class = [JwtAuthentication,] def get (self,request ): ..
因为基本上所有的请求,都是要认证再进行业务的。所以可以配置在
settings.py
1 2 3 4 5 6 7 SECRET_KEY = "xxxxxxxxxxxx自己设置即可xxxxxxxxxxxxx" REST_FRAMEWORK = [ "DEFAULT_AUTHENTICATION_CLASS" :[ "myserver.mytoken.JwtAuthentication" , ] ]
注意,要在登陆视图函数取消认证配置:
1 2 3 4 class LoginView (...): authentication_class = [] def get (... ):
login视图函数
views.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 from django.views.decorators.csrf import ensure_csrf_cookiefrom rest_framework import statusfrom rest_framework.response import Responsefrom rest_framework.views import APIViewfrom .mytoken.release import create_tokenfrom .models import Major,Userfrom .serializers import CourseSerializerclass Login (APIView ): authentication_classes = [] def get (self,request ): return Response({"code" :400 , "msg" :"后端没有login的get" },status=status.HTTP_200_OK) def post (self,request ): username = request.POST.get("username" ) password = request.POST.get("password" ) exists = User.objects.filter (username=username,password=password).exists() if exists: token = create_token({"username" :username}) res = { "code" :200 , "msg" :"登陆成功!" , "data" :{ "aams-token" :token, } } return Response(res,status=status.HTTP_200_OK) return Response({"code" :400 , "msg" :"登陆失败!" },status=status.HTTP_400_BAD_REQUEST)
若想一天不失效,可以:
1 token = create_token({"username" :username},timeout=60 *60 *24 )
0x03 认证测试
正如前面所说,我们采用JWT认证,那么测试登录和认证十分重要。
在第一次登录成功之后,应该由后端颁发一个token(我的系统命名为aams-token)。此后,每次网络请求必须带上这个aams-token作为请求头,后端会在请求头里取到aams-token,由中间件进行认证拦截,认证成功则会放行,否则请求失败。
登录测试
首先看一下能否在登录成功后得到一个正确的token:
认证测试
除登录API,随便选择一个API,发起请求,分别看在没有token和带有token的情况下,会收到什么响应:
注:以下所有API的response都是自行设计的code和msg。
为了方便测试,我只采用了少量的code(同时也尽量贴合状态码的原意)和msg。
code 状态码:200 处理成功;400 处理失败;301 重定向;202 接收处理但等待创建。
msg 状态信息:解释每种情况下的具体信息。
情况1:不带token
如下图,可见code状态码为400,msg状态信息为token认证失败。
情况2:带失效的token
如下图,可见code状态码为400,msg状态信息为token已失效。
情况3:带有效的token
如下图,可见code状态码为200,msg状态信息为获取成功。