- 使用 Spring Initializr 创建 Spring Boot 应用程序
- 在Spring Boot中配置Cassandra
- 在 Spring Boot 上配置 Tomcat 连接池
- 将Camel消息路由到嵌入WildFly的Artemis上
快速上手JWT签发token和认证,有这一篇就够了,DRF自带的和自定义的都帮你总结好了,拿去用~
上篇中对JWT有了基本的认知,这篇来略谈JWT的使用
签发:一般我们登录成功后签发一个token串,token串分为三段,头部,载荷,签名
1)用基本信息公司信息存储json字典,采用base64算法得到 头字符串
2)用关键信息存储json字典,采用base64算法得到 荷载字符串,过期时间,用户id,用户名
3)用头、体加密字符串通过加密算法+秘钥加密得到 签名字符串
拼接成token返回给前台
认证:根据客户端带token的请求 反解出 user 对象
1)将token按 . 拆分为三段字符串,第一段 头部加密字符串 一般不需要做任何处理
2)第二段 体加密字符串,要反解出用户主键,通过主键从User表中就能得到登录用户,过期时间是安全信息,确保token没过期
3)再用 第一段 + 第二段 + 加密方式和秘钥得到一个加密串,与第三段 签名字符串 进行比较,通过后才能代表第二段校验得到的user对象就是合法的登录用户
JWT可以使用如下两种:
djangorestframework-jwt
和djangorestframework-simplejwt
djangorestframework-jwt:https://github.com/jpadilla/django-rest-framework-jwt
djangorestframework-simplejwt:https://github.com/jazzband/djangorestframework-simplejwt
区别:https://blog.csdn.net/lady_killer9/article/details/103075076
官网文档:https://jpadilla.github.io/django-rest-framework-jwt/
导入:pip3 install djangorestframework-jwt
步骤
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
path('login/', obtain_jwt_token),
]
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6IkhhbW1lciIsImV4cCI6MTY0OTUyNDY2MiwiZW1haWwiOiIifQ.P1Y8Z3WhdndHoWE0PjW-ygd53Ng0T46U04oY8_0StwI"
}
base64反解
import base64
# 第一段
s1 = b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9'
print(base64.b64decode(s1))
# b'{"typ":"JWT","alg":"HS256"}'
# 第二段
s2 = b'eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6IkhhbW1lciIsImV4cCI6MTY0OTUyNDY2MiwiZW1haWwiOiIifQ=='
print(base64.b64decode(s2))
# b'{"user_id":1,"username":"Hammer","exp":1649524662,"email":""}'
# 我们发现第二段可以反解密出用户信息,是有一定的风险,可以使用,但是不能更改,就好比你的身份证丢了,别人可以在你不挂失的情况下去网吧上网
'''第三段不能不能反解,只能做base64解码,第三段使用base64编码只是为了统一格式'''
我们没有认证的时候,直接访问接口就可以返回数据,比如访问/books/
发送GET请求就可以获取所有book信息,那么现在添加认证,需要访问通过才能访问才更合理
步骤:
key是Authorization
value是jwt token串
Authorization : jwt token串
'''注意jwt和token串中间有空格'''
视图
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework.permissions import IsAuthenticated
class BookView(GenericViewSet,ListModelMixin):
···
# JSONWebTokenAuthentication :rest_framework_jwt模块写的认证类
authentication_classes = [JSONWebTokenAuthentication,]
# 需要配合一个权限类
permission_classes = [IsAuthenticated,]
···
JWT默认的配置是,我们登录成功后只返回一个token串,这也是默认的配置,我们如果想签发token后返回更多数据需要我们自定制
步骤
JWT_AUTH
utils.py
# 定义签发token(登陆接口)返回格式
def jwt_response_payload_handler(token, user=None, request=None):
return {
'code': 100,
'msg': "登陆成功",
'token': token,
'username': user.username
}
settings.py
JWT_AUTH = {
'JWT_RESPONSE_PAYLOAD_HANDLER': 'app01.utils.jwt_response_payload_handler',
}
1.入口:path('login/', obtain_jwt_token)
2.obtain_jwt_token--->obtain_jwt_token = ObtainJSONWebToken.as_view()
ObtainJSONWebToken.as_view(),其实就是一个视图类.as_view()
3.ObtainJSONWebToken类源码
'''
class ObtainJSONWebToken(JSONWebTokenAPIView):
serializer_class = JSONWebTokenSerializer
'''
4.登录签发token肯定需要一个post方法出来,但是ObtainJSONWebToken类内没有父类JSONWebTokenAPIView写了post方法:
def post(self, request, *args, **kwargs):
# 获取数据:{'username': 'Hammer', 'password': '7410'}
serializer = self.get_serializer(data=request.data)
# 校验
if serializer.is_valid():
user = serializer.object.get('user') or request.user # 获取用户
token = serializer.object.get('token') # 获取token
response_data = jwt_response_payload_handler(token, user, request)
# {'code': 100, 'msg': '登陆成功', 'token': 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6IkhhbW1lciIsImV4cCI6MTY0OTU4MTU0NiwiZW1haWwiOiIifQ.2oAjKQ90SV2S9Yxrwppo7BwAOv0xFW4i4AHHBX5Cg2Q', 'username': 'Hammer'}
response = Response(response_data)
if api_settings.JWT_AUTH_COOKIE:
···
return response # 定制什么返回什么
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
5.get_serializer(data=request.data)如何获取到用户数据?
JSONWebTokenSerializer序列化类中全局钩子中获取当前登录用户和签发token
···
payload = jwt_payload_handler(user)
return {
'token': jwt_encode_handler(payload),
'user': user
}
···
签发总结
从obtain_jwt_token开始, 通过ObtainJSONWebToken视图类处理,其实是父类JSONWebTokenAPIView的post方法通过传入的用户名和密码处理获取当前用户,签发了token
# 视图类内认证类搭配权限类使用
authentication_classes = [JSONWebTokenAuthentication, ]
permission_classes = [IsAuthenticated, ]
我们在前面写过,如果需要认证肯定需要重写authenticate方法,这里从列表内的认证类作为入口分析:
'''认证类源码'''
class JSONWebTokenAuthentication(BaseJSONWebTokenAuthentication):
www_authenticate_realm = 'api'
def get_jwt_value(self, request):
# 获取传入的Authorization:jwt token串,然后切分
auth = get_authorization_header(request).split()
auth_header_prefix = api_settings.JWT_AUTH_HEADER_PREFIX.lower()
# 获取不到的情况
if not auth:
if api_settings.JWT_AUTH_COOKIE:
return request.COOKIES.get(api_settings.JWT_AUTH_COOKIE)
return None # 直接返回None,也不会报错,所以必须搭配权限类使用
···
return auth[1] # 一切符合判断条件,通过split切分的列表索引到token串
'''认证类父类源码'''
def authenticate(self, request):
jwt_value = self.get_jwt_value(request) # 获取真正的token,三段式,上面分析
if jwt_value is None: # 如果没传token,就不认证了,直接通过,所以需要配合权限类一起用
return None
try:
payload = jwt_decode_handler(jwt_value)# 验证签名
except jwt.ExpiredSignature:
msg = _('Signature has expired.') # 过期了
raise exceptions.AuthenticationFailed(msg)
except jwt.DecodeError:
msg = _('Error decoding signature.')# 被篡改了
raise exceptions.AuthenticationFailed(msg)
except jwt.InvalidTokenError:
raise exceptions.AuthenticationFailed()# 不知名的错误
user = self.authenticate_credentials(payload)
return (user, jwt_value)
导入:from rest_framework_jwt.views import obtain_jwt_token,refresh_jwt_token,verify_jwt_token
obtain_jwt_token = ObtainJSONWebToken.as_view() # 获取token
refresh_jwt_token = RefreshJSONWebToken.as_view() # 更新token
verify_jwt_token = VerifyJSONWebToken.as_view() # 认证token
refresh_jwt_token
用法
# 配置文件
JWT_AUTH = {
'JWT_ALLOW_REFRESH': True
}
# 路由
path('refresh/', refresh_jwt_token)
verify_jwt_token
用法
path('verify/', verify_jwt_token),
上面我们写道,签发token是基于Django自带的auth_user
表签发,如果我们自定义User表该如何签发token,如下:
视图
# 自定义表签发token
from rest_framework.views import APIView
from rest_framework.viewsets import ViewSetMixin
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework_jwt.settings import api_settings
from app01 import models
class UserView(ViewSetMixin,APIView):
@action(methods=['POST'],detail=False)
def login(self,request):
username = request.data.get('username')
password = request.data.get('password')
user = models.UserInfo.objects.filter(username=username,password=password).first()
response_dict = {'code':None,'msg':None}
# 源码copy错来使用
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
if user:
'''
签发token去源码copy过来使用
'''
# 载荷字典
payload = jwt_payload_handler(user)
print(payload)
# {'user_id': 1, 'username': 'Hammer', 'exp': datetime.datetime(2022, 4, 10, 13, 13, 15, 363206), 'email': '123@qq.com', 'orig_iat': 1649596095}
# 通过荷载得到token串
token = jwt_encode_handler(payload)
response_dict['code'] = 2000
response_dict['msg'] = '登录成功'
response_dict['token'] = token
else:
response_dict['code'] = 4001
response_dict['msg'] = '登录失败,用户名或密码错误'
return Response(response_dict)
模型
# user表
class UserInfo(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=32)
email = models.EmailField()
路由
from rest_framework.routers import SimpleRouter
router = SimpleRouter()
router.register('user',views.UserView,'user')
源码中签发校验都在序列化类中完成,这种写法确实比较常用,我们来使用这种方式自定义,将上面视图的校验逻辑写到序列化类中,这个序列化类只用来做反序列化,这样我们就可以利用 反序列化 的字段校验功能来帮助我们校验(模型中的条件),但是我们不做保存操作
视图
from .serializer import UserInfoSerializer
class UserView(ViewSetMixin,APIView):
@action(methods=['POST'],detail=False)
def login(self,request):
# 如果想获取什么这里可以实例化对象写入,比如request
serializer = UserInfoSerializer(data=request.data, context={'request': request})
response_dict = {'code':None,'msg':None}
# 校验,局部钩子,全局钩子都校验完才算校验通过,走自己的校验规则
if serializer.is_valid():
# 从序列化器对象中获取token和username
token = serializer.context.get('token')
username = serializer.context.get('username')
response_dict['code']=2000
response_dict['msg']='登录成功'
response_dict['token'] = token
response_dict['username'] = username
else:
response_dict['code'] = 4001
response_dict['msg'] = '登录失败,用户名或密码错误'
return Response(response_dict)
序列化器
from rest_framework.exceptions import ValidationError
class UserInfoSerializer(serializers.ModelSerializer):
class Meta:
model = UserInfo
# 根据模型里的字段写
fields = ['username', 'password']
# 全局钩子
def validate(self, attrs):
# attrs是校验过的字段,这里利用
username = attrs.get('username')
password = attrs.get('password')
user = UserInfo.objects.filter(username=username, password=password).first()
from rest_framework_jwt.settings import api_settings
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
if user: # 登录成功
payload = jwt_payload_handler(user) # 得到荷载字典
token = jwt_encode_handler(payload) # 通过荷载得到token串
# 将token放入context字典中
self.context['token'] = token
self.context['username'] = username
# context是serializer和视图类沟通的桥梁
print(self.context.get('request').method)
else: # 登录失败
raise ValidationError('用户名或密码错误')
return attrs
总结
需要我们注意的是,context
只是我们定义的字典,比如上面写到的实例化序列化类中指定的context,那么就可以从序列化类打印出请求的方法,context是序列化类和视图类沟通的桥梁
auth.py
import jwt
from django.utils.translation import ugettext as _
from rest_framework import exceptions
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from rest_framework_jwt.settings import api_settings
from .models import UserInfo
class JWTAuthentication(BaseAuthentication):
def authenticate(self, request):
# 第一步、取出传入的token,从请求头中取
# 这里注意,获取的时候格式为:HTTP_请求头的key大写
jwt_value = request.META.get('HTTP_TOKEN')
jwt_decode_handler = api_settings.JWT_DECODE_HANDLER
# 验证token:验证是否过期,是否被篡改,是否有其他未知错误,从源码copy过来使用
if jwt_value:
try:
payload = jwt_decode_handler(jwt_value)
except jwt.ExpiredSignature:
msg = _('Signature has expired.')
raise exceptions.AuthenticationFailed(msg)
except jwt.DecodeError:
msg = _('Error decoding signature.')
raise exceptions.AuthenticationFailed(msg)
except jwt.InvalidTokenError:
msg = _('Unknown Error.')
raise exceptions.AuthenticationFailed(msg)
# 第二部、通过payload获得当前登录用户,本质是用户信息通过base64编码到token串的第二段载荷中
user = UserInfo.objects.filter(pk=payload['user_id']).first()
# 返回user和token
return (user, jwt_value)
else:
raise AuthenticationFailed('No token was detected')
视图
from rest_framework.viewsets import ModelViewSet
from .models import Book
from .serializer import BookSerializer
from .auth import JWTAuthentication
class BookView(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
authentication_classes = [JWTAuthentication,]
序列化器
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = '__all__'
路由
from rest_framework.routers import SimpleRouter
router = SimpleRouter()
router.register('book',views.BookView,'book')
正常的情况
不携带token的情况
总结
HTTP_KEY
,key要大写HTTP请求的数据在META中
HttpRequest.META
一个标准的Python 字典,包含所有的HTTP 首部。具体的头部信息取决于客户端和服务器,下面是一些示例:
取值:
CONTENT_LENGTH —— 请求的正文的长度(是一个字符串)。
CONTENT_TYPE —— 请求的正文的MIME 类型。
HTTP_ACCEPT —— 响应可接收的Content-Type。
HTTP_ACCEPT_ENCODING —— 响应可接收的编码。
HTTP_ACCEPT_LANGUAGE —— 响应可接收的语言。
HTTP_HOST —— 客服端发送的HTTP Host 头部。
HTTP_REFERER —— Referring 页面。
HTTP_USER_AGENT —— 客户端的user-agent 字符串。
QUERY_STRING —— 单个字符串形式的查询字符串(未解析过的形式)。
REMOTE_ADDR —— 客户端的IP 地址。
REMOTE_HOST —— 客户端的主机名。
REMOTE_USER —— 服务器认证后的用户。
REQUEST_METHOD —— 一个字符串,例如"GET" 或"POST"。
SERVER_NAME —— 服务器的主机名。
SERVER_PORT —— 服务器的端口(是一个字符串)。
从上面可以看到,除 CONTENT_LENGTH 和 CONTENT_TYPE 之外,请求中的任何 HTTP 首部转换为 META 的键时,
都会将所有字母大写并将连接符替换为下划线最后加上 HTTP_ 前缀。
所以,一个叫做 X-Bender 的头部将转换成 META 中的 HTTP_X_BENDER 键。
*** 有错请指正,感谢~
我正在使用 Tornado 与 twitter 等第三方进行身份验证。 我的登录处理程序看起来像这样 class AuthLoginHandler(BaseHandler, tornado.auth.
有没有一种真正的方法可以在 Pylons 中添加身份验证?我见过很多不同的方法,但大多数方法要么过时,要么过于复杂。是否有教程可以解释如何以良好而可靠的方式添加身份验证? 最佳答案 考虑使用 repo
RESTful 身份验证是什么意思,它是如何工作的?我在谷歌上找不到很好的概述。我唯一的理解是您在 URL 中传递了 session key (记住),但这可能是非常错误的。 最佳答案 如何在 RES
我正在考虑在基于插件的系统中实现安全性的多种方式。现在,当我说“安全”时,我的意思是: a) 插件系统的开发人员如何确保插件在核心平台上的使用是安全的。b) 插件开发人员如何确保在其平台上使用的插件是
我正在使用 WCF Webhttp 服务。我创建了一堆服务,剩下的就是放入用户身份验证... 问题 与其余架构风格保持一致,我是否应该针对用户 db 验证每个服务调用。 如果是这样,我应该在每次调用服
假设我想对 Mifare Classic 进行身份验证。 我如何知道要发送到卡的确切类型的 APDU? 例子。 这段代码: bcla = 0xFF; bins = 0x86; bp1 = 0x0;
我通过在文件 xyz.php 中编写以下代码登录到网站。当我运行这个文件时,我会登录到 moodle 网站。有什么方法可以像下面的登录代码一样注销吗? $user = authenticate_use
我有一个应用程序可以匿名访问除几个之外的所有 xpages。我需要强制用户登录这些 xpages。是使用 beforepageload 事件来检查用户登录页面并将其重定向到正确的方式还是有更好的方法?
我想用 ember.js 实现身份验证。 因此,当应用程序启动时,在路由器处理请求的 url 之前,我想检查用户状态。如果用户未通过身份验证,我想保存请求的 url 并重定向到特定的 url (/lo
您如何执行 jQuery Ajax 调用并在发送请求之前对调用进行身份验证? 我还没有登录所以必须进行身份验证。安全不是任何人都可以访问的问题,只需要进行身份验证。它只是基本的 http 身份验证,您
我尝试使用找到的 swift 代码 here在网站上找到here ,但响应是带有两个错误的 html 代码:“您必须输入密码!”和“您必须输入用户名!”我是 NSURLSession 的新手,并尝试更
我正在尝试连接到 Visa Direct API,但我没有通过基本的 SSL 证书认证,这是我的代码: import requests headers = { 'Content
我正在用 tornado 在 python 中开发一个 REST API,我将实现身份验证和授权,试图避免锁定到其他大项目,即 django。我也在通过论坛和 SO 环顾四周,我喜欢一个可能适合的解决
很难说出这里要问什么。这个问题模棱两可、含糊不清、不完整、过于宽泛或夸夸其谈,无法以目前的形式得到合理的回答。如需帮助澄清此问题以便重新打开,visit the help center . 关闭10
如何在 Android 中通过 HTTP 进行身份验证? 最佳答案 我非常难以在 Android 中通过 HTTP 进行身份验证,因为在浏览器(Web 和 Android native )中它工作完美
我有一些关于登录和 session 的问题。我有这段代码: 数据库查询: login: function(req,callback) { var query = 'SELECT id FROM
我开始使用 Swift 开发 iOS 应用。现在我正处于需要创建登录系统的部分。但是,我们需要人们提供的 LinkedIn 信息。 我如何在 iOS 中使用 OAuth2 API 来实现这一点? 我已
如果没有找到用户,问题出在每个 $routeChangeStart 上,如果我只输入 url,它仍然会引导我访问页面。 现在我已经在服务器上重写了规则。 Options +FollowSymlinks
简单代码 require 'net/http' url = URI.parse('get json/other data here [link]') req = Net::HTTP::Get.new(
参考文档: https://docs.sonarqube.org/latest/instance-administration/security/ 概述 SonarQube具有
我是一名优秀的程序员,十分优秀!