- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
@ 。
用户找回密码是在用户没有登录时进行的,因此需要先校验身份(除用户名+密码外的第二种身份验证方式)。 第二种身份验证的前提是绑定了手机号或者邮箱,如果没有绑定,那么只能通过管理员进行原始密码重置.
密码强制过期策略,是指用户在一段时间内没有修改密码,在下次登录时系统阻止用户登录,直到用户修改了密码后方可继续登录。此策略提高用户账号的安全性.
找回密码和密码过期重置密码,两种机制有相近的业务逻辑,即密码重置。今天我们来实现这个功能.
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服务商管理端后台申请一个短信模板,用于重置密码.
打开短信验证码的领域服务类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);
}
这两个接口分别在用户忘记密码的两个阶段调用, 。
在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天时,将阻止登录,并提示账户密码已过期,需要修改密码 。
创建Web端的重置密码页面,用于用户重置密码.
当用户通过短信或邮箱接收到重置密码的链接后,点击链接,会跳转到重置密码的页面,用户输入新密码后,点击提交,就可以完成密码重置.
连接格式如下 。
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);
});
},
在登录页面中,添加忘记密码的控件.
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>
用户点击 点此修改密码 按钮时将跳转至重置密码页面.
Github:matoapp-samples 。
最后此篇关于用Abp实现找回密码和密码强制过期策略的文章就讲到这里了,如果你想了解更多关于用Abp实现找回密码和密码强制过期策略的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
Internal Server Error: /admin/account/customuser/add/ Traceback (most recent call last): File "C:\
有问题!虽然我发现几乎相似的线程但没有帮助:( 我编写了一个 php 脚本来从我的 MySQL 数据库中获取注册用户的数量。该脚本在我的本地主机上运行良好;它使用给定的用户名、密码和主机名,分别是“r
我正在做一项基于密码的作业,我将 key 和消息放在单独的数组中。我想创建第三个数组,其中包含围绕消息大小的 key ,如下所示: message keykeyk 我已经在这个问题上苦苦挣扎了一段时间
我的几个客户要求我实现图形密码检查器,例如 关于如何实现这种 UI 有什么想法吗? 最佳答案 试着看看这个:https://code.google.com/p/android-lockpattern/
我正在使用 MAMP,每次登录 phpMyAdmin 时,都会收到以下错误/警告消息: the configuration file now needs a secret passphrase (bl
我正在尝试通过将 Visual Studio 2013 连接到我的测试机来调试 WDF 驱动程序。它创建一个名为 WDKRemoteUser 的用户,并在进行测试时尝试自动登录。有人知道这个用户的密码
使用具有指定用户名和密码的 SVN 提交。我希望服务器抛出错误;所以我可以告诉我的用户他/她的密码错误。 相反,在使用错误密码提交后: svn commit "test_file.txt" --use
我正在尝试实现 friend 推荐。 它从节点“你”开始。而且,我想找到节点“安娜”。 换句话说,这是我的两个或更多 friend 共同认识的人。上面的示例节点是 Anna。 如果您的帮助,我将不胜感
我都尝试过 wget --user=myuser --password=mypassword myfile 和 wget --ftp-user=myuser --ftp-password=mypass
我的一位用户提示说,每当他尝试使用默认管理界面(Django 的管理员)添加新用户(auth.User)时,新用户名和密码都会自动填充他自己的。 问题是他在登录时要求 Firefox 记住他的用户名/
我们正在开发一款应用(当然)用于应用购买 (IAP)。我已完成指南中的所有操作以启用 iap,并且一切正常,直到我想要购买为止。 部分代码: MainViewController.m -(vo
我试图创建两个可选匹配项的并集(如下所示),但我得到的不是并集,而是两者的交集。我应该如何更改此查询以获得所需的联合? optional match (a:PA)-[r2:network*2]-(b:
我想将Ansible用作另一个Python软件的一部分。在该软件中,我有一个包含其用户名/密码的主机列表。 有没有一种方法可以将SSH连接的用户/密码传递给Ansible ad-hoc命令或以加密方式
嗨,我在使用xampp的Apache Web服务器上收到错误500。直到我使用.htaccess,.htpasswd文件,错误才出现。我搜索了,但找不到语法错误。我只有1张图片和要保护的索引文件。以下
我一直使用它来编辑用户帐户信息: $this->validate($request, [ 'password' => 'min:6', 'password_confirmation'
我需要使用InstallUtil来安装C# Windows服务。我需要设置服务登录凭据(用户名和密码)。这一切都需要默默地完成。 有没有办法做这样的事情: installutil.exe myserv
已关闭。此问题不符合Stack Overflow guidelines 。目前不接受答案。 这个问题似乎不是关于 a specific programming problem, a software
如果我有一个随机的、16 个字符长的字母数字盐(不同大小写),它是为每个用户生成和存储的,我是否还需要一个站点范围的盐? 换句话说,这样好吗? sha1($user_salt . $password)
我正在开发一个空白程序,该程序将允许用户创建一个帐户,以便他们可以存储其余额和提款/存款。用户输入用户名和密码后,如何存储这些信息以便用户可以登录并查看其余额?我不一定要尝试使其非常安全,我只是希望能
我正在尝试寻找一种通用方法来搜索没有链接到另一个节点或节点集的节点或节点集。例如,我能够找到特定类型(例如 :Style)的所有节点,这些节点以某种方式连接到一组特定的节点(例如 :MetadataR
我是一名优秀的程序员,十分优秀!