token原理

前言

access_token: 用户每次请求携带,用来校验权限
refresh_token: 服务端存储,当access_token过期了,服务端用来刷新用户的access_token,对用户透明

设计原理

access_token:过期时间为2小时,即两个小时内可用,如过期则用refresh_token重新生成返回给用户
refresh_token:过期时间为48小时,即48小时候强制用户下线,需要重新登录

流程图

image

通过django中间件实现

class CheckLoginMiddleware(MiddlewareMixin):
    def process_request(self, request):
        remote_address = request.META.get("REMOTE_ADDR", '')
        req_url = re.sub('^/api/', '/', request.path_info)
        request.need_refresh = False
        request._user_name = None
        http_auth = request.META.get("HTTP_AUTHORIZATION", '')
        try:
            auth = http_auth.split()
        except AttributeError:
            logger.exception(f'token解析异常, http_auth={http_auth}')
            return HttpResponse(status=401)

        if req_url.startswith("/ws/") or req_url.startswith("/wss/"):
            if request.user.is_authenticated:
                logger.debug('is websocket pass')
                return None
            else:
                return HttpResponse(status=403)
        # 如果是本机调试,直接通过
        if remote_address == '127.0.0.1':
            logger.debug('is 127.0.0.1 pass check_login_permission')
            return None
        # 检查白名单, 登录,获取token直接通过
        if req_url in white_url_list:
            logger.debug('url is in whiteList pass check_login_permission')
            return None
        try:
            access_token = auth[1]
        except Exception as e:
            logger.exception(f'access token解析异常, auth={auth}')
            return HttpResponse(status=401)

        # 检查access_token状态
        access_token_res = self.validate_token(token=access_token)
        access_token_status = access_token_res.get('status')
        if access_token_status == TOKEN_INVALID:
            return HttpResponse(status=401)
        elif access_token_status == TOKEN_EXPIRED:
            # access_token过期
            # 使用refresh_token更新access_token
            # 如果refresh_token过期,返回401
            user_uid = access_token_res.get('user_uid')
            refresh_token = CacheDevopsData().get_refresh_token_by_user_uid(user_uid)
            # 检查refresh_token状态
            refresh_token_res = self.validate_token(refresh_token)
            refresh_token_status = refresh_token_res.get('status')
            if refresh_token_status != TOKEN_NORMAL:
                logger.error(f'refresh token error, return 401')
                return HttpResponse(status=401)
            # refresh_token状态正常时,才可以用它刷新access_token
            new_token = Token(user_uid)
            new_access_token = new_token.token
            request.need_refresh = True
            request.access_token = new_access_token
            request._user_name = access_token_res.get("user_name")
            request._user_uid = user_uid
            return None

        # 获取缓存的用户信息
        redis = get_redis_connection()
        user_info = redis.get(access_token)
        if not user_info:
            logger.debug('not userinfo return 401')
            return HttpResponse(status=401)
        user_info = json.loads(userInfo)
        request._user_name = user_info.get('user_name')
        request._user_uid = user_info.get('user_uid')
        # 如果是系统管理员直接通过
        if user_info['sys_admin']:
            logger.debug('user is admin role pass check_login_permission')
            return None
        # 检查权限, 若使用了DRF的接口patch/delete的url后会自动添加ID,影响url校验
        if request.method == 'GET' or request.method == "PATCH" or request.method == "DELETE":
            return None
        api_privileges = userInfo['apiPrivileges']
        if req_url not in api_privileges:
            return JsonResponse({'code': -1, 'message': '没有该权限'})

    def validate_token(self, token):
	"""校验token是否有效"""
        try:
            jwt_decoded = jwt.decode(token, settings.SECRET_KEY, algorithms=['HS256'])
            return {'status': TOKEN_NORMAL, **self.get_user_from_token(jwt_decoded)}
        except jwt.ExpiredSignatureError:
            data_dict_expired = jwt.decode(token, settings.SECRET_KEY, algorithms=['HS256'], verify=False)
            return {'status': TOKEN_EXPIRED, **self.get_user_from_token(data_dict_expired)}
        except jwt.InvalidTokenError:
            logger.exception('token无效')
            return {'status': TOKEN_INVALID}
        except Exception as e:
            logger.exception('jwt解密异常')
            return {'status': TOKEN_INVALID}

    def process_response(self, request, response):
        if request.need_refresh:
            response['Authorization'] = request.access_token
            # 响应头添加自定义的字段Authorization
            # 默认情况下,前端获取不到自定义字段
            # 所以需要把这个字段添加到Access-Control-Expose-Headers
            response['Access-Control-Expose-Headers'] = 'Authorization'
        return response
posted @ 2022-05-18 11:19  大切切  阅读(93)  评论(0编辑  收藏  举报