You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为.NET Core 6.0网站实现仅指定移动/平板设备访问的增强安全?

安卓设备唯一标识验证方案(适配.NET Core 6 + SQL Server 2019)

核心结论

安卓设备可以在登录请求中发送唯一标识用于验证,推荐使用Android ID(安卓8+每个应用独立,无需特殊权限)或自定义UUID(首次启动生成并本地存储),避免使用IMEI(安卓10+非系统应用无法获取)。


一、安卓端实现

  1. 获取设备标识
    • 方案1:获取Android ID(推荐,无需额外权限)
      import android.provider.Settings
      val deviceId = Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID)
      
    • 方案2:生成自定义UUID(卸载后会重置)
      import android.content.SharedPreferences
      import java.util.UUID
      
      val prefs = getSharedPreferences("AppDevicePrefs", MODE_PRIVATE)
      var deviceId = prefs.getString("DEVICE_UUID", null)
      if (deviceId == null) {
          deviceId = UUID.randomUUID().toString()
          prefs.edit().putString("DEVICE_UUID", deviceId).apply()
      }
      
  2. 登录请求携带标识
    用OkHttp/Retrofit发送POST请求,将deviceId放入请求体:
    data class LoginRequest(
        val username: String,
        val password: String,
        val deviceId: String
    )
    
    // Retrofit接口示例
    interface AuthApi {
        @POST("api/auth/login")
        suspend fun login(@Body request: LoginRequest): Response<LoginResponse>
    }
    

二、后端.NET Core 6实现

1. 数据库表设计(SQL Server)

创建设备授权表,关联用户与设备标识:

CREATE TABLE Devices (
    Id INT IDENTITY(1,1) PRIMARY KEY,
    DeviceId NVARCHAR(255) NOT NULL UNIQUE,
    UserId NVARCHAR(450) NOT NULL, -- 关联AspNetUsers表主键
    IsAuthorized BIT NOT NULL DEFAULT 0, -- 0=未授权,1=已授权
    CreatedTime DATETIME2 NOT NULL DEFAULT GETUTCDATE(),
    UpdatedTime DATETIME2 NOT NULL DEFAULT GETUTCDATE(),
    FOREIGN KEY (UserId) REFERENCES AspNetUsers(Id)
)

-- 索引优化查询效率
CREATE INDEX IX_Devices_DeviceId ON Devices(DeviceId)
CREATE INDEX IX_Devices_UserId_IsAuthorized ON Devices(UserId, IsAuthorized)

2. 登录接口验证逻辑

在AuthController中实现账号密码+设备授权双重验证:

using Microsoft.AspNetCore.Identity;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;

[ApiController]
[Route("api/auth")]
public class AuthController : ControllerBase
{
    private readonly UserManager<IdentityUser> _userManager;
    private readonly IConfiguration _configuration;
    private readonly AppDbContext _dbContext;

    public AuthController(UserManager<IdentityUser> userManager, IConfiguration configuration, AppDbContext dbContext)
    {
        _userManager = userManager;
        _configuration = configuration;
        _dbContext = dbContext;
    }

    [HttpPost("login")]
    public async Task<IActionResult> Login([FromBody] LoginRequest request)
    {
        // 1. 验证账号密码
        var user = await _userManager.FindByNameAsync(request.Username);
        if (user == null || !await _userManager.CheckPasswordAsync(user, request.Password))
        {
            return Unauthorized("账号或密码错误");
        }

        // 2. 验证设备授权状态
        var device = await _dbContext.Devices
            .FirstOrDefaultAsync(d => d.DeviceId == request.DeviceId && d.UserId == user.Id);
        
        if (device == null)
        {
            // 新设备自动注册,等待管理员审核
            _dbContext.Devices.Add(new Device
            {
                DeviceId = request.DeviceId,
                UserId = user.Id,
                IsAuthorized = false
            });
            await _dbContext.SaveChangesAsync();
            return Unauthorized("设备未注册,请联系管理员授权");
        }

        if (!device.IsAuthorized)
        {
            return Unauthorized("设备未授权,请联系管理员");
        }

        // 3. 生成带设备标识的JWT Token
        var claims = new List<Claim>
        {
            new Claim(ClaimTypes.NameIdentifier, user.Id),
            new Claim("DeviceId", request.DeviceId)
        };

        var key = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes(_configuration["Jwt:Key"]));
        var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha512Signature);

        var tokenDescriptor = new SecurityTokenDescriptor
        {
            Subject = new ClaimsIdentity(claims),
            Expires = DateTime.Now.AddHours(2),
            SigningCredentials = creds,
            Issuer = _configuration["Jwt:Issuer"],
            Audience = _configuration["Jwt:Audience"]
        };

        var tokenHandler = new JwtSecurityTokenHandler();
        var token = tokenHandler.CreateToken(tokenDescriptor);

        return Ok(new LoginResponse
        {
            Token = tokenHandler.WriteToken(token),
            Expiration = tokenDescriptor.Expires.Value
        });
    }
}

// 模型定义
public class LoginRequest
{
    public string Username { get; set; }
    public string Password { get; set; }
    public string DeviceId { get; set; }
}

public class LoginResponse
{
    public string Token { get; set; }
    public DateTime Expiration { get; set; }
}

public class Device
{
    public int Id { get; set; }
    public string DeviceId { get; set; }
    public string UserId { get; set; }
    public bool IsAuthorized { get; set; }
    public DateTime CreatedTime { get; set; }
    public DateTime UpdatedTime { get; set; }
}

3. 后续接口的设备一致性验证(可选)

添加中间件防止Token跨设备盗用:

public class DeviceValidationMiddleware
{
    private readonly RequestDelegate _next;

    public DeviceValidationMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        // 跳过登录接口
        if (context.Request.Path.StartsWithSegments("/api/auth/login"))
        {
            await _next(context);
            return;
        }

        var deviceIdClaim = context.User.FindFirst("DeviceId");
        if (deviceIdClaim == null)
        {
            context.Response.StatusCode = StatusCodes.Status401Unauthorized;
            await context.Response.WriteAsync("设备验证失败");
            return;
        }

        // 从请求头获取设备标识(也可从请求体获取)
        var requestDeviceId = context.Request.Headers["X-Device-Id"].FirstOrDefault();
        if (string.IsNullOrEmpty(requestDeviceId) || requestDeviceId != deviceIdClaim.Value)
        {
            context.Response.StatusCode = StatusCodes.Status401Unauthorized;
            await context.Response.WriteAsync("设备不匹配");
            return;
        }

        await _next(context);
    }
}

// 在Program.cs注册中间件
var app = builder.Build();
// ... 其他中间件注册 ...
app.UseAuthentication();
app.UseAuthorization();
app.UseMiddleware<DeviceValidationMiddleware>(); // 放在授权之后
app.MapControllers();
app.Run();

三、管理员ACL管理功能

实现后台设备授权接口:

[ApiController]
[Route("api/admin/devices")]
[Authorize(Roles = "Admin")]
public class AdminDeviceController : ControllerBase
{
    private readonly AppDbContext _dbContext;
    private readonly UserManager<IdentityUser> _userManager;

    public AdminDeviceController(AppDbContext dbContext, UserManager<IdentityUser> userManager)
    {
        _dbContext = dbContext;
        _userManager = userManager;
    }

    // 获取所有设备(可筛选授权状态)
    [HttpGet]
    public async Task<IActionResult> GetAllDevices([FromQuery] bool? isAuthorized)
    {
        var query = _dbContext.Devices.AsQueryable();
        if (isAuthorized.HasValue)
        {
            query = query.Where(d => d.IsAuthorized == isAuthorized.Value);
        }

        var devices = await query.Select(d => new
        {
            d.Id,
            d.DeviceId,
            UserName = _userManager.FindByIdAsync(d.UserId).Result.UserName,
            d.IsAuthorized,
            d.CreatedTime
        }).ToListAsync();

        return Ok(devices);
    }

    // 授权指定设备
    [HttpPut("{id}/authorize")]
    public async Task<IActionResult> AuthorizeDevice(int id)
    {
        var device = await _dbContext.Devices.FindAsync(id);
        if (device == null)
        {
            return NotFound();
        }

        device.IsAuthorized = true;
        device.UpdatedTime = DateTime.UtcNow;
        await _dbContext.SaveChangesAsync();

        return Ok("设备已授权");
    }
}

四、安全注意事项

  • 必须使用HTTPS传输所有请求,防止设备标识和Token被截获。
  • 不要将设备标识作为唯一验证依据,需结合账号密码与JWT Token。
  • 自定义UUID卸载后会丢失,若需跨卸载保留,可绑定用户的第三方账号(如Google账号,需用户授权)。
  • 定期清理未授权的设备记录,避免数据库冗余。

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 20:47:11