使用 feathersjs oauth 调用承诺未处理的拒绝

Unhandled rejection at promise with feathersjs oauth call

我正在尝试创建一个带有 feathers-vuex 的前端 Vue(x) 应用程序和一个仅使用 oauth 进行身份验证的后端 feathers API。

如果我直接在 localhost:3030/oauth/google 上点击后端,那么正确的流程和重定向发生在 google 并且我最终回到 localhost:8080/#/access_token=ey.... 并且有一个有效的 jwt 所以 oauth配置在后端和 google 处的 oauth 配置似乎很好。配置为..

"oauth": {
  "redirect": "http://localhost:8080/",
  "google": {
    "key": "GOOGLE_CLIENT_KEY",
    "secret": "GOOGLE_CLIENT_SECRET",
    "scope": [
      "email",
      "profile",
      "openid"
    ]
  }
}

但是,当我在 localhost:8080 上从我的前端应用 运行 单击调用 login 方法的内容时..

login() {
  this.$store.dispatch('auth/authenticate', {strategy: 'google'})
}

.. 我在后端收到拒绝的承诺。使用 DEBUG=feathers*,@feathersjs* 我看到以下输出:


  @feathersjs/transport-commons Got 'create' call for service 'authentication' +0ms
  @feathersjs/transport-commons Running method 'create' on service 'authentication' {
  provider: 'socketio',
  headers: {
    host: 'localhost:3030',
    connection: 'Upgrade',
    pragma: 'no-cache',
    'cache-control': 'no-cache',
    'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.79 Safari/537.36',
    upgrade: 'websocket',
    origin: 'http://localhost:8080',
    'sec-websocket-version': '13',
    'accept-encoding': 'gzip, deflate, br',
    'accept-language': 'en-GB,en-US;q=0.9,en;q=0.8',
    cookie: 'connect.sid=s%3ACZWmOVc_1gtoJwDYsb0kvJ7vx06U-Rmd.jbQnNQL1GlFLWsJzyDP312ALpnI32mmYK7BIRka9Ro4',
    'sec-websocket-key': 'umfZAU0K/7k+sKUg1A/wvA==',
    'sec-websocket-extensions': 'permessage-deflate; client_max_window_bits'
  }
} [ { strategy: 'google' }, {}, [Function] ] +0ms
  @feathersjs/authentication/base Running authenticate for strategy google [ 'jwt', 'google' ] +16s
  @feathersjs/authentication-oauth/strategy getProfile of oAuth profile from grant-profile with { strategy: 'google' } +0ms
  @feathersjs/transport-commons Error in method 'create' on service 'authentication' Error: 401 Unauthorized
    at module.exports (/home/darren/projects/stbgfc/develop/admin-api/node_modules/request-compose/utils/error.js:3:15)
    at /home/darren/projects/stbgfc/develop/admin-api/node_modules/request-compose/response/status.js:11:11
    at processTicksAndRejections (internal/process/task_queues.js:93:5) {
  message: '401 Unauthorized',
  res: IncomingMessage {
    _readableState: ReadableState {
      objectMode: false,
      highWaterMark: 16384,
      buffer: BufferList { head: null, tail: null, length: 0 },
      length: 0,
      pipes: null,
      pipesCount: 0,
      flowing: true,
      ended: true,
      endEmitted: true,
      reading: false,
      sync: false,
      needReadable: false,
      emittedReadable: false,
      readableListening: false,
      resumeScheduled: false,
      paused: false,
      emitClose: true,
      autoDestroy: false,
      destroyed: false,
      defaultEncoding: 'utf8',
      awaitDrain: 0,
      readingMore: false,
      decoder: null,
      encoding: null
    },
    readable: false,
    _events: [Object: null prototype] {
      end: [Array],
      data: [Function],
      error: [Function]
    },
    _eventsCount: 3,
    _maxListeners: undefined,
    socket: TLSSocket {
      _tlsOptions: [Object],
      _secureEstablished: true,
      _securePending: false,
      _newSessionPending: false,
      _controlReleased: true,
      _SNICallback: null,
      servername: 'openidconnect.googleapis.com',
      alpnProtocol: false,
      authorized: true,
      authorizationError: null,
      encrypted: true,
      _events: [Object: null prototype],
      _eventsCount: 11,
      connecting: false,
      _hadError: false,
      _parent: null,
      _host: 'openidconnect.googleapis.com',
      _readableState: [ReadableState],
      readable: false,
      _maxListeners: undefined,
      _writableState: [WritableState],
      writable: false,
      allowHalfOpen: false,
      _sockname: null,
      _pendingData: null,
      _pendingEncoding: '',
      server: undefined,
      _server: null,
      ssl: null,
      _requestCert: true,
      _rejectUnauthorized: true,
      timeout: 5000,
      parser: null,
      _httpMessage: [ClientRequest],
      write: [Function: writeAfterFIN],
      [Symbol(res)]: [TLSWrap],
      [Symbol(asyncId)]: 125,
      [Symbol(kHandle)]: null,
      [Symbol(lastWriteQueueSize)]: 0,
      [Symbol(timeout)]: Timeout {
        _idleTimeout: -1,
        _idlePrev: null,
        _idleNext: null,
        _idleStart: 19758,
        _onTimeout: null,
        _timerArgs: undefined,
        _repeat: null,
        _destroyed: true,
        [Symbol(refed)]: null,
        [Symbol(asyncId)]: 135,
        [Symbol(triggerId)]: 133
      },
      [Symbol(kBuffer)]: null,
      [Symbol(kBufferCb)]: null,
      [Symbol(kBufferGen)]: null,
      [Symbol(kBytesRead)]: 689,
      [Symbol(kBytesWritten)]: 150,
      [Symbol(connect-options)]: [Object]
    },
    connection: TLSSocket {
      _tlsOptions: [Object],
      _secureEstablished: true,
      _securePending: false,
      _newSessionPending: false,
      _controlReleased: true,
      _SNICallback: null,
      servername: 'openidconnect.googleapis.com',
      alpnProtocol: false,
      authorized: true,
      authorizationError: null,
      encrypted: true,
      _events: [Object: null prototype],
      _eventsCount: 11,
      connecting: false,
      _hadError: false,
      _parent: null,
      _host: 'openidconnect.googleapis.com',
      _readableState: [ReadableState],
      readable: false,
      _maxListeners: undefined,
      _writableState: [WritableState],
      writable: false,
      allowHalfOpen: false,
      _sockname: null,
      _pendingData: null,
      _pendingEncoding: '',
      server: undefined,
      _server: null,
      ssl: null,
      _requestCert: true,
      _rejectUnauthorized: true,
      timeout: 5000,
      parser: null,
      _httpMessage: [ClientRequest],
      write: [Function: writeAfterFIN],
      [Symbol(res)]: [TLSWrap],
      [Symbol(asyncId)]: 125,
      [Symbol(kHandle)]: null,
      [Symbol(lastWriteQueueSize)]: 0,
      [Symbol(timeout)]: Timeout {
        _idleTimeout: -1,
        _idlePrev: null,
        _idleNext: null,
        _idleStart: 19758,
        _onTimeout: null,
        _timerArgs: undefined,
        _repeat: null,
        _destroyed: true,
        [Symbol(refed)]: null,
        [Symbol(asyncId)]: 135,
        [Symbol(triggerId)]: 133
      },
      [Symbol(kBuffer)]: null,
      [Symbol(kBufferCb)]: null,
      [Symbol(kBufferGen)]: null,
      [Symbol(kBytesRead)]: 689,
      [Symbol(kBytesWritten)]: 150,
      [Symbol(connect-options)]: [Object]
    },
    httpVersionMajor: 1,
    httpVersionMinor: 1,
    httpVersion: '1.1',
    complete: true,
    headers: {
      pragma: 'no-cache',
      date: 'Thu, 02 Jan 2020 22:13:43 GMT',
      'cache-control': 'no-cache, no-store, max-age=0, must-revalidate',
      expires: 'Mon, 01 Jan 1990 00:00:00 GMT',
      'content-type': 'application/json; charset=utf-8',
      vary: 'X-Origin, Referer, Origin,Accept-Encoding',
      server: 'ESF',
      'x-xss-protection': '0',
      'x-frame-options': 'SAMEORIGIN',
      'x-content-type-options': 'nosniff',
      'alt-svc': 'quic=":443"; ma=2592000; v="46,43",h3-Q050=":443"; ma=2592000,h3-Q049=":443"; ma=2592000,h3-Q048=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000',
      'accept-ranges': 'none',
      connection: 'close'
    },
    rawHeaders: [
      'Pragma',
      'no-cache',
      'Date',
      'Thu, 02 Jan 2020 22:13:43 GMT',
      'Cache-Control',
      'no-cache, no-store, max-age=0, must-revalidate',
      'Expires',
      'Mon, 01 Jan 1990 00:00:00 GMT',
      'Content-Type',
      'application/json; charset=utf-8',
      'Vary',
      'X-Origin',
      'Vary',
      'Referer',
      'Server',
      'ESF',
      'X-XSS-Protection',
      '0',
      'X-Frame-Options',
      'SAMEORIGIN',
      'X-Content-Type-Options',
      'nosniff',
      'Alt-Svc',
      'quic=":443"; ma=2592000; v="46,43",h3-Q050=":443"; ma=2592000,h3-Q049=":443"; ma=2592000,h3-Q048=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000',
      'Accept-Ranges',
      'none',
      'Vary',
      'Origin,Accept-Encoding',
      'Connection',
      'close'
    ],
    trailers: {},
    rawTrailers: [],
    aborted: false,
    upgrade: false,
    url: '',
    method: null,
    statusCode: 401,
    statusMessage: 'Unauthorized',
    client: TLSSocket {
      _tlsOptions: [Object],
      _secureEstablished: true,
      _securePending: false,
      _newSessionPending: false,
      _controlReleased: true,
      _SNICallback: null,
      servername: 'openidconnect.googleapis.com',
      alpnProtocol: false,
      authorized: true,
      authorizationError: null,
      encrypted: true,
      _events: [Object: null prototype],
      _eventsCount: 11,
      connecting: false,
      _hadError: false,
      _parent: null,
      _host: 'openidconnect.googleapis.com',
      _readableState: [ReadableState],
      readable: false,
      _maxListeners: undefined,
      _writableState: [WritableState],
      writable: false,
      allowHalfOpen: false,
      _sockname: null,
      _pendingData: null,
      _pendingEncoding: '',
      server: undefined,
      _server: null,
      ssl: null,
      _requestCert: true,
      _rejectUnauthorized: true,
      timeout: 5000,
      parser: null,
      _httpMessage: [ClientRequest],
      write: [Function: writeAfterFIN],
      [Symbol(res)]: [TLSWrap],
      [Symbol(asyncId)]: 125,
      [Symbol(kHandle)]: null,
      [Symbol(lastWriteQueueSize)]: 0,
      [Symbol(timeout)]: Timeout {
        _idleTimeout: -1,
        _idlePrev: null,
        _idleNext: null,
        _idleStart: 19758,
        _onTimeout: null,
        _timerArgs: undefined,
        _repeat: null,
        _destroyed: true,
        [Symbol(refed)]: null,
        [Symbol(asyncId)]: 135,
        [Symbol(triggerId)]: 133
      },
      [Symbol(kBuffer)]: null,
      [Symbol(kBufferCb)]: null,
      [Symbol(kBufferGen)]: null,
      [Symbol(kBytesRead)]: 689,
      [Symbol(kBytesWritten)]: 150,
      [Symbol(connect-options)]: [Object]
    },
    _consuming: true,
    _dumped: false,
    req: ClientRequest {
      _events: [Object: null prototype],
      _eventsCount: 5,
      _maxListeners: undefined,
      outputData: [],
      outputSize: 0,
      writable: true,
      _last: true,
      chunkedEncoding: false,
      shouldKeepAlive: false,
      useChunkedEncodingByDefault: false,
      sendDate: false,
      _removedConnection: false,
      _removedContLen: false,
      _removedTE: false,
      _contentLength: 0,
      _hasBody: true,
      _trailer: '',
      finished: true,
      _headerSent: true,
      socket: [TLSSocket],
      connection: [TLSSocket],
      _header: 'GET /v1/userinfo HTTP/1.1\r\n' +
        'user-agent: grant-profile 0.0.8\r\n' +
        'authorization: Bearer undefined\r\n' +
        'Host: openidconnect.googleapis.com\r\n' +
        'Connection: close\r\n' +
        '\r\n',
      _onPendingData: [Function: noopPendingOutput],
      agent: [Agent],
      socketPath: undefined,
      timeout: 5000,
      method: 'GET',
      path: '/v1/userinfo',
      _ended: true,
      res: [Circular],
      aborted: false,
      timeoutCb: [Function: emitRequestTimeout],
      upgradeOrConnect: false,
      parser: null,
      maxHeadersCount: null,
      [Symbol(kNeedDrain)]: false,
      [Symbol(isCorked)]: false,
      [Symbol(kOutHeaders)]: [Object: null prototype]
    }
  },
  body: {
    error: 'invalid_request',
    error_description: 'Invalid Credentials'
  },
  raw: '{\n' +
    '  "error": "invalid_request",\n' +
    '  "error_description": "Invalid Credentials"\n' +
    '}',
  hook: {
    type: 'before',
    arguments: [ [Object], [Object] ],
    service: {
      app: [EventEmitter],
      strategies: [Object],
      configKey: 'authentication',
      create: [Function: newMethod],
      remove: [Function: newMethod],
      methods: [Object],
      hooks: [Function: hooks],
      _events: [Object: null prototype],
      _eventsCount: 2,
      _maxListeners: undefined,
      setMaxListeners: [Function: setMaxListeners],
      getMaxListeners: [Function: getMaxListeners],
      emit: [Function: emit],
      addListener: [Function: addListener],
      on: [Function: addListener],
      prependListener: [Function: prependListener],
      once: [Function: once],
      prependOnceListener: [Function: prependOnceListener],
      removeListener: [Function: removeListener],
      off: [Function: removeListener],
      removeAllListeners: [Function: removeAllListeners],
      listeners: [Function: listeners],
      rawListeners: [Function: rawListeners],
      listenerCount: [Function: listenerCount],
      eventNames: [Function: eventNames],
      publish: [Function: publish],
      registerPublisher: [Function: registerPublisher],
      _super: undefined,
      [Symbol(@feathersjs/transport-commons/publishers)]: [Object]
    },
    app: [Function: app] EventEmitter {
      _events: [Object: null prototype],
      _eventsCount: 6,
      _maxListeners: undefined,
      setMaxListeners: [Function: setMaxListeners],
      getMaxListeners: [Function: getMaxListeners],
      emit: [Function: emit],
      addListener: [Function: addListener],
      on: [Function: addListener],
      prependListener: [Function: prependListener],
      once: [Function: once],
      prependOnceListener: [Function: prependOnceListener],
      removeListener: [Function: removeListener],
      off: [Function: removeListener],
      removeAllListeners: [Function: removeAllListeners],
      listeners: [Function: listeners],
      rawListeners: [Function: rawListeners],
      listenerCount: [Function: listenerCount],
      eventNames: [Function: eventNames],
      init: [Function: init],
      defaultConfiguration: [Function: defaultConfiguration],
      lazyrouter: [Function: lazyrouter],
      handle: [Function: handle],
      use: [Function: newMethod],
      route: [Function: route],
      engine: [Function: engine],
      param: [Function: param],
      set: [Function: set],
      path: [Function: path],
      enabled: [Function: enabled],
      disabled: [Function: disabled],
      enable: [Function: enable],
      disable: [Function: disable],
      acl: [Function],
      bind: [Function],
      checkout: [Function],
      connect: [Function],
      copy: [Function],
      delete: [Function],
      get: [Function],
      head: [Function],
      link: [Function],
      lock: [Function],
      'm-search': [Function],
      merge: [Function],
      mkactivity: [Function],
      mkcalendar: [Function],
      mkcol: [Function],
      move: [Function],
      notify: [Function],
      options: [Function],
      patch: [Function],
      post: [Function],
      propfind: [Function],
      proppatch: [Function],
      purge: [Function],
      put: [Function],
      rebind: [Function],
      report: [Function],
      search: [Function],
      source: [Function],
      subscribe: [Function],
      trace: [Function],
      unbind: [Function],
      unlink: [Function],
      unlock: [Function],
      unsubscribe: [Function],
      all: [Function: all],
      del: [Function],
      render: [Function: render],
      listen: [Function: newMethod],
      request: [IncomingMessage],
      response: [ServerResponse],
      cache: {},
      engines: {},
      settings: [Object],
      locals: [Object: null prototype],
      mountpath: '/',
      configure: [Function: configure],
      service: [Function: service],
      setup: [Function: newMethod],
      version: '4.4.3',
      methods: [Array],
      mixins: [Array],
      services: [Object],
      providers: [Array],
      _setup: false,
      hookTypes: [Array],
      hooks: [Function: hooks],
      eventMappings: [Object],
      _super: undefined,
      _router: [Function],
      rest: [Object],
      channel: [Function: channel],
      publish: [Function: publish],
      registerPublisher: [Function: registerPublisher],
      lookup: [Function: lookup],
      defaultAuthentication: [Function],
      logger: [DerivedLogger],
      io: [Server],
      _isSetup: true,
      [Symbol(@feathersjs/transport-commons/channels)]: [Object],
      [Symbol(@feathersjs/transport-commons/publishers)]: [Object],
      [Symbol(@feathersjs/transport-commons/router)]: [Object]
    },
    method: 'create',
    path: 'authentication',
    data: { strategy: 'google' },
    params: {
      query: {},
      route: {},
      connection: [Object],
      provider: 'socketio',
      headers: [Object]
    }
  }
} +2s
error: Unhandled Rejection at: Promise 

然后几秒钟后,由于调用未返回导致前端超时错误:

Timeout {type: "FeathersError", name: "Timeout", message: "Timeout of 5000ms exceeded calling create on authentication", code: 408, className: "timeout", …}
type: "FeathersError"
name: "Timeout"
message: "Timeout of 5000ms exceeded calling create on authentication"
code: 408
className: "timeout"
data: {timeout: 5000, method: "create", path: "authentication"}
errors: {}
hook: {type: "before", arguments: Array(2), service: {…}, app: {…}, method: "create", …}
stack: "Timeout: Timeout of 5000ms exceeded calling create on authentication↵    at new Timeout (webpack-internal:///./node_modules/@feathersjs/errors/lib/index.js:135:17)↵    at eval (webpack-internal:///./node_modules/@feathersjs/transport-commons/lib/client.js:60:55)"
__proto__: FeathersError

似乎在抱怨 authentication 服务上对 create 的调用本身需要身份验证(我阅读了后端调试输出顶部附近可以看到的 401 响应消息).

我在前端的 feathers-client.js 几乎是从文档中逐字记录的..

import feathers from '@feathersjs/feathers'
import socketio from '@feathersjs/socketio-client'
import auth from '@feathersjs/authentication-client'
import io from 'socket.io-client'
import { iff, discard } from 'feathers-hooks-common'
import feathersVuex from 'feathers-vuex'

const socket = io('http://localhost:3030', { transports: ['websocket'] })

const feathersClient = feathers()
  .configure(socketio(socket))
  .configure(auth({ 
    storageKey: 'auth',
    storage: window.localStorage 
  }))
  .hooks({
    before: {
      all: [
        iff(
          context => ['create', 'update', 'patch'].includes(context.method),
          discard('__id', '__isTemp')
        )
      ]
    }
  })

export default feathersClient

// Setting up feathers-vuex
const { makeServicePlugin, makeAuthPlugin, BaseModel, models, FeathersVuex } = feathersVuex(
  feathersClient,
  {
    serverAlias: 'api', // optional for working with multiple APIs (this is the default value)
    idField: '_id', // Must match the id field in your database table/collection
    whitelist: ['$regex', '$options']
  }
)

export { makeAuthPlugin, makeServicePlugin, BaseModel, models, FeathersVuex }

.. 并且考虑到后端被调用表明 vuex auth 插件也被正确配置和使用,但这可能是推测。

任何人都可以就如何进行提出建议吗?不幸的是,我在任何地方都没有遇到这种工作的例子(我看到的所有使用羽毛身份验证的例子都只使用本地策略)。

对于碰巧遇到类似问题的任何人:我似乎在项目中安装了旧的且不兼容的 feathers-vuex 版本。更新到 3.3.0 使其工作。