This document is about: FUSION 1
SWITCH TO

커스텀 인증

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

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

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

인증 흐름

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

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

Photon Cloud 설정

Photon 애플리케이션 관리 화면에서 모든 인증 공급자에 대하여 설정할 수 있습니다.
애플리케이션 상세 페이지로 가서 커스텀 인증 섹션을 오픈합니다.

커스텀 인증에 대해서 설정할 때 적용될 때까지 시간이 걸릴 수 있다는 것을 기억해 주시기 바랍니다.

인증 공급자 추가

커스텀 인증 공급자에 대한 환경설정은 쉬우며 Photon 애플리케이션 관리 화면에서 수 초 내에 완료할 수 있습니다.

Photon Cloud: Custom Authentication Creation
커스텀 인증 공급자 생성

스크린샷에서 보이고 있는 것처럼 인증 URL 을 입력할 수 있고, 인증 서비스가 온라인이 아닌 경우와 또는 다른 이유에 의해서 동작하지 않을 경우에 클라이언트를 거부할지 결정할 수 있습니다.
추가적으로 선택적인 키/값 쌍을 추가하여 인증 서비스에 요청마다 쿼리 스트링 파라미터에 전송할 수 있도록 할 수 있습니다.

모법 사례는 대시보드에서 "민감"으로 설정하고 정적 키/값 쌍은 클라이언트에게 "보이지 않는"것으로 설정하는 것입니다. 예:
  • API 공개 키로 실제 Photon 서버 중에서 오는 요청인지 확인합니다.
  • 커스텀 인증의 API 버전은 앞으로 바뀌게 될 사항에 대해서 더 잘 처리하기 위하여 사용합니다.

또한 설정된 인증 공급자 없이도 애플리케이션에 접속하는 익명의 클라이언트를 허용할지 거부할지를 설정할 수 있습니다.
기본적으로 허용하는 것으로 되어 있으며 초기에는 모든 클라이언트들이 애플리케이션에 인증 여부에 관계없이 접속할 수 있다는 것을 의미합니다.
Photon 애플리케이션 대시보드 애플리케이션 상세 페이지의 인증 섹션에서 체크 할 수 있습니다.
이 옵션은 최소 하나 이상의 인증 공급자를 추가할 때만 보입니다.
설정된 공급자가 없으면 기본 값(사용)으로 숨겨지게 됩니다.

Photon Cloud: Custom Authentication, Allow Anonymous Clients
커스텀 인증을 이용한 익명 클라이언트의 접속 허용

인증 공급자의 변경 또는 삭제

애플리케이션 상세 페이지에서 기존의 인증 공급자를 편집할 수 있습니다.
편집 양식에서 모든 설정을 변경하거나 인증 공급자 전체를 삭제할 수 있습니다.

Photon Cloud: Custom Authentication Editing
기존 인증 공급자에 대한 변경 및 삭제

구현

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

클라이언트 측

클라이언트 측에서 LoadBalancing 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, 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" }
    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 구현 및 이중 인증에 유용하다는 것을 나타냅니다.

클라이언트로 온 데이터 읽기

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

  // all other methods from INetworkRunnerCallbacks
}

데이터 타입 변환

이 섹션에서는 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"
    ]
}

문제 해결

커스텀 인증에 실패할 때, Photon Fusion은 INetworkRunnerCallbacks을 통해 처리할 수 있는 적절한 ShutdownReason으로 종료됩니다.

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}");
  }

  // all other methods from INetworkRunnerCallbacks
}

관리 화면에서 구성한 인증 URL이 일부 HTTP 오류를 반환하는 경우 Photon 서버는 일부 오버헤드를 방지하기 위해 인증 호출을 잠시 일시 중지합니다.
URL을 구성하거나 테스트할 때 이 "백오프" 시간을 고려하십시오.

모범 사례

  • 인증 공급자로부터 반환된 결과에는, 특히 오류 발생 시 읽을 수 있는 메시지가 포함되어야 합니다.
    이렇게 하면 힘든 디버깅 과정을 줄일 수 있습니다.
  • 관리 화면에서 정적 키/값이 클라이언트에서 설정될 수 없도록 하세요.
    쿼리 스트링 결과에 중복 키를 방지하게 해 줍니다.
  • 보안상의 이유로 패스워드를 인증 파라미터로 일반 텍스트로 전송하지 마시기 바랍니다.
  • Photon 관리 화면에서 쿼리 문자열 파라미터를 설정하는 것을 권장하고 있습니다.
    이 방식을 통해 요청의 원천을 확인할 수 있습니다.
  • AuthenticationValues를 이용하여 파라미터를 설정하여 AuthGetParameters의 값에 직접적인 영향이 가지 않도록 해주세요.
    이렇게 하면 잘못된 쿼리 스트링을 방지할 수 있습니다.

유즈 케이스 예제: Block 이전 클라이언터 버전

커스텀 인증을 사용하여 이전 버전(또는 예기치 않은 버전)을 사용하는 클라이언트에서 들어오는 연결을 거부하고 사용자에게 업데이트를 요청할 수 있도록 특정 오류를 리턴할 수 있습니다.
이렇게 하려면 커스텀 인증 요청에서 버전을 전송해야 합니다. 쿼리 문자열 매개 변수 또는 POST 데이터 인수로 할지 결정하는 것은 사용자의 몫입니다.
아래 예에서는 쿼리 문자열 매개 변수를 사용합니다:

C#

using Fusion;
using Fusion.Sockets;
using Fusion.Photon.Realtime;

//...

public Task<StartGameResult> StartRunner(NetworkRunner runner, GameMode gameMode, string sessionName) {

  var version = GetGameVersion(); // your custom implementation to get the Game Version

  // Create a new AuthenticationValues
  AuthenticationValues authentication = new AuthenticationValues();
    
  // Setup 
  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 }을 리턴합니다.
그렇지 않으면 사용자 지정 값(1과 다름)이 포함된 ResultCode를 메시지와 함께 반환해야 합니다.
예: { "ResultCode": 5, "Message": "Version not allowed." }.

Back to top