This document is about: SERVER 5
SWITCH TO

커스텀 인증

기본적으로 모든 애플리케이션은 익명의 사용자가 접속할 수 있으며 인증 메커니즘이 없어도 됩니다. Photon 은 Photon 애플리케이션에 대해 커스텀 인증을 구현할 수 있는 옵션을 제공하고 있습니다.

Photon의 커스텀 인증은 유연합니다. 이에 대해서 완전 개인화된 솔루션 뿐만 아니라 잘 알려진 제3자 인증 공급자를 지원하고 있습니다.

Git 리포지토리에서 인증 공급자 구현에 대한 샘플을 제공 하고 있습니다. 리포지토리를 마음껏 포크 하시고 요청을 저희에게 보내 주세요. GitHub 에서 소스를 볼 수 있습니다.

인증 흐름

다음 단계들에는 인증 프로세스의 일반적인 흐름을 설명하고 있습니다.

photon cloud: custom authentication flow diagram
커스텀 인증 흐름 다이어그램
  1. 인증 공급자가 사용할 정보와 인증에 필요한 데이터를 Connect() 할때 같이 Photon 서버로 클라이언트가 전달합니다.
  2. Photon 서버는 애플리케이션에 필요한 인증 공급자를 얻고 다음 단계 중의 하나를 처리합니다.
    • 인증 공급자 환경 설정을 찾았으면 -> 인증은 단계 3에서 처리됩니다.
    • 인증 공급자 환경 설정을 찾지 못했으면 -> 애플리케이션 설정에 따라 클라이언트는 접속이 허용 또는 거부됩니다.
  3. Photon 서버는 Connect()에서 전달된 인증 정보를 가지고 인증 공급자를 호출 합니다.
    • 인증 공급자가 온라인의 경우 -> 인증은 단계 4로 처리됩니다.
    • 인증 공급자가 오프라인의 경우 -> 상응하는 공급자 설정에 따라 클라이언트가 접속 허용 또는 거부 됩니다.
  4. 인증 공급자는 인증 정보를 처리하고 Photon 서버로 결과를 리턴해 줍니다.
  5. 인증 결과에 따라서 클라이언트는 성공적으로 인증 또는 거부 되게 될 것입니다.

Photon Server의 셋업

커스텀 인증은 애플리케이션 NameServer 애플리케이션 환경 구성 파일: "deploy\NameServer\bin\NameServer.xml.config" 에서 설정할 수 있습니다.

다음 코드는 커스텀 인증 공급자의 환경 구성의 예제입니다:

XML

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

Photon의 원시 설정은 다음과 같습니다:

  • AllowAnonymous: 익명 클라이언트의 인증 없이 연결을 허용할지 여부를 나타내는 플래그
  • Name: 설정된 인증 공급자 이름
  • AuthenticationType: 인증 공급자의 유형.
  • AuthUrl: 인증 공급자의 엔드포인트.
  • RejectIfUnavailable: 인증 서비스가 작동하지 않는 경우 클라이언트를 거부할지 여부를 나타내는 플래그

<CustomAttributes> 아래에 AuthProvider 노드에 대한 추가적인 XML 속성을 추가할 수도 있습니다. 이러한 키/값들은 쿼리 문자열 파라미터로 전송됩니다. 주어진 샘플에서는 (Key1="Val1", Key2="Val2") 입니다.

구현

Photon Cloud 와 페이스북 인증을 사용하는 경우 이 부분은 건너띄어도 좋습니다.

클라이언트 측

클라이언트 측에서 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 호출이 없고 로그인 흐름이 간단 해지므로 매우 유용합니다.

인증에 사용되는 기본 HTTP 메소드는 GET입니다. 따라서 파라미터들은 키/값의 쌍으로 쿼리 스트링 내에서 전송될 수 있습니다. 최종 URL에는 대시보드에서 설정된 클라이언트로부터 온 키/값의 "union"이 포함되어 있을 것입니다. 만일의 경우에 대비하여 키는 양쪽에서 사용됩니다. 웹서버 사용 여부에 따라 예측할 수 없는 행위가 야기될 것 입니다. 모범사례의 관리 화면에서 모든 "sensitive" 정적 값들이 클라이언트에게 "invisible"로 설정되어 있는 이유입니다. 예, API key, 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." }.

Back to top