如何为.NET Core 6.0网站实现仅指定移动/平板设备访问的增强安全?
安卓设备唯一标识验证方案(适配.NET Core 6 + SQL Server 2019)
核心结论
安卓设备可以在登录请求中发送唯一标识用于验证,推荐使用Android ID(安卓8+每个应用独立,无需特殊权限)或自定义UUID(首次启动生成并本地存储),避免使用IMEI(安卓10+非系统应用无法获取)。
一、安卓端实现
- 获取设备标识
- 方案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() }
- 方案1:获取Android ID(推荐,无需额外权限)
- 登录请求携带标识
用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
相关产品推荐
相关产品推荐

