新增找回密码功能,支持通过手机号、邮箱、保密问题找回。

This commit is contained in:
thinkgem
2018-07-08 22:45:10 +08:00
parent e55fb4274d
commit 81793205c1
5 changed files with 355 additions and 348 deletions

View File

@@ -18,10 +18,10 @@ import org.springframework.web.bind.annotation.ResponseBody;
import com.jeesite.common.collect.MapUtils;
import com.jeesite.common.config.Global;
import com.jeesite.common.lang.StringUtils;
import com.jeesite.common.msg.EmailUtils;
import com.jeesite.common.msg.SmsUtils;
import com.jeesite.common.service.ServiceException;
import com.jeesite.common.web.BaseController;
import com.jeesite.modules.msg.entity.MsgPush;
import com.jeesite.modules.msg.utils.MsgPushUtils;
import com.jeesite.modules.sys.entity.User;
import com.jeesite.modules.sys.service.UserService;
import com.jeesite.modules.sys.utils.UserUtils;
@@ -49,21 +49,21 @@ public class AccountController extends BaseController{
}
/**
* 获取短信、邮件验证码
* 获取找回密码短信、邮件验证码
* @param validCode 图片验证码,防止重复机器人。
* @param validType 验证方式mobile、email
*/
@PostMapping(value = "getValidCode")
@PostMapping(value = "getFpValidCode")
@ResponseBody
public String getValidCode(User user, String validCode, String validType, HttpServletRequest request) {
public String getFpValidCode(User user, String validCode, String validType, HttpServletRequest request) {
// 校验图片验证码,防止重复机器人。
if (!ValidCodeUtils.validate(request, validCode)){
return renderResult(Global.FALSE, "验证码不正确或已失效!");
return renderResult(Global.FALSE, "图片验证码不正确或已失效,请点击图片刷新");
}
if (!"mobile".equals(validType) && !"email".equals(validType)){
return renderResult(Global.FALSE, "非法操作。");
}
User u = userService.getByLoginCode(user);
User u = UserUtils.getByLoginCode(user.getLoginCode());
if(u == null){
return renderResult(Global.FALSE, "登录账号不正确!");
}
@@ -106,17 +106,25 @@ public class AccountController extends BaseController{
String validCode = (String)UserUtils.getCache("fpValidCode");
Date date = (Date)UserUtils.getCache("fpLastDate");
// 一同验证保存的用户名和验证码是否正确(如果只校验验证码,不验证用户名,则会有获取验证码后修改用户名的漏洞)
if (!(userCode != null && loginCode != null && loginCode.equals(user.getLoginCode()))){
return renderResult(Global.FALSE, "请重新获取验证码!");
}
// 清理验证码,验证码只允许使用一次。
UserUtils.removeCache("fpUserCode");
UserUtils.removeCache("fpLoginCode");
UserUtils.removeCache("fpValidCode");
UserUtils.removeCache("fpLastDate");
// 验证码是否超时
boolean isTimeout = true;
String validTime = Global.getConfig("sys.account.validCodeTimeout", "10"); //验证码有效时间单位分钟0表示不限制默认值10
if("0".equals(validTime) || (date != null && (System.currentTimeMillis()-date.getTime())/(1000L) < 60*Long.parseLong(validTime))){
isTimeout = false;
}
// 一同验证保存的用户名和验证码是否正确(如果只校验验证码,不验证用户名,则会有获取验证码后修改用户名的漏洞)
if (!(userCode != null && loginCode != null && loginCode.equals(user.getLoginCode())
&& validCode != null && validCode.equals(fpValidCode) && !isTimeout)){
return renderResult(Global.FALSE, "验证码不正确或已失效!");
if (!(validCode != null && validCode.equals(fpValidCode) && !isTimeout)){
return renderResult(Global.FALSE, "验证码不正确或已失效,请重新获取验证码!");
}
// 更新为新密码。
@@ -125,12 +133,6 @@ public class AccountController extends BaseController{
}catch(ServiceException se){
return renderResult(Global.FALSE, se.getMessage());
}
// 修改密码成功后清理验证码,验证码只允许使用一次。
UserUtils.removeCache("fpUserCode");
UserUtils.removeCache("fpLoginCode");
UserUtils.removeCache("fpValidCode");
UserUtils.removeCache("fpLastDate");
return renderResult(Global.TRUE, "恭喜你,您的账号 "+loginCode+" 密码修改成功!");
}
@@ -143,13 +145,26 @@ public class AccountController extends BaseController{
public String getPwdQuestion(User user, String validCode, HttpServletRequest request) {
// 校验图片验证码,防止重复机器人。
if (!ValidCodeUtils.validate(request, validCode)){
return renderResult(Global.FALSE, "验证码不正确或已失效!");
return renderResult(Global.FALSE, "图片验证码不正确或已失效,请点击图片刷新");
}
// 账号是否存在验证
User u = userService.getByLoginCode(user);
User u = UserUtils.getByLoginCode(user.getLoginCode());
if (u == null){
return renderResult(Global.FALSE, "登录账号不正确!");
}
// 操作是否频繁验证, 如果离上次获取验证码小于20秒则提示操作频繁。
Date date = (Date)UserUtils.getCache("fpLastDate");
if (date != null && (System.currentTimeMillis()-date.getTime())/(1000L) < 20L){
return renderResult(Global.FALSE, "您当前操作太频繁,请稍等一会再操作!");
}else{
UserUtils.putCache("fpLastDate", new Date());
}
// 未设置密保
if (StringUtils.isAnyBlank(u.getPwdQuestion(), u.getPwdQuestion2(), u.getPwdQuestion3())){
return renderResult(Global.FALSE, "该账号未设置密保问题!");
}
// 获取保密问题,并缓存
Map<String, String> data = MapUtils.newHashMap();
data.put("pwdQuestion", u.getPwdQuestion());
@@ -171,9 +186,18 @@ public class AccountController extends BaseController{
public String savePwdByPwdQuestion(User user, HttpServletRequest request) {
String userCode = (String)UserUtils.getCache("fpUserCode");
String loginCode = (String)UserUtils.getCache("fpLoginCode");
User u = userService.getByLoginCode(user);
// 一同验证保存的用户名和验证码是否正确(如果只校验验证码,不验证用户名,则会有获取验证码后修改用户名的漏洞)
if (!(userCode != null && loginCode != null && loginCode.equals(user.getLoginCode()))){
return renderResult(Global.FALSE, "请重新获取保密问题!");
}
// 清理保密问题,每次获取只允许使用一次。
UserUtils.removeCache("fpUserCode");
UserUtils.removeCache("fpLoginCode");
// 验证三个密保问题是否正确。
User u = UserUtils.getByLoginCode(user.getLoginCode());
if (!(u != null && loginCode.equals(user.getLoginCode())
&& UserService.validatePassword(user.getPwdQuestionAnswer(), u.getPwdQuestionAnswer())
&& UserService.validatePassword(user.getPwdQuestionAnswer2(), u.getPwdQuestionAnswer2())
@@ -187,10 +211,6 @@ public class AccountController extends BaseController{
}catch(ServiceException se){
return renderResult(Global.FALSE, se.getMessage());
}
// 验证成功后清理缓存。
UserUtils.removeCache("fpUserCode");
UserUtils.removeCache("fpLoginCode");
return renderResult(Global.TRUE, "验证通过");
}
@@ -199,7 +219,6 @@ public class AccountController extends BaseController{
* @param user 用户信息参数
*/
@RequestMapping(value = "registerUser")
@ResponseBody
public String registerUser(User user, HttpServletRequest request) {
return "modules/sys/account/registerUser";
}
@@ -209,12 +228,12 @@ public class AccountController extends BaseController{
* @param user 用户信息参数
* @param validType 验证方式mobile、email
*/
@PostMapping(value = "getRegisterUserValidCode")
@PostMapping(value = "getRegValidCode")
@ResponseBody
public String getRegisterUserValidCode(User user, String validCode, String validType, HttpServletRequest request) {
public String getRegValidCode(User user, String validCode, String validType, HttpServletRequest request) {
// 校验图片验证码,防止重复机器人。
if (!ValidCodeUtils.validate(request, validCode)){
return renderResult(Global.FALSE, "验证码不正确或已失效!");
return renderResult(Global.FALSE, "图片验证码不正确或已失效,请点击图片刷新");
}
if (!"mobile".equals(validType) && !"email".equals(validType)){
return renderResult(Global.FALSE, "非法操作。");
@@ -238,7 +257,7 @@ public class AccountController extends BaseController{
UserUtils.putCache("regLastDate", new Date());
}
// 验证用户编码是否存在。
if (userService.getByLoginCode(user) != null){
if (UserUtils.getByLoginCode(user.getLoginCode()) != null){
return renderResult(Global.FALSE, "登录账号已存在!");
}
// 生成验证码,并缓存。
@@ -271,9 +290,9 @@ public class AccountController extends BaseController{
* @param user 用户信息参数
* @param validType 验证方式mobile、email
*/
@PostMapping(value = "saveRegisterUserByValidCode")
@PostMapping(value = "saveRegByValidCode")
@ResponseBody
public String saveRegisterUserByValidCode(User user, String regValidCode, HttpServletRequest request) {
public String saveRegByValidCode(User user, String regValidCode, HttpServletRequest request) {
if (!"true".equals(Global.getConfig("sys.account.registerUser"))){
return renderResult(Global.FALSE, "当前系统没有开启注册功能!");
}
@@ -285,6 +304,11 @@ public class AccountController extends BaseController{
String mobile = (String)UserUtils.getCache("regMobile");
String validCode = (String)UserUtils.getCache("regValidCode");
Date date = (Date)UserUtils.getCache("regLastDate");
// 一同验证保存的用户名和验证码是否正确(如果只校验验证码,不验证用户名,则会有获取验证码后修改用户名的漏洞)
if (!(loginCode != null && loginCode.equals(user.getLoginCode()))){
return renderResult(Global.FALSE, "非法操作。");
}
// 验证码是否超时
boolean isTimeout = true;
@@ -292,11 +316,8 @@ public class AccountController extends BaseController{
if("0".equals(validTime) || (date != null && (System.currentTimeMillis()-date.getTime())/(1000L) < 60*Long.parseLong(validTime))){
isTimeout = false;
}
// 一同验证保存的用户名和验证码是否正确(如果只校验验证码,不验证用户名,则会有获取验证码后修改用户名的漏洞)
if (!(loginCode != null && loginCode.equals(user.getLoginCode())
&& validCode != null && validCode.equals(regValidCode) && !isTimeout)){
return renderResult(Global.FALSE, "验证码不正确或已失效!");
if (!(validCode != null && validCode.equals(regValidCode) && !isTimeout)){
return renderResult(Global.FALSE, "验证码不正确或已失效,请重新获取验证码!");
}
// 非空数据校验。
@@ -333,33 +354,39 @@ public class AccountController extends BaseController{
* 发送邮件验证码
*/
private String sendEmailValidCode(User user, String code, String title){
String account = user.getEmail();
try {
title = user.getUserName() + "" + user.getLoginCode() + ""+title+"验证码";
String content = "尊敬的用户,您好!\n\n您的验证码是" + code +"(请勿透露给其他人)\n\n"
+ "请复制后,填写在你的验证码窗口完成验证。\n\n本邮件由系统自动发出请勿回复。\n\n感谢您的使用";
String receiveUserCode = "[CODE]"+user.getEmail();
MsgPushUtils.push(MsgPush.TYPE_EMAIL, title, content, null, null, receiveUserCode);
+ "请复制后,填写在你的验证码窗口完成验证。\n\n本邮件由系统自动发出请勿回复。\n\n感谢您的使用";
// String receiveUserCode = "[CODE]"+account;
// MsgPushUtils.push(MsgPush.TYPE_EMAIL, title, content, null, null, receiveUserCode);
EmailUtils.send(account, title, content);
} catch (Exception e) {
logger.error(title+"发送邮件错误。", e);
return renderResult(Global.FALSE, "系统出现了点问题,错误信息:" + e.getMessage());
}
return renderResult(Global.TRUE, "邮件已发送,请接收并填写验证码!");
account = account.replaceAll("([\\w\\W]?)([\\w\\W]+)([\\w\\W])(@[\\w\\W]+)", "$1****$3$4");
return renderResult(Global.TRUE, "验证码已发送到“"+account+"”邮箱账号,请尽快查收!");
}
/**
* 发送短信验证码
*/
private String sendSmsValidCode(User user, String code, String title){
String account = user.getMobile();
try {
title = user.getUserName() + "" + user.getLoginCode() + ""+title+"验证码";
String content = "您好,您的验证码是:" + code +"(请勿透露给其他人)感谢您的使用。";
String receiveUserCode = "[CODE]"+user.getMobile();
MsgPushUtils.push(MsgPush.TYPE_SMS, title, content, null, null, receiveUserCode);
// String receiveUserCode = "[CODE]"+account;
// MsgPushUtils.push(MsgPush.TYPE_SMS, title, content, null, null, receiveUserCode);
SmsUtils.send(content, account);
} catch (Exception e) {
logger.error(title+"发送短信错误。", e);
return renderResult(Global.FALSE, "系统出现了点问题,错误信息:" + e.getMessage());
}
return renderResult(Global.TRUE, "短信已发送,请接收并填写验证码!");
account = account.replaceAll("(\\d{3})(\\d+)(\\d{3})","$1****$3");
return renderResult(Global.TRUE, "验证码已发送到“"+account+"”的手机号码,请尽快查收!");
}
}

View File

@@ -155,6 +155,13 @@ user:
# 多租户模式SAAS模式专业版
useCorpModel: false
# 自助账号服务
account:
# 注册用户
registerUser:
enabled: true
userTypes: 0, 1
# 任务调度(个人版+
job:
@@ -421,10 +428,10 @@ msg:
# 短信网关
sms:
beanName: smsSendService
url: http://localhost:80/msg/sendSms
data: account=demo&pswd=demo&product=
prefix: ~
suffix: 【JeeSite】
url: http://lehuo520.cn/a/sms/api
data: username=jeesite&password=jeesite.com
prefix: 【JeeSite】
suffix: ~
# 微信相关
weixin:

View File

@@ -0,0 +1,187 @@
<% layout('/layouts/default.html', {title: '忘记密码', libs: ['validate'], bodyClass: 'login-page'}){ %>
<% include('/include/upgrade.html'){} // 如果客户浏览器版本过低,则显示浏览器升级提示。 %>
<link rel="stylesheet" href="${ctxStatic}/icheck/1.0/square/blue.css?${_version}">
<link rel="stylesheet" href="${ctxStatic}/jquery-toastr/2.0/toastr.min.css?${_version}">
<link rel="stylesheet" href="${ctxStatic}/modules/sys/sysLogin.css?${_version}">
<% var productName = @Global.getConfig('productName'), productVersion = @Global.getConfig('productVersion'); %>
<div class="login-box" style="margin-top:4%">
<div class="login-logo" title="${productName}">
<a href="${ctxPath}/account/forgetPwd"><b>${productName}</b> <small>${productVersion}</small></a>
</div>
<div class="login-box-body">
<form id="forgetForm" action="${ctxPath}/account/forgetPwd" method="post">
<div class="form-group has-feedback">
<select id="fp_validType" name="op" class="form-control">
<option value="mobile">使用手机号码找回您的密码</option>
<option value="email">使用电子邮箱找回您的密码</option>
<option value="question">使用保密问题找回您的密码</option>
</select>
<script type="text/javascript">
$(document).ready(function(){
$('#fp_validType').change(function(){
var val = $(this).val(), action = '';
$('.fp-element').addClass('hide').removeClass('block');
$('.fp-'+val).addClass('block').removeClass('hide');
setTimeout(function(){
$('#fp_loginCode').focus();
}, 100);
if (val == 'mobile' || val == 'email'){
var txt = (val == 'mobile' ? '手机' : '邮箱')
$('#fpValidCode').attr('placeholder', txt+'验证码')
.attr('data-msg-required', '请填写'+txt+'验证码.');
$('#sendFpValidCode').val('获取'+txt+'验证码');
action = '${ctxPath}/account/savePwdByValidCode';
}else if(val == 'question'){
action = '${ctxPath}/account/savePwdByPwdQuestion';
}
$('#forgetForm').attr('action', action);
}).change();
});
</script>
</div>
<div class="form-group has-feedback">
<span class="fa fa-user form-control-feedback"></span>
<input type="text" id="fp_loginCode" name="loginCode" class="form-control required" data-msg-required="请填写登录账号." placeholder="登录账号" />
</div>
<div class="form-group has-feedback fp-element fp-mobile fp-email fp-question">
<#form:validcode id="fp_validCode" name="validCode" isRequired="true" isRemote="true" isLazy="false"/>
</div>
<div class="form-group has-feedback fp-element fp-mobile fp-email">
<div class="input-group">
<input type="text" id="fpValidCode" name="fpValidCode" class="form-control required"
data-msg-required="请填写手机验证码." placeholder="手机验证码" />
<span class="input-group-btn">
<input type="button" id="sendFpValidCode" value="获取手机验证码" class="btn btn-flat"/>
</span>
</div>
<script type="text/javascript">
var waitTime = 60;
function sendTime(o) {
if (waitTime == 0) {
o.removeAttribute("disabled");
o.value = "获取验证码";
waitTime = 60;
} else {
o.setAttribute("disabled", true);
o.value = "重新发送(" + waitTime + ")";
waitTime--;
setTimeout(function() {
sendTime(o)
}, 1000);
}
}
$('#sendFpValidCode').click(function() {
var $this = this;
js.ajaxSubmit('${ctxPath}/account/getFpValidCode', {
validType: $('#fp_validType').val(),
loginCode : $('#fp_loginCode').val(),
validCode : $('#fp_validCode').val()
}, function(data){
js.showMessage(data.message);
if (data.result == 'true'){
sendTime($this);
}
});
});
</script>
</div>
<div class="form-group has-feedback fp-element fp-question clearfix">
<input type="button" id="fp_getQuestion" value="获取保密问题" class="btn btn-default btn-block btn-flat"/>
<script type="text/javascript">
$('#fp_getQuestion').click(function() {
js.ajaxSubmit('${ctxPath}/account/getPwdQuestion', {
loginCode : $('#fp_loginCode').val(),
validCode : $('#fp_validCode').val()
}, function(data){
js.showMessage(data.message);
if (data.result == 'true'){
$('#fp_q1').text(data.pwdQuestion);
$('#fp_q2').text(data.pwdQuestion2);
$('#fp_q3').text(data.pwdQuestion3);
}
});
});
</script>
</div>
<div class="form-group has-feedback fp-element fp-question">
问题1<span id="fp_q1"></span>
</div>
<div class="form-group has-feedback fp-element fp-question">
<span class="fa fa-question-circle form-control-feedback"></span>
<input type="text" name="pwdQuestionAnswer" class="form-control required"
data-msg-required="请填写答案1." placeholder="答案1 " />
</div>
<div class="form-group has-feedback fp-element fp-question">
问题2<span id="fp_q2"></span>
</div>
<div class="form-group has-feedback fp-element fp-question">
<span class="fa fa-question-circle form-control-feedback"></span>
<input type="text" name="pwdQuestionAnswer2" class="form-control required"
data-msg-required="请填写答案2." placeholder="答案2" />
</div>
<div class="form-group has-feedback fp-element fp-question">
问题3<span id="fp_q3"></span>
</div>
<div class="form-group has-feedback fp-element fp-question">
<span class="fa fa-question-circle form-control-feedback"></span>
<input type="text" name="pwdQuestionAnswer3" class="form-control required"
data-msg-required="请填写答案3." placeholder="答案3" />
</div>
<div class="form-group has-feedback clearfix">
<strong>设置新密码:</strong>
</div>
<div class="form-group has-feedback">
<span class="fa fa-lock form-control-feedback"></span>
<input type="password" autocomplete="off" id="fp_password" name="password"
class="form-control required" data-msg-required="请填写新密码."
rangelength="3,50" data-msg-rangelength="新密码长度不能小于3并大于50个字符."
placeholder="新密码" />
</div>
<div class="form-group has-feedback">
<span class="fa fa-lock form-control-feedback"></span>
<input type="password" autocomplete="off" id="fp_confirmPassword" name="confirmPassword"
class="form-control required" data-msg-required="请填写确认新密码."
rangelength="3,50" data-msg-rangelength="新密码长度不能小于3并大于50个字符."
equalTo="#fp_password" data-msg-equalTo="新密码与确认新密码不同."
placeholder="确认新密码" />
</div>
<div class="row">
<div class="col-xs-6">
<button type="submit" class="btn btn-primary btn-block btn-flat"
id="btnSubmit">${text('提交')}</button>
</div>
<div class="col-xs-6">
<button type="button" class="btn btn-default btn-block btn-flat"
id="btnReset">${text('返回')}</button>
</div>
</div>
<div class="clearfix"></div>
</form>
</div>
<div class="login-copyright">
&copy; ${@DateUtils.getYear()} ${productName} - Powered By <a href="http://jeesite.com">JeeSite</a>.
</div>
</div>
<% } %>
<script>var secretKey = '${@Global.getConfig("shiro.loginSubmit.secretKey")}';</script>
<script src="${ctxStatic}/jquery-toastr/2.0/toastr.min.js?${_version}"></script>
<script src="${ctxStatic}/common/des.js?${_version}"></script>
<script>
$('#forgetForm').validate({
ignore: ":hidden",
submitHandler: function(form) {
js.ajaxSubmitForm($(form), function(data){
if (data.result == "true"){
alert(data.message);
location = "${ctx}/login";
}else{
js.showMessage(data.message);
$('#forgetForm').reset();
}
});
}
});
$('#btnReset').click(function(){
location = '${ctx}/login';
});
</script>