在項目里集成 Unity Online Services(UOS) 服務的過程中,你或許正被一系列技術知識點所困擾:UserId 和 PersonaId究竟有何不同?JWT的身份驗證機制又該如何理解?Passport Login 與 External Login在實際應用時,究竟該如何抉擇?
這些看似細微的技術要點,卻在很大程度上決定了系統的安全性高低以及用戶體驗是否流暢。
與此同時,云函數作為后端開發不可或缺的有力工具,其應用邏輯、FuncContext所發揮的作用、云函數調用 UOS 服務的最優方法,還有在云函數中驗證 JWT并精準提取關鍵用戶信息的操作,都是開發者必須熟練掌握的核心技能。
為助力大家成功攻克這些技術知識點,本教程將深入剖析上述關鍵主題,全面解答大家的疑惑!
本教程中涉及 UOS 服務包括:
云函數服務 Func Stateless (C#):用于便捷部署并運行服務端邏輯代碼
玩家通行證服務 Passport:集成 Passport Login 驗證玩家身份,以 Passport Feature 支持實時游戲交互
云存檔服務 CRUD Save:用于在云端安全存儲與管理玩家數據
云函數服務 Func Stateless
在保障游戲或應用安全性的重要考量下, 確保關鍵邏輯和數據的權威性處理僅在服務器端執行顯得尤為重要 。這是因為 客戶端的代碼包體容易被破解或篡改 ,從而引發不公平競爭、數據泄露等安全問題。UOS Func Stateless (C#) 云函數以其高效、靈活、安全且成本低的特點,為游戲開發者提供了一個理想的服務端邏輯解決方案,助力他們在快速迭代的市場環境中保持競爭優勢。
Func Stateles s 支持在本地開發環境中直接調試云函數 ,無需部署到云端即可驗證邏輯的正確性,降低了調試難度,加快了開發迭代速度。
Func Stateless 能夠 自動將服務端邏輯代碼打包并部署到云端 ,簡化了部 署流程,減少了人為錯誤,讓開發者更專注于業務邏輯的實現。
玩家通行證服務 Passport
Passport 是 UOS 官方提供的玩家通行證服務,包括玩家登錄系統 (Login),以及與玩家相關的眾多游戲功能 (Feature)。
Passport Login 是一個可以開箱即用的玩家登錄系統,通過非常簡便的集成方式,便可以獲得如下功能: 靈活可 配的 UI 登錄界面,包含用戶協議、登錄、實名認證; 支持手機號登錄,以及微信、QQ、 AppleID 、TapTap、好游快爆等 主流第三方 OAuth 登錄;可以配置游戲服務器,以及管理游戲角色。
Passport Feature 是以玩家為中心,包含了在線游戲中各種常見的功能。包括:排行榜、公會、游戲禮包、防沉迷系統、經濟系統、郵件系統、成就系統、戰令系統、公告、任務系統。
云存檔服務 CRUD Save
借助 UOS Save 所提供的全面而專業的玩家數據存儲、檢索及管理服務,開發者能夠極為便捷地為廣大游戲玩家打造出一個跨越不同平臺與設備的、安全且高度可用的游戲存檔系統。
這一服務不僅確保了玩家能夠在任何時間、任何地點無縫地繼續他們的游戲進程,而且通過嚴格的數據安全保障措施,讓玩家的游戲數據始終處于嚴密的保護之下。同時,其高可用性的設計也保證了玩家數據的實時同步與持久存儲,為玩家帶來了更加流暢、穩定的游戲體驗。
教程視頻
教程學習大綱
Unity 項目工程準備工作
創建 UOS App 并啟用 Func Stateless / CRUD Save / Passport 服務
解析 Passport 相關的概念術語
使用 Passport UI Login 的方式登錄
使用 External Login 的方式登錄
在云函數服務端驗證 JWT,并獲取 UserId / PersonaId
云函數調用 Passport 服務
云函數調用 CRUD Save 服務
上傳云函數
教程示例工程源文件
大家可通過下方鏈接,下載本教程對應的完整示例工程源文件 :
示例 Demo 項目工程下載鏈接
https://uos-1314001764.cos.ap-shanghai.myqcloud.com/func/stateless-csharp/UOSAuthDemo.zip
教程操作步驟
接下來讓我們來看看項目中的具體用法吧!你可以根據教程的步驟一步步從頭跟著操作,也可以直接下載鏈接提供的完整示例工程源文件。
1. Unity項目工程準備工作
1.1 創建一個空的項目工程
打開 Unity Hub,選擇創建「新項目」,教程這里使用的 Unity 編輯器版本是2022.3.53f1c1 版本,大家可以自行選擇電腦上已安裝的版本。
項目模板可以選擇「3D(Built-in)」,自定義項目名稱和位置。可以勾選選項「啟用游戲云服務」,勾選后會自動為你的項目工程安裝 UOS Launcher 的,然后可以直接通過 UOS Launcher 來啟用想要使用的服務即可。
最后點擊「創建項目」,等待項目的創建和加載。
1.2 場景中創建用于 Passport 登錄的 UI 按鈕
打開項目工程以后,我們使用 UGUI 在場景中創建兩個 Button 按鈕,在后面的步驟中作為實現 Passport 的登錄按鈕。在 Hierarchy 窗口中,點擊「+」→「UI」→「Button - TextMeshPro」來創建一個按鈕,在彈出窗口中,點擊「Import TMP Essentials」來導入相關資源:
設置 UI 自適應:找到 Canvas 對象上的 Canvas Scaler 組件,將「UI Scale Mode」設置為:Scale With Screen Size,分辨率暫時選擇 1920 *1080。
修改按鈕的名字為:PassportUI,是后面使用 Passport UI Login 方式登錄時用到的按鈕。大家可以自己調整想要的按鈕的大小、位置以及按鈕的顏色或者按鈕的背景貼圖等。
選中 PassportUI 按鈕,按下 Ctrl+D 復制一份,重命名為:ExternalLogin,作為后續使用 ExternalLogin 的方式登錄時用的按鈕。
1.3 場景中創建空對象,并掛載 UIController.cs 腳本組件
場景中創建一個空對象,可命名為 UIController。然后在 Project 窗口中,創建腳本文件夾 Scripts/UIController,創建一個腳本 UIController.cs,并將該腳本掛載在空對象 UIController 上,后續的代碼中會用到這個腳本。
2.綁定 UOS App 并開啟服務
溫馨提示:當前項目中已安裝好 UOS Launcher,不需要再次安裝了。大家可以參考之前的的公眾號文章教程,來為你當前的項目綁定你創建好的 UOS App 。
2.1 綁定 UOS App
點擊 Launcher 面板的「LinkApp」按鈕,在彈出窗口中選擇「By Unity project」,在「Select organization」這里選擇一個自己的項目組織,然后在「Select project 」選項這里,我們選擇「Create a new project 」,自行設置修改項目名字「Project name」。
教程中,我們就先選擇綁定創建好的 UOSAuthDemo 應用了。
2.2 開啟 Passport服務
在編輯器內 Unity Online Services 窗口的下拉服務列表中,找到「Passport Login」和 「Passport Feature」,點擊「Enable」開啟服務,同時安裝Passport Login SDK和Passport Feature SDK。
導入 UI 資源
在安裝Passport Login SDK的過程中,編輯器會提示「導入 PassportUI 資源」,點擊導入。 如未導入,請手動點擊編輯器菜單「UOS -> Passport -> Import PassportUI」導入。
2.3 開啟 Func-Stateless 服務
大家可以查看之前分享過的公眾號文章 ,來了解更多關于云函數服務(Func Stateless)的實戰用法!
接著繼續在 UOS Launcher 的下拉服務窗口列表中,找到「Func-Stateless」,點擊「Enable」開啟服務,并安裝Func-Stateless SDK。
2.4 開啟 CRUD Save 服務
可以查看之前分享過的公眾號文章 ,來了解更多關于云存檔服務(CRUD Save)的實戰用法!
接著繼續在 UOS Launcher 的下拉服務窗口列表中,找到「CRUD-Save」,點擊「Enable」開啟服務,并安裝CRUD Save SDK。
3.解析 Passport 相關的概念術語
使用 Passport 服務來實現游戲中的玩家登錄系統時,可以通過Passport UI Login 和 External Login兩種方式來實現登錄功能,兩種方式均可以實現接入 Passport Feature SDK 中的功能。
3.1 什么是 Passport UI Login
使用的前提條件:開通 Passport 服務,并安裝 Passport Login SDK 。
Passport Login 是一個可以開箱即用的玩家登錄系統,可以通過非常簡便的集成方式,來獲得如下功能:
提供了靈活可配的 UI 登錄界面,包含用戶協議、登錄、實名認證;
支持手機號登錄,以及微信、QQ、AppleID、TapTap、好游快爆等主流第三方 OAuth 登錄;
提供了游戲服務器、游戲角色的管理。
External Login 是外部賬號系統登錄的方式,如果 External Login 結合 Passport Feature SDK 一起使用:
當在項目中接入 Passport 的 Feature SDK(如排行榜、禮包等功能)時,不想使用 Passport Login 進行用戶登錄和管理的情況下,還可以使用 External Login 的方式來登錄。
External Login 是 UOS Passport 為已有玩家 ID 系統的開發者提供的外部賬號登錄的方式,來接入 Passport Feature 的相關功能。在應用開發過程中,可以在 SDK 中使用 AuthTokenManager.ExternalLogin 方法,傳入外部賬號登錄系統的 UserID 或者 PersonaID。
需要注意的是:當 External Login 結合 UOS SDK 一起使用時,如果當前 UOS App 開啟了 Passport 的功能,系統就會創建角色;反之,如果沒開通 Passport 功能的話,則不會創建角色,僅僅用于 SDK 鑒權以獲取 AccessToken。
3.3 什么是 UserId(用戶ID)
UserID 對應游戲中用戶賬號的概念,用戶首次登錄集成了 UOS Passport SDK 的游戲之后, UOS Passport 會創建一個對應的用戶賬號。
一個游戲中,一個玩家就一個賬號,一般賬號和手機號、微信等綁定。
3.4 什么是 PersonaId(角色ID)
PersonaId 對應游戲服務器中角色的概念,用戶可在同一個用戶賬號(UserID)下的不同游戲服務器(Realm)中創建不同的角色(Persona)。
通俗地說,就是一個游戲可以有多個區服,一個玩家可以在每個區服里面創建一個角色。
4.使用 Passport UI Login 的方式登錄
了解了登錄相關的概念后,接下來讓我們看下在代碼中,是如何獲取 UserID 和 PersonID 的!
4.1 接入 Passport UI Login SDK
接下來,在 Unity 項目中接入 Passport UI Login SDK。使用方法可以參考文檔鏈接: https://uos.unity.cn/doc/passport/login#loginAccessExample
然后在之前創建的 UIController.cs 腳本中,添加下面的代碼,這段代碼大家也可以直接從上面提供的文檔鏈接中復制過來。
_config是 Passport SDK 進行初始化時的相關配置;
_callback是 Passport SDK 的回調函數:Passport 中已經封裝注冊好了,在用戶拒絕協議、完成登錄、完成所有流程、用戶登出這幾個狀態下的回調事件。
登錄完成后,會選擇一個角色進入游戲。在腳本的 _callback 方法的Completed 狀態下,看到會調用選擇角色的方法SelectPersona。
//新引入的 namespace---------------------------------------------------------------
using System;
using System.Linq;
using System.Threading.Tasks;
using Passport;
using Unity.Passport.Runtime;
using Unity.Passport.Runtime.UI;
//-------------------------------------------------------------------------------
using UnityEngine;
namespace UIController
{
public class UIController : MonoBehaviour
{
#region UsePassportUI
// sdk 配置(Config 是 SDK 初始化時的配置)
private readonly PassportUIConfig _config = new()
{
AutoRotation = true, // 是否開啟自動旋轉,默認值為 false。
InvokeLoginManually = false, // 是否通過自行調用 Login 函數啟動登錄面板,默認值為 false。
Theme = PassportUITheme.Dark, // 風格主題配置。
UnityContainerId = "unity-container" // WebGL 場景下 Unity 實例
};
// sdk 回調函數
private async void _callback(PassportEvent e)
{
// event: 不同情況下的回調事件,詳情可以參考下面的回調類型。
switch (e)
{
case PassportEvent.RejectedTos:
Debug.Log("用戶拒絕了協議");
break;
case PassportEvent.LoggedIn:
Debug.Log("完成登錄");
break;
case PassportEvent.Completed:
Debug.Log("完成所有流程");
await SelectPersona();
break;
case PassportEvent.LoggedOut:
Debug.Log("用戶登出");
break;
default:
throw new ArgumentOutOfRangeException(nameof(e), e, null);
}
}
public void Logout()
{
PassportUI.Logout();
}
// 選擇角色
private async Task SelectPersona()
{
// 選擇域
var realms = await PassportSDK.Identity.GetRealms(); // 獲取域列表
var realmID = realms[0].RealmID; // 根據需要自行選擇域
// var realmID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"; // 也可以填寫固定的 RealmID 而不是動態獲取
// 獲取(或創建)與選擇角色
Persona persona = null;
var personas = await PassportSDK.Identity.GetPersonas(); // 獲取角色列表
if (!personas.Any())
{
// 若沒有角色,則新建角色
persona = await PassportSDK.Identity.CreatePersona("YourDisplayName", realmID);
}
else
{
// 若有角色,則選擇第一個角色
persona = personas[0];
}
// 選擇角色
await PassportSDK.Identity.SelectPersona(persona.PersonaID);
}
#endregion
}
}
4.2 調用 Passport UI SDK 的初始化
封裝一個 UsePassportUI 方法,來實現點擊「PassportUI」按鈕時響應的事件。在方法內通過調用 PassportUI.Init 來實現 Passport Login SDK 的初始化。
//點擊「PassportUI」按鈕,響應的事件
public async void UsePassportUI()
{
Debug.Log("Click PassportUI");
// 調用 Passport SDK 初始化
await PassportUI.Init(_config, _callback);
}
找到場景中的 UI 按鈕對象 PassportUI,確保它已經綁定了 UIController.cs 腳本中的 UsePassportUI 方法。
4.3 運行測試并查看已創建的角色
然后點擊運行游戲,點擊 Game 窗口中的 PassportUI 按鈕,使用手機號短信驗證的方式來登錄,并進行身份實名認證:
可以查看控制臺的日志輸出信息:
查看 UOS 網頁端創建的用戶和角色:
在 Passport 的「用戶管理」頁面,可以看到以手機號形式登錄的「用戶ID」信息。
在 Passport 的「角色管理」頁面,可以看到剛才的「用戶ID」創建的角色所屬的「角色ID」和「角色名稱」。
5.使用ExternalLogin 的方式登錄5.1 調用 External Login 方法來實現外部登錄
https://uos.unity.cn/doc/passport/external-login
在 UIController.cs 腳本中使用 External Login 的方式來登錄,我們封裝 UseExternalLogin 方法,來實現點擊「External Login」按鈕時響應的事件。
如果結合 PassportFeature 的 SDK 一起使用的話,我們先調用 Initialize 方法來完成 PassportFeatureSDK 的初始化,這個操作同時也會自動實現 AuthTokenManager 的初始化。
需要自己定義一個唯一的 Id,作為參數賦值給 userId 變量。然后調用 ExternalLogin 方法自行傳入外部 ID 系統的 UserId/PersonalId/DisplayName。
我們會根據 userId 是否一致,來決定是否新建用戶的。
//新引入的 namespace---------------------------------------------------------------
using Unity.UOS.Auth;
//-------------------------------------------------------------------------------
//點擊「ExternalLogin」按鈕,響應的事件
public async void UseExternalLogin()
{
Debug.Log("Click ExternalLogin");
// 使用 UOS Launcher 方式初始化 Passport SDK
try
{
await PassportFeatureSDK.Initialize();
}
catch (PassportException e)
{
Debug.Log($"failed to initialize sdk: {e.Message}");
throw;
}
// 在 SDK 中使用 ExternalLogin 方法時傳入的是 外部 ID系統 的 UserID/PersonalID/DisplayName
const string userId = " " ;// 需要登錄的、外部系統的用戶Id
const string personaId = " " ;// 可選, 需要登錄的、外部系統的角色ID
const string personaDisplayName = " " ;//可選, 需要登錄的、角色的昵稱
Debug.Log("Call External Login");
await AuthTokenManager.ExternalLogin(userId, personaId, personaDisplayName);
}
找到場景中的 UI 按鈕對象 ExternalLogin,確保它已經綁定了 UIController.cs 腳本中的 UseExternalLogin 方法。
5.2 運行測試并查看已創建的角色
然后點擊運行游戲,點擊 Game 窗口中的 ExternalLogin 按鈕,可以看到控制臺的日志輸出信息:
查看 UOS 網頁端創建的用戶和角色:
在 Passport 的「用戶管理」頁面,可以看到以「服務端登錄」形式登錄的「用戶ID」信息。
在 Passport 的「角色管理」頁面,可以看到剛才的「用戶ID」創建的角色所屬的「角色ID」和「角色名稱」。
5.3 UserId 不變,修改 PersonId,再次測試
使用 ExternalLogin 的方式登錄時,保持用戶的 UserId 不變,僅修改 PersonaId 的話。不會新增用戶,但是會為已有的用戶新創建一個角色的。
打開腳本中的代碼:將角色 ID 的值由原來的 變更為 。
// 在 SDK 中使用 ExternalLogin 方法時傳入的是 外部 ID系統 的 UserID/PersonalID/DisplayName
const string userId = " " ;// 需要登錄的、外部系統的用戶Id
const string personaId = " " ;// 可選, 需要登錄的、外部系統的角色ID
const string personaDisplayName = " " ;//可選, 需要登錄的、角色的昵稱
Debug.Log("Call External Login");
await AuthTokenManager.ExternalLogin(userId, personaId, personaDisplayName);
運行測試后,查看 UOS 網頁端:
在「用戶管理」這里,由于 UserId 沒變,所以并沒有新增用戶。
在「角色管理」這里,可以看到同一個用戶 ID 下,新增了一條角色 ID 的信息。
6.在云函數服務端驗證 JWT,并獲取 UserId/PersonaId6.1 介紹 JWT6.1.1 什么是 JSON Web Token
JWT網頁鏈接:https://jwt.io/introduction
JWT 指的是 JSON 網絡令牌(全稱是 JSON Web Token),它是一種用于在網絡應用之間安全傳輸聲明的開放標準(RFC 7519)。它定義了一種緊湊且自包含的方式,以 JSON 對象的形式在各方之間安全地傳輸信息。由于這些信息經過了數字簽名,所以可以被驗證且值得信賴。
6.1.2 什么時候應該使用 JWT 呢?
以下是一些 JWT 發揮作用的場景:
授權:這是使用 JWT 最常見的場景。一旦用戶登錄,后續的每一次請求都將包含 JWT,憑借該令牌,用戶能夠訪問被允許的路由、服務和資源。如今,單點登錄是一項廣泛使用 JWT 的功能,這得益于它的低開銷以及易于在不同域之間使用的特性。
信息交換:JWT 是在各方之間安全傳輸信息的良好方式。由于 JWT 可以進行簽名(例如使用公私鑰對),你能夠確定發送方的真實身份。此外,因為簽名是通過對頭部和負載進行計算得出的,所以你還可以驗證內容未被篡改。
在云函數服務端驗證 JWT 并獲取 UserId/PersonaId,是確保系統安全性和識別用戶身份的重要步驟。
通過驗證 JWT 的簽名和有效期,可以確保請求是合法的,并且來自受信任的客戶端。而 UserId 和 PersonaId 可以幫助服務端識別用戶和用戶的角色,從而根據用戶的權限和角色來處理請求。
6.2 介紹 FuncContext(函數上下文)6.2.1 FuncContext 的作用
找到 Project 窗口中的 Packages 目錄,在 UOS Func Stateless SDK 路徑下可以看到 FuncContext.cs 腳本。
FuncContext 是一個靜態類,主要用于管理和存儲與函數調用相關的上下文信息。
這些信息通常涵蓋了所創建的 UOS 應用的 AppId、密鑰(AppSecret)、令牌(Token)、用戶 ID(UserId)、角色 ID (PersonaId)以及一些自定義屬性(Properties)。
該類提供了一系列方法,用于設置、獲取和清除這些信息。同時,該類支持從 HTTP Head 解析信息以及生成 HTTP Head。此外,FuncContext 還具備 JWT 令牌驗證的功能,為信息的安全性和有效性提供保障。
namespace Unity.UOS.Func.Stateless.Core.Context
{
public static class FuncContext
{
private static string AppId { get; set; }
private static string AppSecret { get; set; }
private static string Token { get; set; }
private static string UserId { get; set; }
private static string PersonaId { get; set; }
private static Dictionary
Properties { get; } public static void Clear() { //...... } public static Dictionary
FormHeaders() { //...... } public static void ParseFromHeader(Dictionary
headers) { //...... } public static async Task ValidateTokenAsync() { //...... } //此處省略其它代碼行...... } }
6.2.2 實現原理
HTTP Header 生成:當客戶端需要與服務端進行交互時,可調用FormHeaders方法。該方法會將 FuncContext 中存儲的上下文信息轉化為一個字典,然后作為 HTTP Header 添加到請求中。這樣一來,服務端在接收到請求后,能夠從 HTTP Header 中提取出應用 ID、應用密鑰、令牌、用戶 ID、角色 ID 以及自定義屬性等信息,為處理客戶端請求提供必要的數據支持。
HTTP Header 解析:當客戶端接收到服務端的響應,可以調用ParseFromHeader方法。該方法會對 HTTP Header 中的信息進行解析,并將解析結果更新到 FuncContext 的上下文信息中。經過更新后,本地代碼便能使用這些信息來執行后續的操作,比如依據用戶 ID 執行特定業務邏輯,或者根據角色 ID 判斷用戶權限等。
通過上述生成和解析 HTTP Header 的過程,FuncContext 類實現了上下文信息在客戶端和服務端之間的傳遞與管理,極大地方便了函數調用過程中的上下文處理,提升了系統的交互效率與穩定性。
6.3 在云函數中校驗 JWT ,獲取 UserId/PersonaId
我們剛才已經在客戶端里面調用了 Passport Login,接著在云函數服務端驗證 JWT 并獲取 UserId/PersonaId。云函數服務端內置了 JWT 簽名驗證,也就是說如果客戶端被破解,被故意傳入非法的 JWT,是無法通過云函數服務端驗證的。
6.3.1 實現思路
使用 Passport 登錄之后,在客戶端代碼中獲取到服務器簽名過的 JWT,這個 Token 中包含了 UserId/PersonaId,且無法被篡改。
將需要權限校驗的邏輯放到 Func Stateless 中來執行,客戶端通過函數上下文(FuncContext)將 JWT 從客戶端傳給服務端。
https://uos.unity.cn/doc/func/stateless/csharp-guide#func-context
服務端從 FuncContext 中獲取 Token 后,進行 Token 校驗,并且從中提取出 UserId/PersonaId。
在 Unity Editor 菜單欄中先點擊「UOS -> Func Stateless -> Open Panel」按鈕,打開 Func Stateless Tool 面板。
點擊「+新建云函數」,會自動為我們創建云函數所在目錄 Scripts/CloudService 的,并默認提供了一個云函數。
然后再點擊「UOS -> Func Stateless -> Import NuGetForUnity」,來導入 UOS 版本的 NuGetForUnity 工具。
接下來在代碼中,來看看具體的用法!
6.3.3 客戶端獲取 JWT ,并將 JWT 從客戶端傳給服務端
在客戶端場景下,通常在與服務端進行交互前,需要確保客戶端所持有的令牌是有效的。在 UIController.cs 腳本中封裝一個方法 CallStateless 來驗證獲取到的令牌,確保令牌的有效性,以便后續可以正常訪問服務端的受保護資源。
調用 AuthTokenManager 類中的GetAccessToken方法,從服務端異步獲取訪問令牌;
然后在客戶端通過函數上下文(FuncContext)中的SetToken方法,將獲取到的訪問令牌設置到上下文中,以便后續的操作可以使用該令牌。
//新引入的 namespace---------------------------------------------------------------
using Unity.UOS.Func.Stateless.Core.Context;
//-------------------------------------------------------------------------------
#region ValidateFuncStateless
private async Task CallStateless()
{
//validate jwt token
var getToken = await AuthTokenManager.GetAccessToken();
Debug.Log($"{getToken}");
FuncContext.SetToken(getToken);
}
#endregion
如果是 Passport UI 的方式登錄,調用方法 CallStateless:
我們在完成登錄的所有流程、選擇角色之后,調用下云函數校驗令牌的方法 CallStateless。
private async void _callback(PassportEvent e)
{
// event: 不同情況下的回調事件,詳情可以參考下面的回調類型。
switch (e)
{
case PassportEvent.RejectedTos:
Debug.Log("用戶拒絕了協議");
break;
case PassportEvent.LoggedIn:
Debug.Log("完成登錄");
break;
case PassportEvent.Completed:
Debug.Log("完成所有流程");
await SelectPersona();
await CallStateless();
break;
case PassportEvent.LoggedOut:
Debug.Log("用戶登出");
break;
default:
throw new ArgumentOutOfRangeException(nameof(e), e, null);
}
}
如果是 External Login 的方式登錄,調用方法 CallStateless:
//點擊「ExternalLogin」按鈕,響應的事件
public async void UseExternalLogin()
{
Debug.Log("Click ExternalLogin");
// 使用 UOS Launcher 方式初始化 Passport SDK
try
{
await PassportFeatureSDK.Initialize();
}
catch (PassportException e)
{
Debug.Log($"failed to initialize sdk: {e.Message}");
throw;
}
// 在 SDK 中使用 ExternalLogin 方法時傳入的是 外部 ID系統 的 UserID/PersonalID/DisplayName
const string userId = " " ;// 需要登錄的、外部系統的用戶Id
const string personaId = " " ;// 可選, 需要登錄的、外部系統的角色ID
const string personaDisplayName = " " ;//可選, 需要登錄的、角色的昵稱
//const string personaDisplayName = " ";//可選, 需要登錄的、角色的昵稱
Debug.Log("Call External Login");
await AuthTokenManager.ExternalLogin(userId, personaId, personaDisplayName);
await CallStateless();
}
運行測試后,在控制臺查看輸出結果,可以看到服務器簽名過的 JWT 信息:
6.3.4 服務端進行 JWT 校驗,從中提取出 UserId/PersonaId
首先在「Func Stateless Tool」工具面板中,可以點擊「+新建」按鈕,新創建一個云函數類腳本 ValidateJwtToken,然后腳本中添加一個用來校驗 Token 的云函數Validate。
云函數 Validate 的主要功能是來驗證 JWT 令牌。
代碼中的具體步驟包括:
首先調用 FuncContext 類的GetToken方法來獲取 JWT 令牌;
然后異步調用 FuncContext 類的ValidateTokenAsync方法來驗證 JWT 令牌;
最后再通過調用GetUserId和GetPersonaId方法,就可以來獲取到 UserId 和 PersonaId。
using System.Threading.Tasks;
using Unity.UOS.Func.Stateless.Core.Attributes;
using Unity.UOS.Func.Stateless.Core.Context;
using UnityEngine;
namespace CloudService
{
[CloudService]
public class ValidateJwtToken
{
[CloudFunc]
public async Task
Validate() { var jwtToken = FuncContext.GetToken(); Debug.Log($"get jwt token: {jwtToken}"); await FuncContext.ValidateTokenAsync(); var logMsg = $"validate token, userId: {FuncContext.GetUserId()}, personaId: {FuncContext.GetPersonaId()}"; Debug.Log(logMsg); return logMsg; } } }
溫馨提醒:如果你是 JavaScript 版本,在云函數中校驗 JWT 的話,可以根據下面的 UOS 官網鏈接,查看具體的用法:
https://uos.unity.cn/doc/func/stateless/crud-connect#js-verify-jwt-token
然后繼續在 CallStateless 方法中,創建 ValidateJwtToken 類的實例,調用該實例的云函數 Validate 來驗證存儲在 FuncContext 中的令牌,并將驗證結果輸出到日志中。
//新引入的 namespace---------------------------------------------------------------
using CloudService;
//-------------------------------------------------------------------------------
#region ValidateFuncStateless
private async Task CallStateless()
{
//validate jwt token
var getToken = await AuthTokenManager.GetAccessToken();
Debug.Log($"{getToken}");
FuncContext.SetToken(getToken);
var s = new ValidateJwtToken();
var resp = await s.Validate();
Debug.Log($"validateJwtTokenResp: {resp}");
}
#endregion
運行測試,再次查看輸出結果,就可以看到獲取到的 UserId 和 PersonaId 了。
7.云函數調用 Passport 服務
接著,給大家講解下在云函數中調用 Passport SDK 的功能。大家也可以進入 UOS 網頁的文檔手冊查看使用方法。
https://uos.unity.cn/doc/func/stateless/csharp-guide#use-passport
在云函數調用 Passport Feature SDK 時,會利用玩家的 JWT 進行穿透調用。穿透調用,即是把 Token 完整傳遞到后續流程。此 Token 含玩家身份、權限等信息,后續各服務環節和組件能依此驗證玩家身份,按權限決定能否訪問特定功能。
接下來,以調用 Passport Feature SDK 提供的排行榜功能為例進行詳細說明。在調用時,會將玩家的 JWT 作為關鍵參數一并傳遞給 SDK。SDK 接收到調用請求和 JWT 后,先核驗玩家身份,若身份驗證通過,再依權限判斷能否訪問排行榜。只有身份和權限都合規,SDK 才執行排行榜查詢操作,并將查詢結果返回給云函數,再由云函數反饋給客戶端。
通過這種JWT 穿透調用的方式,不僅確保了玩家訪問權限的嚴格管控,同時也在云函數與 Passport Feature SDK 的交互過程中,實現了高效、安全且精準的功能調用,為玩家提供了流暢且符合其權限范圍的服務體驗。
7.1 創建一個排行榜
先來創建一個排行榜,在 UOS Passport 服務的左側頁面,選中「排行榜」,然后點擊「立即創建」,創建一個排行榜。
自定義排行榜的名稱和唯一標識,排行榜窗口中的其它參數含義,詳情可參考下面鏈接:
https://uos.unity.cn/doc/passport/leaderboard#basic
在使用代碼訪問 PassportFeature SDK 提供的功能前,需要先進行 PassportFeature SDK 的初始化。
如果是 Passport UI Login 的方式,先添加初始化 PassportFeature SDK 的代碼。
//點擊「PassportUI」按鈕,響應的事件
public async void UsePassportUI()
{
Debug.Log("Click PassportUI");
//使用 UOS Launcher 方式初始化 Passport Feature SDK
try
{
await PassportFeatureSDK.Initialize();
}
catch (PassportException e)
{
Debug.Log($"failed to initialize sdk: {e.Message}");
throw;
}
// 調用 Passport SDK 初始化
await PassportUI.Init(_config, _callback);
}
7.3 云函數調用 Passport 服務中的排行榜功能
在 ValidateJwtToken.cs 類的腳本中,新增一個云函數 CallPassport。編寫云函數 CallPassport,在云函數內可以直接使用 Passport Feature SDK 所提供的獲取排行榜的功能。
通過 GetLeaderboards 方法獲取排行榜列表,測試時,我們可以打印輸出排行榜的名字即可。
//新引入的 namespace---------------------------------------------------------------
using Unity.Passport.Runtime;
//-------------------------------------------------------------------------------
[CloudFunc]
public async Task
CallPassport() { // 獲取排行榜列表 var leaderBoardList = await PassportFeatureSDK.Leaderboard.GetLeaderboards(); foreach (var t in leaderBoardList.Leaderboards) { Debug.Log($"displayName: {t.DisplayName}");//輸出排行榜的名字 } return leaderBoardList.Leaderboards[0].SlugName; }
然后在 Func Stateless Tool 工具面板中,會看到新添加的云函數 CallPassport:
接著在客戶端調用剛才的云函數,找到 CallStateless 的方法,添加調用云函數 CallPassport 的代碼,同時打印輸出一下排行榜的唯一標識。
private async Task CallStateless()
{
//validate jwt token
var getToken = await AuthTokenManager.GetAccessToken();
FuncContext.SetToken(getToken);
var s = new ValidateJwtToken();
var resp = await s.Validate();
Debug.Log($"validateJwtTokenResp: {resp}");
//call passport
resp = await s.CallPassport();
Debug.Log($"callPassportResp: {resp}");//云函數的返回值:排行榜的唯一標識(SlugName)
}
運行測試,查看控制臺輸出的結果:會輸出當前排行榜的名字以及排行榜的唯一標識名。
8.云函數調用 CRUD Save 服務
我們也可以在云函數中直接使用 CRUD Save SDK 所提供的功能,同樣的,可以進入 UOS 網頁的文檔手冊查看使用方法。
https://uos.unity.cn/doc/func/stateless/csharp-guide#use-save
在這里,我們以「通過字節數組創建一個存檔文件」為示例講解。
8.1 CRUD Save SDK 的初始化
在使用代碼訪問 CRUD Save SDK 提供的功能前,需要先進行 CRUD Save SDK 的初始化。
使用 Passport UI Login 的方式時,在 UsePassportUI 方法中初始化 CRUD Save SDK:
//新引入的 namespace---------------------------------------------------------------
using Unity.UOS.CloudSave.Exception;
using Unity.UOS.Func.Stateless.Core.Context;
//-------------------------------------------------------------------------------
//點擊「PassportUI」按鈕,響應的事件
public async void UsePassportUI()
{
Debug.Log("Click PassportUI");
// 使用 UOS Launcher 方式初始化 PassportFeature SDK
// 此處省略其它代碼行......
//使用 UOS Launcher 方式初始化 CloudSave SDK
try
{
await CloudSaveSDK.InitializeAsync();
}
catch (CloudSaveClientException e)
{
Debug.LogErrorFormat($"failed to initialize sdk, clientEx: {e}");
throw;
}
catch (CloudSaveServerException e)
{
Debug.LogErrorFormat($"failed to initialize sdk, serverEx: {e}");
throw;
}
// 調用 Passport SDK 初始化
await PassportUI.Init(_config, _callback);
}
使用 External Login 的方式時,在 UseExternalLogin 方法中初始化CRUD SaveSDK:
// 點擊「ExternalLogin」按鈕,響應的事件
public async void UseExternalLogin()
{
Debug.Log("Click ExternalLogin");
// 使用 UOS Launcher 方式初始化 Passport Feature SDK
// 此處省略其它代碼行......
// 使用 UOS Launcher 方式初始化 CloudSave SDK
try
{
await CloudSaveSDK.InitializeAsync();
}
catch (CloudSaveClientException e)
{
Debug.LogErrorFormat($"failed to initialize sdk, clientEx: {e}");
throw;
}
catch (CloudSaveServerException e)
{
Debug.LogErrorFormat($"failed to initialize sdk, serverEx: {e}");
throw;
}
// 在 SDK 中使用 ExternalLogin 方法時傳入的是 外部 ID系統 的 UserID/PersonalID/DisplayName
const string userId = " " ;// 需要登錄的、外部系統的用戶Id
const string personaId = " " ;// 可選, 需要登錄的、外部系統的角色ID
const string personaDisplayName = " " ;//可選, 需要登錄的、角色的昵稱
Debug.Log("Call External Login");
await AuthTokenManager.ExternalLogin(userId, personaId, personaDisplayName);
await CallStateless();
}
8.2 云函數調用 CRUD Save 服務中的功能
在 ValidateJwtToken.cs 類的腳本中,新增一個云函數 CallSave。編寫以下云函數 CallSave,在云函數內直接使用 CloudSave SDK 所提供的功能。
通過 CreateAsync 方法創建一個存檔文件,傳入參數:存檔文件的名稱 name 和存檔文件的內容 bytes 。
功能測試時,我們可以打印輸出存檔文件的 ID 號即可。
//新引入的 namespace---------------------------------------------------------------
using Unity.UOS.CloudSave;
//-------------------------------------------------------------------------------
[CloudFunc]
public async Task
CallSave() { var name = "save name"; var bytes = new byte[] { 0x01, 0x02 }; var saveId = await CloudSaveSDK.Instance.Files.CreateAsync(name, bytes); Debug.Log($"saveId: {saveId}"); return saveId; }
然后在「Func Stateless Tool」工具面板中,會看到新添加的云函數 CallSave:
接著在客戶端調用剛才的云函數,找到 CallStateless 的方法,添加調用云函數 CallSave 的代碼,同時打印輸出一下存檔文件的 ID 號。
private async Task CallStateless()
{
//validate jwt token
var getToken = await AuthTokenManager.GetAccessToken();
FuncContext.SetToken(getToken);
var s = new ValidateJwtToken();
var resp = await s.Validate();
Debug.Log($"validateJwtTokenResp: {resp}");
//call passport
resp = await s.CallPassport();
Debug.Log($"callPassportResp: {resp}");//云函數的返回值:排行榜的唯一標識(SlugName)
// call save
resp = await s.CallSave();
Debug.Log($"callSaveResp: {resp}");//云函數的返回值:存檔ID
}
運行測試,查看控制臺輸出的結果:會輸出當前云存儲文件的存檔 ID 號。
刷新下 UOS 網頁端,可以看到云存檔文件。
9. 上傳云函數
在「Func Stateless Tool」工具面板中,點擊「上傳云函數」按鈕,等待云函數的構建和上傳。
查看 UOS 網頁端,可以看到之前創建的云函數(validatejwttoken)已經自動上傳了。
當云函數上傳成功后,將云函數的調用模式從「本地調用」切換成「遠程調用」的方式。
在遠程調用模式下,云函數的代碼自動發生了修改:
namespace CloudService
{
// add suffix to avoid conflict
public static class ValidateJwtTokenq3k9FwJi5A
{
public static string Domain = "stateless.unity.cn";
}
}
namespace CloudService
{
[CloudService]
public class ValidateJwtToken
{
[CloudFunc]
public async Task
Validate() { var jwtToken = await AuthTokenManager.GetAccessToken(Settings.AppID); FuncContext.SetAppIdSecret(Settings.AppID, Settings.AppSecret); FuncContext.SetToken(jwtToken); var json = "{" + $"" + "}"; var jsonResult = await HttpClient.Call($"https://{ValidateJwtTokenq3k9FwJi5A.Domain}/release/4704f96a-6d8a-42c1-ac34-1cca7ece2813/validatejwttoken", "validate", json); try { return JsonConvert.DeserializeObject
(jsonResult); } catch (Exception ex) { Debug.LogException(ex); throw new ExecuteException(jsonResult); } } //此處省略云函數 CallPassport 和 CallSave 的代碼行................................. } }
在云函數中會先根據 Assets/Resources 文件夾下的 UOSSettings.asset 文件中的 AppID,來獲取 JWT 令牌;
然后將 UOS App 的 AppID 和 AppSecret 設置到函數上下文中,后續的請求會用到這些信息;
同時將剛才獲取到的 JWT 令牌設置到函數上下文中,用于后續請求的身份驗證;
接著通過 HttpClient 調用遠程的云函數接口;
最后嘗試將返回的 JSON 結果反序列化為字符串并返回。
通過本教程的深度剖析,相信大家已充分掌握 UserId 與 PersonaId 的區別、JWT 身份驗證機制的精髓,也能熟練運用 Passport Login 和 External Login 策略。同時,對云函數在 UOS 服務中的應用邏輯、FuncContext 的關鍵作用,以及相關操作要點,也都有了扎實的理解。
希望開發者們將這些知識運用到實際項目中,充分發揮 UOS 鑒權的優勢,打造出更安全可靠、用戶體驗卓越的游戲產品!
本期教程我們先講解到這里,大家趕快下載 Demo 項目試試吧!我們下一期再見哦!
Unity Online Services (UOS) 是一個專為游戲開發者設計的一站式游戲云服務平臺,提供覆蓋游戲全生命周期的開發、運營和推廣支持。
了解更多 UOS 相關信息:
官網:https://uos.unity.cn
技術交流 QQ 群:823878269
公眾號:UOS 游戲云服務
Unity 官方微信
第一時間了解Unity引擎動向,學習進階開發技能
每一個“點贊”、“在看”,都是我們前進的動力
特別聲明:以上內容(如有圖片或視頻亦包括在內)為自媒體平臺“網易號”用戶上傳并發布,本平臺僅提供信息存儲服務。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.