1. JWT
1.1 JWT概述
基于django-rest-framework的登陆认证方式常用的大体可分为四种:
- BasicAuthentication:账号密码登陆验证
- SessionAuthentication:基于session机制会话验证
- TokenAuthentication: 基于令牌的验证
- JSONWebTokenAuthentication:基于Json-Web-Token的验证(对第三种进行封装,最常用的方式)
我们前面演示的就是第1,2种认证方式,而实际项目中,最常用的就是JWT的方式
JWT具有以下优点: - 签名的方式验证用户信息,安全性较之一般的认证高
- 加密后的字符串保存于客户端浏览器中,减少服务器存储压力
- 签名字符串中存储了用户部分的非私密信息,能够减少服务器数据库的开销
- 能够不需要做任何额外工作,即可实现单点登录
缺点: - 采用对称加密,一旦被恶意用户获取到加密方法,就可以不断破解入侵获取信息
- 加大了服务器的计算开销
61
1.2 JWT简介
JWT 是一个开放标准(RFC 7519),它定义了一种用于简洁,自包含的用于通信双方之间以 JSON 对象的形式安全传递信息的方法。
1.3 JWT 组成
- header、Payload和Signature之间用 . 号连接
- Header 头部
头部包含了两部分,token 类型和采用的加密算法
{
"alg": "HS256",
"typ": "JWT"
}
- typ : (Type)类型,指明类型是 JWT 。
- alg : (Algorithm)算法,必须是JWS支持的算法,主要是HS256和RS256
它会使用 base64url编码组成 JWT 结构的第一部分 - Payload 负载
这部分就是我们存放信息的地方了,你可以把用户 ID 等信息放在这里,JWT 规范里面对这部分有进行了比较详细的介绍,JWT 规定了7个官方字段,供选用
iss (issuer):签发人
exp (expiration time):过期时间,时间戳
sub (subject):主题
aud (audience):受众
nbf (Not Before):生效时间,时间戳
iat (Issued At):签发时间,时间戳
jti (JWT ID):编号
常用的有iss、iat、exp、aud和sub
同样的,它会使用 base64url 编码组成 JWT 结构的第二部分
- Signature 签名
签名的作用是保证 JWT 没有被篡改过
前面两部分都是使用 base64url 进行编码的,前端可以解开知道里面的信息。Signature 需要使用编码后的 header 和 payload 以及我们提供的一个密钥,这个密钥只有服务器才知道,不能泄露给用户,然后使用 header 中指定的签名算法(HS256)进行签名。
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,就可以返回给用户
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNTY3MTU2MTA5LCJlbWFpbCI6ImFkbWluQGV4YW1wbGUuY29tIiwib3JpZ19pYXQiOjE1NjY1NTEzMDl9.VwVwdkQalKip4Cp1_8QjcqR0n_S9w3jgUJEf3oO4PoI
- 签名的目的
最后一步签名的过程,实际上是对头部以及负载内容进行签名,防止内容被篡改。如果有人对头部以及负载的内容解码之后进行修改,再进行编码,最后加上之前的签名组合形成新的JWT的话,那么服务器端会判断出新的头部和负载形成的签名和JWT附带上的签名是不一样的。如果要对新的头部和负载进行签名,在不知道服务器加密时用的密钥的话,得出来的签名也是不一样的。
2. JWT的使用方式
2.1 JWT流程
客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage。
此后,客户端每次与服务器通信,都要带上这个 JWT。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求的头信息 Authorization 字段里面。
- 首先,前端通过Web表单将自己的用户名和密码发送到后端的接口。这一过程一般是一个HTTP POST请求。建议的方式是通过SSL加密的传输(https协议),从而避免敏感信息被嗅探。
- 后端核对用户名和密码成功后,将用户的id等其他信息作为JWTPayload(负载),将其与头部分别进行Base64编码拼接后签名,形成一个JWT。形成的JWT就是一个形同lll.zzz.xxx的字符串。
- 后端将JWT字符串作为登录成功的返回结果返回给前端。前端可以将返回的结果保存在localStorage或sessionStorage上,退出登录时前端删除保存的JWT即可。
- 前端在每次请求时将JWT放入HTTP Header中的Authorization位。(解决XSS和XSRF问题)
- 后端检查是否存在,如存在验证JWT的有效性。例如,检查签名是否正确;检查Token是否过期;检查Token的接收方是否是自己(可选)。
2.2 JWT 的几个特点
- JWT 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。
- JWT 不加密的情况下,不能将秘密数据写入 JWT。
- JWT 不仅可以用于认证,也可以用于交换信息。有效使用JWT,可以降低服务器查询数据库的次数。
- JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。
- JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。
为了减6. 少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用
HTTPS 协议传输。
3.项目中应用JWT
3.1 安装JWT
pip install djangorestframework-jwt==1.11.0
3.2 配置settings
REST_FRAMEWORK = {
# 默认的验证是按照验证列表 从上到下 的验证
'DEFAULT_AUTHENTICATION_CLASSES': (
# 配置JWT认证
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
# 配置session_id认证
'rest_framework.authentication.SessionAuthentication',
# 配置默认的认证方式 base:账号密码验证
'rest_framework.authentication.BasicAuthentication',
)
}
import datetime
JWT_AUTH = {
# 允许刷新token
'JWT_ALLOW_REFRESH': True,
# 每次刷新后,token的有效时间
'JWT_EXPIRATION_DELTA':datetime.timedelta(days=1),
# 生成 token 后,最大的有效时间:在有效期内通过刷新可以保持token有效;超过这个时间后,token失效,刷新也不起作用
'JWT_REFRESH_EXPIRATION_DELTA':datetime.timedelta(days=30),
}
3.3 URLS
在根路由或子应用项目下配置JWT登录的url路由,本问配置在根urls下:
根路由
from django.contrib import admin
from django.urls import path,include
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
path('api-token-auth/', obtain_jwt_token),
path('admin/', admin.site.urls),
path('',include('rest_app.urls')),
path('api-auth/',include('rest_framework.urls')),
]
3.4 登录JWT,获取token
#url参数提交
curl http://127.0.0.1:8000/api-token-auth/ -i -X POST -d "username=admin&password=admin"
#json格式提交
curl http://127.0.0.1:8000/api-token-auth/ -i -X POST -H "Content-Type:application/json" -d {\"username\":\"admin\",\"password\":\"admin\"}
3.5 应用jwt操作数据
新增数据
curl http://127.0.0.1:8000/students/ -X POST -H "Content-Type:application/json" -d {\"name\":\"jwt_stu\",\"age\":24,\"sex\":1} -H "Authorization:JWT eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNjcwNTgzNDUyLCJlbWFpbCI6ImFkbWluQGFkbWluLmNvbSIsIm9yaWdfaWF0IjoxNjcwNDk3MDUyfQ.StD1MHJHTvLgKy6yFEpBnp9D8BP3D_WyXJsVD6ObjBo"
删除数据
curl http://127.0.0.1:8000/students/9/ -i -X DELETE -H"Authorization:JWT eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNjcwNTgzNDUyLCJlbWFpbCI6ImFkbWluQGFkbWluLmNvbSIsIm9yaWdfaWF0IjoxNjcwNDk3MDUyfQ.StD1MHJHTvLgKy6yFEpBnp9D8BP3D_WyXJsVD6ObjBo"
{“detail”:“You do not have permission to perform this action.”}
若无携带token时,会存在此报错
3.6 刷新token
添加配置url
from django.contrib import admin
from django.urls import path,include
from rest_framework_jwt.views import obtain_jwt_token,refresh_jwt_token
urlpatterns = [
path('api-token-refresh/', refresh_jwt_token),
]
使用
curl http://127.0.0.1:8000/api-token-refresh/ -X POST -H "Content-Type:application/json" -d {\"token\":\"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNjcwNTgzNDUyLCJlbWFpbCI6ImFkbWluQGFkbWluLmNvbSIsIm9yaWdfaWF0IjoxNjcwNDk3MDUyfQ.StD1MHJHTvLgKy6yFEpBnp9D8BP3D_WyXJsVD6ObjBo\"}
3.7 认证token
有些应用中,有专门的服务器认证token,当其他其他服务器得到token之后,传递给专门的服务器认证token
urls
from django.contrib import admin
from django.urls import path,include
from rest_framework_jwt.views import obtain_jwt_token,refresh_jwt_token,verify_jwt_token
urlpatterns = [
path('api-token-auth/', obtain_jwt_token),
path('api-token-refresh/', refresh_jwt_token), # POST刷新JWT的token的url
path("api-token-verify/", verify_jwt_token), # 传递给专门的服务器认证token
path('admin/', admin.site.urls),
path('',include('rest_app.urls')),
path('api-auth/',include('rest_framework.urls')),
]
使用
curl http://127.0.0.1:8000/api-token-verify/ -X POST -H "Content-Type: application/json" -d {\"token\":\"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNjcwNTgzNDUyLCJlbWFpbCI6ImFkbWluQGFkbWluLmNvbSIsIm9yaWdfaWF0IjoxNjcwNDk3MDUyfQ.StD1MHJHTvLgKy6yFEpBnp9D8BP3D_WyXJsVD6ObjBo\"}
token正确与错误的效果