token原理
前言
access_token: 用户每次请求携带,用来校验权限
refresh_token: 服务端存储,当access_token过期了,服务端用来刷新用户的access_token,对用户透明
设计原理
access_token:过期时间为2小时,即两个小时内可用,如过期则用refresh_token重新生成返回给用户
refresh_token:过期时间为48小时,即48小时候强制用户下线,需要重新登录
流程图
通过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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示