用Abp实现找回密码和密码强制过期策略

这篇具有很好参考价值的文章主要介绍了用Abp实现找回密码和密码强制过期策略。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

@

目录
  • 重置密码
  • 找回密码
    • 发送验证码
    • 校验验证码
    • 发送重置密码链接
    • 创建接口
  • 密码强制过期策略
    • 改写接口
  • Vue网页端开发
    • 重置密码页面
    • 忘记密码控件
    • 密码过期提示
  • 项目地址

用户找回密码,确切地说是重置密码,为了保证用户账号安全,原始密码将不再以明文的方式找回,而是通过短信或者邮件的方式发送一个随机的重置校验码(带校验码的页面连接),用户点击该链接,跳转到重置密码页面,输入新的密码。这个重置校验码是一次性的,用户重置密码后立即失效。

用户找回密码是在用户没有登录时进行的,因此需要先校验身份(除用户名+密码外的第二种身份验证方式)。
第二种身份验证的前提是绑定了手机号或者邮箱,如果没有绑定,那么只能通过管理员进行原始密码重置。

密码强制过期策略,是指用户在一段时间内没有修改密码,在下次登录时系统阻止用户登录,直到用户修改了密码后方可继续登录。此策略提高用户账号的安全性。

用Abp实现找回密码和密码强制过期策略

找回密码和密码过期重置密码,两种机制有相近的业务逻辑,即密码重置。今天我们来实现这个功能。

重置密码

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 Task<bool> 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);
    Task<bool> VerifyCaptchaAsync(string token, string purpose = "IDENTITY_VERIFICATION");
}

添加一个用于重置密码的purpose,在CaptchaPurpose枚举类型中添加RESET_PASSWORD

public class CaptchaPurpose
{
    ...

    public const string RESET_PASSWORD = "RESET_PASSWORD";

}

在SMS服务商管理端后台申请一个短信模板,用于重置密码。

用Abp实现找回密码和密码强制过期策略

打开短信验证码的领域服务类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类,用于处理找回密码和密码过期重置密码的业务逻辑。
注入UserManager,ISmsService,SmsCaptchaManager,EmailCaptchaManager。

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 aliyun
    model.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);

}

这两个接口分别在用户忘记密码的两个阶段调用,

  1. 第一阶段是发送验证码,
  2. 第二阶段是校验验证码并发送重置密码的链接。

用Abp实现找回密码和密码强制过期策略

密码强制过期策略

在User实体中添加一个属性,用于记录密码最后修改时间,在登录时验证这个时间至此时的时间跨度,如果超过一定时间(例如90天),强制用户重置密码。

[Required]
public DateTime LastPasswordModificationTime { get; set; }

改写接口

将重置校验码PasswordResetCode添加到AuthenticateResultModel中

public string PasswordResetCode { get; set; }

打开TokenAuthController,注入ResetPasswordManager服务对象

登录验证终节点方法Authenticate中,添加对密码强制过期的逻辑代码

[HttpPost]
public async Task<AuthenticateResultModel> Authenticate([FromBody] AuthenticateModel model)
{

    var loginResult = await GetLoginResultAsync(
                model.UserNameOrEmailAddress,
                model.Password,
                GetTenancyNameOrNull()
            );


    ...

    //Password Expiration Check
    if (DateTime.Now - loginResult.User.LastPasswordModificationTime > TimeSpan.FromDays(90))
    {
        loginResult.User.SetNewPasswordResetCode();

        return new AuthenticateResultModel
        {
            PasswordResetCode = loginResult.User.PasswordResetCode,
            UserId = loginResult.User.Id,
        };
    }

}

当登录账号的LastPasswordModificationTime距此时大于90天时,将阻止登录,并提示账户密码已过期,需要修改密码
用Abp实现找回密码和密码强制过期策略
用Abp实现找回密码和密码强制过期策略

Vue网页端开发

重置密码页面

创建Web端的重置密码页面,用于用户重置密码。
用Abp实现找回密码和密码强制过期策略

当用户通过短信或邮箱接收到重置密码的链接后,点击链接,会跳转到重置密码的页面,用户输入新密码后,点击提交,就可以完成密码重置。

连接格式如下

http://localhost:8080/reset-password-sample/reset.html?code=f16b5fbb057d4a04bce5b9e7f24e1d56&userId=1

项目参与实际生产中请加密参数,在此为了简单起见采用明文传递。

<template>
  <div id="app">
    <div class="title-container center">
      <h3 class="title">修改密码</h3>
    </div>
    <el-row>
      <el-form
        ref="loginForm"
        :model="input"
        class="login-form"
        autocomplete="on"
        label-position="left"
      >
        <el-form-item label="验证码">
          <el-input v-model="input.code" placeholder="请输入验证码" clearable />
        </el-form-item>
        <el-form-item label="新密码" prop="newPassword">
          <el-input
            v-model="input.newPassword"
            placeholder="请输入新密码"
            clearable
            show-password
          />
        </el-form-item>
        <el-form-item label="新密码(确认)" prop="newPassword2">
          <el-input
            v-model="input.newPassword2"
            placeholder="请再次输入新密码"
            clearable
            show-password
          />
        </el-form-item>

        <el-row type="flex" class="row-bg">
          <el-col :offset="6" :span="10">
            <el-button
              type="primary"
              style="width: 100%"
              @click.native.prevent="submit"
              >修改
            </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接口,将UserId,newPassword以及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);
        });
    },

忘记密码控件

在登录页面中,添加忘记密码的控件。

用Abp实现找回密码和密码强制过期策略

resetPasswordStage 是判定当前是哪个阶段的变量,
0表示正常用户名密码登录(初始状态),1表示输入手机号或邮箱验证身份,2表示通过验证即将发送重置密码的链接。

默认两种方式,一种是短信验证码,一种是邮箱验证码,这里我们采用了elementUI的tab组件,来实现两种方式的切换。

<template v-else-if="resetPasswordStage == 1">
    <p>
    请输入与要找回的账户关联的手机号或邮箱。我们将为你发送密码重置连接
    </p>
    <el-tabs tab-position="top" v-model="forgotPasswordProvider.provider">
    <el-tab-pane :lazy="true" label="通过手机号找回" name="Phone">
        <el-row>
        <el-col :span="24">
            <el-input
            v-model="forgotPasswordProvider.providerNumber"
            :placeholder="'请输入手机号'"
            tabindex="2"
            >
            <el-button
                slot="append"
                @click="sendResetPasswordLink"
                :disabled="forgotPasswordProvider.providerNumber == ''"
                >下一步</el-button
            >
            </el-input>
        </el-col>
        </el-row>
    </el-tab-pane>

    <el-tab-pane :lazy="true" label="通过邮箱找回" name="Email">
        <el-row>
        <el-col :span="24">
            <el-alert
            v-if="showResetRequireSuccess"
            title="密码重置连接已发送至登录用户对应的邮箱,请查收"
            type="info"
            >
            </el-alert>
        </el-col>
        <el-col :span="24">
            <p>建设中..</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-alert
    v-if="passwordResetCode != null"
    close-text="点此修改密码"
    title="密码已超过90天未修改,为了安全,请修改密码"
    type="info"
    @close="
        gotoUrl(
        '/reset-password-sample/reset.html?code=' +
            passwordResetCode +
            '&userId=' +
            userId
        )
    "
    >
</el-alert>

用Abp实现找回密码和密码强制过期策略
用户点击点此修改密码按钮时将跳转至重置密码页面。

项目地址

Github:matoapp-samples文章来源地址https://www.toymoban.com/news/detail-413022.html

到了这里,关于用Abp实现找回密码和密码强制过期策略的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Redis的key过期策略是怎么实现的

    这是一道经典的Redis面试题,一个Redis中可能存在很多很多的key,这些key中可能有很大一部分都有过期时间,此时Redis服务器咋知道哪些key已经过期,哪些还没过期呢? 如果直接遍历所有的key,这显然是行不通的,效率非常低!! Redis整体的策略是定期删除和惰性删除相结合。

    2024年01月19日
    浏览(31)
  • redis的Key的过期策略是如何实现的?

    Key的过期策略 一个redis中可能同时存在很多很多key,这些key可能有很大一部分都有过期时间,此时,redis服务器咋知道哪些key已经过期要被删除,哪些key还没有过期? 如果直接遍历所有的key,显然是行不通的,效率极低! Redis的Key有3种过期删除策略,具体如下: 原理 :在设

    2024年02月13日
    浏览(34)
  • git硬重置(hard reset)重找回

    首先进行git版本回退 1、git log查找历史commit_id  2、版本回退  3、找回你的提交(commit), 因为Git对每件事都会有日志,且都会保存几天。  4、选择你想要回到的提交(commit)的SHA,再重置一次:

    2024年02月07日
    浏览(44)
  • 基于VUE3+Layui从头搭建通用后台管理系统(前端篇)三:找回密码界面及对应功能实现

      本章实现找回密码功能,包括短信验证码找回、邮箱验证码找回等功能,并通过node-send-email发送邮箱验证码,实现找回密码界面、接口等功能。 1. 详细课程地址: https://edu.csdn.net/course/detail/38183 2. 源码下载地址: 点击下载

    2024年02月12日
    浏览(55)
  • 【安卓app开发一】Android Studio + Bmob后端云实现注册&登录账号、密码找回、意见反馈及数据可视化

    目录 前言 概览 Bmob后端云介绍 Bmob后端云与Android Studio配置 一、Bmob后端云  二、Android Studio配置 工具类 一、User类 二、Suit类 三、Code类 实现类  一、登录代码  二、注册代码  三、找回密码代码  四、想法反馈代码         本项目尚在开发阶段,主要针对的是安卓用户,通

    2024年02月08日
    浏览(59)
  • Android Mediatek 增加Recovery模式下读cmdline的强制工厂重置选项

    目录 修改的文件: 文件修改说明: 1. recovery.cpp 2. mt_boot.c 3. platform.c 4. codegen.dws 这个修改主要是在多个文件中增加了一个选项,当设备在恢复模式下并且特定的GPIO引脚被设置时,设备将进行强制的工厂重置。 bootable/recovery/recovery.cpp vendor/mediatek/proprietary/bootable/bootloader/lk/ap

    2024年02月17日
    浏览(48)
  • Windows server 远程桌面授权过期,无法使用远程桌面问题解决,重置远程桌面剩余时间

    Windows server 12/16/19版本远程桌面服务过期,重置远程服务时间。一招搞定。 接下来请看《保姆级教学》: 1.win+r打开运行 2.找到路径: HKEY_LOCAL_MACHINESYSTEMCurrentControlSetControlTerminal ServerRCMGracePeriod.如果超过120天后RCM下面会有一个GracePeriod,先备份这项注册表,再删除除了默

    2024年02月12日
    浏览(49)
  • 宇视摄像机密码忘记找回方式(详细找回步骤)

    宇视摄像机密码重置方法(详细找回步骤) IPC 摄像机密码忘记,操作步骤如下: 尝试默认密码是否可以正常登陆,用户名admin,密码默认admin或者123456。 确认IPC是否有添加到上级VM平台,并且在线 ,如有,行业、通用IPC可以从平台侧修改找回,分销IPC不支持。 找回方法:平

    2024年02月08日
    浏览(388)
  • 密码找回安全

    用户提交修改密码请求; 账号认证:服务器发送唯一ID (例如信验证码)只有账户所有者才能看的地方,完成身份验证; 身份验证:用户提交验证码完成身份验证; 修改密码:用户修改密码。 登录metinfo4.0网站,点击网站管理: 输入用户名admin密码123456登录管理员账号: 另起一个网页

    2024年02月09日
    浏览(26)
  • Rancher 密码重置,重置密码后无法登录

    Rancher 密码重置,重置密码后无法登录 使用单节点 Docker 安装时: 使用 Helm 的高可用安装时: 使用单节点 Docker 安装时: 使用 Helm 的高可用安装时: Jumpserver 修改用户密码

    2024年02月09日
    浏览(34)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包