从 Ember 发送到 DRF API 时出现 406(不可接受)
406 (Not Acceptable) when POSTing from Ember to DRF API
我正在使用 ember-simple-auth
和 ember-simple-auth-token
来允许用户登录我的应用程序。但是,当我使用 POST 请求在后端调用 Django Rest Framework 以使用用户名和密码进行身份验证时,我收到 406(不可接受)错误。这在 DRF 可浏览 API 中不会发生,因此后端似乎工作正常。
我怀疑与 CORS 有关。我在 Django 中使用 django-cors-headers
,并在我的开发环境中允许所有。如果重要的话,我也使用 django-rest-framework-jwt
和 django-rest-framework-json-api
包。
我的 API 显示了一个选项,然后是一个 POST 调用:
[09/Mar/2016 07:15:54] "OPTIONS /api-token-auth/ HTTP/1.1" 200 0
[09/Mar/2016 07:15:54] "POST /api-token-auth/ HTTP/1.1" 406 114
回应headers:
HTTP/1.0 406 Not Acceptable
Date: Wed, 09 Mar 2016 07:15:54 GMT
Server: WSGIServer/0.2 CPython/3.5.1
X-Frame-Options: SAMEORIGIN
Access-Control-Allow-Origin: *
Content-Type: application/vnd.api+json
Allow: POST, OPTIONS
Vary: Accept
请求headers:
POST /api-token-auth/ HTTP/1.1
Host: localhost:8000
Connection: keep-alive
Content-Length: 2
Accept: application/json, text/javascript
Origin: http://localhost:4200
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/48.0.2564.116 Chrome/48.0.2564.116 Safari/537.36
Content-Type: application/json
Referer: http://localhost:4200/login
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.8
请求 headers 不显示 application/vnd.api+json
,而是显示 application/json
。不幸的是,无论我在 Ember 中做什么都能够解决这个问题。我尝试将应用程序的 JSONAPI 适配器的 headers 设置为 "Accept": "application/vnd.api+json"
并在 ENV['ember-simple-auth-token']
.
中未成功
您应该能够在适配器中明确设置内容类型:
export default DS.JSONAPIAdapter.extend({
// set content-type upon every ajax request
ajax: function(url, type, hash){
hash = hash || {} ;
hash.headers = hash.headers || {};
hash.headers['Content-Type'] = 'application/vnd.api+json';
return this._super(url, type, hash);
}
});
它能解决您的问题吗?
实施您自己的身份验证器,它设置 headers 在身份验证请求期间使用:
// your-app/authenticators/your-custom-authenticator.js
import OAuth2PasswordGrant from 'ember-simple-auth/authenticators/oauth2-password-grant';
export default OAuth2PasswordGrant.extend({
/**
* your backend authentication endpoint
* @overrides
*/
serverTokenEndpoint: `https://your.authentication.endpoint.sth/login`,
/**
* Makes a request to the authentication server.
* This is what you need to override to customize your headers
* set up for purposes of authentication.
* @overrides
*/
makeRequest(url, data) {
const options = {
url: url,
data: data,
type: 'GET',
dataType: 'json',
accept: 'application/vnd.api+json',
headers: {
"Content-Type": 'application/vnd.api+json'
}
};
return Ember.$.ajax(options);
}
});
在您的(登录)route/controller/wherever中引用此自定义身份验证器,您需要:
this.get('session').authenticate('authenticator:yourCustomAuthenticator', username, password).then(() => {
// success, redirect, as you like..
})
查看 ember-simple-auth 文档的 Authenticators 部分,选择最接近您需要的 parent 身份验证器:ember-simple-auth - Authenticators
我或多或少设法解决了这个问题。这是一个不幸的软件包组合,导致 JSON API 规范在 Ember 和 DRF 之间出现一些问题。
首先,通过简单地将 headers 作为参数添加到 .authenticate
中,我设法在 controllers/login.js
中覆盖了 headers。任何参数都会传递给 ember-simple-auth
验证器。 (正如 Pavol 在他的回答中所建议的那样,我不需要实现自己的身份验证器。)
// controllers/login.js
import Ember from 'ember';
export default Ember.Controller.extend({
session: Ember.inject.service('session'),
actions: {
authenticate: function() {
var credentials = this.getProperties('identification', 'password'),
authenticator = 'authenticator:jwt',
// Set headers to accept JSON API format
headers = {
'Accept': 'application/vnd.api+json',
'Content-Type': 'application/vnd.api+json'
};
this.get('session').authenticate(authenticator, credentials, headers);
}
}
});
这引入了下一个问题:我的内容类型实际上不是 JSON API 规范,所以我确实需要实现自己的身份验证器来将 ember-simple-auth-token
的 JWT 身份验证器转换为产生 JSON API 规范兼容格式。没有让它工作,但像这样:
// authenticators/jwt.js
import Base from 'ember-simple-auth-token/authenticators/token';
export default Base.extend({
/**
Returns an object used to be sent for authentication.
@method getAuthenticateData
@return {object} An object with properties for authentication.
*/
// Make sure this is JSON API compatible format.
getAuthenticateData(credentials) {
const authentication = {
// This is apparently not valid JSON API spec, but you get the gist...
'data': [{
[this.identificationField]: credentials.identification,
[this.passwordField]: credentials.password
}]
};
return authentication;
}
});
现在,在后端,rest_framework_jwt
和 rest_framework_json_api
仍然不能很好地协同工作。
在这一点上,我决定在 auth 端点上放弃对 JSON API 规范的需求要简单得多:Ember 的包没有产生它,而 DRF 无法解析它!
因此,我还原了 Ember 方面的所有内容,让它根据我原来的问题生成请求。在 DRF 方面,我将 rest_framework_jwt
的视图子类化并将解析器设置为 DRF 的默认值 JSONParser
:
"""
Make the JWT Views ignore JSON API package and use standard JSON.
"""
from rest_framework_jwt.views import ObtainJSONWebToken, RefreshJSONWebToken, \
VerifyJSONWebToken
from rest_framework.parsers import JSONParser
from rest_framework.renderers import JSONRenderer
class ObtainJSONWebTokenPlainJSON(ObtainJSONWebToken):
parser_classes = (JSONParser, )
renderer_classes = (JSONRenderer, )
class RefreshJSONWebTokenPlainJSON(RefreshJSONWebToken):
parser_classes = (JSONParser, )
renderer_classes = (JSONRenderer,)
class VerifyJSONWebTokenPlainJSON(VerifyJSONWebToken):
parser_classes = (JSONParser, )
renderer_classes = (JSONRenderer,)
最终结果:通过让我的 API 遵循 JSON API 规范来解决,除了令牌身份验证端点。
我正在使用 ember-simple-auth
和 ember-simple-auth-token
来允许用户登录我的应用程序。但是,当我使用 POST 请求在后端调用 Django Rest Framework 以使用用户名和密码进行身份验证时,我收到 406(不可接受)错误。这在 DRF 可浏览 API 中不会发生,因此后端似乎工作正常。
我怀疑与 CORS 有关。我在 Django 中使用 django-cors-headers
,并在我的开发环境中允许所有。如果重要的话,我也使用 django-rest-framework-jwt
和 django-rest-framework-json-api
包。
我的 API 显示了一个选项,然后是一个 POST 调用:
[09/Mar/2016 07:15:54] "OPTIONS /api-token-auth/ HTTP/1.1" 200 0
[09/Mar/2016 07:15:54] "POST /api-token-auth/ HTTP/1.1" 406 114
回应headers:
HTTP/1.0 406 Not Acceptable
Date: Wed, 09 Mar 2016 07:15:54 GMT
Server: WSGIServer/0.2 CPython/3.5.1
X-Frame-Options: SAMEORIGIN
Access-Control-Allow-Origin: *
Content-Type: application/vnd.api+json
Allow: POST, OPTIONS
Vary: Accept
请求headers:
POST /api-token-auth/ HTTP/1.1
Host: localhost:8000
Connection: keep-alive
Content-Length: 2
Accept: application/json, text/javascript
Origin: http://localhost:4200
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/48.0.2564.116 Chrome/48.0.2564.116 Safari/537.36
Content-Type: application/json
Referer: http://localhost:4200/login
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.8
请求 headers 不显示 application/vnd.api+json
,而是显示 application/json
。不幸的是,无论我在 Ember 中做什么都能够解决这个问题。我尝试将应用程序的 JSONAPI 适配器的 headers 设置为 "Accept": "application/vnd.api+json"
并在 ENV['ember-simple-auth-token']
.
您应该能够在适配器中明确设置内容类型:
export default DS.JSONAPIAdapter.extend({
// set content-type upon every ajax request
ajax: function(url, type, hash){
hash = hash || {} ;
hash.headers = hash.headers || {};
hash.headers['Content-Type'] = 'application/vnd.api+json';
return this._super(url, type, hash);
}
});
它能解决您的问题吗?
实施您自己的身份验证器,它设置 headers 在身份验证请求期间使用:
// your-app/authenticators/your-custom-authenticator.js
import OAuth2PasswordGrant from 'ember-simple-auth/authenticators/oauth2-password-grant';
export default OAuth2PasswordGrant.extend({
/**
* your backend authentication endpoint
* @overrides
*/
serverTokenEndpoint: `https://your.authentication.endpoint.sth/login`,
/**
* Makes a request to the authentication server.
* This is what you need to override to customize your headers
* set up for purposes of authentication.
* @overrides
*/
makeRequest(url, data) {
const options = {
url: url,
data: data,
type: 'GET',
dataType: 'json',
accept: 'application/vnd.api+json',
headers: {
"Content-Type": 'application/vnd.api+json'
}
};
return Ember.$.ajax(options);
}
});
在您的(登录)route/controller/wherever中引用此自定义身份验证器,您需要:
this.get('session').authenticate('authenticator:yourCustomAuthenticator', username, password).then(() => {
// success, redirect, as you like..
})
查看 ember-simple-auth 文档的 Authenticators 部分,选择最接近您需要的 parent 身份验证器:ember-simple-auth - Authenticators
我或多或少设法解决了这个问题。这是一个不幸的软件包组合,导致 JSON API 规范在 Ember 和 DRF 之间出现一些问题。
首先,通过简单地将 headers 作为参数添加到 .authenticate
中,我设法在 controllers/login.js
中覆盖了 headers。任何参数都会传递给 ember-simple-auth
验证器。 (正如 Pavol 在他的回答中所建议的那样,我不需要实现自己的身份验证器。)
// controllers/login.js
import Ember from 'ember';
export default Ember.Controller.extend({
session: Ember.inject.service('session'),
actions: {
authenticate: function() {
var credentials = this.getProperties('identification', 'password'),
authenticator = 'authenticator:jwt',
// Set headers to accept JSON API format
headers = {
'Accept': 'application/vnd.api+json',
'Content-Type': 'application/vnd.api+json'
};
this.get('session').authenticate(authenticator, credentials, headers);
}
}
});
这引入了下一个问题:我的内容类型实际上不是 JSON API 规范,所以我确实需要实现自己的身份验证器来将 ember-simple-auth-token
的 JWT 身份验证器转换为产生 JSON API 规范兼容格式。没有让它工作,但像这样:
// authenticators/jwt.js
import Base from 'ember-simple-auth-token/authenticators/token';
export default Base.extend({
/**
Returns an object used to be sent for authentication.
@method getAuthenticateData
@return {object} An object with properties for authentication.
*/
// Make sure this is JSON API compatible format.
getAuthenticateData(credentials) {
const authentication = {
// This is apparently not valid JSON API spec, but you get the gist...
'data': [{
[this.identificationField]: credentials.identification,
[this.passwordField]: credentials.password
}]
};
return authentication;
}
});
现在,在后端,rest_framework_jwt
和 rest_framework_json_api
仍然不能很好地协同工作。
在这一点上,我决定在 auth 端点上放弃对 JSON API 规范的需求要简单得多:Ember 的包没有产生它,而 DRF 无法解析它!
因此,我还原了 Ember 方面的所有内容,让它根据我原来的问题生成请求。在 DRF 方面,我将 rest_framework_jwt
的视图子类化并将解析器设置为 DRF 的默认值 JSONParser
:
"""
Make the JWT Views ignore JSON API package and use standard JSON.
"""
from rest_framework_jwt.views import ObtainJSONWebToken, RefreshJSONWebToken, \
VerifyJSONWebToken
from rest_framework.parsers import JSONParser
from rest_framework.renderers import JSONRenderer
class ObtainJSONWebTokenPlainJSON(ObtainJSONWebToken):
parser_classes = (JSONParser, )
renderer_classes = (JSONRenderer, )
class RefreshJSONWebTokenPlainJSON(RefreshJSONWebToken):
parser_classes = (JSONParser, )
renderer_classes = (JSONRenderer,)
class VerifyJSONWebTokenPlainJSON(VerifyJSONWebToken):
parser_classes = (JSONParser, )
renderer_classes = (JSONRenderer,)
最终结果:通过让我的 API 遵循 JSON API 规范来解决,除了令牌身份验证端点。