目录
一、jwt配置文件
需要记住的配置信息
'JWT_RESPONSE_PAYLOAD_HANDLER': 'app01.utils.jwt_response_payload_handler',
我们通过之前的学习可以得知jwt_response_payload_handler这个方法是定制返回信息的格式的,但是因为要使用我们自定义的方法所以需要在配置信息中注册,这里就是注册信息
'JWT_EXPIRATION_DELTA': datetime.timedelta(seconds=300),
这里是设置jwt认证的过期时间的
需要了解的配置信息
'JWT_AUTH_HEADER_PREFIX': 'JWT',
当我们在用postman朝接口发送请求的时候我们需要在Authorization中输入jwt,同时前面需要写jwt加空格来拼接,这里就是设置开头拼接字符的配置
'JWT_SECRET_KEY': settings.SECRET_KEY,
这就是密匙,不重要但是很关键
二、drf-jwt源码执行流程(了解)
2.0 auth的user表的补充知识
1、django 的auth user表,密码是加密的,即便的同样的密码,密文都不一样
-每次加密,都随机生成一个盐,把盐拼在加密后的串中
# 比如
pbkdf2_sha256$260000$B9ZRmPFpWb3H4kdDDmgYA9$CM3Q/ZfYyXzxwvjZ+HOcdovaJS7681kDsW77hr5fo5o=
明文:lqz12345
盐:B9ZRmPFpWb3H4kdDDmgYA9
后期来了明文lqz12345
2、自定义用户表,生成密码用密文
这里老师点了一些,没有展开讲(不是重点内容,建议课后自行研究),对于密码的加密,用的是make_password方法。
from django.contrib.auth.models import AbstractUser
'我们从auth的models中找相关的方法'
make_password被使用与下方的方法中,通过代码我们可以得知他就是对密码进行了处理
class UserManager(BaseUserManager):
use_in_migrations = True
def _create_user(self, username, email, password, **extra_fields):
"""
Create and save a user with the given username, email, and password.
"""
if not username:
raise ValueError('The given username must be set')
email = self.normalize_email(email)
# Lookup the real model class from the global app registry so this
# manager method can be used in migrations. This is fine because
# managers are by definition working on the real model.
GlobalUserModel = apps.get_model(self.model._meta.app_label, self.model._meta.object_name)
username = GlobalUserModel.normalize_username(username)
user = self.model(username=username, email=email, **extra_fields)
user.password = make_password(password)
user.save(using=self._db)
return user
点进make_password的源码我们开始分析
def make_password(password, salt=None, hasher='default'):
if password is None:
return UNUSABLE_PASSWORD_PREFIX + get_random_string(UNUSABLE_PASSWORD_SUFFIX_LENGTH)
'根据我们的尝试,没传密码的时候就是触发了第一行的if判断,通过查看几个变量和方法的内容,可以简单判断出他就是产生了一串随机字符串'
if not isinstance(password, (bytes, str)):
raise TypeError(
'Password must be a string or bytes, got %s.'
% type(password).__qualname__
)
'根据这里的if判断,我们可以简单看出他是用来判断密码的数据类型的,如果不是字符串或是二进制,就报错'
hasher = get_hasher(hasher)
salt = salt or hasher.salt()
return hasher.encode(password, salt)
'然后这里的get_hasher我看源码也只是一知半解,具体的作用差不多就是对我们传入的密码进行加密,通过注释得知这里的参数就是用来判断是否指定了加密用的某个东西,没有就用默认的,因此hasher就是返回了一个加密用的东西,然后salt我不太明白,但是现在已经不影响我理解整体意思了,最后的return出去的内容就是对密码进行加密并且转换成二进制的内容,'
def get_hasher(algorithm='default'):
"""
Return an instance of a loaded password hasher.
If algorithm is 'default', return the default hasher. Lazily import hashers
specified in the project's settings file if needed.
"""
if hasattr(algorithm, 'algorithm'):
return algorithm
elif algorithm == 'default':
return get_hashers()[0]
else:
hashers = get_hashers_by_algorithm()
try:
return hashers[algorithm]
except KeyError:
raise ValueError("Unknown password hashing algorithm '%s'. "
"Did you specify it in the PASSWORD_HASHERS "
"setting?" % algorithm)
'通过会议encode的知识,我们可以得知这个salt其实是用于指定字符编码类型的,那我们可以产生一个模糊认知,我们只用了salt这个东西,就完成了密码的加密,并指定了转换成二进制时的编码类型'
讲解完了密码的加密,自然得有密码的校验,这里老师也做了提示,check_password方法就是用于校验密码的(回头有空再写,感觉博客来不及了)。
3、用户表密码忘了怎么办
方法一:新增一个用户,创建完成后把这个用户的密码复制到数据库中忘记密码的账号的密码的位置(建议用完改回去,不然知道密码的人用不了了解释起来也麻烦)
方式二:直接去掉认证,让这个用户直接可以登陆(更不推荐了)
2.1 签发(登录)
# 登录接口,路由匹配成功,执行obtain_jwt_token---》post请求---》ObtainJSONWebToken的post方法
path('login/', obtain_jwt_token),
# ObtainJSONWebToken的post方法 继承APIView
def post(self, request, *args, **kwargs):
# 实例化得到序列化类
serializer = self.get_serializer(data=request.data)
# 做校验:字段自己,局部钩子,全局钩子
if serializer.is_valid():
# user:当前登录用户
user = serializer.object.get('user') or request.user
# 签发的token
token = serializer.object.get('token')
# 构造返回格式,咱们可以自定制---》讲过了
response_data = jwt_response_payload_handler(token, user, request)
response = Response(response_data)
if api_settings.JWT_AUTH_COOKIE:
expiration = (datetime.utcnow() +
api_settings.JWT_EXPIRATION_DELTA)
response.set_cookie(api_settings.JWT_AUTH_COOKIE,
token,
expires=expiration,
httponly=True)
#最终返回了咱们定制的返回格式
return response
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
# 如何得到user,如何签发的token----》在序列化类的全局钩子中得到的user和签发的token
-JSONWebTokenSerializer---全局钩子---validate
#前端传入,校验过后的数据---》{"username":"lqz","password":"lqz1e2345"}
def validate(self, attrs):
credentials = {
# self.username_field: attrs.get(self.username_field),
'username':attrs.get('username')
'password': attrs.get('password')
}
if all(credentials.values()):
# auth 模块,authenticate 可以传入用户名,密码如果用户存在,就返回用户对象,如果不存就是None
# 正确的用户
user = authenticate(**credentials)
if user:
# 校验用户是否是活跃用户,如果禁用了,不能登录成功
if not user.is_active:
msg = _('User account is disabled.')
raise serializers.ValidationError(msg)
# 荷载----》通过user得到荷载 {id,name,email,exp}
payload = jwt_payload_handler(user)
return {
# jwt_encode_handler通过荷载得到token串
'token': jwt_encode_handler(payload),
'user': user
}
else:
msg = _('Unable to log in with provided credentials.')
raise serializers.ValidationError(msg)
else:
msg = _('Must include "{username_field}" and "password".')
msg = msg.format(username_field=self.username_field)
raise serializers.ValidationError(msg)
### 重点:
1 通过user得到荷载:payload = jwt_payload_handler(user)
2 通过荷载签发token:jwt_encode_handler(payload)
## 了解:
# 翻译函数,只要做了国际化,放的英文,会翻译成该国语言(配置文件配置的)
from django.utils.translation import ugettext as _
msg = _('Unable to log in with provided credentials.')
2.2 认证(认证类)
# JSONWebTokenAuthentication---->父类BaseJSONWebTokenAuthentication----》authenticate方法
def authenticate(self, request):
# 前端带在请求头中的token 值
jwt_value = self.get_jwt_value(request)
# 如果没有携带token,就不校验了
if jwt_value is None:
return None
try:
# jwt_value就是token
# 通过token,得到荷载,中途会出错
# 出错的原因:
-篡改token
-过期了
-未知错误
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()
# 如果能顺利解开,没有被异常捕获,说明token是可以信任的
# payload就可以使用,通过payload得到当前登录用户
user = self.authenticate_credentials(payload)
# 返回当前登录用户,token
return (user, jwt_value)
# jwt_value = self.get_jwt_value(request)
def get_jwt_value(self, request):
# 拿到了前端请求头中传入的 jwt dasdfasdfasdfa
# auth=[jwt,asdfasdfasdf]
auth = get_authorization_header(request).split()
# 'jwt'
auth_header_prefix = api_settings.JWT_AUTH_HEADER_PREFIX.lower()
if not auth:
# 请求头中如果没带,去cookie中取
if api_settings.JWT_AUTH_COOKIE:
return request.COOKIES.get(api_settings.JWT_AUTH_COOKIE)
return None
if smart_text(auth[0].lower()) != auth_header_prefix:
return None
if len(auth) == 1:
msg = _('Invalid Authorization header. No credentials provided.')
raise exceptions.AuthenticationFailed(msg)
elif len(auth) > 2:
msg = _('Invalid Authorization header. Credentials string '
'should not contain spaces.')
raise exceptions.AuthenticationFailed(msg)
return auth[1]
# 认证类配置了,如果不传jwt,不会校验,一定配合权限类使用
三、自定义用户表实现jwt的签发和认证
3.1 签发
from rest_framework_jwt.settings import api_settings
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
from rest_framework.viewsets import ViewSet
from rest_framework.decorators import action
from rest_framework.response import Response
from .models import UserInfo
from .authentication import JsonWebTokenAuthentication
class UserView(ViewSet):
@action(methods=['POST'], detail=False)
def login(self, request, *args, **kwargs):
username = request.data.get('username')
password = request.data.get('password')
user = UserInfo.objects.filter(username=username, password=password).first()
if user:
# 登录成功,签发token
# 通过user得到payload
payload = jwt_payload_handler(user)
# 通过payload得到token
token = jwt_encode_handler(payload)
return Response({'code': 1000, 'msg': '登录成功', 'token': token})
else:
return Response({'code': 1001, 'msg': '用户名或密码错误'})
3.2 认证
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
import jwt
from .models import UserInfo
from rest_framework_jwt.settings import api_settings
jwt_decode_handler = api_settings.JWT_DECODE_HANDLER
class JsonWebTokenAuthentication(BaseAuthentication):
def authenticate(self, request):
# 取出token----》请求头中,就叫token
token = request.META.get('HTTP_TOKEN')
if token:
try:
payload = jwt_decode_handler(token)
# 得到当前登录用户----》
user = UserInfo.objects.get(pk=payload.get('user_id'))
# 只要访问一次需要登录的接口,就会去UserInfo表中查一次用户---》优化
# user=UserInfo(id=payload.get('user_id'),username=payload.get('username'))
# user={'id':payload.get('user_id')}
return user, token
except jwt.ExpiredSignature:
raise AuthenticationFailed('token过期')
except jwt.DecodeError:
raise AuthenticationFailed('token认证失败')
except jwt.InvalidTokenError:
raise AuthenticationFailed('token无效')
except Exception as e:
raise AuthenticationFailed('未知异常')
raise AuthenticationFailed('token没有传,认证失败')
四、simpleui
# 之前公司里,做项目,要使用权限,要快速搭建后台管理,使用djagno的admin直接搭建,django的admin界面不好
#第三方的美化:
-xadmin:作者弃坑了,bootstrap+jq
-simpleui: vue,界面更好看
# 现在阶段,一般前后端分离比较多:django+vue
-带权限的前端端分离的快速开发框架
-django-vue-admin
-自己写
4.1 使用步骤
# 1 安装
pip install django-simpleui
# 2 在app中注册
# 3 调整左侧导航栏----》
-menu_display对应menus name
-如果是项目的app,就menus写app
-菜单可以多级,一般咱们内部app,都是一级
-可以增加除咱们app外的其它链接---》如果是外部链接,直接写地址,如果是内部链接,跟之前前后端混合项目一样的写法:咱们的案例---》show 的路由
SIMPLEUI_CONFIG = {
'system_keep': False,
'menu_display': ['图书管理', '权限认证', '张红测试'], # 开启排序和过滤功能, 不填此字段为默认排序和全部显示, 空列表[] 为全部不显示.
'dynamic': True, # 设置是否开启动态菜单, 默认为False. 如果开启, 则会在每次用户登陆时动态展示菜单内容
'menus': [
{
'name': '图书管理',
'app': 'app01',
'icon': 'fas fa-code',
'models': [
{
'name': '图书',
'icon': 'fa fa-user',
'url': 'app01/book/'
},
{
'name': '出版社',
'icon': 'fa fa-user',
'url': 'app01/publisssh/'
},
{
'name': '作者',
'icon': 'fa fa-user',
'url': 'app01/author/'
},
{
'name': '作者详情',
'icon': 'fa fa-user',
'url': 'app01/authordetail/'
},
]
},
{
'app': 'auth',
'name': '权限认证',
'icon': 'fas fa-user-shield',
'models': [
{
'name': '用户',
'icon': 'fa fa-user',
'url': 'auth/user/'
},
{
'name': '组',
'icon': 'fa fa-user',
'url': 'auth/group/'
},
]
},
{
'name': '张红测试',
'icon': 'fa fa-file',
'models': [
{
'name': 'Baidu',
'icon': 'far fa-surprise',
# 第三级菜单 ,
'models': [
{
'name': '爱奇艺',
'url': 'https://www.iqiyi.com/dianshiju/'
# 第四级就不支持了,element只支持了3级
}, {
'name': '百度问答',
'icon': 'far fa-surprise',
'url': 'https://zhidao.baidu.com/'
}
]
},
{
'name': '大屏展示',
'url': '/show/',
'icon': 'fab fa-github'
}]
}
]
}
# 4 内部app,图书管理系统某个链接要展示的字段---》在admin.py 中----》自定义按钮
@admin.register(Book)
class BookAdmin(admin.ModelAdmin):
list_display = ('nid', 'name', 'price', 'publish_date', 'publish')
# 增加自定义按钮
actions = ['custom_button']
def custom_button(self, request, queryset):
print(queryset)
custom_button.confirm = '你是否执意要点击这个按钮?'
# 显示的文本,与django admin一致
custom_button.short_description = '测试按钮'
# icon,参考element-ui icon与https://fontawesome.com
# custom_button.icon = 'fas fa-audio-description'
# # 指定element-ui的按钮类型,参考https://element.eleme.cn/#/zh-CN/component/button
custom_button.type = 'danger'
# # 给按钮追加自定义的颜色
# custom_button.style = 'color:black;'
# 5 app名字显示中文,字段名字显示中文
-新增,查看修改展示中文,在表模型的字段上加:verbose_name='图书名字',help_text='这里填图书名'
-app名字中文:apps.py---》verbose_name = '图书管理系统'
# 6 其它配置项
SIMPLEUI_LOGIN_PARTICLES = False #登录页面动态效果
SIMPLEUI_LOGO = 'https://avatars2.githubusercontent.com/u/13655483?s=60&v=4'#图标替换
SIMPLEUI_HOME_INFO = False #首页右侧github提示
SIMPLEUI_HOME_QUICK = False #快捷操作
SIMPLEUI_HOME_ACTION = False # 动作
4.2 大屏展示
# 监控大屏展示
-https://search.gitee.com/?skin=rec&type=repository&q=%E5%B1%95%E7%A4%BA%E5%A4%A7%E5%B1%8F
-就是前后端混合项目,js,css,图片对应好,就可以了
五、权限控制(acl,rbac)
# 公司内部项目
-rbac:是基于角色的访问控制(Role-Based Access Control )在 RBAC 中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。这就极大地简化了权限的管理。这样管理都是层级相互依赖的,权限赋予给角色,而把角色又赋予用户,这样的权限设计很清楚,管理起来很方便
-用户表:用户和角色多对多关系,
-角色表
-一个角色可能有多个权限----》
-开发角色:拉取代码,上传代码
-财务角色:开工资,招人,开除人
-
-权限表:角色和权限是多多多
-拉取代码
-上传代码
-部署项目
-开工资
-招人
-开除人
-
-通过5张表完成rbac控制:用户表,角色表,权限表, 用户角色中间表, 角色权限中间表
-如果某个人,属于财务角色,单只想要拉取代码权限,不要上传代码权限
-通过6张表:django的admin----》后台管理就是使用这套权限认证
用户表,
角色表,
权限表,
用户角色中间表,
角色权限中间表
用户和权限中间表
# 互联网项目
-acl:Access Control List 访问控制列表,权限放在列表中
-权限:权限表----》 发视频,评论,开直播
-用户表:用户和权限是一对多
张三:[发视频,]
李四:[发视频,评论,开直播]
# 演示了 django-admin 的权限控制
-授予lqz 某个组
-单独授予权限
# django -auth--6张表
auth_user 用户表
auth_group 角色表,组表
auth_permission 权限表
-----------
auth_user_groups 用户和角色中间表
auth_group_permissions 角色和权限中间表
-------------
auth_user_user_permissions 用户和权限中间表
# java:若依
# go :gin-vue-admin
# python :django-vue-admin
六、作业
1 整理 jwt 签发,认证流程
2 自定义用户表,签发,认证
3 自定义用户表,使用make_password保存密码
4 simpleui 写个图书管理系统
5 演示一下admin 的rbac
----------------------
6 研究 python casbin 支持acl rbac,abac。。。。权限控制
7 双token认证