基于自定义表编写认证类、django-jwt源码分析、权限介绍、simpleui的使用
基于自定义表编写认证类
补充:翻译函数
只要做了国际化,就会显示当前国家的语言from django.utils.translation import gettext_lazy as _ msg = _('Signature has expired.') # _是个函数的别名,这个函数是翻译函数,只要做了国际化,它就是中文
认证类
from rest_framework_jwt.authentication import JSONWebTokenAuthentication from rest_framework.authentication import BaseAuthentication from rest_framework.exceptions import AuthenticationFailed import jwt from rest_framework_jwt.settings import api_settings from .models import User jwt_decode_handler = api_settings.JWT_DECODE_HANDLER from django.utils.translation import ugettext as _ class JWTLoginAuthentication(BaseAuthentication): def authenticate(self, request): # 1 取出用户传入的token----》带在哪里?后端规定的---》规定放在请求头 token=asfas.asfdas.asdfa token = request.META.get('HTTP_TOKEN') # 2 验证token-->django-jwt 提供了一个校验的方法---》 jwt_decode_handler 通过传入token传,校验,校验通过返回payload,校验失败抛异常 try: payload = jwt_decode_handler(token) # 通过payload 得到当前用户 # 认证类,配好后,只要有认证的,都会走这里---》每次走这里都会查询一次数据库 # 做个优化? # 1 自己根据payload数据,创建一个用户,有的参数没传,会使用默认值,跟我真实用户还是有区别的 # user=User(id=payload.get('user_id'),username=payload.get('username')) # 2 直接返回用户id,后续 的request.user 都是用户id,如果真正要用的时候,再查 user = User.objects.get(pk=payload.get('user_id')) except jwt.ExpiredSignature as e: # msg = _('Signature has expired.') # _是个函数的别名,这个函数是翻译函数,只要做了国际化,它就是中文 msg = '签名过期' raise AuthenticationFailed(msg) except jwt.DecodeError: msg = 'token错误' raise AuthenticationFailed(msg) except jwt.InvalidTokenError: raise AuthenticationFailed('不合法的token') except Exception: raise AuthenticationFailed('未知错误,请联系系统管理员') return (user, token) # 后续的request.user 都是用户id,如果真正要用的时候,再查 ''' 每次访问需要登录的接口,都会去查数据库 所以咱们可以做优化 1 自己生成一个user对象 2 直接返回user_id,以后用的时候,再查数据库 '''
登录接口
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER from rest_framework.viewsets import ViewSet from rest_framework.decorators import action # ### ### ### ### ##1 自己定义的用户表,签发token# ### ### ### ### ### ## class UserView(ViewSet): @action(methods=['POST'],detail=False) def login(self, request): username = request.data.get('username') password = request.data.get('password') user = User.objects.filter(username=username, password=password).first() if user: # 签发token # 1 通过user生成payload---》jwt 提供一个方法(username),传入user,返回payload payload = jwt_payload_handler(user) # payload={'username':'asdfasdf','exp':1694401763} # 2 生成token---》jwt提供了方法,把payload放入--》token token = jwt_encode_handler(payload) return Response({'code': 101, 'msg': '登录成功', 'token': token, 'username': user.username}) else: return Response({'code': 101, 'msg': '用户名或密码错误'})
路由
router.register('user', UserView, 'user') # 127.0.0.1:8000/user/login 的post请求 router.register('books', BooView, 'books') # 127.0.0.1:8000/user/login 的post请求
视图类
from rest_framework.viewsets import GenericViewSet # 登录后才能访问 from .auth import JWTLoginAuthentication class BooView(GenericViewSet): authentication_classes = [JWTLoginAuthentication] def list(self, request): return Response("你看到我了")
django-jwt源码分析
签发
# 1 入口是:obtain_jwt_token---》from rest_framework_jwt.views import obtain_jwt_token # 2 obtain_jwt_token本质是:ObtainJSONWebToken.as_view() # 3 看ObtainJSONWebToken视图类 -前端post请求,提交了用户名密码,就能签发token -去ObtainJSONWebToken中找---》post # 4 发现没有 class ObtainJSONWebToken(JSONWebTokenAPIView): serializer_class = JSONWebTokenSerializer #就是它 # 5 去父类中找JSONWebTokenAPIView class JSONWebTokenAPIView(APIView): def post(self, request, *args, **kwargs): #serializer = self.get_serializer(data=request.data) # 配置的序列化类 serializer =JSONWebTokenSerializer(data=request.data) if serializer.is_valid(): # 字段自己,局部,全局 user = serializer.object.get('user') token = serializer.object.get('token') # 定制返回格式--》如果咱们写了,走自己的 response_data = jwt_response_payload_handler(token, user, request) response = Response(response_data) return response return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) # 6 校验用户名密码和生成token,写在了序列化类的--》全局钩子validate中--》JSONWebTokenSerializer class JSONWebTokenSerializer(Serializer): def validate(self, attrs): credentials = { 'username': attrs.get("username"), 'password': attrs.get('password') } if all(credentials.values()): # credentials字典的value必须不能为空 # authenticate django的auth,通过用户名和明文密码校验用户是否存的函数:authenticate # User.objects.filter(username='lqz',password='123').first() # user = authenticate(username='lqz',password='123') user = authenticate(**credentials) if user: # 登录成功,前token if not user.is_active: # 判断用户是否锁定 msg = _('User account is disabled.') raise serializers.ValidationError(msg) payload = jwt_payload_handler(user) return { '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 前端携带用户名密码到后端---》执行后端你的post方法---》后端生成一个序列化类的对象---》执行校验---》走了全局钩子--》全局钩子中通过用户名密码获取用户(如果获取不到抛异常)---》获取到后签发token---》签发完返回----》在视图类中---》取出来,返回给前端
认证
# 1 从哪开始看---》认证类JSONWebTokenAuthentication # 2 JSONWebTokenAuthentication 的 class JSONWebTokenAuthentication(BaseJSONWebTokenAuthentication): def get_jwt_value(self, request): 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 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] # 2 父类中BaseJSONWebTokenAuthentication的authenticate def authenticate(self, request): # 拿出前端传入的token,可能前端没传,就是None jwt_value = self.get_jwt_value(request) if jwt_value is None: # None就不管了,认证类继续往后走,相当于没有认证,于是咱们才需要权限类 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) # 总结:逻辑,跟咱们自定义的一模一样 -获取token复杂一点,有个前端
权限介绍
所有项目都会有权限控制===》https://www.cnblogs.com/liuqingzheng/articles/16972464.html
ACL(Access Control List,访问控制列表)
===》针对互联网用户,多半是这个
将用户与权限对接(多对多)
张三 [发视频,点赞,评论]
李四 [看视频]
RBAC(Role-Based Access Control,基于属性的访问控制)
===》公司内部项目
将用户与角色对接,然后角色与对象的权限对接
django 的 admin + auth 就是使用了这套认证机制
ABAC(Attribute-Based Access Control,基于属性的访问控制)
又称 PBAC(Policy-Based Access Control,基于策略的访问控制),CBAC(Claims-Based Access Control,基于声明的访问控制)
传统的ACL、RBAC的架构是{subject,action,object},而ABAC的架构是{subject,action,object,contextual}且为他们添加了parameter(参数)。
subject属性:比如用户的年龄、部门、角色、威望、积分等主题属性
action属性:比如查看、读取、编辑、删除等行为属性。
object属性:比如银行账户、文章、评论等对象或资源属性
contextual:比如时段、IP位置、天气等环境属性
python写公司内部项目比较多,使用RBAC控制居多
RBAC 是基于角色的访问控制(Role-Based Access Control )在 RBAC 中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。这就极大地简化了权限的管理。这样管理都是层级相互依赖的,权限赋予给角色,而把角色又赋予用户,这样的权限设计很清楚,管理起来很方便。
RBAC表如何设计
用户表==》一堆用户:张三、李四等
角色表(group组)==》用户的角色:后勤部、开发部、总裁办等
权限表==》放了一堆权限:发电脑、提交代码、删除代码、发工资等
用户和角色多对多==》一个用户属于多个角色,一个角色属于多个用户
角色和权限多对多==》一个角色有多个权限,一个权限属于多个角色
--------rabc----通过5张表可以实现
-django为了更细粒度划分---》多了一张表,用户和权限多对多
simpleui的使用
公司内部,做公司内的项目需要使用这套权限控制
方案一:使用django-admin写
有的公司,不怎么写前端,直接使用django的admin,快速写出一套具有权限管理的系统
django admin的界面不好看:第三方美化--》simpleui
方案二:自己写,前端使用vue,后端使用django,做公司内部的项目
第三方开源的权限控制 项目
python界:django-vue-admin 7期学长写的
java界:若依
go界:gin-vue-admin
django admin的美化: simpleui