在项目里集成 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引擎动向,学习进阶开发技能
每一个“点赞”、“在看”,都是我们前进的动力
热门跟贴