This document is about: FUSION 2
SWITCH TO

Custom Authentication

預設下,所有應用程式都允許匿名使用者連接,並且沒有任何身份驗證機制。
Photon提供了為Photon應用程式實作自訂身份驗證的選項。

Photon的自訂身份驗證非常靈活。
它支援知名的第三方身份驗證提供者以及完全個人化的解決方案。

*自訂身份驗證提供者,由您組建,也可能由您託管。
我們透過Git存儲庫提供身份驗證提供者的範例實作。
請隨時分支存儲庫並向我們發送您的拉取請求。
您可以在GitHub上找到原始程式碼。

身份驗證流程

以下步驟描述了身份驗證過程的一般流程。

Photon Cloud: Custom Authentication Flow Diagram
自訂身份驗證流程圖
  1. 您的客戶端使用Connect()將有關使用哪個身份驗證提供者的資訊和必要的身份驗證資料傳遞給Photon伺服器。這由Photon Fusion和Photon Cloud整合在內部處理。
  2. Photon伺服器為您的應用程式獲取所需的身份驗證提供者,並採取以下步驟之一
    • 找到身份驗證提供者配置->身份驗證繼續執行步驟3。
    • 找不到身份驗證提供者配置->根據應用程式的設定,客戶端將被允許連接或拒絕
  3. Photon伺服器使用透過Connect()傳遞的身份驗證資訊調用身份驗證提供者。
    • 身份驗證提供者在線上->身份驗證繼續執行步驟4。
    • 身份驗證提供者處於離線狀態->根據相應的提供者設定,客戶端將被允許連接或拒絕。
  4. 身份驗證提供者處理驗證資訊並將結果傳回給Photon伺服器
  5. 根據身份驗證結果,客戶端將被成功驗證或拒絕

Photon Cloud的設定

您可以從Photon應用程式的儀表板設定所有身份驗證提供者。
前往應用程式的詳細資訊頁面,打開自訂身份驗證部分。

設置自訂身份驗證時,請記住,更改可能需要一段時間才能散播。

新增身份驗證提供者

設置自訂身份驗證提供者很容易,可以在幾秒鐘內從您的Photon應用程式的儀表板完成。

Photon Cloud: Custom Authentication Creation
創建自訂身份驗證提供者

如螢幕截圖所示,您可以輸入身份驗證URL,並如果您的身份驗證服務不在線上或因任何其他原因無法工作,則可以決定是否應拒絕客戶端。
此外,您可以選擇增加鍵/值對,這些對將作為査詢字串參數,隨每個請求一起發送到身份驗證服務。

最佳做法是從儀表板中僅設定對客戶端「不可見」的「敏感」和靜態鍵/值對。 示例:
  • API公開金鑰,以確保請求確實來自Photon伺服器之一。
  • API版本的自訂身份驗證,以更好地處理未來的更改。

此外,無論是否設置了任何身份驗證提供者,都有可能允許或拒絕嘗試連接到您的應用程式的匿名客戶端。
預設下,這是啟用的,這意味著最初所有客戶端都有權連接到您的應用程式,無論是否經過身份驗證。
您可以在Photon應用程式的儀表板的應用程式詳細資訊頁面上的身份驗證部分查看此資訊。
當您增加至少一個身份驗證提供者時,才會顯示該選項。
如果沒有設置身份驗證提供者,它將以預設值(啟用)隱藏。

Photon Cloud: Custom Authentication, Allow Anonymous Clients
允許匿名客戶端使用自訂身份驗證

更新或刪除身份驗證提供者

此外,在應用程式詳細資訊頁面中,您可以選擇編輯現有的身份驗證提供者。
在編輯表單上,您可以更新所有設定或完全刪除身份驗證提供者。

Photon Cloud: Custom Authentication Editing
更新或刪除現有的自訂身份驗證提供者

實作

如果您在Photon Cloud中使用facebook身份驗證,則可以跳過此部分。

客戶端側

在客戶端側,API將處理自訂身份驗證-只需設定一次相關參數和目標自訂身份驗證服務。
設定後,連接並處理最終錯誤。

示例

Photon Fusion可以在啟動Fusion Runner時使用身份驗證憑證,此時,同儕節點將連接到Photon Cloud,並且可以進行身份驗證,以便例如加入大廳或創建遊戲階段。
Fusion將負責將此資訊發送到Photon Cloud,並處理來自自訂身份驗證伺服器的任何資料。
以下程式碼顯示了如何在叫用NetworkRunner.StartGame時創建和傳遞AuthenticationValues

C#

using Fusion;
using Fusion.Sockets;
using Fusion.Photon.Realtime;

//...

public Task<StartGameResult> StartRunner(NetworkRunner runner, GameMode gameMode, string sessionName) {

  // Create a new AuthenticationValues
  AuthenticationValues authentication = new AuthenticationValues();

  // Setup
  authentication.AuthType = CustomAuthenticationType.Custom;
  authentication.AddAuthParameter("user", "user");
  authentication.AddAuthParameter("pass", "pass");

  return runner.StartGame(new StartGameArgs() {
    SessionName = sessionName,
    GameMode = gameMode,
    SceneObjectProvider = GetSceneProvider(runner),
    AuthValues = authentication // pass the AuthenticationValues
  });
}

在這個程式碼片段中,為了簡化事情,我們選擇了一個非常基本的基於密碼的身份驗證憑證。
這將產生以下査詢字串:?user={user}&pass={pass}
通常,這些憑證是一對值,第一個值是唯一識別字(用戶ID、用戶名、電子郵件等),另一個是「真實性證明」(雜湊密碼、金鑰、秘密、令牌等)。
出於安全原因,不建議發送純文字密碼。

身份驗證操作

身份驗證操作是將身份驗證值實際發送到伺服器的地方。
它通常由我們的API使用,而不是直接由您的客戶端程式碼使用。

需要注意的是:連接到伺服器總是包括一個身份驗證步驟。
第一次,操作將實際身份驗證值作為加密操作發送。
對於後續的伺服器交換,Photon提供了自己的令牌,該令牌經過加密並自動使用。

伺服器側

一旦web伺服器收到身份驗證請求,就應該檢查和驗證査詢參數。
例如,可以將憑證與存儲在資料庫中的現有憑證進行比較。

如果接收到的參數缺失或無效,則返回的結果應為{ "ResultCode": 3, "Message": "Invalid parameters." }

完成驗證後,應按如下方式返回結果:

  • 成功:{ "ResultCode": 1, "UserId": <userId> }
  • 失敗:{ "ResultCode": 2, "Message": "Authentication failed. Wrong credentials." }

高級功能

除了對用戶進行身份驗證外,還可以從身份驗證提供者返回額外的資訊。
為了做到這一點,用戶應該在客戶端和扮演「身份驗證器」角色的web服務之間建立某種協定。

向伺服器發送資料

最簡單的方法是「要麼全有要麼全無」的策略:
選擇是否向客戶端返回靜態數量的變數。
但有些用例需要一種更複雜的方法,即web服務根據客戶端的請求「按需」返回資料。
本小節解釋了客戶端如何向web服務發送資料。
資料可以是身份驗證所需的憑證,也可以加上任何額外的參數。
除其他事項外,額外的參數可用於請求在身份驗證回應中返回伺服器端的可用的資料。
這非常有用,因為它可以節省額外的API調用並簡化登入工作流程。

身份驗證中使用的預設HTTP方法是GET。 因此,參數可以作為査詢字串中的鍵/值對而發送。 最終的URL將包括從客戶端設定的鍵/值對和從儀表板設定的鍵-值對的「聯合」。 如果在兩個地方使用相同的金鑰,則只會發送儀表板值。 最佳做法是從儀表板設定任何對客戶端「不可見」的「敏感」靜態值; 例如:API金鑰、API版本。 此外,儀表板鍵/值可以 動態 更改,而無需更新客戶端。

在極少數情況下,身份驗證可能需要大量資料。
另一方面,大多數web伺服器對査詢字串中使用的字元數或URL長度有限制。
這就是為什麼Photon提供了從客戶端將HTTP方法更改為POST的可能性,在C# SDK中,這是通過顯式設定AuthenticationValues.AuthPostData欄位為一個值來完成的。
後者的類型可以是stringbyte[]Dictionary<string, object>
Dictionary<string, object>的情況下,載荷將被轉換為JSON字串,而HTTP請求的內容-類型將被設定為「applicaton/json」。
在C# SDK中,AuthenticationValues類別為每種支援的類型提供了setter方法。

由於這可能是一個要求或約束,因此POST方法選項也適用於選擇以POST方法從web服務接收身份驗證請求的任何人。

換句話說,要發送身份驗證參數,您可以自由使用査詢字串或POST資料,或兩者兼而有之。
下表給出了可能的組合。

AuthPostData AuthGetParameters HTTP方法
空值 * GET
空的字串 * GET
字串(不是空值,不是空的) * POST
位元[](不是空值,可以是空的) * POST
字典<字串,物件>(不是空值,可以是空的) * POST (內容-類型="application/json")

將資料返回給客戶端

由於Photon伺服器是客戶端和web服務之間的代理,因此您應該注意Photon伺服器可以處理的變數。

與Photon伺服器收到的所有HTTP傳入回應一樣,web伺服器應返回一個JSON物件,其中包括一個ResultCode和一個可選的Message
此外,這裡列出了Photon伺服器在身份驗證期間可以從web服務中獲得什麼。

  • UserId
    這可以用作身份驗證本身的參數,也可以從客戶端請求。
    當Photon伺服器接收到該消息時,它始終會轉發給客戶端。
    否則,如果AuthenticationValues.UserId最初沒有設定,隨機生成的UserId將被發送回客戶端。
    這將覆寫客戶端中的UserId值,且此後無法更改。
    僅當ResultCode值為1時,才應返回此值。
    示例:{ "ResultCode": 1, "UserId": "SomeUniqueStringId" }
  • Data:JSON物件,包含應返回給客戶端的任何額外值。
    請記住,不支援巢狀陣列或物件。
    僅當ResultCode值為0或1時,才應返回它。
    示例:{ "ResultCode": 0, "Data": { "S": "Vpqmazljnbr=", "A": [ 1, -5, 9 ] } }

ResultCode是唯一必需的返回變數,其他任何變數都是可選的。
下表總結了web伺服器可能返回的內容。

ResultCode 說明 UserId 暱稱 AuthCookie 資料
0 身份驗證未完成,只傳回資料。*
1 身份驗證成功。 (可選) (可選) (可選) (可選)
2 身份驗證失敗。錯誤憑證。
3 無效參數。

*:例如這可能有助於實作OAuth 2.0或兩步驗證。

從客戶端讀取資料

啟動Photon Fusion後,它將自動連接到Photon Cloud,運行前面描述的身份驗證過程。
一旦此過程完成,身份驗證伺服器發送的所有自訂資料都將透過INetworkRunnerCallbacks.OnCustomAuthenticationResponse的任何實作傳遞給同儕節點。

以下是如何從回應中獲取返回值的程式碼片段:

C#

using System;
using System.Collections.Generic;
using UnityEngine;
using Fusion;
using Fusion.Sockets;

public class SimulationEvents : SimulationBehaviour, INetworkRunnerCallbacks {

  public void OnCustomAuthenticationResponse(NetworkRunner runner, Dictionary<string, object> data) {
    foreach (var item in data.Keys) {
      Debug.Log($"{item}={data[item]}");
    }
  }

  // all other methods from INetworkRunnerCallbacks
}

Data Types Conversion

In this section, only the type of data exchanged between Photon server and the web service is explained.
For more information about data types between clients and Photon servers please refer to serialization in Photon page.

Photon Server -> Web Service

C# / .NET (Photon supported types) JavaScript / JSON
byte number
short
int
long
double
bool bool
string string
byte[] (byte array length < short.MaxValue) string (Base64 encoded)
T[] (array of supported type T, length < short.MaxValue) array
Hashtable (of supported types, count < short.MaxValue, preferably Photon implementation) object
Dictionary (keys and values of supported types, count < short.MaxValue) object
null null

Sample request data (types are concatenated)

As sent from Photon Server:<!--

JSON

{
    "(Dictionary<String,Object>)Dictionary":{
        "(Int32)dk_int":"1",
        "(String)dk_str":"dv2",
        "(Boolean)dk_bool":"True"
    },
    "(Hashtable)Hashtable":{
        "(Byte)hk_byte":"255",
        "(Object[])hk_array":[
            "(Int32)0",
            "(String)xy",
            "(Boolean)False"
        ],
        "hk_null":"null"
    },
    "null":"null",
    "(String[])string[]":[
        "PUN",
        "TB",
        "RT",
        "Bolt",
        "Chat"
    ],
    "(Byte[])byte[]":[
        "255",
        "0"
    ],
    "(Int16[])short[]":[
        "-32768",
        "32767"
    ],
    "(Int32[])int[]":[
        "-2147483648",
        "2147483647"
    ],
    "(Int64[])long[]":[
        "-9223372036854775808",
        "9223372036854775807"
    ],
    "(Single[])float[]":[
        "-3.402823E+38",
        "3.402823E+38"
    ],
    "(Double[])double[]":[
        "-1.79769313486232E+308",
        "1.79769313486232E+308"
    ],
    "(Boolean[])bool[]":[
        "True",
        "False"
    ]
}

As read by Web Service:

JSON

{
    "(object)Dictionary":{
        "dk_int":"(number)1",
        "dk_str":"(string)dv2",
        "dk_bool":"(boolean)true"
    },
    "(object)Hashtable":{
        "(number)hk_byte":"255",
        "(array)hk_array":[
            "(number)0",
            "(string)xy",
            "(boolean)false"
        ],
        "hk_null":null
    },
    "null":null,
    "(array)string[]":[
        "(string)PUN",
        "(string)TB",
        "(string)RT",
        "(string)Bolt",
        "(string)Chat"
    ],
    "byte[]":"(string)/wA=",
    "(array)short[]":[
        "(number)-32768",
        "(number)32767"
    ],
    "(array)int[]":[
        "(number)-2147483648",
        "(number)2147483647"
    ],
    "(array)long[]":[
        "(number)-9223372036854776000",
        "(number)9223372036854776000"
    ],
    "(array)float[]":[
        "(number)-3.40282347e+38",
        "(number)3.40282347e+38"
    ],
    "(array)double[]":[
        "(number)-1.7976931348623157e+308",
        "(number)1.7976931348623157e+308"
    ],
    "(array)bool[]":[
        "(boolean)true",
        "(boolean)false"
    ]
}

Web Service -> Photon Server

Here is a table that matches each JavaScript/JSON type to its equivalent one in C#/.Net :

JavaScript / JSON C# / .Net
object Dictionary
array object[] (array of objects)
number (integral) long
number (floating) double
string string
boolean bool
null (not a type) null
undefined (when sent) null

Sample response data (types are concatenated)

As sent from Web Service:<!--

JSON

{
    "(object)number": {
        "(number)MAX_VALUE": "1.7976931348623157e+308",
        "(number)MIN_VALUE": "5e-324"
    },
    "(object)object": {
        "(string)string": "xyz",
        "null": null,
        "(boolean)bool": "false",
        "(undefined)undefined": "undefined",
        "(number)float": "-3.14",
        "(number)integer": "123456"
    },
    "(array)array": [
        "(string)xyz",
        "(number)0",
        "(boolean)true",
        null,
        "(undefined)undefined"
    ]
}

As read from Photon Server:<!--

JSON

{
    "(Dictionary<String,Object>)number":{
        "(Double)MAX_VALUE":"1.79769313486232E+308",
        "(Double)MIN_VALUE":"4.94065645841247E-324"
    },
    "(Dictionary<String,Object>)object":{
        "(String)string":"xyz",
        "null":"null",
        "(Boolean)bool":"False",
        "(Double)float":"-3.14",
        "(Int64)integer":"123456"
    },
    "(Object[])array":[ 
        "(String)xyz",
        "(Int64)0",
        "(Boolean)True",
        "null",
        "null"
    ]
}

故障排除

當自訂身份驗證失敗時,Photon Fusion將使用正確的ShutdownReason關閉,該原因可以透過INetworkRunnerCallbacks進行處理。

C#

using System;
using System.Collections.Generic;
using UnityEngine;
using Fusion;
using Fusion.Sockets;

public class SimulationEvents : SimulationBehaviour, INetworkRunnerCallbacks {

  public void OnShutdown(NetworkRunner runner, ShutdownReason shutdownReason) {
    Debug.LogWarning($"{nameof(OnShutdown)}: {nameof(shutdownReason)}: {shutdownReason}");
  }

  // all other methods from INetworkRunnerCallbacks
}

如果您在儀表板中設置的身份驗證URL返回一些HTTP錯誤,Photon伺服器會暫停身份驗證調用一段時間,以避免一些開銷。
在設置或測試URL時,請考慮此「回退」時間。

最佳做法

  • 身份驗證提供者返回的結果應包含可讀的Message,特別是在失敗的情況下。
    這將為您節省大量偵錯的麻煩。
  • 在儀表板上,設定不應從客戶端側設定的靜態鍵/值對。
    這將防止生成的査詢字串中出現重複鍵。
  • 出於安全原因,不要將純文字密碼作為身份驗證參數發送。
  • 建議從Photon儀表板設定査詢字串參數。
    這樣您就可以檢查請求的來源。
  • 使用AuthenticationValues方法設定參數,不要直接影響AuthGetParameters的值。
    這將防止査詢字串格式錯誤。

用例示例:封鎖舊客戶端版本

您可以使用自訂身份驗證來拒絕來自使用舊版本(或意外版本)的客戶端的連接,並返回特定錯誤,以便您可以要求用戶進行更新。
為此,您需要在自訂身份驗證請求中發送版本。由您決定是將其作為査詢字串參數還是POST資料引數來完成它。
在下方的示例中,我們將使用査詢字串參數:

C#

using Fusion;
using Fusion.Sockets;
using Fusion.Photon.Realtime;

//...

public Task<StartGameResult> StartRunner(NetworkRunner runner, GameMode gameMode, string sessionName) {

  var version = GetGameVersion(); // your custom implementation to get the Game Version

  // Create a new AuthenticationValues
  AuthenticationValues authentication = new AuthenticationValues();

  // Setup
  authentication.AuthType = CustomAuthenticationType.Custom;
  authentication.AddAuthParameter("version", version);

  return runner.StartGame(new StartGameArgs() {
    SessionName = sessionName,
    GameMode = gameMode,
    SceneObjectProvider = GetSceneProvider(runner),
    AuthValues = authentication
  });
}

如果您的自訂身份驗證URL為https://example.com,則請求將按此方式發送https://example.com?version={version}
從您的身份驗證提供者實作中,您應該獲取並比較收到的版本。
如果版本是允許的,則返回{ "ResultCode": 1 }
如果沒有,您應該返回一個帶有您選擇的自訂值(不同於1)的ResultCode,最好是帶一條訊息。
示例:{ "ResultCode": 5, "Message": "Version not allowed." }

Back to top