为基于用户的工作流配置 OAuth 2

使用 PKCE 方法授权用户。 使用 PKCE 授权确保集成安全,尤其是与移动应用程序的集成。

PKCE 授权流程

使用 PKCE 可以防止中间人攻击,这种攻击会拦截来自 OAuth 2 客户端的响应,并劫持重定向 URL 以获取访问令牌。
  1. 终端用户连接到您的应用程序。
  2. 您的应用程序会将用户重定向到由 Faspex 身份验证组件提供的 Faspex 登录页面。
  3. 用户在登录页面输入自己的凭据。
  4. 您的应用程序将这些凭证与代码验证器(与注册的 OAuth 2 客户端上存储的代码挑战相匹配)一起提交给 Faspex 身份验证组件,以获取一个承载令牌。
  5. 当经过身份验证的用户与您的应用程序交互时,您的应用程序会使用承载令牌向 Faspex 资源服务器发出 API 调用。
  6. Faspex 授权组件服务器根据与承载令牌相关联的身份对 API 调用进行授权并提供服务。

关于本任务

为应用程序配置 OAuth 2:

过程

  1. 使用 Faspex UI 为网络应用程序注册新的 API 客户端。
    1. 以管理员用户身份通过用户界面登录。
    2. 进入管理应用程序。
    3. 转到 配置 > API 客户端,然后单击 创建新的.
    4. 输入 名称 和网络应用程序使用的任何 重定向 URI

      重定向 URI 或回复 URL 是 Faspex 登录页面成功授权并授予授权码后,Faspex API 发送给用户的位置。 Faspex 会将代码发送到重定向 URI,因此重定向 URI 应该是网络应用程序用来检索和存储用户访问令牌的端点。

      标准端点是 https://server/token

      注意: Faspex 要求重定向 URI 使用 HTTPS 格式。 如果在开发过程中没有有效的证书供应用程序使用,可以使用自签名证书。
    5. 单击保存
  2. 保存生成的客户 ID。
  3. 在网络应用程序中实施重定向 URI 端点。

    您的移动应用程序必须接受 codestate 两个参数,并使用这些参数的值通过 aspera/faspex/auth/token 端点向 API 服务器请求令牌。

    下面的 Ruby 示例代码会返回一个有效的承载令牌,供随后的 API 调用使用:

    require "net/http"
    require "json"
    
    # Expect code and state from API call
    authorization_code = params["code"]
    state = params["state"]
    
    # Set variables from environment
    faspex_hostname = ENV["FASPEX_HOSTNAME"]
    faspex_client_id = ENV["FASPEX_CLIENT_ID"]
    app_redirect_uri = ENV["APP_REDIRECT_URI"]
    code_verifier = ENV["CODE_VERIFIER"]
    
    # Build token request header and payload
    body = {
      "client_id": ENV["FASPEX_CLIENT_ID"],
      "grant_type": "authorization_code",
      "code": authorization_code,
      "code_verifier": Base64.encode64(params["state"]), # In this example, we assume the verifier and the state are the same
      "redirect_uri": ENV["APP_REDIRECT_URI"],
      "state": state
    }
    # Make a GET request to /aspera/faspex/auth/token
    uri = URI("https://#{FASPEX_HOSTNAME}/aspera/faspex/auth/token")
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = true
    http.verify_mode = OpenSSL::SSL::VERIFY_NONE # If using self-signed certs
    request = Net::HTTP::Post.new(uri.request_uri)
    request.body = body.to_json
    request.content_type = "application/json"
    response = http.request(request)
    # Get bearer token from the response
    JSON.parse(response.body)["access_token"]
  4. 测试授权流程。
    1. 以 HTTPS 模式启动网络应用程序,以便在 /token 端点接收授权代码和状态:
      FASPEX_HOSTNAME=faspex_hostname FASPEX_CLIENT_ID=faspex_hostname APP_REDIRECT_URI=https://your_server/token puma -b 'ssl://localhost:8443?key=ssl_certificate_key&cert=ssl_certificate_crt'
    2. 登录 Faspex,请求授权代码。 使用已注册 API 客户端的客户端 ID 和重定向 ID 生成登录 URL。
      URL 的语法是
      GET https://server/aspera/faspex/auth/authorize?client_id=faspex_client_id&redirect_uri=redirect_uri&response_type=code&state=state&code_challenge=code_challenge&code_challenge_method=code_challenge_method"
      参数 定义 示例
      client_id 您注册的 API 客户端的 ID。 266c8e7b-8bcb-40c2-b605-078b46c39d2a
      redirect_uri Faspex 登录页面成功授权并授予授权码后,Faspex API 向用户发送信息的位置。 https://faspex5.example.com/token
      state /auth/token 请求中使用的唯一标识符,用于向 API 服务器证明请求承载令牌的客户端与收到授权码的客户端是同一个。 96339c21-7d10-4d79-8043-93e7ab4cbe52
      code_challenge

      代码挑战是代码校验器的 Base64-encoded SHA256 哈希值。 密码挑战用于 PKCE 请求。

      ZWYyYmJlMmMtODA4MC00NWQ3LThiN2QtYTY1YmZjOGY5Mjkz\n (代码验证器:ef2bbe2c-8080-45d7-8b7d-a65bfc8f9293)
      code_challenge_method

      用于生成代码挑战的代码挑战方法。

      s256
    3. 成功登录后,应用程序应向您提供一个有效的不记名令牌。 使用该令牌检索您的账户信息:
      示例 curl 命令:
      curl "https://server/aspera/faspex/api/v5/account" \
        -H "Content-Type: application/json" \
        -H "Authorization: Bearer token"
      Ruby 代码示例
      require "net/http"
      
      access_token = access_token
      uri = URI("https://#{ENV['FASPEX_HOST']}/aspera/faspex/api/v5/account")
      http = Net::HTTP.new(uri.host, uri.port)
      http.use_ssl = true
      http.verify_mode = OpenSSL::SSL::VERIFY_NONE # If using self-signed certs
      request = Net::HTTP::Get.new(uri.request_uri)
      request.content_type = "application/json"
      request["Authorization"] = "Bearer #{access_token}"
      http.request(request)