커스텀 인증
기본적으로 모든 애플리케이션은 익명의 사용자가 접속할 수 있으며 인증 메커니즘이 없어도 됩니다.
Photon 은 Photon 애플리케이션에 대해 커스텀 인증을 구현할 수 있는 옵션을 제공하고 있습니다.
Photon의 커스텀 인증은 유연합니다.
이에 대해서 완전 개인화된 솔루션 뿐만 아니라 잘 알려진 제3자 인증 공급자를 지원하고 있습니다.
Git 리포지토리에서 인증 공급자 구현에 대한 샘플을 제공 하고 있습니다.
리포지토리를 마음껏 포크 하시고 요청을 저희에게 보내 주세요.
GitHub 에서 소스를 볼 수 있습니다.
인증 흐름
다음 단계들에는 인증 프로세스의 일반적인 흐름을 설명하고 있습니다.
- 인증 공급자가 사용할 정보와 인증에 필요한 데이터를
Connect()
할때 같이 Photon 서버로 클라이언트가 전달합니다. - Photon 서버는 애플리케이션에 필요한 인증 공급자를 얻고 다음 단계 중의 하나를 처리합니다.
- 인증 공급자 환경 설정을 찾았으면 -> 인증은 단계 3에서 처리됩니다.
- 인증 공급자 환경 설정을 찾지 못했으면 -> 애플리케이션 설정에 따라 클라이언트는 접속이 허용 또는 거부됩니다.
- Photon 서버는
Connect()
에서 전달된 인증 정보를 가지고 인증 공급자를 호출 합니다.- 인증 공급자가 온라인의 경우 -> 인증은 단계 4로 처리됩니다.
- 인증 공급자가 오프라인의 경우 -> 상응하는 공급자 설정에 따라 클라이언트가 접속 허용 또는 거부 됩니다.
- 인증 공급자는 인증 정보를 처리하고 Photon 서버로 결과를 리턴해 줍니다.
- 인증 결과에 따라서 클라이언트는 성공적으로 인증 또는 거부 되게 될 것입니다.
Photon Server의 셋업
커스텀 인증은 애플리케이션 구성 파일에서 설정할 수 있습니다.
로드 밸런싱 애플리케이션은 "deploy\LoadBalancing\Master\bin\Photon.LoadBalancing.dll.config"로 되어야 합니다.
NameServer 애플리케이션은 "deploy\NameServer\bin\Photon.NameServer.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
: 인증 공급자의 유형.0
은 커스텀 서버이고2
는 페이스북입니다AuthUrl
: 인증 공급자의 앤드 포인트RejectIfUnavailable
: 인증 서비스가 동작하지 않고 있는 경우에 클라이언트를 거부할지를 나태내는 플래그
AuthProvider
노드에 추가적인 XML 속성들을 추가할 수도 있습니다.
이러한 키/값은 쿼리 스트링 파라미터로 전송되게 됩니다.
주어진 예제에서는 (Key1="Val1"
, Key2="Val2"
) 두 가지 예가 있습니다.
구현
클라이언트 측
클라이언트 측에서 LoadBalancing 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, username, email 등)이며 다른 하나는 "진짜의 증명" (해시 된 비밀번호, 키, 시크릿, 토큰 등)입니다.
보안상의 이유로 패스워드를 일반 텍스트로 전송하는 것은 권장하지 않습니다.
인증 오퍼레이션
인증 오퍼레이션은 인증값이 실제로 서버에게 전송되는 것입니다.
일반적으로 클라이언트 코드에서 직접적으로 사용되는 것이 아닌 당사의 API에 의해 사용됩니다.
중요한 부분: 서버로의 접속은 항상 인증 단계가 포함됩니다.
최초에 오퍼레이션은 실제의 인증 값을 암호화된 오퍼레이션으로 전송합니다.
이후 Photon 자신의 토큰을 제공하고 있는 서버는 자동으로 암호화하여 사용되게 됩니다.
서버측
웹서버가 인증 요청을 받은 즉시 쿼리 파라미터들이 유효한지 검사되어야 합니다.
예를 들어, 자격 증명은 데이터베이스에서 저장된 것과 비교 할 수 있습니다.
만약 수신된 파라미터들이 누락되었거나 유효하지 않는 것이면 결과는 { "ResultCode": 3, "Message": "Invalid parameters." }
로 리턴되어야 합니다.
검증이 끝난 후 결과는 다음과 같이 리턴되어야 합니다:
- 성공:
{ "ResultCode": 1, "UserId": <userId> }
- 실패:
{ "ResultCode": 2, "Message": "Authentication failed. Wrong credentials." }
고급 기능
사용자의 인증 이외에 인증 공급자로부터 추가적인 정보가 리턴 될 수 있습니다.
이렇게 하기 위해서 사용자는 클라이언트와 인증자 역할을 하는 웹 서비스 간에 특정한 프로토콜을 만들어야 합니다.
서버로 데이터 전송
가장 쉽고 간단한 방법은 "모두 아니면 아무것도(All or noting)" 전략입니다:
클라이언트에게 변수의 정적 숫자를 리턴할지 하지 않을지를 선택하세요.
하지만 클라이언트가 요청한 것을 기반으로 "온-디멘드" 데이터를 리턴하는 웹서비스와 같은 일부 유즈 케이스에서는 더 복잡한 접근법이 요구됩니다.
다음의 하위 섹션에서는 클라이언트가 웹서비스로 어떻게 데이터를 전송하는지에 대해 설명합니다.
데이터는 인증과 추가 파라미터의 인증에 필요한 자격 증명이 될 수 있습니다.
추가 파라미터들은 인증 응답의 리턴으로 서버측으로 부터 사용할 수 있는 데이터를 요청하기 위해서 사용 될 수 있습니다.
이것은 추가적인 API 호출이 없고 로그인 흐름이 간단 해지므로 매우 유용합니다.
아주 드문 경우지만 인증할 때 수많은 데이터가 필요하여 부하가 크게 걸릴 수도 있습니다.
다른 한편으로 대부분의 웹서버는 쿼리 스트링 또는 URL의 길이는 제한을 가지고 있습니다.
이 사항 때문에 Photon 은 C# SDK에서 AuthenticationValues.AuthPostData
를 명시적으로 설정하여 클라이언트에서 오는 HTTP 메소드를 POST로 변경을 제안하는 것입니다.
후자는 string
또는 byte[]
또는 Dictionary<string, object>
타입이 될 수 있습니다.
Dictionary<string, object>
의 경우, 페이로드는 JSON 문자열로 변환된고 HTTP 요청의 Content-Type는 "applicaton/json"입니다.
C# SDK에서, AuthenticationValues
클래스는 각 타입에 대하여 두 개의 setter 를 제공 하고 있습니다.
이것은 필요사항 또는 제약사항이 될 수 있으므로 웹 서비스에게 POST 메소드 인증 요청을 선택한 누구라도 가능하도록 해야 합니다.
다시 말하자면, 인증 파라미터를 전송하기 위해서 쿼리 스트링을 사용하던 POST 데이터를 사용하던 둘 다 사용하든지 관계없습니다.
다음 테이블에는 가능한 조합을 보여 주고 있습니다.
AuthPostData | AuthGetParameters | HTTP 메소드 |
---|---|---|
null | * | GET |
빈 문자열 | * | GET |
string (null 아님, 빈 문자열 아님) | * | POST |
byte[] (null 아님, 빈 문자열 가능) | * | POST |
Dictionary<string, object> (null 아님, 빈 문자열 가능) | * | POST (Content-Type="application/json") |
클라이언트에게 데이터 리턴
Photon 서버가 클라이언트와 웹 서비스 사이의 프록시이기 때문에 Photon 서버에 의해서 처리되는 변수들에 대해서 주의해야 합니다.
Photon 서버에 의해 수신되는 HTTP 수신 응답과 같이 웹 서버는 ResultCode
와 선택적으로 Message
를 포함한 JSON 객체를 리턴해야 합니다.
추가적으로 인증하는 동안에 웹 서비스로부터 수신할 수 있는 목록이 있습니다.
UserId
:
인증 자체의 파라미터로 사용되거나 클라이언트 측에서 요구 될 수 도 있습니다.
이것은 Photon 서버에 의해 수신될 때 클라이언트에게 항상 포워드 됩니다.
만약 초기에AuthenticationValues.UserId
값이 설정되어 있지 않으면 클라이언트로 다시 전송될 때 UserId는 무작위로 생성되어 전송됩니다.
이렇게 하면 클라이언트 내에서UserId
의 값을 오버라이드하고 이후에 변경될 수 없습니다.
ResultCode
값이 1인 경우에만 리턴되어야 합니다.
예:{ "ResultCode": 1, "UserId": "SomeUniqueStringId" }
Nickname
:
인증 자체의 파라미터로 사용되거나 클라이언트 측에서 요구될 수도 있습니다.
웹 서비스로부터 리턴 될 때 이것은 클라이언트의Nickname
을 오버라이드 하게 됩니다.
이Nickname
은 이후에 클라이언트에서 변경될 수 있습니다.
ResultCode
값이 1인 경우에만 리턴되어야 합니다.
예:{ "ResultCode": 1, "UserId": "SomeUniqueStringId", "Nickname": "SomeNiceDisplayName" }
AuthCookie
:
보안 데이터라고도 불리우는 이것은 웹 서비스에 의해서 리턴되는 JSON 객체이지만 수신된 암호화된 토큰에 임베드 되어 있을 것이므로 클라이언트 측에서 접근할 수 없을 것입니다.
나중에 Webhook 또는 WebRPC HTTP 요청으로 전송될 수도 있습니다.
ResultCode
값이 1인 경우에만 리턴되어야 합니다.
예:{ "ResultCode": 1, "UserId": "SomeUniqueStringId", "AuthCookie": { "SecretKey": "SecretValue", "Check": true, "AnotherKey": 1000 } }
{% endif %}Data
: JSON 객체로 클라이언트에게 리턴되어야 하는 모든 추가적인 값을 가지고 있습니다.
중첩된 배열 또는 객체들이 지원되지 않는다는 것에 주의하세요.
ResultCode
값이 1 또는 0인 경우에만 리턴되어야 합니다.
예:{ "ResultCode": 0, "Data": { "S": "Vpqmazljnbr=", "A": [ 1, -5, 9 ] } }
ResultCode
가 필수 리턴 변수이며 나머지 값은 다 선택적인 것들입니다.
다음의 테이블에서 웹 서버가 리턴해 줄 수 있는 것에 대해 요약해 놓았습니다.
ResultCode | Description | UserId | Nickname | Data | AuthCookie |
---|---|---|---|---|---|
0 | 인증 미완료, 데이터만 리턴됨.* | ||||
1 | 인증 성공. | (optional) | (optional) | (optional) | (optional) |
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
데이터 타입 변환
이 섹션에서는 Photon 서버와 웹 서비스간의 데이터 교환의 타입에 대해서만 설명합니다.
클라이언트와 Photon 서버간의 데이터 타입에 대해서는 Photon 의 직렬화 페이지를 참고 하시기 바랍니다.
Photon 서버 -> 웹 서비스
C# / .NET (Photon 지원 타입) | JavaScript / JSON |
---|---|
byte
|
number |
short
|
|
int
|
|
long
|
|
double
|
|
bool
|
bool |
string
|
string |
byte[] (byte 배열 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
|
샘플 요청 데이터 (타입들이 연결되어 있습니다)
Photon 서버 전송:
JavaScript
{
"(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":[
"(Object)0",
"(Object)xy",
"(Object)False"
],
"hk_null":"null"
},
"null":"null",
"(String[])string[]":[
"(String)PUN",
"(String)TB",
"(String)RT",
"(String)Bolt",
"(String)Chat"
],
"(Byte[])byte[]":[
"(Byte)255",
"(Byte)0"
],
"(Int16[])short[]":[
"(Int16)-32768",
"(Int16)32767"
],
"(Int32[])int[]":[
"(Int32)-2147483648",
"(Int32)2147483647"
],
"(Int64[])long[]":[
"(Int64)-9223372036854775808",
"(Int64)9223372036854775807"
],
"(Single[])float[]":[
"(Single)-3.402823E+38",
"(Single)3.402823E+38"
],
"(Double[])double[]":[
"(Double)-1.79769313486232E+308",
"(Double)1.79769313486232E+308"
],
"(Boolean[])bool[]":[
"(Boolean)True",
"(Boolean)False"
]
}
Web 서비스 수신:
JavaScript
{
"(object)Dictionary":{
"dk_int":"(number)1",
"dk_str":"(string)dv2",
"dk_bool":"(boolean)true"
},
"(object)Hashtable":{
"hk_byte":"(number)255",
"hk_null":null,
"hk_array":[
"(number)0",
"(string)xy",
"(boolean)false"
]
},
"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"
]
}
웹 서비스 -> Photon 서버
C#/.Net 에서 JavaScript/JSON 타입과 각각 대응하는 테이블입니다.
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
|
응답 데이터 샘플 (타입들이 연결되어 있습니다)
Web Service 전송:
JavaScript
{
"(object)number": {
"MAX_VALUE": "(number)1.7976931348623157e+308",
"MIN_VALUE": "(number)5e-324"
},
"(object)object": {
"string": "(string)xyz",
"null": null,
"bool": "(boolean)false",
"undefined": "(undefined)undefined",
"number": "(number)-3.14"
},
"(array)array": [
"(string)xyz",
"(number)0",
"(boolean)true",
null,
"(undefined)undefined"
]
}
Photon Server 수신:
JavaScript
{
"(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)number":"-3.14"
},
"(Object[])array":[
"(Object)xyz",
"(Object)0",
"(Object)True",
"null",
"null"
]
}
문제 해결
사용자 정의 인증이 실패하면, 다음의 콜백이 트리거 됩니다:
C#
void OnCustomAuthenticationFailed(string debugMessage)
{
// The `debugMessage` could be what the authentication provider returned.
}
관리 화면에서 구성한 인증 URL이 일부 HTTP 오류를 반환하는 경우 Photon 서버는 일부 오버헤드를 방지하기 위해 인증 호출을 잠시 일시 중지합니다.
URL을 구성하거나 테스트할 때 이 "백오프" 시간을 고려하십시오.
모범 사례
- 인증 공급자로부터 반환된 결과에는, 특히 오류 발생 시 읽을 수 있는
메시지
가 포함되어야 합니다.
이렇게 하면 힘든 디버깅 과정을 줄일 수 있습니다. - 관리 화면에서 정적 키/값이 클라이언트에서 설정될 수 없도록 하세요.
쿼리 스트링 결과에 중복 키를 방지하게 해 줍니다. - 보안상의 이유로 패스워드를 인증 파라미터로 일반 텍스트로 전송하지 마시기 바랍니다.
- Photon 관리 화면에서 쿼리 문자열 파라미터를 설정하는 것을 권장하고 있습니다.
이 방식을 통해 요청의 원천을 확인할 수 있습니다. AuthenticationValues
를 이용하여 파라미터를 설정하여AuthGetParameters
의 값에 직접적인 영향이 가지 않도록 해주세요.
이렇게 하면 잘못된 쿼리 스트링을 방지할 수 있습니다.
유즈 케이스 예제: Block 이전 클라이언터 버전
커스텀 인증을 사용하여 이전 버전(또는 예기치 않은 버전)을 사용하는 클라이언트에서 들어오는 연결을 거부하고 사용자에게 업데이트를 요청할 수 있도록 특정 오류를 리턴할 수 있습니다.
이렇게 하려면 커스텀 인증 요청에서 버전을 전송해야 합니다. 쿼리 문자열 매개 변수 또는 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." }
.