最近做的一个需求是:有两个后端服务器,一个是老项目(django),一个是新项目(djangorestframework),老项目不能做大的改动,只能在新项目进行修改,并且前端只能使用老项目的。老项目的登录认证是最简单的方式:数据库保存账号密码,登录时发送账号密码,检测是否正常,即算是登录成功。新后端是使用jwt认证方式,使用Django用户模块保存用户信息。以上是需求的前提,现在要做的是在老项目登录成功后,在前端请求新项目接口也能通过用户认证。
使用cookies保存jwt认证token
在老项目的登录接口中,使用requests方式向新后端发送一个登录请求,将返回的token设置到cookies中
def login(request):
if request.method == 'POST':
user = User.objects.filter(username=username, passowrd=password)
......
res = requests.get('http://www.abc.com/login', data=data)
response = HttpResponse({'result': 'success', 'message': '登录成功'})
if res:
response.set_cookie('access-token', res.get('token'))
return response
前端发送请求时,将token通过header传递到新后端
前端跨域请求
$.ajax({
type: 'GET',
url: 'http://www.abc.com/user/info',
success: function (data) {
console.log(data);
}
});
在进行跨域请求的时候,我们必须将jwt生成的token传递到后端,这里我使用$.ajaxSetup进行全局拦截,给所有进行跨域请求的header上增加access-token,并在后端获取后,使用jwt进行验证。
$.ajaxSetup({
beforeSend: function (xhr, settings) {
if (settings.url.indexOf('www.abc.com') != -1) {
xhr.setRequestHeader('access-token', 'Token1111111')
}
}
});
但是请求的时候报错了,报错如下:
跨域验证失败
这里错误的意思是token在Access-Control-Allow-Headers中不识别,我们在使用Django跨域验证时,使用的是django-cors-headers库,其中有一个配置项:CORS_ALLOW_HEADERS
这里的配置是允许跨域验证的headers,我们在前端请求拦截里给headers中增加了token这个项,因此要在这个配置增加一下
获取数据成功,并可以在前端显示
后端JWT认证
class JwtAuthentication(BaseAuthentication):
def authenticate(self, request):
access_token = request.META.get('HTTP_TOKEN', None)
if access_token:
jwt = JwtUtil()
data = jwt.check_jwt_token(access_token)
if data:
username = data.get('username')
telephone = data.get('telephone')
exp = data.get('exp')
if time.time() > exp:
raise AuthenticationFailed('authentication time out')
try:
user = UserInfo.objects.get(Q(username=username) | Q(telephone=telephone))
_thread_local.user = user
except (UserInfo.DoesNotExist, UserInfo.MultipleObjectsReturned) as e:
return (None, None)
else:
return (user, None)
raise AuthenticationFailed('authentication failed')
获取headers中的HTTP_TOKEN信息,进行jwt认证处理即可
在进行以上处理的时候,我们发现了以下问题
为什么会多了一个OPTIONS请求
Request URL: http://127.0.0.1:8000/info
Request method: OPTIONS
Status Code: 200 OK
Remote Address: 127.0.0.1:8000
Referrer Policy: no-referrer-when-downgrade
看到这里的时候一脸懵逼,为什么捏?于是乎各种搜索
AJAX中出现OPTIONS请求
最全的Ajax跨域详解
跨域资源共享CORS详解
通过以上几篇文章,我知道为什么会变成OPTIONS请求?因为我们增加了自定义的header,所以请求变成了非简单请求。非简单请求和CORS请求会在证实通信之前,增加一次HTTP查询请求,成为“预检”请求(preflight request)。浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP方法和头信息字段,只有得到肯定答复,浏览器才会发出真实的XMLHttpRequest请求,否则就报错。
OPTIONS请求的Response信息如下:
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: accept, accept-encoding, authorization, content-type, dnt, origin, user-agent, x-csrftoken, x-requested-with
Access-Control-Allow-Methods: DELETE, GET, OPTIONS, PATCH, POST, PUT
Access-Control-Allow-Origin: 127.0.0.1:8080
Access-Control-Max-Age: 86400
Content-Length: 0
Content-Type: text/html; charset=utr-8
Access-Control-Allow-Headers显示了后端支持的所有头信息
Access-Control-Allow-Methods显示了后端支持的所有请求类型
而且前端也报了如下错误:
Access to XMLHttpRequest at 'http://127.0.0.1:8000/info' from origin 'http://127.0.0.1:8080' has been blocked by CORS policy: Request header field access-token is not allowed by Access-Control-Allow-Headers in preflight response.
错误的意思是Access-Control-Allow-Headers不支持access-token头部字段。再看上面OPTIONS请求的返回值,可以知道确实是不支持access-token。只要在后端增加配置即可:
CORS_ALLOW_HEADERS = (
'accept',
'accept-encoding',
'authorization',
'content-type',
'dnt',
'origin',
'user-agent',
'x-csrftoken',
'x-requested-with',
'access-token'
)
再次请求发现出现两个请求,第一个为OPTIONS请求,第二个为正常访问的请求。第二个请求头出现了我们发送的access-token信息。
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN, zh; q=0.9,en;q=0.8
access-token: token11111111111,
Cache-Control: no-cache
在后端获取头部信息,进行JWT校验
class JwtAuthentication(BaseAuthentication):
def authenticate(self, request):
access_token = request.META.get('HTTP_ACCESS_TOKEN')
if access_token and access_token != 'null':
try:
jwt = JwtUtils() # 这是自己写的Jwt验证类
token_info = jwt.check_jwt(access_token)
username = token_info.get('username')
try:
user = TblUser.objects.get(username=username)
except TblUser.DoesNotExist as e:
LOG.exception(e)
else:
return (user, None)
except Exception as e:
LOG.exception(e)
raise APIException('Authentication failed')
PS: Django对于header的处理,处理特殊的header项,一般的都使用一下方式处理:
- 所有字符转大写
- 中划线-变为下划线_
- 前面增加HTTP_
因此access-token在后端变成HTTP_ACCESS_TOKEN来获取信息
网友评论