Files
ShrinkNetwork/Editor/Scaffolding/ServerProjectTemplate/AuthServerModule.cs.txt
T
cneicy 8eaaa3040a
Publish UPM package / publish (push) Failing after 1s
chore: initialize standalone UPM package
2026-08-26 02:50:34 +08:00

185 lines
8.2 KiB
Plaintext

using Cysharp.Threading.Tasks;
using ShrinkNetwork;
using ShrinkNetwork.ServerHost.Framework;
namespace ShrinkNetwork.ServerHost;
public sealed class AuthServerModule : IShrinkServerModule
{
public const int LoginRequestOpcode = 1201;
public const int LoginResponseOpcode = 1202;
public const int RefreshRequestOpcode = 1203;
public const int RefreshResponseOpcode = 1204;
public const string LoginRoute = "server/auth/login";
public const string RefreshRoute = "server/auth/refresh";
private static readonly string[] DefaultGrantedPermissions = { "auth.ok", "room.access" };
public string Name => "Auth";
public void ConfigureService(ServerModuleContext context, ShrinkNetworkService service, string transportName)
{
service.RegisterMessage<ServerAuthLoginRequest>(LoginRequestOpcode, LoginRoute);
service.RegisterMessage<ServerAuthLoginResponse>(LoginResponseOpcode, "server/auth/login_response");
service.RegisterMessage<ServerAuthRefreshRequest>(RefreshRequestOpcode, RefreshRoute);
service.RegisterMessage<ServerAuthRefreshResponse>(RefreshResponseOpcode, "server/auth/refresh_response");
service.RegisterRequestHandler<ServerAuthLoginRequest, ServerAuthLoginResponse>((ctx, request) =>
HandleLoginAsync(context, transportName, ctx, request));
service.RegisterRequestHandler<ServerAuthRefreshRequest, ServerAuthRefreshResponse>((ctx, request) =>
HandleRefreshAsync(context, ctx, request));
}
public UniTask StartAsync(ServerModuleContext context, CancellationToken cancellationToken)
{
return UniTask.CompletedTask;
}
private UniTask<ServerAuthLoginResponse> HandleLoginAsync(
ServerModuleContext moduleContext,
string transportName,
ShrinkNetworkContext context,
ServerAuthLoginRequest request)
{
var authToken = moduleContext.Options.SharedAuthToken;
var requiresAuth = !string.IsNullOrWhiteSpace(authToken);
if (!requiresAuth && !moduleContext.Options.AllowAnonymousWhenAuthTokenMissing)
{
return UniTask.FromResult(new ServerAuthLoginResponse
{
ErrorCode = 503,
ErrorMessage = "服务端未配置 SHRINK_SERVER_AUTH_TOKEN,且未显式允许匿名登录。",
IsAuthenticated = false
});
}
if (requiresAuth && !string.Equals(authToken, request.Token, StringComparison.Ordinal))
{
return UniTask.FromResult(new ServerAuthLoginResponse
{
ErrorCode = ShrinkRpcErrorCode.AuthenticationFailed,
ErrorMessage = "鉴权失败,口令不正确。",
IsAuthenticated = false
});
}
var authName = string.IsNullOrWhiteSpace(request.ClientName)
? $"{transportName}-{context.Session.SessionId}"
: request.ClientName.Trim();
var authState = moduleContext.AuthStore.MarkAuthenticated(context.Service, context.Session.SessionId, authName,
BuildSessionTokenTtl(moduleContext.Options));
context.Session.Items["auth.name"] = authName;
context.Session.Items["auth.at_utc"] = DateTimeOffset.UtcNow;
context.Session.SetSessionToken(authState.SessionToken, authState.SessionTokenExpiresAtUtc);
foreach (var permission in DefaultGrantedPermissions)
context.Session.GrantPermission(permission);
return UniTask.FromResult(new ServerAuthLoginResponse
{
IsAuthenticated = true,
AuthName = authName,
SessionToken = authState.SessionToken,
SessionTokenExpiresAtUnixTimeSeconds = authState.SessionTokenExpiresAtUtc.ToUnixTimeSeconds(),
RefreshRecommendedAtUnixTimeSeconds = BuildRefreshRecommendedAtUnixTimeSeconds(
authState.SessionTokenExpiresAtUtc,
moduleContext.Options),
GrantedPermissions = DefaultGrantedPermissions.ToArray(),
ServerMessage = requiresAuth ? "鉴权成功。" : "服务端已显式允许匿名登录。"
});
}
private UniTask<ServerAuthRefreshResponse> HandleRefreshAsync(
ServerModuleContext moduleContext,
ShrinkNetworkContext context,
ServerAuthRefreshRequest request)
{
var sessionToken = string.IsNullOrWhiteSpace(request.SessionToken)
? context.Packet.SessionToken
: request.SessionToken.Trim();
if (!moduleContext.AuthStore.TryRefreshSessionToken(context.Service, context.Session.SessionId, sessionToken,
BuildSessionTokenTtl(moduleContext.Options), BuildRefreshWindow(moduleContext.Options), out var authState,
out var failureReason))
{
return UniTask.FromResult(new ServerAuthRefreshResponse
{
ErrorCode = string.Equals(failureReason, "会话令牌已过期。", StringComparison.Ordinal)
? ShrinkRpcErrorCode.SessionTokenExpired
: ShrinkRpcErrorCode.AuthenticationFailed,
ErrorMessage = failureReason,
IsAuthenticated = false
});
}
context.Session.SetSessionToken(authState.SessionToken, authState.SessionTokenExpiresAtUtc);
context.Session.Items["auth.name"] = authState.AuthName;
foreach (var permission in DefaultGrantedPermissions)
context.Session.GrantPermission(permission);
return UniTask.FromResult(new ServerAuthRefreshResponse
{
IsAuthenticated = true,
AuthName = authState.AuthName,
SessionToken = authState.SessionToken,
SessionTokenExpiresAtUnixTimeSeconds = authState.SessionTokenExpiresAtUtc.ToUnixTimeSeconds(),
RefreshRecommendedAtUnixTimeSeconds = BuildRefreshRecommendedAtUnixTimeSeconds(
authState.SessionTokenExpiresAtUtc,
moduleContext.Options),
ServerMessage = "会话令牌已刷新。"
});
}
private static TimeSpan BuildSessionTokenTtl(ServerHostOptions options)
{
return TimeSpan.FromSeconds(Math.Max(60, options.SessionTokenTtlSeconds));
}
private static TimeSpan BuildRefreshWindow(ServerHostOptions options)
{
return TimeSpan.FromSeconds(Math.Max(0, options.SessionTokenRefreshWindowSeconds));
}
private static long BuildRefreshRecommendedAtUnixTimeSeconds(DateTimeOffset expiresAtUtc, ServerHostOptions options)
{
var refreshWindow = BuildRefreshWindow(options);
if (refreshWindow <= TimeSpan.Zero)
return 0;
return expiresAtUtc.Subtract(refreshWindow).ToUnixTimeSeconds();
}
}
[ShrinkNetworkMessage(AuthServerModule.LoginRequestOpcode, AuthServerModule.LoginRoute)]
public sealed class ServerAuthLoginRequest : IShrinkNetworkRequest
{
public string ClientName { get; set; } = string.Empty;
public string Token { get; set; } = string.Empty;
}
[ShrinkNetworkMessage(AuthServerModule.LoginResponseOpcode, "server/auth/login_response")]
public sealed class ServerAuthLoginResponse : ShrinkRpcResponseBase
{
public bool IsAuthenticated { get; set; }
public string AuthName { get; set; } = string.Empty;
public string SessionToken { get; set; } = string.Empty;
public long SessionTokenExpiresAtUnixTimeSeconds { get; set; }
public long RefreshRecommendedAtUnixTimeSeconds { get; set; }
public string[] GrantedPermissions { get; set; } = Array.Empty<string>();
public string ServerMessage { get; set; } = string.Empty;
}
[ShrinkNetworkMessage(AuthServerModule.RefreshRequestOpcode, AuthServerModule.RefreshRoute)]
public sealed class ServerAuthRefreshRequest : IShrinkNetworkRequest
{
public string SessionToken { get; set; } = string.Empty;
}
[ShrinkNetworkMessage(AuthServerModule.RefreshResponseOpcode, "server/auth/refresh_response")]
public sealed class ServerAuthRefreshResponse : ShrinkRpcResponseBase
{
public bool IsAuthenticated { get; set; }
public string AuthName { get; set; } = string.Empty;
public string SessionToken { get; set; } = string.Empty;
public long SessionTokenExpiresAtUnixTimeSeconds { get; set; }
public long RefreshRecommendedAtUnixTimeSeconds { get; set; }
public string ServerMessage { get; set; } = string.Empty;
}