当我尝试使用 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 中(成功登录后)我尝试使用 cURLPostman 发送请求 使用存储的令牌和服务器总是 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”解决了问题。