Custom Authentication
默認情況下,所有應用程式都允許匿名用戶連接,沒有任何認証機制。
Photon提供了為Photon應用執行自定義認証的選項。
Photon的自定義認証很靈活。
它支持公認的第三方認証供應商,也支持完全個性化的解決方案。
- 自定義認証供應商,由您建立-也可由您主持。
我們通過Git倉儲提供認証提供者的執行範例。
您可以自由地區分該倉儲,並向我們發送您的拉動請求。
您可以在GitHub上找到資源。
認証流程
以下步驟描述了認証過程的一般流程。
- 您的客戶端通過
Connect()
向Photon伺服器傳遞關於使用哪個認証供應商的訊息和必要的認証數據。這由Photon Fusion和Photon Cloud整合內部處理。 - Photon伺服器為您的應用程式獲取所需的認証供應商,並採取以下步驟之一
- 找到認証供應商的配置 -> 認証繼續進行第3步。
- 沒有找到認証供應商的配置 -> 客戶端將被允許連接或拒絕,這取決於您的應用程式的設置。
- Photon伺服器用
Connect()
傳遞的認証訊息來呼叫認証供應商。- 認証供應商在線 -> 認証繼續進行第4步。
- 認証供應商離線 -> 客戶端將被允許連接或拒絕,這取決於相應的供應商設置。
- 認証供應商處理認証訊息並將結果返回給Photon伺服器。
- 根據認証結果,客戶端將被成功認証或被拒絕。
為Photon Cloud設置
您可以從您的Photon應用程式介面設置所有的認証供應商。
進入應用程式的詳細訊息頁面,打開自定義認証部分。
添加一個認証供應商
配置自定義認証供應商很簡單,只需幾秒鐘就可以從您的Photon Applications' Dashboard完成。
如截圖所示,您可以輸入您的認証URL,並決定在您的認証服務不在線或因其他原因不工作時是否拒絕客戶。
此外,您還可以選擇添加鍵/值對,這些鍵/值對將作為查詢字符串參數在每個請求中被發送到認証服務。
- API公鑰,以確保請求確實來自Photon伺服器之一。
- API版本的自定義認証,以更好地處理未來的變化。
此外,還可以允許或拒絕試圖連接到您的應用程式的匿名客戶,而不考慮任何配置的認証供應商。
默認情況下這是啟用的,意味著最初所有客戶都被授權連接到您的應用程式,無論是否經過認証。
您可以在您的Photon Applications' Dashboard的應用程式詳情頁面的認証部分檢查這一點。
該選項只有在您添加了至少一個認証供應商時才會顯示。
如果沒有配置認証供應商,它將以默認值(啟用)隱藏。
更新或刪除一個認証供應商
您也可以從應用程式的詳細訊息頁面選擇編輯現有的認証供應商。
在編輯表格中,您可以更新所有設置或完全刪除認証供應商。
執行
客戶端
在客戶端,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}
。
一般來說,這些憑証是一對值,第一個值是唯一的標識符(userId、用戶名、電子郵件等),另一個是”真實性証明"(散列的密碼、密鑰、秘密、令牌等)。
出於安全原因,不建議發送純文本密碼。
認證操作
認證操作是將認証值實際發送到伺服器的地方。
它通常由我們的API使用,而不是由您的客戶端代碼直接使用。
需要注意的是:連接到一個伺服器總是包括一個認証步驟。
第一次,操作會將實際的認証值作為加密操作發送。
對於後續的伺服器切換,Photon會提供它自己的令牌,該令牌會被加密並自動使用。
伺服器端
一旦Web伺服器收到認証請求,就應該對查詢參數進行檢查和驗証。
例如,可以將憑証與存儲在數據庫中的現有憑証進行比較。
如果收到的參數丟失或無效,返回的結果應該是{ "ResultCode": 3, "Message": "Invalid parameters." }
在完成驗証後,結果應返回如下:
- 成功:
{ "ResultCode": 1, "UserId": <userId> }
- 失敗:
{ "ResultCode": 2, "Message": "Authentication failed. Wrong credentials." }
進階功能
除了對用戶進行認証外,還可以從認証供應商那裡返回額外的訊息。
為了做到這一點,用戶應該在客戶端和扮演”認証者”角色的網路服務之間建立某種協議。
向伺服器發送數據
最容易和最簡單的是”全有或全無”的策略:
選擇是否向客戶端返回靜態的變數數量。
但有些用例需要一種更復雜的方法,即網路服務根據客戶的要求”按需求”返回數據。
本小節解釋了客戶機如何向Web服務發送數據。
該數據可以是認証所需的憑証以及任何額外的參數。
額外的參數可以用來請求從伺服器端獲得的數據,並在認証響應中返回。
這非常有用,因為它節省了額外的API呼叫並簡化了登錄工作流程。
在一些罕見的情況下,認証可能需要大量的數據。
另一方面,大多數網路伺服器對查詢字符串中使用的字符數有限制,或者對URL的長度有閾值。
這就是為什麼Photon提供了從客戶端將HTTP方法改為POST的可能性,在C# SDKs中,這可以通過明確設置AuthenticationValues.AuthPostData
字段為一個值來執行。
後者可以是string
或byte[]
或Dictionary<string, object>
類型。
如果是Dictionary<string, object>
,有效載荷將被轉換為JSON字符串,HTTP請求的Content-Type將被設置為”applicaton/json”。
在C# SDK中,AuthenticationValues
類別為每個支持的類型提供設置方法。
由於這可能是一個要求或限制,所以POST方法選項也適用於任何選擇以POST方法從Web服務接收認証請求的人。
換句話說,為了發送認証參數,您可以自由地使用查詢字符串或POST數據或兩者。
下表給出了可能的組合。
AuthPostData | AuthGetParameters | HTTP method |
---|---|---|
null | * | GET |
empty string | * | GET |
string (not null, not empty) | * | POST |
byte[] (not null, can be empty) | * | POST |
Dictionary<string, object> (not null, can be empty) | * | POST (Content-Type="application/json") |
向客戶返回數據
由於Photon伺服器是客戶端和網路服務之間的代理,您應該注意到可以由Photon伺服器處理的變數。
與Photon伺服器收到的所有HTTP傳入響應一樣,網路伺服器應該返回一個JSON對象,其中包括一個ResultCode
和一個可選的Message
。
此外,以下是Photon伺服器在認証過程中可以從網路服務期待的內容。
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
是唯一需要的返回變數,其他都是可選的。
下表總結了網路伺服器可能返回的內容。
ResultCode | Description | UserId | Nickname | AuthCookie | Data |
---|---|---|---|---|---|
0 | Authentication incomplete, only Data returned.* | ||||
1 | Authentication successful. | (optional) | (optional) | (optional) | (optional) |
2 | Authentication failed. Wrong credentials. | ||||
3 | Invalid parameters. |
*: 這對於執行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]}");
}
}
// 來自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}");
}
// 來自INetworkRunnerCallbacks的所有其他方法
}
如果您在介面中配置的認証URL返回一些HTTP錯誤,Photon伺服器會暫停認証呼叫一小段時間以避免一些負擔。
在配置或測試您的URL時,要考慮到這個”backoff”時間。
最佳實踐
- 從認証供應商返回的結果應該包含一個可讀的
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(); // 您的自定義執行可以獲得遊戲版本
// 創建一個新的AuthenticationValues
AuthenticationValues authentication = new AuthenticationValues();
// 設置
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 }
。
如果不允許,您應該返回一個ResultCode
,上面有您選擇的自定義值(不同於1),最好附帶一條訊息。
例子:{ "ResultCode": 5, "Message": "Version not allowed." }
。