当我尝试使用 apl 平台(管理组件)访问安全端点时找不到 JWT 令牌
JWT Token not found when I am trying to access secured endpoints with apl platform (admin component)
我正在尝试使用 API 平台 (Symfony 4) 构建我的 API,一切似乎都很好,但是当我使用管理组件访问 安全端点时(我登录成功后)server always returns 401 "JWT Token not found"。我发现 Admin component 没有发送 'Authorization' header with token 所以服务器无法检查令牌授权请求。
由于 Admin 登录工作正常并且令牌始终存储在 localStorage 中(成功登录后)我尝试使用 cURL 和 Postman 发送请求 使用存储的令牌和服务器总是 returns 有效响应。
有什么想法吗?
更新 1
我正在使用 API 平台文档 (https://api-platform.com/docs/admin/authentication-support/#authentication-support) 中的代码。
App.js
import React from 'react';
import parseHydraDocumentation from '@api-platform/api-doc-parser/lib/hydra/parseHydraDocumentation';
import {fetchHydra as baseFetchHydra, HydraAdmin, hydraClient} from '@api-platform/admin';
import authProvider from './authProvider';
import {Redirect, Route} from 'react-router-dom';
const entrypoint = process.env.REACT_APP_API_ENTRYPOINT;
const fetchHeaders = {'Authorization': `Bearer ${localStorage.getItem('token')}`};
const fetchHydra = (url, options = {}) => baseFetchHydra(url, {
...options,
headers: new Headers(fetchHeaders),
});
const dataProvider = api => hydraClient(api, fetchHydra);
const apiDocumentationParser = entrypoint =>
parseHydraDocumentation(entrypoint, {
headers: new Headers(fetchHeaders),
}).then(
({api}) => ({api}),
result => {
const {api, status} = result;
if (status === 401) {
return Promise.resolve({
api,
status,
customRoutes: [
<Route path="/" render={() => <Redirect to="/login"/>}/>,
],
});
}
return Promise.reject(result);
}
);
export default () => (
<HydraAdmin
apiDocumentationParser={apiDocumentationParser}
authProvider={authProvider}
entrypoint={entrypoint}
dataProvider={dataProvider}
/>
);
authProvider.js
import {AUTH_CHECK, AUTH_ERROR, AUTH_LOGIN, AUTH_LOGOUT} from 'react-admin';
const authenticationTokenUri = `${process.env.REACT_APP_API_ENTRYPOINT}/authentication_token`;
export default (type, params) => {
switch (type) {
case AUTH_LOGIN:
const {username, password} = params;
const request = new Request(authenticationTokenUri, {
method: 'POST',
body: JSON.stringify({email: username, password}),
headers: new Headers({'Content-Type': 'application/json'}),
});
return fetch(request)
.then(response => {
if (response.status < 200 || response.status >= 300) throw new Error(response.statusText);
return response.json();
})
.then(({token}) => {
localStorage.setItem('token', token);
window.location.replace('/');
});
case AUTH_LOGOUT:
localStorage.removeItem('token');
break;
case AUTH_ERROR:
if (401 === params.status || 403 === params.status) {
localStorage.removeItem('token');
return Promise.reject();
}
break;
case AUTH_CHECK:
return localStorage.getItem('token') ? Promise.resolve() : Promise.reject();
default:
return Promise.resolve();
}
}
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import * as serviceWorker from './serviceWorker';
ReactDOM.render(<App />, document.getElementById('root'));
serviceWorker.unregister();
更新 2
我发现带有令牌的授权 header 是随一些请求一起发送的,但是所有发送到 API 端点的请求都是在未经授权 header 的情况下发送的,这会导致 401。
dev tools network img
correct request headers img
a endpoint request headers - miss authorization header img
dev tools console log img
更新 3
security.yaml
security:
encoders:
App\Entity\User:
algorithm: bcrypt
providers:
app_user_provider:
entity:
class: App\Entity\User
property: email
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: true
stateless: true
provider: app_user_provider
json_login:
check_path: /api/authentication_token
username_path: email
password_path: password
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
guard:
authenticators:
- lexik_jwt_authentication.jwt_token_authenticator
access_control:
# api platform has prefix /api
- { path: ^/api/authentication_token$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api/, roles: IS_AUTHENTICATED_FULLY }
lexik_jwt_authentication.yaml
lexik_jwt_authentication:
secret_key: '%env(resolve:JWT_SECRET_KEY)%'
public_key: '%env(resolve:JWT_PUBLIC_KEY)%'
pass_phrase: '%env(JWT_PASSPHRASE)%'
问候实体
namespace App\Entity;
use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* This is a dummy entity. Remove it!
*
* @ApiResource
* @ORM\Entity
*/
class Greeting
{
/**
* @var int The entity Id
*
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private $id;
/**
* @var string A nice person
*
* @ORM\Column
* @Assert\NotBlank
*/
public $name = '';
public function getId(): int
{
return $this->id;
}
}
更新 4
如您所见,使用 Postman 对安全端点的请求有效。
Postman printscreen
您需要自己设置Auth Header。默认情况下不会完成。
const fetchHeaders = {'Authorization': `Bearer ${localStorage.getItem('token')}`};
const fetchHydra = (url, options = {}) => baseFetchHydra(url, {
...options,
headers: new Headers(fetchHeaders),
});
实际上,将“@api-platform/admin”从“⁰0.6.3”降级为“0.6.2”解决了问题。
我正在尝试使用 API 平台 (Symfony 4) 构建我的 API,一切似乎都很好,但是当我使用管理组件访问 安全端点时(我登录成功后)server always returns 401 "JWT Token not found"。我发现 Admin component 没有发送 'Authorization' header with token 所以服务器无法检查令牌授权请求。
由于 Admin 登录工作正常并且令牌始终存储在 localStorage 中(成功登录后)我尝试使用 cURL 和 Postman 发送请求 使用存储的令牌和服务器总是 returns 有效响应。
有什么想法吗?
更新 1
我正在使用 API 平台文档 (https://api-platform.com/docs/admin/authentication-support/#authentication-support) 中的代码。
App.js
import React from 'react';
import parseHydraDocumentation from '@api-platform/api-doc-parser/lib/hydra/parseHydraDocumentation';
import {fetchHydra as baseFetchHydra, HydraAdmin, hydraClient} from '@api-platform/admin';
import authProvider from './authProvider';
import {Redirect, Route} from 'react-router-dom';
const entrypoint = process.env.REACT_APP_API_ENTRYPOINT;
const fetchHeaders = {'Authorization': `Bearer ${localStorage.getItem('token')}`};
const fetchHydra = (url, options = {}) => baseFetchHydra(url, {
...options,
headers: new Headers(fetchHeaders),
});
const dataProvider = api => hydraClient(api, fetchHydra);
const apiDocumentationParser = entrypoint =>
parseHydraDocumentation(entrypoint, {
headers: new Headers(fetchHeaders),
}).then(
({api}) => ({api}),
result => {
const {api, status} = result;
if (status === 401) {
return Promise.resolve({
api,
status,
customRoutes: [
<Route path="/" render={() => <Redirect to="/login"/>}/>,
],
});
}
return Promise.reject(result);
}
);
export default () => (
<HydraAdmin
apiDocumentationParser={apiDocumentationParser}
authProvider={authProvider}
entrypoint={entrypoint}
dataProvider={dataProvider}
/>
);
authProvider.js
import {AUTH_CHECK, AUTH_ERROR, AUTH_LOGIN, AUTH_LOGOUT} from 'react-admin';
const authenticationTokenUri = `${process.env.REACT_APP_API_ENTRYPOINT}/authentication_token`;
export default (type, params) => {
switch (type) {
case AUTH_LOGIN:
const {username, password} = params;
const request = new Request(authenticationTokenUri, {
method: 'POST',
body: JSON.stringify({email: username, password}),
headers: new Headers({'Content-Type': 'application/json'}),
});
return fetch(request)
.then(response => {
if (response.status < 200 || response.status >= 300) throw new Error(response.statusText);
return response.json();
})
.then(({token}) => {
localStorage.setItem('token', token);
window.location.replace('/');
});
case AUTH_LOGOUT:
localStorage.removeItem('token');
break;
case AUTH_ERROR:
if (401 === params.status || 403 === params.status) {
localStorage.removeItem('token');
return Promise.reject();
}
break;
case AUTH_CHECK:
return localStorage.getItem('token') ? Promise.resolve() : Promise.reject();
default:
return Promise.resolve();
}
}
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import * as serviceWorker from './serviceWorker';
ReactDOM.render(<App />, document.getElementById('root'));
serviceWorker.unregister();
更新 2
我发现带有令牌的授权 header 是随一些请求一起发送的,但是所有发送到 API 端点的请求都是在未经授权 header 的情况下发送的,这会导致 401。
dev tools network img
correct request headers img
a endpoint request headers - miss authorization header img
dev tools console log img
更新 3
security.yaml
security:
encoders:
App\Entity\User:
algorithm: bcrypt
providers:
app_user_provider:
entity:
class: App\Entity\User
property: email
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: true
stateless: true
provider: app_user_provider
json_login:
check_path: /api/authentication_token
username_path: email
password_path: password
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
guard:
authenticators:
- lexik_jwt_authentication.jwt_token_authenticator
access_control:
# api platform has prefix /api
- { path: ^/api/authentication_token$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api/, roles: IS_AUTHENTICATED_FULLY }
lexik_jwt_authentication.yaml
lexik_jwt_authentication:
secret_key: '%env(resolve:JWT_SECRET_KEY)%'
public_key: '%env(resolve:JWT_PUBLIC_KEY)%'
pass_phrase: '%env(JWT_PASSPHRASE)%'
问候实体
namespace App\Entity;
use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* This is a dummy entity. Remove it!
*
* @ApiResource
* @ORM\Entity
*/
class Greeting
{
/**
* @var int The entity Id
*
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private $id;
/**
* @var string A nice person
*
* @ORM\Column
* @Assert\NotBlank
*/
public $name = '';
public function getId(): int
{
return $this->id;
}
}
更新 4
如您所见,使用 Postman 对安全端点的请求有效。 Postman printscreen
您需要自己设置Auth Header。默认情况下不会完成。
const fetchHeaders = {'Authorization': `Bearer ${localStorage.getItem('token')}`};
const fetchHydra = (url, options = {}) => baseFetchHydra(url, {
...options,
headers: new Headers(fetchHeaders),
});
实际上,将“@api-platform/admin”从“⁰0.6.3”降级为“0.6.2”解决了问题。