You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Unity开发安卓游戏:基于.Net REST API与SQL的免密注册登录需求

Great question—this passwordless, auto-create/auto-login flow is exactly what modern mobile gamers expect, and it's totally achievable with your Unity + .NET/SQL stack. Let's walk through the implementation step by step, covering backend setup, client integration, and key security considerations.

Core Concept: Seamless Auth Without Passwords

The core idea is to use a stable device identifier as the initial auth trigger:

  1. On first launch, the game sends the device ID to your backend. If no user is linked to that ID, a new anonymous account is created automatically.
  2. To enable cross-device access, we add an optional account linking step (e.g., email/phone verification) so users can associate their anonymous account with a recoverable credential.
  3. Return a JWT token for subsequent API requests to authenticate the user without re-sending device IDs every time.

1. Backend (.NET REST API + SQL) Setup

Database Table Design

First, create a Users table in your SQL database to store account data:

ColumnTypeDescription
UserIdGUIDPrimary key, unique account identifier
DeviceIdVARCHAR(255)Linked device identifier (supports multiple devices per user via updates)
EmailVARCHAR(255)Nullable, for cross-device account recovery
PhoneNumberVARCHAR(50)Nullable, alternative recovery method
UserDataNVARCHAR(MAX)JSON blob for game progress, inventory, etc.
CreatedAtDATETIMEAccount creation timestamp
LastLoginAtDATETIMELast active timestamp

API Endpoints & Implementation

We'll need 3 core endpoints for this flow:

Auto-Login / Account Creation

This endpoint handles both new and returning users:

[ApiController]
[Route("api/auth")]
public class AuthController : ControllerBase
{
    private readonly AppDbContext _dbContext;
    private readonly IConfiguration _config;
    private readonly IVerificationService _verificationService;

    public AuthController(AppDbContext dbContext, IConfiguration config, IVerificationService verificationService)
    {
        _dbContext = dbContext;
        _config = config;
        _verificationService = verificationService;
    }

    [HttpPost("login")]
    public async Task<IActionResult> AutoLogin([FromBody] LoginRequest request)
    {
        // Check if device is already linked to an account
        var existingUser = await _dbContext.Users
            .FirstOrDefaultAsync(u => u.DeviceId == request.DeviceId);

        if (existingUser != null)
        {
            // Update last login timestamp
            existingUser.LastLoginAt = DateTime.UtcNow;
            await _dbContext.SaveChangesAsync();
            
            var token = GenerateJwtToken(existingUser.UserId);
            return Ok(new AuthResponse
            {
                UserId = existingUser.UserId,
                UserData = existingUser.UserData,
                Token = token,
                IsNewUser = false
            });
        }
        else
        {
            // Create new anonymous account
            var newUser = new User
            {
                UserId = Guid.NewGuid(),
                DeviceId = request.DeviceId,
                UserData = "{}", // Empty initial game data
                CreatedAt = DateTime.UtcNow,
                LastLoginAt = DateTime.UtcNow
            };
            _dbContext.Users.Add(newUser);
            await _dbContext.SaveChangesAsync();
            
            var token = GenerateJwtToken(newUser.UserId);
            return Ok(new AuthResponse
            {
                UserId = newUser.UserId,
                UserData = newUser.UserData,
                Token = token,
                IsNewUser = true
            });
        }
    }

    // Helper: Generate JWT token for authenticated requests
    private string GenerateJwtToken(Guid userId)
    {
        var claims = new[] { new Claim(ClaimTypes.NameIdentifier, userId.ToString()) };
        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:SecretKey"]));
        var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

        var token = new JwtSecurityToken(
            issuer: _config["Jwt:Issuer"],
            audience: _config["Jwt:Audience"],
            claims: claims,
            expires: DateTime.UtcNow.AddDays(30),
            signingCredentials: creds);

        return new JwtSecurityTokenHandler().WriteToken(token);
    }
}

// Request/Response DTOs
public class LoginRequest { public string DeviceId { get; set; } }
public class AuthResponse
{
    public Guid UserId { get; set; }
    public string UserData { get; set; }
    public string Token { get; set; }
    public bool IsNewUser { get; set; }
}

Link Account for Cross-Device Access

Allow users to link their anonymous account to an email/phone (requires JWT authentication):

[HttpPost("link-email")]
[Authorize]
public async Task<IActionResult> LinkEmail([FromBody] LinkEmailRequest request)
{
    var userId = Guid.Parse(User.FindFirst(ClaimTypes.NameIdentifier)?.Value);
    var user = await _dbContext.Users.FindAsync(userId);

    if (user == null) return NotFound("Account not found");
    
    // Verify OTP sent to user's email
    if (!await _verificationService.VerifyEmailOtp(request.Email, request.Otp))
        return BadRequest("Invalid verification code");

    // Ensure email isn't linked to another account
    if (await _dbContext.Users.AnyAsync(u => u.Email == request.Email && u.UserId != userId))
        return Conflict("Email already linked to another account");

    user.Email = request.Email;
    await _dbContext.SaveChangesAsync();
    return Ok("Email linked successfully");
}

public class LinkEmailRequest { public string Email { get; set; } public string Otp { get; set; } }

Retrieve Account on New Device

Let users access their account via linked email/phone on a new device:

[HttpPost("retrieve-via-email")]
public async Task<IActionResult> RetrieveAccount([FromBody] RetrieveAccountRequest request)
{
    if (!await _verificationService.VerifyEmailOtp(request.Email, request.Otp))
        return BadRequest("Invalid verification code");

    var user = await _dbContext.Users.FirstOrDefaultAsync(u => u.Email == request.Email);
    if (user == null) return NotFound("No account linked to this email");

    // Link new device to the existing account
    user.DeviceId = request.NewDeviceId;
    user.LastLoginAt = DateTime.UtcNow;
    await _dbContext.SaveChangesAsync();

    var token = GenerateJwtToken(user.UserId);
    return Ok(new AuthResponse
    {
        UserId = user.UserId,
        UserData = user.UserData,
        Token = token,
        IsNewUser = false
    });
}

public class RetrieveAccountRequest
{
    public string Email { get; set; }
    public string Otp { get; set; }
    public string NewDeviceId { get; set; }
}

2. Unity Client Integration

Get Stable Device ID

On Android, use Google's Advertising ID (more stable than SystemInfo.deviceUniqueIdentifier, which can change after factory resets):

using UnityEngine;

public static class DeviceIdHelper
{
    public static string GetDeviceId()
    {
#if UNITY_ANDROID
        try
        {
            AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
            AndroidJavaObject currentActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
            AndroidJavaClass adIdClient = new AndroidJavaClass("com.google.android.gms.ads.identifier.AdvertisingIdClient");
            AndroidJavaObject adInfo = adIdClient.CallStatic<AndroidJavaObject>("getAdvertisingIdInfo", currentActivity);
            
            string adId = adInfo.Call<string>("getId");
            bool isAdTrackingEnabled = adInfo.Call<bool>("isLimitAdTrackingEnabled");
            
            // Fallback to device unique ID if user disabled ad tracking
            return isAdTrackingEnabled ? SystemInfo.deviceUniqueIdentifier : adId;
        }
        catch (System.Exception e)
        {
            Debug.LogError($"Failed to get Advertising ID: {e.Message}");
            return SystemInfo.deviceUniqueIdentifier;
        }
#else
        return SystemInfo.deviceUniqueIdentifier;
#endif
    }
}

Auth Manager for API Calls

Wrap the backend API calls in a reusable Unity script:

using UnityEngine;
using UnityEngine.Networking;
using System.Threading.Tasks;

public class GameAuthManager : MonoBehaviour
{
    private const string ApiBaseUrl = "https://your-backend-url.com/api/auth";
    private string _authToken;

    private void Start()
    {
        // Load saved token on app launch
        _authToken = PlayerPrefs.GetString("AuthToken", string.Empty);
        if (!string.IsNullOrEmpty(_authToken))
        {
            // Optionally validate token with backend here
        }
    }

    public async Task<AuthResponse> AutoLogin()
    {
        string deviceId = DeviceIdHelper.GetDeviceId();
        var requestData = new LoginRequest { DeviceId = deviceId };

        using (var request = new UnityWebRequest($"{ApiBaseUrl}/login", "POST"))
        {
            string json = JsonUtility.ToJson(requestData);
            byte[] body = System.Text.Encoding.UTF8.GetBytes(json);
            
            request.uploadHandler = new UploadHandlerRaw(body);
            request.downloadHandler = new DownloadHandlerBuffer();
            request.SetRequestHeader("Content-Type", "application/json");

            await request.SendWebRequest();

            if (request.result != UnityWebRequest.Result.Success)
            {
                Debug.LogError($"Login failed: {request.error}");
                return null;
            }

            AuthResponse response = JsonUtility.FromJson<AuthResponse>(request.downloadHandler.text);
            _authToken = response.Token;
            PlayerPrefs.SetString("AuthToken", _authToken);
            PlayerPrefs.Save();
            
            return response;
        }
    }

    public async Task<bool> LinkEmail(string email, string otp)
    {
        if (string.IsNullOrEmpty(_authToken)) return false;
        
        var requestData = new LinkEmailRequest { Email = email, Otp = otp };
        using (var request = new UnityWebRequest($"{ApiBaseUrl}/link-email", "POST"))
        {
            string json = JsonUtility.ToJson(requestData);
            byte[] body = System.Text.Encoding.UTF8.GetBytes(json);
            
            request.uploadHandler = new UploadHandlerRaw(body);
            request.downloadHandler = new DownloadHandlerBuffer();
            request.SetRequestHeader("Content-Type", "application/json");
            request.SetRequestHeader("Authorization", $"Bearer {_authToken}");

            await request.SendWebRequest();
            return request.result == UnityWebRequest.Result.Success;
        }
    }

    // Serializable classes for JSON serialization
    [System.Serializable] private class LoginRequest { public string DeviceId; }
    [System.Serializable] public class AuthResponse
    {
        public string UserId;
        public string UserData;
        public string Token;
        public bool IsNewUser;
    }
    [System.Serializable] private class LinkEmailRequest { public string Email; public string Otp; }
}

3. Key Security & Reliability Tips

  • JWT Security: Store your JWT secret key in environment variables (never hardcode it). Set a reasonable token expiry (e.g., 30 days) and implement a token refresh endpoint for long sessions.
  • OTP Best Practices: Use short-lived (5-10 minute) one-time codes for email/phone verification. Store used codes in your database to prevent reuse.
  • Device ID Fallbacks: Always have a fallback for when the Advertising ID isn't available (e.g., older Android devices).
  • Error Handling: Show user-friendly messages for failed requests (e.g., "Invalid verification code" or "Network error, please try again").
  • Data Sync: Ensure game data is pulled from the backend on login, not just stored locally, to keep cross-device progress in sync.

内容的提问来源于stack exchange,提问作者Riddlah

火山引擎 最新活动