在項目里集成 Unity Online Services(UOS) 服務(wù)的過程中,你或許正被一系列技術(shù)知識點所困擾:UserId 和 PersonaId究竟有何不同?JWT的身份驗證機制又該如何理解?Passport Login 與 External Login在實際應(yīng)用時,究竟該如何抉擇?
這些看似細微的技術(shù)要點,卻在很大程度上決定了系統(tǒng)的安全性高低以及用戶體驗是否流暢。
與此同時,云函數(shù)作為后端開發(fā)不可或缺的有力工具,其應(yīng)用邏輯、FuncContext所發(fā)揮的作用、云函數(shù)調(diào)用 UOS 服務(wù)的最優(yōu)方法,還有在云函數(shù)中驗證 JWT并精準提取關(guān)鍵用戶信息的操作,都是開發(fā)者必須熟練掌握的核心技能。
為助力大家成功攻克這些技術(shù)知識點,本教程將深入剖析上述關(guān)鍵主題,全面解答大家的疑惑!
本教程中涉及 UOS 服務(wù)包括:
云函數(shù)服務(wù) Func Stateless (C#):用于便捷部署并運行服務(wù)端邏輯代碼
玩家通行證服務(wù) Passport:集成 Passport Login 驗證玩家身份,以 Passport Feature 支持實時游戲交互
云存檔服務(wù) CRUD Save:用于在云端安全存儲與管理玩家數(shù)據(jù)
云函數(shù)服務(wù) Func Stateless
在保障游戲或應(yīng)用安全性的重要考量下, 確保關(guān)鍵邏輯和數(shù)據(jù)的權(quán)威性處理僅在服務(wù)器端執(zhí)行顯得尤為重要 。這是因為 客戶端的代碼包體容易被破解或篡改 ,從而引發(fā)不公平競爭、數(shù)據(jù)泄露等安全問題。UOS Func Stateless (C#) 云函數(shù)以其高效、靈活、安全且成本低的特點,為游戲開發(fā)者提供了一個理想的服務(wù)端邏輯解決方案,助力他們在快速迭代的市場環(huán)境中保持競爭優(yōu)勢。
Func Stateles s 支持在本地開發(fā)環(huán)境中直接調(diào)試云函數(shù) ,無需部署到云端即可驗證邏輯的正確性,降低了調(diào)試難度,加快了開發(fā)迭代速度。
Func Stateless 能夠 自動將服務(wù)端邏輯代碼打包并部署到云端 ,簡化了部 署流程,減少了人為錯誤,讓開發(fā)者更專注于業(yè)務(wù)邏輯的實現(xiàn)。
玩家通行證服務(wù) Passport
Passport 是 UOS 官方提供的玩家通行證服務(wù),包括玩家登錄系統(tǒng) (Login),以及與玩家相關(guān)的眾多游戲功能 (Feature)。
Passport Login 是一個可以開箱即用的玩家登錄系統(tǒng),通過非常簡便的集成方式,便可以獲得如下功能: 靈活可 配的 UI 登錄界面,包含用戶協(xié)議、登錄、實名認證; 支持手機號登錄,以及微信、QQ、 AppleID 、TapTap、好游快爆等 主流第三方 OAuth 登錄;可以配置游戲服務(wù)器,以及管理游戲角色。
Passport Feature 是以玩家為中心,包含了在線游戲中各種常見的功能。包括:排行榜、公會、游戲禮包、防沉迷系統(tǒng)、經(jīng)濟系統(tǒng)、郵件系統(tǒng)、成就系統(tǒng)、戰(zhàn)令系統(tǒng)、公告、任務(wù)系統(tǒng)。
云存檔服務(wù) CRUD Save
借助 UOS Save 所提供的全面而專業(yè)的玩家數(shù)據(jù)存儲、檢索及管理服務(wù),開發(fā)者能夠極為便捷地為廣大游戲玩家打造出一個跨越不同平臺與設(shè)備的、安全且高度可用的游戲存檔系統(tǒng)。
這一服務(wù)不僅確保了玩家能夠在任何時間、任何地點無縫地繼續(xù)他們的游戲進程,而且通過嚴格的數(shù)據(jù)安全保障措施,讓玩家的游戲數(shù)據(jù)始終處于嚴密的保護之下。同時,其高可用性的設(shè)計也保證了玩家數(shù)據(jù)的實時同步與持久存儲,為玩家?guī)砹烁恿鲿?、穩(wěn)定的游戲體驗。
教程視頻
教程學(xué)習(xí)大綱
Unity 項目工程準備工作
創(chuàng)建 UOS App 并啟用 Func Stateless / CRUD Save / Passport 服務(wù)
解析 Passport 相關(guān)的概念術(shù)語
使用 Passport UI Login 的方式登錄
使用 External Login 的方式登錄
在云函數(shù)服務(wù)端驗證 JWT,并獲取 UserId / PersonaId
云函數(shù)調(diào)用 Passport 服務(wù)
云函數(shù)調(diào)用 CRUD Save 服務(wù)
上傳云函數(shù)
教程示例工程源文件
大家可通過下方鏈接,下載本教程對應(yīng)的完整示例工程源文件 :
示例 Demo 項目工程下載鏈接
https://uos-1314001764.cos.ap-shanghai.myqcloud.com/func/stateless-csharp/UOSAuthDemo.zip
教程操作步驟
接下來讓我們來看看項目中的具體用法吧!你可以根據(jù)教程的步驟一步步從頭跟著操作,也可以直接下載鏈接提供的完整示例工程源文件。
1. Unity項目工程準備工作
1.1 創(chuàng)建一個空的項目工程
打開 Unity Hub,選擇創(chuàng)建「新項目」,教程這里使用的 Unity 編輯器版本是2022.3.53f1c1 版本,大家可以自行選擇電腦上已安裝的版本。
項目模板可以選擇「3D(Built-in)」,自定義項目名稱和位置??梢怨催x選項「啟用游戲云服務(wù)」,勾選后會自動為你的項目工程安裝 UOS Launcher 的,然后可以直接通過 UOS Launcher 來啟用想要使用的服務(wù)即可。
最后點擊「創(chuàng)建項目」,等待項目的創(chuàng)建和加載。
1.2 場景中創(chuàng)建用于 Passport 登錄的 UI 按鈕
打開項目工程以后,我們使用 UGUI 在場景中創(chuàng)建兩個 Button 按鈕,在后面的步驟中作為實現(xiàn) Passport 的登錄按鈕。在 Hierarchy 窗口中,點擊「+」→「UI」→「Button - TextMeshPro」來創(chuàng)建一個按鈕,在彈出窗口中,點擊「Import TMP Essentials」來導(dǎo)入相關(guān)資源:
設(shè)置 UI 自適應(yīng):找到 Canvas 對象上的 Canvas Scaler 組件,將「UI Scale Mode」設(shè)置為:Scale With Screen Size,分辨率暫時選擇 1920 *1080。
修改按鈕的名字為:PassportUI,是后面使用 Passport UI Login 方式登錄時用到的按鈕。大家可以自己調(diào)整想要的按鈕的大小、位置以及按鈕的顏色或者按鈕的背景貼圖等。
選中 PassportUI 按鈕,按下 Ctrl+D 復(fù)制一份,重命名為:ExternalLogin,作為后續(xù)使用 ExternalLogin 的方式登錄時用的按鈕。
1.3 場景中創(chuàng)建空對象,并掛載 UIController.cs 腳本組件
場景中創(chuàng)建一個空對象,可命名為 UIController。然后在 Project 窗口中,創(chuàng)建腳本文件夾 Scripts/UIController,創(chuàng)建一個腳本 UIController.cs,并將該腳本掛載在空對象 UIController 上,后續(xù)的代碼中會用到這個腳本。
2.綁定 UOS App 并開啟服務(wù)
溫馨提示:當前項目中已安裝好 UOS Launcher,不需要再次安裝了。大家可以參考之前的的公眾號文章教程,來為你當前的項目綁定你創(chuàng)建好的 UOS App 。
2.1 綁定 UOS App
點擊 Launcher 面板的「LinkApp」按鈕,在彈出窗口中選擇「By Unity project」,在「Select organization」這里選擇一個自己的項目組織,然后在「Select project 」選項這里,我們選擇「Create a new project 」,自行設(shè)置修改項目名字「Project name」。
教程中,我們就先選擇綁定創(chuàng)建好的 UOSAuthDemo 應(yīng)用了。
2.2 開啟 Passport服務(wù)
在編輯器內(nèi) Unity Online Services 窗口的下拉服務(wù)列表中,找到「Passport Login」和 「Passport Feature」,點擊「Enable」開啟服務(wù),同時安裝Passport Login SDK和Passport Feature SDK。
導(dǎo)入 UI 資源
在安裝Passport Login SDK的過程中,編輯器會提示「導(dǎo)入 PassportUI 資源」,點擊導(dǎo)入。 如未導(dǎo)入,請手動點擊編輯器菜單「UOS -> Passport -> Import PassportUI」導(dǎo)入。
2.3 開啟 Func-Stateless 服務(wù)
大家可以查看之前分享過的公眾號文章 ,來了解更多關(guān)于云函數(shù)服務(wù)(Func Stateless)的實戰(zhàn)用法!
接著繼續(xù)在 UOS Launcher 的下拉服務(wù)窗口列表中,找到「Func-Stateless」,點擊「Enable」開啟服務(wù),并安裝Func-Stateless SDK。
2.4 開啟 CRUD Save 服務(wù)
可以查看之前分享過的公眾號文章 ,來了解更多關(guān)于云存檔服務(wù)(CRUD Save)的實戰(zhàn)用法!
接著繼續(xù)在 UOS Launcher 的下拉服務(wù)窗口列表中,找到「CRUD-Save」,點擊「Enable」開啟服務(wù),并安裝CRUD Save SDK。
3.解析 Passport 相關(guān)的概念術(shù)語
使用 Passport 服務(wù)來實現(xiàn)游戲中的玩家登錄系統(tǒng)時,可以通過Passport UI Login 和 External Login兩種方式來實現(xiàn)登錄功能,兩種方式均可以實現(xiàn)接入 Passport Feature SDK 中的功能。
3.1 什么是 Passport UI Login
使用的前提條件:開通 Passport 服務(wù),并安裝 Passport Login SDK 。
Passport Login 是一個可以開箱即用的玩家登錄系統(tǒng),可以通過非常簡便的集成方式,來獲得如下功能:
提供了靈活可配的 UI 登錄界面,包含用戶協(xié)議、登錄、實名認證;
支持手機號登錄,以及微信、QQ、AppleID、TapTap、好游快爆等主流第三方 OAuth 登錄;
提供了游戲服務(wù)器、游戲角色的管理。
External Login 是外部賬號系統(tǒng)登錄的方式,如果 External Login 結(jié)合 Passport Feature SDK 一起使用:
當在項目中接入 Passport 的 Feature SDK(如排行榜、禮包等功能)時,不想使用 Passport Login 進行用戶登錄和管理的情況下,還可以使用 External Login 的方式來登錄。
External Login 是 UOS Passport 為已有玩家 ID 系統(tǒng)的開發(fā)者提供的外部賬號登錄的方式,來接入 Passport Feature 的相關(guān)功能。在應(yīng)用開發(fā)過程中,可以在 SDK 中使用 AuthTokenManager.ExternalLogin 方法,傳入外部賬號登錄系統(tǒng)的 UserID 或者 PersonaID。
需要注意的是:當 External Login 結(jié)合 UOS SDK 一起使用時,如果當前 UOS App 開啟了 Passport 的功能,系統(tǒng)就會創(chuàng)建角色;反之,如果沒開通 Passport 功能的話,則不會創(chuàng)建角色,僅僅用于 SDK 鑒權(quán)以獲取 AccessToken。
3.3 什么是 UserId(用戶ID)
UserID 對應(yīng)游戲中用戶賬號的概念,用戶首次登錄集成了 UOS Passport SDK 的游戲之后, UOS Passport 會創(chuàng)建一個對應(yīng)的用戶賬號。
一個游戲中,一個玩家就一個賬號,一般賬號和手機號、微信等綁定。
3.4 什么是 PersonaId(角色ID)
PersonaId 對應(yīng)游戲服務(wù)器中角色的概念,用戶可在同一個用戶賬號(UserID)下的不同游戲服務(wù)器(Realm)中創(chuàng)建不同的角色(Persona)。
通俗地說,就是一個游戲可以有多個區(qū)服,一個玩家可以在每個區(qū)服里面創(chuàng)建一個角色。
4.使用 Passport UI Login 的方式登錄
了解了登錄相關(guān)的概念后,接下來讓我們看下在代碼中,是如何獲取 UserID 和 PersonID 的!
4.1 接入 Passport UI Login SDK
接下來,在 Unity 項目中接入 Passport UI Login SDK。使用方法可以參考文檔鏈接: https://uos.unity.cn/doc/passport/login#loginAccessExample
然后在之前創(chuàng)建的 UIController.cs 腳本中,添加下面的代碼,這段代碼大家也可以直接從上面提供的文檔鏈接中復(fù)制過來。
_config是 Passport SDK 進行初始化時的相關(guān)配置;
_callback是 Passport SDK 的回調(diào)函數(shù):Passport 中已經(jīng)封裝注冊好了,在用戶拒絕協(xié)議、完成登錄、完成所有流程、用戶登出這幾個狀態(tài)下的回調(diào)事件。
登錄完成后,會選擇一個角色進入游戲。在腳本的 _callback 方法的Completed 狀態(tài)下,看到會調(diào)用選擇角色的方法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, // 是否開啟自動旋轉(zhuǎn),默認值為 false。
InvokeLoginManually = false, // 是否通過自行調(diào)用 Login 函數(shù)啟動登錄面板,默認值為 false。
Theme = PassportUITheme.Dark, // 風格主題配置。
UnityContainerId = "unity-container" // WebGL 場景下 Unity 實例
};
// sdk 回調(diào)函數(shù)
private async void _callback(PassportEvent e)
{
// event: 不同情況下的回調(diào)事件,詳情可以參考下面的回調(diào)類型。
switch (e)
{
case PassportEvent.RejectedTos:
Debug.Log("用戶拒絕了協(xié)議");
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; // 根據(jù)需要自行選擇域
// var realmID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"; // 也可以填寫固定的 RealmID 而不是動態(tài)獲取
// 獲取(或創(chuàng)建)與選擇角色
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 調(diào)用 Passport UI SDK 的初始化
封裝一個 UsePassportUI 方法,來實現(xiàn)點擊「PassportUI」按鈕時響應(yīng)的事件。在方法內(nèi)通過調(diào)用 PassportUI.Init 來實現(xiàn) Passport Login SDK 的初始化。
//點擊「PassportUI」按鈕,響應(yīng)的事件
public async void UsePassportUI()
{
Debug.Log("Click PassportUI");
// 調(diào)用 Passport SDK 初始化
await PassportUI.Init(_config, _callback);
}
找到場景中的 UI 按鈕對象 PassportUI,確保它已經(jīng)綁定了 UIController.cs 腳本中的 UsePassportUI 方法。

4.3 運行測試并查看已創(chuàng)建的角色
然后點擊運行游戲,點擊 Game 窗口中的 PassportUI 按鈕,使用手機號短信驗證的方式來登錄,并進行身份實名認證:


可以查看控制臺的日志輸出信息:

查看 UOS 網(wǎng)頁端創(chuàng)建的用戶和角色:
在 Passport 的「用戶管理」頁面,可以看到以手機號形式登錄的「用戶ID」信息。

在 Passport 的「角色管理」頁面,可以看到剛才的「用戶ID」創(chuàng)建的角色所屬的「角色ID」和「角色名稱」。

5.使用ExternalLogin 的方式登錄5.1 調(diào)用 External Login 方法來實現(xiàn)外部登錄
https://uos.unity.cn/doc/passport/external-login
在 UIController.cs 腳本中使用 External Login 的方式來登錄,我們封裝 UseExternalLogin 方法,來實現(xiàn)點擊「External Login」按鈕時響應(yīng)的事件。
如果結(jié)合 PassportFeature 的 SDK 一起使用的話,我們先調(diào)用 Initialize 方法來完成 PassportFeatureSDK 的初始化,這個操作同時也會自動實現(xiàn) AuthTokenManager 的初始化。
需要自己定義一個唯一的 Id,作為參數(shù)賦值給 userId 變量。然后調(diào)用 ExternalLogin 方法自行傳入外部 ID 系統(tǒng)的 UserId/PersonalId/DisplayName。
我們會根據(jù) userId 是否一致,來決定是否新建用戶的。
//新引入的 namespace---------------------------------------------------------------
using Unity.UOS.Auth;
//-------------------------------------------------------------------------------
//點擊「ExternalLogin」按鈕,響應(yīng)的事件
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系統(tǒng) 的 UserID/PersonalID/DisplayName
const string userId = " " ;// 需要登錄的、外部系統(tǒng)的用戶Id
const string personaId = " " ;// 可選, 需要登錄的、外部系統(tǒng)的角色ID
const string personaDisplayName = " " ;//可選, 需要登錄的、角色的昵稱
Debug.Log("Call External Login");
await AuthTokenManager.ExternalLogin(userId, personaId, personaDisplayName);
}
找到場景中的 UI 按鈕對象 ExternalLogin,確保它已經(jīng)綁定了 UIController.cs 腳本中的 UseExternalLogin 方法。

5.2 運行測試并查看已創(chuàng)建的角色
然后點擊運行游戲,點擊 Game 窗口中的 ExternalLogin 按鈕,可以看到控制臺的日志輸出信息:

查看 UOS 網(wǎng)頁端創(chuàng)建的用戶和角色:
在 Passport 的「用戶管理」頁面,可以看到以「服務(wù)端登錄」形式登錄的「用戶ID」信息。

在 Passport 的「角色管理」頁面,可以看到剛才的「用戶ID」創(chuàng)建的角色所屬的「角色ID」和「角色名稱」。
5.3 UserId 不變,修改 PersonId,再次測試
使用 ExternalLogin 的方式登錄時,保持用戶的 UserId 不變,僅修改 PersonaId 的話。不會新增用戶,但是會為已有的用戶新創(chuàng)建一個角色的。
打開腳本中的代碼:將角色 ID 的值由原來的 變更為 。
// 在 SDK 中使用 ExternalLogin 方法時傳入的是 外部 ID系統(tǒng) 的 UserID/PersonalID/DisplayName
const string userId = " " ;// 需要登錄的、外部系統(tǒng)的用戶Id
const string personaId = " " ;// 可選, 需要登錄的、外部系統(tǒng)的角色ID
const string personaDisplayName = " " ;//可選, 需要登錄的、角色的昵稱
Debug.Log("Call External Login");
await AuthTokenManager.ExternalLogin(userId, personaId, personaDisplayName);
運行測試后,查看 UOS 網(wǎng)頁端:
在「用戶管理」這里,由于 UserId 沒變,所以并沒有新增用戶。

在「角色管理」這里,可以看到同一個用戶 ID 下,新增了一條角色 ID 的信息。

6.在云函數(shù)服務(wù)端驗證 JWT,并獲取 UserId/PersonaId6.1 介紹 JWT6.1.1 什么是 JSON Web Token
JWT網(wǎng)頁鏈接:https://jwt.io/introduction
JWT 指的是 JSON 網(wǎng)絡(luò)令牌(全稱是 JSON Web Token),它是一種用于在網(wǎng)絡(luò)應(yīng)用之間安全傳輸聲明的開放標準(RFC 7519)。它定義了一種緊湊且自包含的方式,以 JSON 對象的形式在各方之間安全地傳輸信息。由于這些信息經(jīng)過了數(shù)字簽名,所以可以被驗證且值得信賴。
6.1.2 什么時候應(yīng)該使用 JWT 呢?
以下是一些 JWT 發(fā)揮作用的場景:
授權(quán):這是使用 JWT 最常見的場景。一旦用戶登錄,后續(xù)的每一次請求都將包含 JWT,憑借該令牌,用戶能夠訪問被允許的路由、服務(wù)和資源。如今,單點登錄是一項廣泛使用 JWT 的功能,這得益于它的低開銷以及易于在不同域之間使用的特性。
信息交換:JWT 是在各方之間安全傳輸信息的良好方式。由于 JWT 可以進行簽名(例如使用公私鑰對),你能夠確定發(fā)送方的真實身份。此外,因為簽名是通過對頭部和負載進行計算得出的,所以你還可以驗證內(nèi)容未被篡改。
在云函數(shù)服務(wù)端驗證 JWT 并獲取 UserId/PersonaId,是確保系統(tǒng)安全性和識別用戶身份的重要步驟。
通過驗證 JWT 的簽名和有效期,可以確保請求是合法的,并且來自受信任的客戶端。而 UserId 和 PersonaId 可以幫助服務(wù)端識別用戶和用戶的角色,從而根據(jù)用戶的權(quán)限和角色來處理請求。
6.2 介紹 FuncContext(函數(shù)上下文)6.2.1 FuncContext 的作用
找到 Project 窗口中的 Packages 目錄,在 UOS Func Stateless SDK 路徑下可以看到 FuncContext.cs 腳本。
FuncContext 是一個靜態(tài)類,主要用于管理和存儲與函數(shù)調(diào)用相關(guān)的上下文信息。

這些信息通常涵蓋了所創(chuàng)建的 UOS 應(yīng)用的 AppId、密鑰(AppSecret)、令牌(Token)、用戶 ID(UserId)、角色 ID (PersonaId)以及一些自定義屬性(Properties)。
該類提供了一系列方法,用于設(shè)置、獲取和清除這些信息。同時,該類支持從 HTTP Head 解析信息以及生成 HTTP Head。此外,F(xiàn)uncContext 還具備 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 實現(xiàn)原理HTTP Header 生成:當客戶端需要與服務(wù)端進行交互時,可調(diào)用FormHeaders方法。該方法會將 FuncContext 中存儲的上下文信息轉(zhuǎn)化為一個字典,然后作為 HTTP Header 添加到請求中。這樣一來,服務(wù)端在接收到請求后,能夠從 HTTP Header 中提取出應(yīng)用 ID、應(yīng)用密鑰、令牌、用戶 ID、角色 ID 以及自定義屬性等信息,為處理客戶端請求提供必要的數(shù)據(jù)支持。
HTTP Header 解析:當客戶端接收到服務(wù)端的響應(yīng),可以調(diào)用ParseFromHeader方法。該方法會對 HTTP Header 中的信息進行解析,并將解析結(jié)果更新到 FuncContext 的上下文信息中。經(jīng)過更新后,本地代碼便能使用這些信息來執(zhí)行后續(xù)的操作,比如依據(jù)用戶 ID 執(zhí)行特定業(yè)務(wù)邏輯,或者根據(jù)角色 ID 判斷用戶權(quán)限等。
通過上述生成和解析 HTTP Header 的過程,FuncContext 類實現(xiàn)了上下文信息在客戶端和服務(wù)端之間的傳遞與管理,極大地方便了函數(shù)調(diào)用過程中的上下文處理,提升了系統(tǒng)的交互效率與穩(wěn)定性。
6.3 在云函數(shù)中校驗 JWT ,獲取 UserId/PersonaId
我們剛才已經(jīng)在客戶端里面調(diào)用了 Passport Login,接著在云函數(shù)服務(wù)端驗證 JWT 并獲取 UserId/PersonaId。云函數(shù)服務(wù)端內(nèi)置了 JWT 簽名驗證,也就是說如果客戶端被破解,被故意傳入非法的 JWT,是無法通過云函數(shù)服務(wù)端驗證的。
6.3.1 實現(xiàn)思路
使用 Passport 登錄之后,在客戶端代碼中獲取到服務(wù)器簽名過的 JWT,這個 Token 中包含了 UserId/PersonaId,且無法被篡改。
將需要權(quán)限校驗的邏輯放到 Func Stateless 中來執(zhí)行,客戶端通過函數(shù)上下文(FuncContext)將 JWT 從客戶端傳給服務(wù)端。
https://uos.unity.cn/doc/func/stateless/csharp-guide#func-context
服務(wù)端從 FuncContext 中獲取 Token 后,進行 Token 校驗,并且從中提取出 UserId/PersonaId。
在 Unity Editor 菜單欄中先點擊「UOS -> Func Stateless -> Open Panel」按鈕,打開 Func Stateless Tool 面板。
點擊「+新建云函數(shù)」,會自動為我們創(chuàng)建云函數(shù)所在目錄 Scripts/CloudService 的,并默認提供了一個云函數(shù)。
然后再點擊「UOS -> Func Stateless -> Import NuGetForUnity」,來導(dǎo)入 UOS 版本的 NuGetForUnity 工具。
接下來在代碼中,來看看具體的用法!
6.3.3 客戶端獲取 JWT ,并將 JWT 從客戶端傳給服務(wù)端
在客戶端場景下,通常在與服務(wù)端進行交互前,需要確保客戶端所持有的令牌是有效的。在 UIController.cs 腳本中封裝一個方法 CallStateless 來驗證獲取到的令牌,確保令牌的有效性,以便后續(xù)可以正常訪問服務(wù)端的受保護資源。
調(diào)用 AuthTokenManager 類中的GetAccessToken方法,從服務(wù)端異步獲取訪問令牌;
然后在客戶端通過函數(shù)上下文(FuncContext)中的SetToken方法,將獲取到的訪問令牌設(shè)置到上下文中,以便后續(xù)的操作可以使用該令牌。
//新引入的 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 的方式登錄,調(diào)用方法 CallStateless:
我們在完成登錄的所有流程、選擇角色之后,調(diào)用下云函數(shù)校驗令牌的方法 CallStateless。
private async void _callback(PassportEvent e)
{
// event: 不同情況下的回調(diào)事件,詳情可以參考下面的回調(diào)類型。
switch (e)
{
case PassportEvent.RejectedTos:
Debug.Log("用戶拒絕了協(xié)議");
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 的方式登錄,調(diào)用方法 CallStateless:
//點擊「ExternalLogin」按鈕,響應(yīng)的事件
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系統(tǒng) 的 UserID/PersonalID/DisplayName
const string userId = " " ;// 需要登錄的、外部系統(tǒng)的用戶Id
const string personaId = " " ;// 可選, 需要登錄的、外部系統(tǒng)的角色ID
const string personaDisplayName = " " ;//可選, 需要登錄的、角色的昵稱
//const string personaDisplayName = " ";//可選, 需要登錄的、角色的昵稱
Debug.Log("Call External Login");
await AuthTokenManager.ExternalLogin(userId, personaId, personaDisplayName);
await CallStateless();
}
運行測試后,在控制臺查看輸出結(jié)果,可以看到服務(wù)器簽名過的 JWT 信息:
6.3.4 服務(wù)端進行 JWT 校驗,從中提取出 UserId/PersonaId
首先在「Func Stateless Tool」工具面板中,可以點擊「+新建」按鈕,新創(chuàng)建一個云函數(shù)類腳本 ValidateJwtToken,然后腳本中添加一個用來校驗 Token 的云函數(shù)Validate。
云函數(shù) Validate 的主要功能是來驗證 JWT 令牌。
代碼中的具體步驟包括:
首先調(diào)用 FuncContext 類的GetToken方法來獲取 JWT 令牌;
然后異步調(diào)用 FuncContext 類的ValidateTokenAsync方法來驗證 JWT 令牌;
最后再通過調(diào)用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 版本,在云函數(shù)中校驗 JWT 的話,可以根據(jù)下面的 UOS 官網(wǎng)鏈接,查看具體的用法:
https://uos.unity.cn/doc/func/stateless/crud-connect#js-verify-jwt-token
然后繼續(xù)在 CallStateless 方法中,創(chuàng)建 ValidateJwtToken 類的實例,調(diào)用該實例的云函數(shù) Validate 來驗證存儲在 FuncContext 中的令牌,并將驗證結(jié)果輸出到日志中。
//新引入的 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
運行測試,再次查看輸出結(jié)果,就可以看到獲取到的 UserId 和 PersonaId 了。
7.云函數(shù)調(diào)用 Passport 服務(wù)
接著,給大家講解下在云函數(shù)中調(diào)用 Passport SDK 的功能。大家也可以進入 UOS 網(wǎng)頁的文檔手冊查看使用方法。
https://uos.unity.cn/doc/func/stateless/csharp-guide#use-passport
在云函數(shù)調(diào)用 Passport Feature SDK 時,會利用玩家的 JWT 進行穿透調(diào)用。穿透調(diào)用,即是把 Token 完整傳遞到后續(xù)流程。此 Token 含玩家身份、權(quán)限等信息,后續(xù)各服務(wù)環(huán)節(jié)和組件能依此驗證玩家身份,按權(quán)限決定能否訪問特定功能。
接下來,以調(diào)用 Passport Feature SDK 提供的排行榜功能為例進行詳細說明。在調(diào)用時,會將玩家的 JWT 作為關(guān)鍵參數(shù)一并傳遞給 SDK。SDK 接收到調(diào)用請求和 JWT 后,先核驗玩家身份,若身份驗證通過,再依權(quán)限判斷能否訪問排行榜。只有身份和權(quán)限都合規(guī),SDK 才執(zhí)行排行榜查詢操作,并將查詢結(jié)果返回給云函數(shù),再由云函數(shù)反饋給客戶端。
通過這種JWT 穿透調(diào)用的方式,不僅確保了玩家訪問權(quán)限的嚴格管控,同時也在云函數(shù)與 Passport Feature SDK 的交互過程中,實現(xiàn)了高效、安全且精準的功能調(diào)用,為玩家提供了流暢且符合其權(quán)限范圍的服務(wù)體驗。
7.1 創(chuàng)建一個排行榜
先來創(chuàng)建一個排行榜,在 UOS Passport 服務(wù)的左側(cè)頁面,選中「排行榜」,然后點擊「立即創(chuàng)建」,創(chuàng)建一個排行榜。
自定義排行榜的名稱和唯一標識,排行榜窗口中的其它參數(shù)含義,詳情可參考下面鏈接:
https://uos.unity.cn/doc/passport/leaderboard#basic
在使用代碼訪問 PassportFeature SDK 提供的功能前,需要先進行 PassportFeature SDK 的初始化。
如果是 Passport UI Login 的方式,先添加初始化 PassportFeature SDK 的代碼。
//點擊「PassportUI」按鈕,響應(yīng)的事件
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;
}
// 調(diào)用 Passport SDK 初始化
await PassportUI.Init(_config, _callback);
}
7.3 云函數(shù)調(diào)用 Passport 服務(wù)中的排行榜功能
在 ValidateJwtToken.cs 類的腳本中,新增一個云函數(shù) CallPassport。編寫云函數(shù) CallPassport,在云函數(shù)內(nèi)可以直接使用 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 工具面板中,會看到新添加的云函數(shù) CallPassport:
接著在客戶端調(diào)用剛才的云函數(shù),找到 CallStateless 的方法,添加調(diào)用云函數(shù) 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}");//云函數(shù)的返回值:排行榜的唯一標識(SlugName)
}
運行測試,查看控制臺輸出的結(jié)果:會輸出當前排行榜的名字以及排行榜的唯一標識名。
8.云函數(shù)調(diào)用 CRUD Save 服務(wù)
我們也可以在云函數(shù)中直接使用 CRUD Save SDK 所提供的功能,同樣的,可以進入 UOS 網(wǎng)頁的文檔手冊查看使用方法。
https://uos.unity.cn/doc/func/stateless/csharp-guide#use-save
在這里,我們以「通過字節(jié)數(shù)組創(chuàng)建一個存檔文件」為示例講解。
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」按鈕,響應(yīng)的事件
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;
}
// 調(diào)用 Passport SDK 初始化
await PassportUI.Init(_config, _callback);
}
使用 External Login 的方式時,在 UseExternalLogin 方法中初始化CRUD SaveSDK:
// 點擊「ExternalLogin」按鈕,響應(yīng)的事件
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系統(tǒng) 的 UserID/PersonalID/DisplayName
const string userId = " " ;// 需要登錄的、外部系統(tǒng)的用戶Id
const string personaId = " " ;// 可選, 需要登錄的、外部系統(tǒng)的角色ID
const string personaDisplayName = " " ;//可選, 需要登錄的、角色的昵稱
Debug.Log("Call External Login");
await AuthTokenManager.ExternalLogin(userId, personaId, personaDisplayName);
await CallStateless();
}
8.2 云函數(shù)調(diào)用 CRUD Save 服務(wù)中的功能在 ValidateJwtToken.cs 類的腳本中,新增一個云函數(shù) CallSave。編寫以下云函數(shù) CallSave,在云函數(shù)內(nèi)直接使用 CloudSave SDK 所提供的功能。
通過 CreateAsync 方法創(chuàng)建一個存檔文件,傳入?yún)?shù):存檔文件的名稱 name 和存檔文件的內(nèi)容 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」工具面板中,會看到新添加的云函數(shù) CallSave:

接著在客戶端調(diào)用剛才的云函數(shù),找到 CallStateless 的方法,添加調(diào)用云函數(shù) 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}");//云函數(shù)的返回值:排行榜的唯一標識(SlugName)
// call save
resp = await s.CallSave();
Debug.Log($"callSaveResp: {resp}");//云函數(shù)的返回值:存檔ID
}
運行測試,查看控制臺輸出的結(jié)果:會輸出當前云存儲文件的存檔 ID 號。

刷新下 UOS 網(wǎng)頁端,可以看到云存檔文件。

9. 上傳云函數(shù)
在「Func Stateless Tool」工具面板中,點擊「上傳云函數(shù)」按鈕,等待云函數(shù)的構(gòu)建和上傳。

查看 UOS 網(wǎng)頁端,可以看到之前創(chuàng)建的云函數(shù)(validatejwttoken)已經(jīng)自動上傳了。

當云函數(shù)上傳成功后,將云函數(shù)的調(diào)用模式從「本地調(diào)用」切換成「遠程調(diào)用」的方式。

在遠程調(diào)用模式下,云函數(shù)的代碼自動發(fā)生了修改:
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); } } //此處省略云函數(shù) CallPassport 和 CallSave 的代碼行................................. } }
在云函數(shù)中會先根據(jù) Assets/Resources 文件夾下的 UOSSettings.asset 文件中的 AppID,來獲取 JWT 令牌;
然后將 UOS App 的 AppID 和 AppSecret 設(shè)置到函數(shù)上下文中,后續(xù)的請求會用到這些信息;
同時將剛才獲取到的 JWT 令牌設(shè)置到函數(shù)上下文中,用于后續(xù)請求的身份驗證;
接著通過 HttpClient 調(diào)用遠程的云函數(shù)接口;
最后嘗試將返回的 JSON 結(jié)果反序列化為字符串并返回。
通過本教程的深度剖析,相信大家已充分掌握 UserId 與 PersonaId 的區(qū)別、JWT 身份驗證機制的精髓,也能熟練運用 Passport Login 和 External Login 策略。同時,對云函數(shù)在 UOS 服務(wù)中的應(yīng)用邏輯、FuncContext 的關(guān)鍵作用,以及相關(guān)操作要點,也都有了扎實的理解。
希望開發(fā)者們將這些知識運用到實際項目中,充分發(fā)揮 UOS 鑒權(quán)的優(yōu)勢,打造出更安全可靠、用戶體驗卓越的游戲產(chǎn)品!
本期教程我們先講解到這里,大家趕快下載 Demo 項目試試吧!我們下一期再見哦!
Unity Online Services (UOS) 是一個專為游戲開發(fā)者設(shè)計的一站式游戲云服務(wù)平臺,提供覆蓋游戲全生命周期的開發(fā)、運營和推廣支持。
了解更多 UOS 相關(guān)信息:
官網(wǎng):https://uos.unity.cn
技術(shù)交流 QQ 群:823878269
公眾號:UOS 游戲云服務(wù)
Unity 官方微信
第一時間了解Unity引擎動向,學(xué)習(xí)進階開發(fā)技能
每一個“點贊”、“在看”,都是我們前進的動力
熱門跟貼