Custom Authentication
默認情況下,所有應用程式都允許匿名用戶連接,沒有任何認証機制。
Photon提供了為Photon應用執行自定義認証的選項。
Photon的自定義認証很靈活。
它支持公認的第三方認証供應商,也支持完全個性化的解決方案。
我們通過Git倉儲提供認証提供者的執行範例。
您可以自由地區分該倉儲,並向我們發送您的拉動請求。
您可以在GitHub上找到資源。
認証流程
以下步驟描述了認証過程的一般流程。
- 您的客戶端通過
Connect()
向Photon伺服器傳遞關於使用哪個認証供應商的訊息和必要的認証數據。 - Photon伺服器為您的應用程式獲取所需的認証供應商,並採取以下步驟之一
- 找到認証供應商的配置 -> 認証繼續進行第3步。
- 沒有找到認証供應商的配置 -> 客戶端將被允許連接或拒絕,這取決於您的應用程式的設置。
- Photon伺服器用
Connect()
傳遞的認証訊息來呼叫認証供應商。- 認証供應商在線 -> 認証繼續進行第4步。
- 認証供應商離線 -> 客戶端將被允許連接或拒絕,這取決於相應的供應商設置。
- 認証供應商處理認証訊息並將結果返回給Photon伺服器。
- 根據認証結果,客戶端將被成功認証或被拒絕。
為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"
)是兩個例子。
執行
客戶端
在客戶端,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}
。
一般來說,這些憑証是一對值,第一個值是唯一的標識符(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" }
Nickname
:
這可以作為認証本身的一個參數,也可以從客戶端請求。
當從網路服務返回時,它將覆蓋客戶端的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
是唯一需要的返回變數,其他都是可選的。
下表總結了網路伺服器可能返回的內容。
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或雙重驗証等可能是有用的。
###從客戶端讀取數據
下面是一個如何從響應中獲取返回值的代碼片段:
C#
// 從適當的接口中執行呼叫返回,或從執行它的類別中覆蓋它
void OnCustomAuthenticationResponse(Dictionary<string, object> data)
{
// 在這裡您可以訪問返回的數據
}
C++
// 多重認証的情況下,只需執行:
Listener::onCustomAuthenticationIntermediateStep();
// 在ResultCode:0的情況下,LoadBalancing::Client將呼叫該函數並將中間數據作為參數傳遞給它
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)
{
// `debugMessage`可能是認証供應商返回的內容。
}
如果您在介面中配置的認証URL返回一些HTTP錯誤,Photon伺服器會暫停認証呼叫一小段時間以避免一些負擔。
在配置或測試您的URL時,要考慮到這個”backoff”時間。
最佳實踐
- 從認証供應商返回的結果應該包含一個可讀的
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 }
。
如果不允許,您應該返回一個ResultCode
,上面有您選擇的自定義值(不同於1),最好附帶一條訊息。
例子:{ "ResultCode": 5, "Message": "Version not allowed." }
。