网站找百度做可以嘛,wordpress 自带模板,建设网站要什么,一个静态网页多少钱用户找回密码#xff0c;确切地说是重置密码#xff0c;为了保证用户账号安全#xff0c;原始密码将不再以明文的方式找回#xff0c;而是通过短信或者邮件的方式发送一个随机的重置校验码#xff08;带校验码的页面连接#xff09;#xff0c;用户点击该链接#xff0…用户找回密码确切地说是重置密码为了保证用户账号安全原始密码将不再以明文的方式找回而是通过短信或者邮件的方式发送一个随机的重置校验码带校验码的页面连接用户点击该链接跳转到重置密码页面输入新的密码。这个重置校验码是一次性的用户重置密码后立即失效。 用户找回密码是在用户没有登录时进行的因此需要先校验身份除用户名密码外的第二种身份验证方式。 第二种身份验证的前提是绑定了手机号或者邮箱如果没有绑定那么只能通过管理员进行原始密码重置。 
密码强制过期策略是指用户在一段时间内没有修改密码在下次登录时系统阻止用户登录直到用户修改了密码后方可继续登录。此策略提高用户账号的安全性。 找回密码和密码过期重置密码两种机制有相近的业务逻辑即密码重置。今天我们来实现这个功能。 
重置密码 
Abp框架中AbpUserBase类中已经定义了重置校验码PasswordResetCode属性以及SetNewPasswordResetCode方法用于生成新的重置校验码。 
[StringLength(328)]
public virtual string PasswordResetCode { get; set; }public virtual void SetNewPasswordResetCode()
{PasswordResetCode  Guid.NewGuid().ToString(N).Truncate(328);
}在UserAppService中添加ResetPasswordByCode,用于响应重置密码的请求。 在其参数ResetPasswordByLinkDto中携带了校验信息PasswordResetCode因此添加了特性[AbpAllowAnonymous]不需要登录认证即可调用此接口 
密码更新完成后立刻将PasswordResetCode重置为null以防止重复使用。 
[AbpAllowAnonymous]
public async Taskbool ResetPasswordByCode(ResetPasswordByLinkDto input)
{await _userManager.InitializeOptionsAsync(AbpSession.TenantId);var currentUser  await _userManager.GetUserByIdAsync(input.UserId);if (currentUser  null || currentUser.PasswordResetCode.IsNullOrEmpty() || currentUser.PasswordResetCode ! input.ResetCode){throw new UserFriendlyException(PasswordResetCode不正确);}var loginAsync  await _logInManager.LoginAsync(currentUser.UserName, input.NewPassword, shouldLockout: false);if (loginAsync.Result  AbpLoginResultType.Success){throw new UserFriendlyException(重置的密码不应与之前密码相同);}if (currentUser.IsDeleted || !currentUser.IsActive){return false;}CheckErrors(await _userManager.ChangePasswordAsync(currentUser, input.NewPassword));currentUser.PasswordResetCode  null;currentUser.LastPasswordModificationTime  DateTime.Now;await this._userManager.UpdateAsync(currentUser);return true;
}找回密码 
发送验证码 
使用AbpBoilerplate.Sms作为短信服务库。 
之前的项目中我们定义好了ICaptchaManager接口已经实现了验证码的发送、验证码校验、解绑手机号、绑定手机号 
这4个功能通过定义用途purpose字段以校验区分短信模板 
public interface ICaptchaManager
{Task BindAsync(string token);Task UnbindAsync(string token);Task SendCaptchaAsync(long userId, string phoneNumber, string purpose);Taskbool VerifyCaptchaAsync(string token, string purpose  IDENTITY_VERIFICATION);
}添加一个用于重置密码的purpose在CaptchaPurpose枚举类型中添加RESET_PASSWORD 
public class CaptchaPurpose
{...public const string RESET_PASSWORD  RESET_PASSWORD;} 
在SMS服务商管理端后台申请一个短信模板用于重置密码。 打开短信验证码的领域服务类SmsCaptchaManager 添加RESET_PASSWORD对应短信模板的编号 
public async Task SendCaptchaAsync(long userId, string phoneNumber, string purpose)
{var captcha  CommonHelper.GetRandomCaptchaNumber();var model  new SendSmsRequest();model.PhoneNumbers  new string[] { phoneNumber };model.SignName  MatoApp;model.TemplateCode  purpose switch{...CaptchaPurpose.RESET_PASSWORD  SMS_1587660    //添加重置密码对应短信模板的编号};...
}接下来我们创建ResetPasswordManager类用于处理找回密码和密码过期重置密码的业务逻辑。 注入UserManagerISmsServiceSmsCaptchaManagerEmailCaptchaManager。 
public class ResetPasswordManager : ITransientDependency
{private readonly UserManager userManager;private readonly ISmsService smsService;private readonly SmsCaptchaManager smsCaptchaManager;private readonly EmailCaptchaManager emailCaptchaManager;public ResetPasswordManager(UserManager userManager,ISmsService smsService,SmsCaptchaManager smsCaptchaManager,EmailCaptchaManager emailCaptchaManager){this.userManager  userManager;this.smsService  smsService;this.smsCaptchaManager  smsCaptchaManager;this.emailCaptchaManager  emailCaptchaManager;}在ResetPasswordManager中添加SendForgotPasswordCaptchaAsync方法用于短信或邮箱方式的身份验证。 
public async Task SendForgotPasswordCaptchaAsync(string provider, string phoneNumberOrEmail)
{User user;if (provider  Email){user  await userManager.FindByEmailAsync(phoneNumberOrEmail);if (user  null){throw new UserFriendlyException(未找到绑定邮箱的用户);}await emailCaptchaManager.SendCaptchaAsync(user.Id, user.EmailAddress, CaptchaPurpose.RESET_PASSWORD);}else if (provider  Phone){user  await userManager.FindByNameOrPhoneNumberAsync(phoneNumberOrEmail);if (user  null){throw new UserFriendlyException(未找到绑定手机号的用户);}await smsCaptchaManager.SendCaptchaAsync(user.Id, user.PhoneNumber, CaptchaPurpose.RESET_PASSWORD);}}校验验证码 
添加VerifyAndSendResetPasswordLinkAsync方法用于校验验证码并发送重置密码的链接。 
public async Task VerifyAndSendResetPasswordLinkAsync(string token, string provider)
{if (provider  Email){EmailCaptchaTokenCacheItem currentItem  await emailCaptchaManager.GetToken(token);if (currentItem  null || currentItem.Purpose ! CaptchaPurpose.RESET_PASSWORD){throw new UserFriendlyException(验证码不正确或已过期);}var user  await userManager.GetUserByIdAsync(currentItem.UserId);var emailAddress  currentItem.EmailAddress;await SendEmailResetPasswordLink(user, emailAddress);await emailCaptchaManager.RemoveToken(token);}else if (provider  Phone){SmsCaptchaTokenCacheItem currentItem  await smsCaptchaManager.GetToken(token);if (currentItem  null || currentItem.Purpose ! CaptchaPurpose.RESET_PASSWORD){throw new UserFriendlyException(验证码不正确或已过期);}var user  await userManager.GetUserByIdAsync(currentItem.UserId);var phoneNumber  currentItem.PhoneNumber;await SendSmsResetPasswordLink(user, phoneNumber);await smsCaptchaManager.RemoveToken(token);}else{throw new UserFriendlyException(验证码提供者错误);}} 
发送重置密码链接 
创建SendSmsResetPasswordLink用于对当前用户产生一个NewPasswordResetCode并发送重置密码的短信链接。 
private async Task SendSmsResetPasswordLink(User user, string phoneNumber)
{var model  new SendSmsRequest();user.SetNewPasswordResetCode();var passwordResetCode  user.PasswordResetCode;model.PhoneNumbers  new string[] { phoneNumber };model.SignName  MatoApp;model.TemplateCode  SMS_255330989;//for aliyunmodel.TemplateParam  JsonConvert.SerializeObject(new { username  user.UserName, code  passwordResetCode });//for tencent-cloud//model.TemplateParam  JsonConvert.SerializeObject(new string[] { user.UserName, passwordResetCode });var result  await smsService.SendSmsAsync(model);if (string.IsNullOrEmpty(result.BizId)  result.Code ! OK){throw new UserFriendlyException(验证码发送失败错误信息:  result.Message);}
} 
创建接口 
在UserAppService暴露出SendForgotPasswordCaptcha和VerifyAndSendResetPasswordLink两个接口 
注意这两个接口都需要添加[AbpAllowAnonymous]特性因为在用户未登录的情况下也需要使用这两个接口。 
[AbpAllowAnonymous]
public async Task SendForgotPasswordCaptcha(ForgotPasswordProviderDto input)
{var provider  input.Provider;var phoneNumberOrEmail  input.ProviderNumber;await forgotPasswordManager.SendForgotPasswordCaptchaAsync(provider, phoneNumberOrEmail);}[AbpAllowAnonymous]
public async Task VerifyAndSendResetPasswordLink(SendResetPasswordLinkDto input)
{var provider  input.Provider;var token  input.Token;await forgotPasswordManager.VerifyAndSendResetPasswordLinkAsync(token, provider);}这两个接口分别在用户忘记密码的两个阶段调用 
第一阶段是发送验证码第二阶段是校验验证码并发送重置密码的链接。 密码强制过期策略 
在User实体中添加一个属性用于记录密码最后修改时间在登录时验证这个时间至此时的时间跨度如果超过一定时间例如90天强制用户重置密码。 
[Required]
public DateTime LastPasswordModificationTime { get; set; } 
改写接口 
将重置校验码PasswordResetCode添加到AuthenticateResultModel中 
public string PasswordResetCode { get; set; } 
打开TokenAuthController注入ResetPasswordManager服务对象 
登录验证终节点方法Authenticate中添加对密码强制过期的逻辑代码 
[HttpPost]
public async TaskAuthenticateResultModel Authenticate([FromBody] AuthenticateModel model)
{var loginResult  await GetLoginResultAsync(model.UserNameOrEmailAddress,model.Password,GetTenancyNameOrNull());...//Password Expiration Checkif (DateTime.Now - loginResult.User.LastPasswordModificationTime  TimeSpan.FromDays(90)){loginResult.User.SetNewPasswordResetCode();return new AuthenticateResultModel{PasswordResetCode  loginResult.User.PasswordResetCode,UserId  loginResult.User.Id,};}} 
当登录账号的LastPasswordModificationTime距此时大于90天时将阻止登录并提示账户密码已过期需要修改密码   Vue网页端开发 
重置密码页面 
创建Web端的重置密码页面用于用户重置密码。 当用户通过短信或邮箱接收到重置密码的链接后点击链接会跳转到重置密码的页面用户输入新密码后点击提交就可以完成密码重置。 
连接格式如下 
http://localhost:8080/reset-password-sample/reset.html?codef16b5fbb057d4a04bce5b9e7f24e1d56userId1 
项目参与实际生产中请加密参数在此为了简单起见采用明文传递。 
templatediv idappdiv classtitle-container centerh3 classtitle修改密码/h3/divel-rowel-formrefloginForm:modelinputclasslogin-formautocompleteonlabel-positionleftel-form-item label验证码el-input v-modelinput.code placeholder请输入验证码 clearable //el-form-itemel-form-item label新密码 propnewPasswordel-inputv-modelinput.newPasswordplaceholder请输入新密码clearableshow-password//el-form-itemel-form-item label新密码确认 propnewPassword2el-inputv-modelinput.newPassword2placeholder请再次输入新密码clearableshow-password//el-form-itemel-row typeflex classrow-bgel-col :offset6 :span10el-buttontypeprimarystylewidth: 100%click.native.preventsubmit修改/el-button/el-col/el-row/el-form/el-row/div
/template创建页面时会根据url中的参数获取code和userId。 
created: async function () {var url  window.location.href;var reg  /[?]([^?#])([^?#])/g;var param  {};var ret  reg.exec(url);while (ret) {param[ret[1]]  ret[2];ret  reg.exec(url);}if (code in param) {this.input.code  param[code];}if (userId in param) {this.input.userId  param[userId];}}, 
点击修改时会触发submit方法这个方法会调用ResetPasswordByCode接口将UserIdnewPassword以及resetCode回传。 async submit() {if ((this.input.newPassword ! this.input.newPassword2)  null) {this.$message.warning(两次输入的密码不一致);return;}await request(${this.host}${this.prefix}/User/ResetPasswordByCode,post,{userId: this.input.userId,newPassword: this.input.newPassword,resetCode: this.input.code,}).catch((re)  {var res  re.response.data;this.errorMessage(res.error.message);}).then(async (res)  {var data  res.data.result;this.successMessage(密码修改成功);window.location.href  /reset-password-sample.html;}).finally(()  {setTimeout(()  {this.loading  false;}, 1.5 * 1000);});}, 
忘记密码控件 
在登录页面中添加忘记密码的控件。 resetPasswordStage 是判定当前是哪个阶段的变量 0表示正常用户名密码登录初始状态1表示输入手机号或邮箱验证身份2表示通过验证即将发送重置密码的链接。 
默认两种方式一种是短信验证码一种是邮箱验证码这里我们采用了elementUI的tab组件来实现两种方式的切换。 
template v-else-ifresetPasswordStage  1p请输入与要找回的账户关联的手机号或邮箱。我们将为你发送密码重置连接/pel-tabs tab-positiontop v-modelforgotPasswordProvider.providerel-tab-pane :lazytrue label通过手机号找回 namePhoneel-rowel-col :span24el-inputv-modelforgotPasswordProvider.providerNumber:placeholder请输入手机号tabindex2el-buttonslotappendclicksendResetPasswordLink:disabledforgotPasswordProvider.providerNumber  下一步/el-button/el-input/el-col/el-row/el-tab-paneel-tab-pane :lazytrue label通过邮箱找回 nameEmailel-rowel-col :span24el-alertv-ifshowResetRequireSuccesstitle密码重置连接已发送至登录用户对应的邮箱请查收typeinfo/el-alert/el-colel-col :span24p建设中../p/el-col/el-row/el-tab-pane/el-tabs
/template不通的阶段将分别调用不同的接口sendResetPasswordLink以及verifyAndSendResetPasswordLink。 
调用verifyAndSendResetPasswordLink接口完毕时resetPasswordStage将设置位初始状态即0。 
async sendResetPasswordLink() {await request(${this.host}${this.prefix}/User/SendForgotPasswordCaptcha,post,this.forgotPasswordProvider).catch((re)  {var res  re.response.data;this.errorMessage(res.error.message);}).then(async (re)  {if (re) {this.successMessage(发送验证码成功);this.resetPasswordStage;}});
},
async verifyAndSendResetPasswordLink() {await request(${this.host}${this.prefix}/User/VerifyAndSendResetPasswordLink,post,{provider: this.forgotPasswordProvider.provider,token: this.captchaToken,}).catch((re)  {var res  re.response.data;this.errorMessage(res.error.message);}).then(async (re)  {if (re) {this.successMessage(发送连接成功);this.resetPasswordStage  0;}});
},密码过期提示 
主页面中添加对passwordResetCode的响应当passwordResetCode不为空时显示一个提示框提示用户密码已超过90天未修改请修改密码。 
el-alertv-ifpasswordResetCode ! nullclose-text点此修改密码title密码已超过90天未修改为了安全请修改密码typeinfoclosegotoUrl(/reset-password-sample/reset.html?code passwordResetCode userId userId)
/el-alert用户点击点此修改密码按钮时将跳转至重置密码页面。  
项目地址 
Github:matoapp-samples