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

如何使用Angular 7结合WebAPI实现带邮件重置链接的密码重置功能?

Angular 7 + WebAPI 实现密码重置功能完整指南

嘿,我之前刚好做过这个场景的实现,给你梳理一下完整的落地流程,分后端WebAPI(以ASP.NET WebAPI为例)和前端Angular 7两部分来讲,代码都是可直接参考的:

一、后端WebAPI部分

1. 生成重置令牌并发送邮件接口

首先需要一个接口接收用户邮箱,验证用户存在后生成加密的重置令牌,再把带令牌的重置链接发送到用户邮箱。

[Route("api/account/forgot-password")]
[HttpPost]
public async Task<IHttpActionResult> ForgotPassword(ForgotPasswordModel model)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    var user = await UserManager.FindByEmailAsync(model.Email);
    if (user == null || !(await UserManager.IsEmailConfirmedAsync(user.Id)))
    {
        // 这里不要暴露用户是否存在,返回统一提示
        return Ok(new { Message = "如果邮箱存在,重置链接已发送" });
    }

    // 生成密码重置令牌
    var token = await UserManager.GeneratePasswordResetTokenAsync(user.Id);
    // 对令牌做URL编码,避免特殊字符破坏链接
    var encodedToken = Uri.EscapeDataString(token);
    var encodedEmail = Uri.EscapeDataString(model.Email);
    // 构建前端重置页面链接
    var resetLink = $"https://your-angular-app.com/reset-password?token={encodedToken}&email={encodedEmail}";

    // 发送邮件(这里用SmtpClient示例,实际可换第三方邮件服务)
    var mailMessage = new MailMessage("noreply@yourapp.com", model.Email)
    {
        Subject = "密码重置请求",
        Body = $"点击链接重置密码:<a href='{resetLink}'>{resetLink}</a>",
        IsBodyHtml = true
    };

    using (var smtp = new SmtpClient("your-smtp-server"))
    {
        await smtp.SendMailAsync(mailMessage);
    }

    return Ok(new { Message = "如果邮箱存在,重置链接已发送" });
}

// 对应的模型类
public class ForgotPasswordModel
{
    [Required]
    [EmailAddress]
    public string Email { get; set; }
}

2. 验证令牌并重置密码接口

这个接口接收令牌、邮箱和新密码,验证令牌有效性后完成密码重置。

[Route("api/account/reset-password")]
[HttpPost]
public async Task<IHttpActionResult> ResetPassword(ResetPasswordModel model)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    var user = await UserManager.FindByEmailAsync(model.Email);
    if (user == null)
    {
        // 同样不暴露用户不存在的信息
        return Ok(new { Message = "密码重置完成" });
    }

    // 验证令牌并重置密码
    var result = await UserManager.ResetPasswordAsync(user.Id, model.Token, model.NewPassword);
    if (result.Succeeded)
    {
        return Ok(new { Message = "密码重置成功" });
    }

    // 返回错误信息
    return BadRequest(string.Join(", ", result.Errors));
}

// 对应的模型类
public class ResetPasswordModel
{
    [Required]
    [EmailAddress]
    public string Email { get; set; }

    [Required]
    public string Token { get; set; }

    [Required]
    [StringLength(100, ErrorMessage = "{0} 至少需要 {2} 个字符长度。", MinimumLength = 6)]
    [DataType(DataType.Password)]
    public string NewPassword { get; set; }
}

二、前端Angular 7部分

1. 创建「忘记密码」组件(ForgotPasswordComponent)

这个组件让用户输入邮箱,提交请求到后端发送重置链接。

模板(forgot-password.component.html)

<div class="container">
  <h2>忘记密码</h2>
  <form [formGroup]="forgotPasswordForm" (ngSubmit)="onSubmit()">
    <div class="form-group">
      <label>邮箱</label>
      <input type="email" formControlName="email" class="form-control" 
             [ngClass]="{'is-invalid': submitted && f.email.errors}">
      <div *ngIf="submitted && f.email.errors" class="invalid-feedback">
        <div *ngIf="f.email.errors.required">邮箱不能为空</div>
        <div *ngIf="f.email.errors.email">请输入有效的邮箱地址</div>
      </div>
    </div>
    <button type="submit" class="btn btn-primary" [disabled]="loading">
      <span *ngIf="loading" class="spinner-border spinner-border-sm mr-1"></span>
      发送重置链接
    </button>
    <div *ngIf="message" class="alert alert-info mt-3">{{ message }}</div>
    <div *ngIf="error" class="alert alert-danger mt-3">{{ error }}</div>
  </form>
</div>

组件逻辑(forgot-password.component.ts)

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { HttpClient } from '@angular/common/http';

@Component({
  selector: 'app-forgot-password',
  templateUrl: './forgot-password.component.html'
})
export class ForgotPasswordComponent implements OnInit {
  forgotPasswordForm: FormGroup;
  submitted = false;
  loading = false;
  message: string;
  error: string;

  constructor(private formBuilder: FormBuilder, private http: HttpClient) { }

  ngOnInit() {
    this.forgotPasswordForm = this.formBuilder.group({
      email: ['', [Validators.required, Validators.email]]
    });
  }

  // 快捷获取表单控件
  get f() { return this.forgotPasswordForm.controls; }

  onSubmit() {
    this.submitted = true;
    this.message = '';
    this.error = '';

    // 如果表单无效,停止提交
    if (this.forgotPasswordForm.invalid) {
      return;
    }

    this.loading = true;
    this.http.post('/api/account/forgot-password', this.forgotPasswordForm.value)
      .subscribe(
        (response: any) => {
          this.message = response.Message;
          this.loading = false;
        },
        (err) => {
          this.error = err.error.Message || '请求失败,请稍后重试';
          this.loading = false;
        }
      );
  }
}

2. 创建「重置密码」组件(ResetPasswordComponent)

这个组件接收URL中的令牌和邮箱参数,让用户输入新密码完成重置。

路由配置(app-routing.module.ts)

import { Routes, RouterModule } from '@angular/router';
import { ResetPasswordComponent } from './reset-password/reset-password.component';
import { ForgotPasswordComponent } from './forgot-password/forgot-password.component';

const routes: Routes = [
  { path: 'forgot-password', component: ForgotPasswordComponent },
  { path: 'reset-password', component: ResetPasswordComponent },
  // 其他路由...
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

模板(reset-password.component.html)

<div class="container">
  <h2>重置密码</h2>
  <form [formGroup]="resetPasswordForm" (ngSubmit)="onSubmit()">
    <div class="form-group">
      <label>新密码</label>
      <input type="password" formControlName="newPassword" class="form-control" 
             [ngClass]="{'is-invalid': submitted && f.newPassword.errors}">
      <div *ngIf="submitted && f.newPassword.errors" class="invalid-feedback">
        <div *ngIf="f.newPassword.errors.required">新密码不能为空</div>
        <div *ngIf="f.newPassword.errors.minlength">密码至少需要6个字符</div>
      </div>
    </div>
    <div class="form-group">
      <label>确认密码</label>
      <input type="password" formControlName="confirmPassword" class="form-control" 
             [ngClass]="{'is-invalid': submitted && (f.confirmPassword.errors || resetPasswordForm.errors?.mismatch)}">
      <div *ngIf="submitted && (f.confirmPassword.errors || resetPasswordForm.errors?.mismatch)" class="invalid-feedback">
        <div *ngIf="f.confirmPassword.errors.required">确认密码不能为空</div>
        <div *ngIf="resetPasswordForm.errors?.mismatch">两次输入的密码不一致</div>
      </div>
    </div>
    <button type="submit" class="btn btn-primary" [disabled]="loading">
      <span *ngIf="loading" class="spinner-border spinner-border-sm mr-1"></span>
      重置密码
    </button>
    <div *ngIf="message" class="alert alert-success mt-3">{{ message }}</div>
    <div *ngIf="error" class="alert alert-danger mt-3">{{ error }}</div>
  </form>
</div>

组件逻辑(reset-password.component.ts)

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators, AbstractControl, ValidationErrors } from '@angular/forms';
import { HttpClient } from '@angular/common/http';
import { ActivatedRoute } from '@angular/router';

// 自定义验证器:检查两次密码是否一致
function matchPassword(control: AbstractControl): ValidationErrors | null {
  const password = control.get('newPassword')?.value;
  const confirmPassword = control.get('confirmPassword')?.value;
  return password === confirmPassword ? null : { mismatch: true };
}

@Component({
  selector: 'app-reset-password',
  templateUrl: './reset-password.component.html'
})
export class ResetPasswordComponent implements OnInit {
  resetPasswordForm: FormGroup;
  submitted = false;
  loading = false;
  message: string;
  error: string;
  private token: string;
  private email: string;

  constructor(
    private formBuilder: FormBuilder, 
    private http: HttpClient,
    private route: ActivatedRoute
  ) { }

  ngOnInit() {
    // 从URL参数中获取令牌和邮箱
    this.token = this.route.snapshot.queryParamMap.get('token');
    this.email = this.route.snapshot.queryParamMap.get('email');

    this.resetPasswordForm = this.formBuilder.group({
      newPassword: ['', [Validators.required, Validators.minLength(6)]],
      confirmPassword: ['', Validators.required]
    }, { validators: matchPassword });
  }

  get f() { return this.resetPasswordForm.controls; }

  onSubmit() {
    this.submitted = true;
    this.message = '';
    this.error = '';

    if (this.resetPasswordForm.invalid) {
      return;
    }

    this.loading = true;
    // 组装请求参数
    const payload = {
      email: this.email,
      token: this.token,
      newPassword: this.f.newPassword.value
    };

    this.http.post('/api/account/reset-password', payload)
      .subscribe(
        (response: any) => {
          this.message = response.Message;
          this.loading = false;
        },
        (err) => {
          this.error = err.error.Message || '密码重置失败,请检查链接是否有效';
          this.loading = false;
        }
      );
  }
}

三、关键注意事项

  • 令牌有效期:建议给重置令牌设置有效期(比如1小时),可以在生成令牌时配置,避免令牌长期有效带来安全风险。
  • URL编码:一定要对令牌和邮箱做URL编码,因为令牌中可能包含+、/等特殊字符,直接放在URL中会被解析错误。
  • 安全提示:生产环境一定要用HTTPS传输,避免令牌被窃取;邮件中的链接也要用HTTPS。
  • 错误提示:不要暴露用户是否存在的信息(比如忘记密码时,不管邮箱是否存在都返回相同提示),防止恶意用户枚举邮箱。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 06:33:14