This document is about: SERVER 4
SWITCH TO

Custom Authentication

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

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

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

身份驗證流程

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

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

Photon伺服器的設定

可以在應用程序設置檔中設定自訂身份驗證。
對於LoadBalancing應用程式,它應該是: "deploy\Loadbalancing\Master\bin\Photon.LoadBalancing.dll.config"。

以下程式碼片段是自訂身份驗證提供者的設置示例:

XML

<AuthSettings Enabled="true" ClientAuthenticationAllowAnonymous="false">
    <AuthProviders>
        <AuthProvider Name="Custom"
            AuthenticationType="0"
            AuthUrl="https://your-custom-auth-provider-url"
            RejectIfUnavailable = "false"
            Key1="Val1"
            Key2="Val2"/>
    </AuthProviders>
</AuthSettings>

Photon的原生設定包括:

  • ClientAuthenticationAllowAnonymous:一個旗標,指示是否允許匿名客戶端在沒有身份驗證的情況下連接。
  • Name:設置的身份驗證提供者的名稱。
  • AuthenticationType:身份驗證提供者的類型。
  • AuthUrl:您的身份驗證提供者的端點。
  • RejectIfUnavailable:一個旗標,指示如果您的身份驗證服務不起作用,是否應該拒絕客戶端。

您還可以為AuthProvider節點增加其他XML屬性。
這些鍵/值將作為査詢字串參數發送。
在給定的示例中,(Key1="Val1", Key2="Val2")是兩個示例。

實作

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

客戶端側

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

示例

C#

AuthenticationValues authValues = new AuthenticationValues();
authValues.AuthType = CustomAuthenticationType.Custom;
authValues.AddAuthParameter("user", userId);
authValues.AddAuthParameter("pass", pass);
authValues.UserId = userId; // this is required when you set UserId directly from client and not from web service
loadBalancingClient.AuthValues = authValues;
// connect

C++

ExitGames::Common::JString params = "user=" + userId + "&pass=" + pass;
ExitGames::LoadBalancing::AuthenticationValues authenticationValues;
authenticationValues.setType(ExitGames::LoadBalancing::CustomAuthenticationType::CUSTOM);
authenticationValues.setParameters(params);
authenticationValues.setUserId(userId); // this is required when you set UserId directly from client and not from web service
// pass authenticationValues as parameter on connect

JavaScript

var queryString = "user=" + userId + "&pass=" + pass;
var type = Photon.LoadBalancing.Constants.CustomAuthenticationType.Custom;
loadBalancingClient.setCustomAuthentication(queryString, type);
// connect

在這個程式碼片段中,為了簡化事情,我們選擇了一個非常基本的基於密碼的身份驗證憑證。
這將產生以下査詢字串:?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" }
  • Nickname
    這可以用作身份驗證本身的參數,也可以從客戶端側請求。
    當從web服務返回時,這將覆寫客戶端中的Nickname值。
    在那之後,客戶端仍可更新Nickname
    僅當ResultCode值為1時,才應返回此值。
    示例:{ "ResultCode": 1, "UserId": "SomeUniqueStringId", "Nickname": "SomeNiceDisplayName" }
  • AuthCookie
    也稱為安全資料,是web服務返回的JSON物件,但由於它將嵌入到接收到的加密令牌中,因此無法從客戶端存取。
    它可以稍後透過Webhook或WebRPC HTTP請求來發送。
    僅當ResultCode值為1時,才應返回它。
    示例:{ "ResultCode": 1, "UserId": "SomeUniqueStringId", "AuthCookie": { "SecretKey": "SecretValue", "Check": true, "AnotherKey": 1000 } }
  • 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或兩步驗證。

從客戶端讀取資料

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

C#

// implement callback from appropriate interface or override from class implementing it
void OnCustomAuthenticationResponse(Dictionary<string, object> data)
{
    // here you can access the returned data
}

C++

// In case of multi-leg authentication simply implement:
Listener::onCustomAuthenticationIntermediateStep();  
// in case of ResultCode:0 LoadBalancing::Client will call that function and pass the intermediate data as parameter to it

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"
    ]
}

故障排除

當自訂身份驗證失敗時,會觸發以下回調:

C#

void OnCustomAuthenticationFailed(string debugMessage)
{
   // The `debugMessage` could be what the authentication provider returned.
}

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

最佳做法

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

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

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

C#

string version = lbClient.AppVersion;
lbClient.AuthValues = new AuthenticationValues();
lbClient.AuthValues.AuthType = CustomAuthenticationType.Custom;
lbClient.AuthValues.AddAuthParameter("version", version);

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

Back to top