如何在.NET Core Blazor应用中结合JWT实现Magic Link功能
.NET Core Blazor 对接现有JWT体系实现Magic Link登录方案
整套方案不需要改动你现有的JWT认证逻辑,只需要新增4个模块的代码即可落地:
1. 存储层设计
新增Magic Link令牌专用表,对应实体字段如下:
public class MagicLinkToken { public long Id { get; set; } // 关联你现有用户表的主键 public string UserId { get; set; } = string.Empty; // 登录令牌值 public string TokenValue { get; set; } = string.Empty; // 过期时间,固定设为创建后10-15分钟 public DateTime ExpiresAt { get; set; } // 是否已使用标记,防重放 public bool IsUsed { get; set; } public DateTime CreatedAt { get; set; } = DateTime.UtcNow; // 可选:记录请求IP、UserAgent做安全审计 public string? RequestIp { get; set; } }
记得给TokenValue字段加唯一索引,提升查询效率。
2. 令牌生成与邮件发送接口
新增匿名访问的接口,接收用户输入的邮箱地址,逻辑如下:
- 校验邮箱格式,查询邮箱是否对应用户,无论是否存在都返回统一提示:如果邮箱已注册,登录链接将发送至你的邮箱,避免邮箱枚举漏洞
- 生成密码学安全的随机令牌,禁止使用Guid、可预测字符串生成:
var tokenBytes = RandomNumberGenerator.GetBytes(32); var magicToken = Convert.ToBase64UrlString(tokenBytes); - 将令牌、关联UserId、过期时间写入数据库,初始
IsUsed为false - 拼接登录回调地址:
{你的Blazor应用域名}/auth/magic-login?token={magicToken} - 调用现有邮件服务将链接发送给用户,邮件中标明链接有效期、仅本人可使用的提示
- 接口必须加限流规则:同IP1分钟最多请求5次,同邮箱10分钟最多接收3封邮件,防轰炸和暴力枚举
3. Blazor侧回调登录页
新增匿名可访问的Razor页面,路由设置为/auth/magic-login,核心逻辑:
@page "/auth/magic-login" @inject NavigationManager NavManager @inject ILocalStorageService LocalStorage @attribute [AllowAnonymous] <p>正在验证登录信息,请稍候...</p> @code { [SupplyParameterFromQuery] public string? token { get; set; } protected override async Task OnInitializedAsync() { if (string.IsNullOrWhiteSpace(token)) { NavManager.NavigateTo("/login", forceLoad: true); return; } // 调用后端验证接口 var httpClient = new HttpClient(); var response = await httpClient.PostAsJsonAsync("你的api地址/api/auth/magic-verify", new { Token = token }); if (response.IsSuccessStatusCode) { var result = await response.Content.ReadFromJsonAsync<LoginResult>(); // 完全复用你现有JWT存储逻辑,和账号密码登录的存储方式保持一致 await LocalStorage.SetItemAsync("access_token", result!.AccessToken); NavManager.NavigateTo("/", forceLoad: true); } else { NavManager.NavigateTo("/login?error=link_expired", forceLoad: true); } } }
不要在前端做任何令牌有效性校验,所有校验逻辑全部放服务端。
4. 后端令牌校验与JWT签发接口
新增匿名访问的校验接口,核心逻辑必须保证原子性,避免并发重放:
[HttpPost("magic-verify")] public async Task<IActionResult> VerifyMagicToken([FromBody] MagicVerifyRequest request) { // 原子操作:匹配有效令牌+直接标记为已使用,避免先查后改的并发漏洞 var affectedRows = await _dbContext.MagicLinkTokens .Where(t => t.TokenValue == request.Token && !t.IsUsed && t.ExpiresAt > DateTime.UtcNow) .ExecuteUpdateAsync(setters => setters.SetProperty(p => p.IsUsed, true)); if (affectedRows == 0) { return Unauthorized("登录链接已失效,请重新申请"); } // 查询令牌关联的用户信息 var validToken = await _dbContext.MagicLinkTokens.AsNoTracking() .FirstAsync(t => t.TokenValue == request.Token); // 完全复用你现有的JWT生成逻辑,和账号密码登录签发的JWT权限、有效期保持一致 var jwtToken = _jwtService.GenerateToken(validToken.UserId); return Ok(new LoginResult { AccessToken = jwtToken }); }
接口同样需要加限流,单IP1分钟最多请求10次,防暴力破解令牌。
必做安全加固项
- 令牌有效期严格控制在15分钟以内,建议10分钟,一次使用立刻失效
- 绝对不要把用户信息直接编码在Magic Link的token里,所有token必须存数据库做状态校验
- 不要在日志、前端控制台打印完整的token值,避免泄露
- 可选:登录成功后给用户发送登录提醒邮件,告知登录时间、IP,方便用户感知异常登录
- 生产环境回调地址必须用HTTPS,防止令牌在传输过程中被窃取
内容的提问来源于stack exchange,提问作者Nikhil Kumar
相关产品推荐
相关产品推荐

