2 个回答
JSON Web Token(JWT)是目前 Token 鉴权机制下最流行的方案,鉴于网上 JWT的介绍众多且不完整,在 Django 项目中如何利用 JWT 实现对 API 的认证有这自己的开发方法。
本实力实现非认证用户不能访问指定内容的功能。
传统的 session 和 JWT 的区别
我们以一个用户,获取用户资料的例子。
session 流程
JWT 流程
发现了吗?好些并没有什么区别,除了session需要服务端存储一份,而 JWT不需要。
- session 存储在服务端占用服务器资源,而 JWT 存储在客户端。
- session 存储在 Cookie 中,存在伪造跨站请求伪造攻击的风险。
- session 只存在一台服务器上,那么下次请求就必须请求这台服务器,不利于分布式应用。
- 存储在客户端的 JWT 比存储在服务端的 session 更具有扩展性。
JWT的工作原理
Django服务端配置流程
安装
pip install djangorestframework-jwt
settings.py配置
### 根据实际情况进行选择
# token rest framework 配置实现
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
# 'rest_framework.permissions.IsAuthenticated', # IsAuthenticated 仅通过认证的用户
# 'rest_framework.permissions.AllowAny', # AllowAny 允许所有用户
# 'rest_framework.permissions.IsAdminUser', # IsAdminUser 仅管理员用户
# 'rest_framework.permissions.IsAuthenticatedOrReadOnly', # IsAuthenticatedOrReadOnly 认证的用户可以完全操作,否则只能get读取
'DEFAULT_AUTHENTICATION_CLASSES': (
# 'rest_framework_simplejwt.authentication.JWTAuthentication',
'rest_framework_jwt.authentication.JSONWebTokenAuthentication', # 在 DRF中配置JWT认证
'rest_framework.authentication.BasicAuthentication', # 在 DRF中基础认证信息,特定认证修改使用
# 'rest_framework.authentication.TokenAuthentication', # 全局Token认证
# 'rest_framework.authentication.SessionAuthentication', # 全局Session认证
# 'article.auth.MyTokenAuthentication', # 自定义的带过期的认证
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
'DEFAULT_PARSER_CLASSES': (
'rest_framework.parsers.JSONParser',
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
# 新版drf schema_class默认用的是rest_framework.schemas.openapi.AutoSchema
# 配置jwt载荷中的有效期设置
JWT_AUTH = {
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1), # 设置Token有效期
'JWT_AUTH_HEADER_PREFIX': 'JWT', # token前缀:headers中 Authorization 值的前缀
'JWT_ALLOW_REFRESH': True, # 刷新token:允许使用旧的token换新token
# 'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(hours=24),# token有效期:token在24小时内过期, 可续期token
'JWT_RESPONSE_PAYLOAD_HANDLER': 'users.utils.jwt_response_payload_handler', # 5.自定义JWT载荷信息:自定义返回格式,需要手工创建 utils.py的路径和其中方法
}
user/urls.py
from django.urls import include, path
from .views import *
from rest_framework.routers import DefaultRouter
from rest_framework_jwt.views import obtain_jwt_token, refresh_jwt_token
# 自动生成路由方法
router = DefaultRouter()
......
urlpatterns = [
# path('user_token/',CreateUserTokenViewSet.as_view(),name="user_token"),# 用户Token
path('token_login/', obtain_jwt_token), # 获取token,登录视图
path('token_refresh/', refresh_jwt_token), # 刷新token
path('api-auth/', include('rest_framework.urls', namespace='rest_framework')), # 认证地址
urlpatterns += router.urls # 模块地址
项目/urls.py
urlpatterns = [
......
# include 各个模块的内容
path('UserSettings/', include("users.urls")), # apps.users里面的内容
urlpatterns += router.urls
user/utils.py
# 该代码修改源码里的内容,return中返回的内容可以自行修改
def jwt_response_payload_handler(token, user=None, request=None, role=None):
自定义jwt认证成功返回数据
:token 返回的jwt
:user 当前登录的用户信息[对象]
:request 当前本次客户端提交过来的数据
:role 角色
if user.first_name:
name = user.first_name
else:
name = user.username
return {
'authenticated': 'true',
'id': user.id,
"role": role,
'name': name,
'username': user.username,
'email': user.email,
'token': token,
}
应用中设置验证权限
from rest_framework.permissions import IsAuthenticated, BasePermission
from rest_framework_jwt.authentication import JSONWebTokenAuthentication # jwt用户认证
# JWT 权限设置
class MyPermission(BasePermission):
message = '自定义的返回信息'
def has_permission(self, request, view): # 列表数据
# # 这个函数返回True或者False,True表示有权限,False表示没有权限,这个函数同时有三个参数,最后一个是view, 这个是在源码中规定的
# if request.user.id == 0:
# return False
# else:
return True
def has_object_permission(self, request, view, obj): # 对象数据
"""用户是否有权限访问添加了权限控制类的数据对象"""
# 需求:用户能够访问id为1,3的对象,其他的不能够访问
if request.user.is_active == 1:
return True
else:
return False
# 内容详情视图,只有验证通过的才能访问文章正文信息
class ArticleDetailListViewSet(viewsets.ReadOnlyModelViewSet):
permission_classes = (IsAuthenticated, MyPermission) # 设置用户认证
authentication_classes = (JSONWebTokenAuthentication,) # 只接受 jwt token认证
......
测试JWT
获取JWT token
JWT token访问
前端使用JWT信息
这里举例将JWT保存到浏览器本地。
Javascript 访问API数据
const token = localStorage.getItem('jwt_token'); //获取本地存储的token
console.log(token);
const info_slug = location.href.split("=")[1]
console.log(info_slut)
function VisitDetail() {
$.ajax({
type: 'get',
dataType: 'json',
contentType: 'application/json',
headers: {
'Content-Type': 'application/json',
'Authorization': 'JWT ' + token, //这里有个空格
url: 'http://127.0.0.1:8000/ArticleSettings/ArticleDetail/' + info_slug,
success: function (result) {
console.log(result) // 显示返回API数据结果
$("#article_detail").html(result.detail_content); //在html页面id=test的标签里显示html内容
error: function (response, ajaxOptions, thrownError) {
$("#article_detail").html("登陆后方可浏览");
HTML详情页部分
<div class="main" id="article_detail">
{{ detail_content |safe }}
</div>
背景
在实现单点登录是时候,发现对于Cookie、Session、Token、JWT的概念比较模糊,所以学习记录下
主体
认证、授权、凭证
在弄清这些概念之前我们先学习下认证、授权、凭证
认证(Authentication) :
- 认证是确认用户或实体的身份的过程。它涉及验证用户是否是其所声称的身份。
- 认证通常通过提供身份凭证来实现,如用户名和密码、数字证书、生物特征(指纹、面部识别等)等。
- 认证的目的是确保只有经过授权的用户或实体能够访问系统、服务或资源。
授权(Authorization) :
- 授权是确定用户是否有权限访问特定资源、执行特定操作或进行某项活动的过程。
- 一旦用户成功认证,系统需要确定用户是否有权执行所请求的操作。
- 授权通常依赖于用户的角色、权限和访问策略,以确定他们可以执行的操作。
凭证(Credential) :
- 凭证是用于进行认证的信息,以证明用户的身份。
- 凭证可以是用户名和密码、数字证书、令牌、生物特征等。
- 用户在认证过程中提供凭证,以验证其身份。
在一个典型的身份验证和访问控制场景中,认证是首先执行的步骤,以确认用户的身份。一旦用户成功认证,系统会根据其身份和权限进行授权(实现授权的方式有:cookie、session、token、OAuth)以确定用户可以执行的操作或访问的资源。凭证是用户用来进行认证的工具,如密码是最常见的凭证。
Cookie是什么
- HTTP 是无状态的协议(对于事务处理没有记忆能力,每次客户端和服务端会话完成时,服务端不会保存任何会话信息):每个请求都是完全独立的,服务端无法确认当前访问者的身份信息,无法分辨上一次的请求发送者和这一次的发送者是不是同一个人。所以服务器与浏览器为了进行会话跟踪(知道是谁在访问我),就必须主动的去维护一个状态,这个状态用于告知服务端前后两个请求是否来自同一浏览器。而这个状态需要通过 cookie 或者 session 去实现。
- cookie 存储在客户端: cookie 是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。
- cookie 是不可跨域的: 每个 cookie 都会绑定单一的域名,无法在别的域名下获取使用,一级域名和二级域名之间是允许共享使用的(靠的是 domain)。
Django中使用cookies
设置cookies(保存数据到客户端)
response.set_cookie(key,value,expires)
key : cookie的名称
value : 保存的cookie的值
expires : 保存的时间,以秒为单位
response = HttpResponse("hello world")
response.set_cookie(key,value,expires)
return response
获取cookies,获取用户发来请求中的cookies
request.COOKIES['username']
request.COOKIES.get('username')
检查cookies是否已经存在
request.COOKIES.has_key('<cookie_name>')
删除cookies
response.delete_cookie('username')
Session是什么
- session 是另一种记录服务器和客户端会话状态的机制
- session 是基于 cookie 实现的,session 存储在服务器端,sessionId 会被存储到客户端的cookie 中
session 认证流程:
- 用户第一次请求服务器的时候,服务器根据用户提交的相关信息,创建对应的 Session
- 请求返回时将此 Session 的唯一标识信息 SessionID 返回给浏览器
- 浏览器接收到服务器返回的 SessionID 信息后,会将此信息存入到 Cookie 中,同时 Cookie 记录此 SessionID 属于哪个域名
- 当用户第二次访问服务器的时候,请求会自动判断此域名下是否存在 Cookie 信息,如果存在自动将 Cookie 信息也发送给服务端,服务端会从 Cookie 中获取 SessionID,再根据 SessionID 查找对应的 Session 信息,如果没有找到说明用户没有登录或者登录失效,如果找到 Session 证明用户已经登录可执行后面操作。
根据以上流程可知, SessionID 是连接 Cookie 和 Session 的一道桥梁 ,大部分系统也是根据此原理来验证用户登录状态。
Django中使用session
关于session的基本配置可以参考官方文档,我这里面说一下关于操作的
操作session
- get:用来从session中获取指定值。
- pop:从session中删除一个值。
- keys:从session中获取所有的键。
- items:从session中获取所有的值。
- clear:清除当前这个用户的session数据。
-
flush:删除session并且删除在浏览器中存储的
session_id
,一般在注销的时候用得比较多。 - set_expiry(value):设置过期时间。
- 整形:代表秒数,表示多少秒后过期。
- 0:代表只要浏览器关闭,session就会过期。
- None:会使用全局的session配置。在settings.py中可以设置SESSION_COOKIE_AGE来配置全局的过期时间。默认是1209600秒,也就是2周的时间。)
- clear_expired:清除过期的session。Django并不会清除过期的session,需要定期手动的清理,或者是在终端,使用命令行python manage.py clearsessions来清除过期的session。
def set_session(request):
"""设置session"""
request.session["username"] = "jkc"
return HttpResponse("session_view")
def get_session(request):
"""获取session"""
username = request.session.get("username")
return HttpResponse(f"session的值为{username}")