新增手机号短信验证码登录 user.loginByValidCode=true;调整

sysLogin、sysIndex、menuTree、forgetPwd、registerUser 的目录,方便覆写和理解;增加 自定义主题视图
的 readme.txt 帮助文件。
This commit is contained in:
thinkgem
2020-09-20 18:24:41 +08:00
parent bbb37722d6
commit faeb49ee98
23 changed files with 396 additions and 248 deletions

View File

@@ -101,7 +101,7 @@ public class FormAuthenticationFilter extends org.apache.shiro.web.filter.authc.
} }
// 登录用户名解密(解决登录用户名明文传输安全问题) // 登录用户名解密(解决登录用户名明文传输安全问题)
String secretKey = Global.getProperty("shiro.loginSubmit.secretKey"); String secretKey = Global.getProperty("shiro.loginSubmit.secretKey");
if (StringUtils.isNotBlank(secretKey)){ if (StringUtils.isNotBlank(username) && StringUtils.isNotBlank(secretKey)){
username = DesUtils.decode(username, secretKey); username = DesUtils.decode(username, secretKey);
if (StringUtils.isBlank(username)){ if (StringUtils.isBlank(username)){
logger.info("登录账号为空或解码错误."); logger.info("登录账号为空或解码错误.");
@@ -128,7 +128,7 @@ public class FormAuthenticationFilter extends org.apache.shiro.web.filter.authc.
} }
// 登录密码解密(解决登录密码明文传输安全问题) // 登录密码解密(解决登录密码明文传输安全问题)
String secretKey = Global.getProperty("shiro.loginSubmit.secretKey"); String secretKey = Global.getProperty("shiro.loginSubmit.secretKey");
if (StringUtils.isNotBlank(secretKey)){ if (StringUtils.isNotBlank(password) && StringUtils.isNotBlank(secretKey)){
password = DesUtils.decode(password, secretKey); password = DesUtils.decode(password, secretKey);
if (StringUtils.isBlank(password)){ if (StringUtils.isBlank(password)){
logger.info("登录密码为空或解码错误."); logger.info("登录密码为空或解码错误.");
@@ -167,7 +167,7 @@ public class FormAuthenticationFilter extends org.apache.shiro.web.filter.authc.
} }
// 登录用户名解密(解决登录用户名明文传输安全问题) // 登录用户名解密(解决登录用户名明文传输安全问题)
String secretKey = Global.getProperty("shiro.loginSubmit.secretKey"); String secretKey = Global.getProperty("shiro.loginSubmit.secretKey");
if (StringUtils.isNotBlank(secretKey)){ if (StringUtils.isNotBlank(captcha) && StringUtils.isNotBlank(secretKey)){
captcha = DesUtils.decode(captcha, secretKey); captcha = DesUtils.decode(captcha, secretKey);
} }
return captcha; return captcha;

View File

@@ -1,13 +1,15 @@
/** /**
* Copyright (c) 2013-Now http://jeesite.com All rights reserved. * Copyright (c) 2013-Now http://jeesite.com All rights reserved.
*/ */
package com.jeesite.modules.sys.web.user; package com.jeesite.modules.sys.web;
import java.util.Date; import java.util.Date;
import java.util.Map; import java.util.Map;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.shiro.authc.AuthenticationException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
@@ -23,6 +25,8 @@ import com.jeesite.common.lang.StringUtils;
import com.jeesite.common.msg.EmailUtils; import com.jeesite.common.msg.EmailUtils;
import com.jeesite.common.msg.SmsUtils; import com.jeesite.common.msg.SmsUtils;
import com.jeesite.common.service.ServiceException; import com.jeesite.common.service.ServiceException;
import com.jeesite.common.shiro.authc.FormToken;
import com.jeesite.common.shiro.filter.FormAuthenticationFilter;
import com.jeesite.common.web.BaseController; import com.jeesite.common.web.BaseController;
import com.jeesite.modules.sys.entity.User; import com.jeesite.modules.sys.entity.User;
import com.jeesite.modules.sys.service.UserService; import com.jeesite.modules.sys.service.UserService;
@@ -38,28 +42,77 @@ import io.swagger.annotations.ApiOperation;
/** /**
* 账号自助服务Controller * 账号自助服务Controller
* @author ThinkGem * @author ThinkGem
* @version 2017-12-7 * @version 2020-9-20
*/ */
@Controller @Controller
@RequestMapping(value = "/account") @RequestMapping(value = "/account")
@ConditionalOnProperty(name="web.core.enabled", havingValue="true", matchIfMissing=true) @ConditionalOnProperty(name="web.core.enabled", havingValue="true", matchIfMissing=true)
@Api(tags = "Account / 账号自助服务、找回密码、账号注册") @Api(tags = "Account / 账号服务:验证码登录、找回密码、账号注册")
public class AccountController extends BaseController{ public class AccountController extends BaseController{
@Autowired @Autowired
private UserService userService; private UserService userService;
/**
* 获取登录短信或邮件验证码
* @param validCode 图片验证码防止重复机器人
* @param validType 验证方式mobileemail
* @author ThinkGem
*/
@PostMapping(value = "getLoginValidCode")
@ResponseBody
@ApiOperation(value = "获取登录的短信或邮件验证码")
@ApiImplicitParams({
@ApiImplicitParam(name = "username", value = "登录账号", required = true, paramType="query", type="String"),
@ApiImplicitParam(name = "validCode", value = "图片验证码,防止重复机器人", required = true),
@ApiImplicitParam(name = "validType", value = "验证方式mobile、email", required = true),
})
public String getLoginValidCode(String username, String validCode, String validType, HttpServletRequest request) {
return getValidCode("login", username, validCode, validType, request, "登录验证码");
}
/**
* 根据短信或邮件验证码登录系统
* @author ThinkGem
*/
@PostMapping(value = "loginByValidCode")
@ResponseBody
@ApiOperation(value = "根据短信或邮件验证码登录系统")
@ApiImplicitParams({
@ApiImplicitParam(name = "username", value = "登录账号", required = true, paramType="query", type="String"),
@ApiImplicitParam(name = "loginValidCode", value = "手机或邮箱接受的验证码", required = true),
})
public String loginByValidCode(String username, String loginValidCode, HttpServletRequest request, HttpServletResponse response) {
if (!Global.getConfigToBoolean("user.loginByValidCode", "true")) {
return renderResult(Global.FALSE, "验证码登录未开启请设置user.loginByValidCode=true");
}
FormToken formToken = FormAuthenticationFilter.newToken(request, response);
String s = validValidCode("login", formToken.getUsername(), loginValidCode, request);
if (s != null) {
return s;
}
// 登录系统
try {
formToken.setInnerLogin(true); // 因为手机验证码已验证所以无需再进行验证密码
UserUtils.getSubject().login(formToken);
FormAuthenticationFilter.onLoginSuccess(request, response);
} catch (AuthenticationException e) {
FormAuthenticationFilter.onLoginFailure(e, request, response);
}
return null;
}
/** /**
* 忘记密码页面 * 忘记密码页面
*/ */
@GetMapping(value = "forgetPwd") @GetMapping(value = "forgetPwd")
@ApiOperation(value = "忘记密码页面") @ApiOperation(value = "忘记密码页面")
public String forgetPwd(Model model) { public String forgetPwd(Model model) {
return "modules/sys/account/forgetPwd"; return "modules/sys/forgetPwd";
} }
/** /**
* 获取找回密码短信邮件验证码 * 获取找回密码短信邮件验证码
* @param validCode 图片验证码防止重复机器人 * @param validCode 图片验证码防止重复机器人
* @param validType 验证方式mobileemail * @param validType 验证方式mobileemail
*/ */
@@ -72,41 +125,7 @@ public class AccountController extends BaseController{
@ApiImplicitParam(name = "validType", value = "验证方式mobile、email", required = true), @ApiImplicitParam(name = "validType", value = "验证方式mobile、email", required = true),
}) })
public String getFpValidCode(User user, String validCode, String validType, HttpServletRequest request) { public String getFpValidCode(User user, String validCode, String validType, HttpServletRequest request) {
// 校验图片验证码防止重复机器人 return getValidCode("fp", user.getLoginCode(), validCode, validType, request, "找回密码");
if (!ValidCodeUtils.validate(request, validCode)){
return renderResult(Global.FALSE, text("图片验证码不正确或已失效,请点击图片刷新!"));
}
if (!"mobile".equals(validType) && !"email".equals(validType)){
return renderResult(Global.FALSE, text("非法操作。"));
}
User u = UserUtils.getByLoginCode(user.getLoginCode());
if(u == null){
return renderResult(Global.FALSE, text("登录账号不正确!"));
}
if("mobile".equals(validType) && StringUtils.isBlank(u.getMobile())){
return renderResult(Global.FALSE, text("该账号未设置手机号码!"));
}else if("email".equals(validType) && StringUtils.isBlank(u.getEmail())){
return renderResult(Global.FALSE, text("该账号未设置邮件地址!"));
}
// 操作是否频繁验证 如果离上次获取验证码小于20秒则提示操作频繁
Date date = (Date)UserUtils.getCache("fpLastDate");
if (date != null && (System.currentTimeMillis()-date.getTime())/(1000L) < 20L){
return renderResult(Global.FALSE, text("您当前操作太频繁,请稍等一会再操作!"));
}else{
UserUtils.putCache("fpLastDate", new Date());
}
// 生成验证码并缓存
String fpValidCode = StringUtils.getRandomNum(6);
UserUtils.putCache("fpUserCode", u.getUserCode());
UserUtils.putCache("fpLoginCode", u.getLoginCode());
UserUtils.putCache("fpValidCode", fpValidCode);
// 发送邮箱或短信验证码
if("mobile".equals(validType)){
return sendSmsValidCode(u, fpValidCode, text("找回密码"));
}else if("email".equals(validType)){
return sendEmailValidCode(u, fpValidCode, text("找回密码"));
}
return null;
} }
/** /**
@@ -121,39 +140,93 @@ public class AccountController extends BaseController{
@ApiImplicitParam(name = "password", value = "新密码", required = true, paramType="query", type="String"), @ApiImplicitParam(name = "password", value = "新密码", required = true, paramType="query", type="String"),
}) })
public String savePwdByValidCode(User user, String fpValidCode, HttpServletRequest request) { public String savePwdByValidCode(User user, String fpValidCode, HttpServletRequest request) {
String userCode = (String)UserUtils.getCache("fpUserCode"); String s = validValidCode("fp", user.getLoginCode(), fpValidCode, request);
String loginCode = (String)UserUtils.getCache("fpLoginCode"); if (s != null) {
String validCode = (String)UserUtils.getCache("fpValidCode"); return s;
Date date = (Date)UserUtils.getCache("fpLastDate"); }
// 更新为新密码
try{
userService.updatePassword(user.getUserCode(), user.getPassword());
}catch(ServiceException se){
return renderResult(Global.FALSE, se.getMessage());
}
return renderResult(Global.TRUE, text("恭喜你,您的账号 {0} 密码修改成功!", user.getUserCode()));
}
/**
* 获取验证码
* @author ThinkGem
*/
private String getValidCode(String type, String loginCode, String validCode, String validType, HttpServletRequest request, String msgTitle) {
// 校验图片验证码防止重复机器人
if (!ValidCodeUtils.validate(request, validCode)){
return renderResult(Global.FALSE, text("图片验证码不正确或已失效,请点击图片刷新!"));
}
if (!"mobile".equals(validType) && !"email".equals(validType)){
return renderResult(Global.FALSE, text("非法操作。"));
}
User u = UserUtils.getByLoginCode(loginCode);
if(u == null){
return renderResult(Global.FALSE, text("登录账号不正确!"));
}
if("mobile".equals(validType) && StringUtils.isBlank(u.getMobile())){
return renderResult(Global.FALSE, text("该账号未设置手机号码!"));
}else if("email".equals(validType) && StringUtils.isBlank(u.getEmail())){
return renderResult(Global.FALSE, text("该账号未设置邮件地址!"));
}
// 操作是否频繁验证 如果离上次获取验证码小于20秒则提示操作频繁
Date date = (Date)UserUtils.getCache(type + "LastDate");
if (date != null && (System.currentTimeMillis()-date.getTime())/(1000L) < 20L){
return renderResult(Global.FALSE, text("您当前操作太频繁,请稍等一会再操作!"));
}else{
UserUtils.putCache(type + "LastDate", new Date());
}
// 生成验证码并缓存
String loginValidCode = StringUtils.getRandomNum(6);
UserUtils.putCache(type + "UserCode", u.getUserCode());
UserUtils.putCache(type + "LoginCode", u.getLoginCode());
UserUtils.putCache(type + "ValidCode", loginValidCode);
// 发送邮箱或短信验证码
if("mobile".equals(validType)){
return sendSmsValidCode(u, loginValidCode, text(msgTitle));
}else if("email".equals(validType)){
return sendEmailValidCode(u, loginValidCode, text(msgTitle));
}
return null;
}
/**
* 验证验证码
* @author ThinkGem
*/
private String validValidCode(String type, String loginCode, String loginValidCode, HttpServletRequest request) {
String userCode = (String)UserUtils.getCache(type + "UserCode");
String loginCode2 = (String)UserUtils.getCache(type + "LoginCode");
String validCode = (String)UserUtils.getCache(type + "ValidCode");
Date date = (Date)UserUtils.getCache(type + "LastDate");
// 一同验证保存的用户名和验证码是否正确如果只校验验证码不验证用户名则会有获取验证码后修改用户名的漏洞 // 一同验证保存的用户名和验证码是否正确如果只校验验证码不验证用户名则会有获取验证码后修改用户名的漏洞
if (!(userCode != null && loginCode != null && loginCode.equals(user.getLoginCode()))){ if (!(userCode != null && loginCode != null && loginCode.equals(loginCode2))){
return renderResult(Global.FALSE, text("请重新获取验证码!")); return renderResult(Global.FALSE, text("请重新获取验证码!"));
} }
// 清理验证码验证码只允许使用一次
UserUtils.removeCache("fpUserCode");
UserUtils.removeCache("fpLoginCode");
UserUtils.removeCache("fpValidCode");
UserUtils.removeCache("fpLastDate");
// 验证码是否超时 // 验证码是否超时
boolean isTimeout = true; boolean isTimeout = true;
String validTime = Global.getConfig("sys.account.validCodeTimeout", "10"); //验证码有效时间单位分钟0表示不限制默认值10 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))){ if("0".equals(validTime) || (date != null && (System.currentTimeMillis()-date.getTime())/(1000L) < 60*Long.parseLong(validTime))){
isTimeout = false; isTimeout = false;
} }
if (!(validCode != null && validCode.equals(fpValidCode) && !isTimeout)){ if (!(validCode != null && validCode.equals(loginValidCode) && !isTimeout)){
return renderResult(Global.FALSE, text("验证码不正确或已失效,请重新获取验证码!")); return renderResult(Global.FALSE, text("验证码不正确或已失效,请重新获取验证码!"));
} }
// 更新为新密码 // 验证成功后清理缓存
try{ UserUtils.removeCache(type + "UserCode");
userService.updatePassword(userCode, user.getPassword()); UserUtils.removeCache(type + "LoginCode");
}catch(ServiceException se){ UserUtils.removeCache(type + "ValidCode");
return renderResult(Global.FALSE, se.getMessage()); UserUtils.removeCache(type + "LastDate");
}
return renderResult(Global.TRUE, text("恭喜你,您的账号 {0} 密码修改成功!", loginCode)); return null;
} }
/** /**
@@ -225,10 +298,6 @@ public class AccountController extends BaseController{
return renderResult(Global.FALSE, text("请重新获取保密问题!")); return renderResult(Global.FALSE, text("请重新获取保密问题!"));
} }
// 清理保密问题每次获取只允许使用一次
UserUtils.removeCache("fpUserCode");
UserUtils.removeCache("fpLoginCode");
// 验证三个密保问题是否正确 // 验证三个密保问题是否正确
User u = UserUtils.getByLoginCode(user.getLoginCode()); User u = UserUtils.getByLoginCode(user.getLoginCode());
if (!(u != null && loginCode.equals(user.getLoginCode()) if (!(u != null && loginCode.equals(user.getLoginCode())
@@ -244,6 +313,11 @@ public class AccountController extends BaseController{
}catch(ServiceException se){ }catch(ServiceException se){
return renderResult(Global.FALSE, se.getMessage()); return renderResult(Global.FALSE, se.getMessage());
} }
// 更新密码后清理缓存
UserUtils.removeCache("fpUserCode");
UserUtils.removeCache("fpLoginCode");
return renderResult(Global.TRUE, text("验证通过")); return renderResult(Global.TRUE, text("验证通过"));
} }
@@ -254,7 +328,7 @@ public class AccountController extends BaseController{
@GetMapping(value = "registerUser") @GetMapping(value = "registerUser")
@ApiOperation(value = "用户注册页面") @ApiOperation(value = "用户注册页面")
public String registerUser(User user, HttpServletRequest request) { public String registerUser(User user, HttpServletRequest request) {
return "modules/sys/account/registerUser"; return "modules/sys/registerUser";
} }
/** /**
@@ -399,7 +473,7 @@ public class AccountController extends BaseController{
u.setStatus(User.STATUS_AUDIT); // 待审核状态 u.setStatus(User.STATUS_AUDIT); // 待审核状态
userService.save(u); userService.save(u);
// 验证成功后清理验证码验证码只允许使用一次 // 注册用户后清理缓存
UserUtils.removeCache("regCorpCode"); UserUtils.removeCache("regCorpCode");
UserUtils.removeCache("regCorpName"); UserUtils.removeCache("regCorpName");
UserUtils.removeCache("regUserType"); UserUtils.removeCache("regUserType");

View File

@@ -131,6 +131,9 @@ user:
# 登录账号是否租户内唯一,否则全局唯一 # 登录账号是否租户内唯一,否则全局唯一
loginCodeCorpUnique: false loginCodeCorpUnique: false
# 是否启用验证码登录(手机、邮箱)
loginByValidCode: true
# 用户类型配置信息employee员工member会员btype往来单位persion个人expert专家...JSON 格式说明如下: # 用户类型配置信息employee员工member会员btype往来单位persion个人expert专家...JSON 格式说明如下:
# {"用户类型":{"beanName":"Service或Dao的Bean名称","loginView":"登录页面视图","indexView":"主框架页面视图,支持 redirect: 前缀"}} # {"用户类型":{"beanName":"Service或Dao的Bean名称","loginView":"登录页面视图","indexView":"主框架页面视图,支持 redirect: 前缀"}}

View File

@@ -69,6 +69,10 @@
登录=Login 登录=Login
欢迎回来=Welcome 欢迎回来=Welcome
账号登录=Login
手机登录=Mobile
没有账号=No account
点击注册=Register
请填写登录账号.=Please enter login account. 请填写登录账号.=Please enter login account.
登录账号=Login account 登录账号=Login account
@@ -77,6 +81,11 @@
请填写登录密码.=Please enter login password. 请填写登录密码.=Please enter login password.
登录密码=Login password 登录密码=Login password
登录租户=Login tenant
请填写手机验证码.=Please enter mobile valid code
手机验证码=Mobile valid code
获取手机验证码=Get valid code
公共场所慎用,下次不需要再填写帐号=Use caution in public places. 公共场所慎用,下次不需要再填写帐号=Use caution in public places.
记住账号=Remember account 记住账号=Remember account
公共场所慎用,下次不需要再填写帐号和密码=Use caution in public places. 公共场所慎用,下次不需要再填写帐号和密码=Use caution in public places.

View File

@@ -70,6 +70,10 @@
登录=ログイン 登录=ログイン
欢迎回来=おかえり 欢迎回来=おかえり
账号登录=ログイン
手机登录=電話登録
没有账号=アカウントなし
点击注册=きにゅう
请填写登录账号.=ログインIDを入力してください. 请填写登录账号.=ログインIDを入力してください.
登录账号=ログインアカウント 登录账号=ログインアカウント
@@ -78,6 +82,11 @@
请填写登录密码.=パスワードを入力してください. 请填写登录密码.=パスワードを入力してください.
登录密码=パスワード 登录密码=パスワード
登录租户=テナント登録
请填写手机验证码.=携帯電話の検証コード
手机验证码=電話の検証コード
获取手机验证码=コードを取得
公共场所慎用,下次不需要再填写帐号=次回からログインIDの入力が不要にします. 公共场所慎用,下次不需要再填写帐号=次回からログインIDの入力が不要にします.
记住账号=ログインID記憶 记住账号=ログインID記憶
公共场所慎用,下次不需要再填写帐号和密码=次回からパスワードの入力が不要にします. 公共场所慎用,下次不需要再填写帐号和密码=次回からパスワードの入力が不要にします.
@@ -90,6 +99,9 @@
注册账号=アカウント作成 注册账号=アカウント作成
忘记密码=パスワードを忘れた 忘记密码=パスワードを忘れた
注册账号=アカウントを登録する
忘记密码=パスワードを忘れる
# =========== 主框架页 =========== # =========== 主框架页 ===========
仪表盘=計器盤 仪表盘=計器盤

View File

@@ -0,0 +1,95 @@
/*!
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
*
* @author ThinkGem
* @version 2019-1-6
*/
$(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();
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);
$('#fpValidCode').focus();
}else{
$('#fp_validCodeImg').click();
}
});
});
$('#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);
}
});
});
$('#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';
});
});

View File

@@ -0,0 +1,87 @@
/*!
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
*
* @author ThinkGem
* @version 2019-1-6
*/
$(function(){
$('#reg_validType').change(function(){
var val = $(this).val(), action = '';
$('.reg-element').addClass('hide').removeClass('block');
$('.reg-'+val).addClass('block').removeClass('hide');
setTimeout(function(){
$('#reg_loginCode').focus();
}, 100);
if (val == 'mobile' || val == 'email'){
var txt = (val == 'mobile' ? '手机' : '邮箱')
$('#regValidCode').attr('placeholder', txt+'验证码')
.attr('data-msg-required', '请填写'+txt+'验证码.');
$('#sendRegValidCode').val('获取'+txt+'验证码');
action = ctxPath + '/account/saveRegByValidCode';
}else if(val == 'question'){
action = ctxPath + '/account/savePwdByPwdQuestion';
}
$('#registerForm').attr('action', action);
}).change();
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);
}
}
$('#sendRegValidCode').click(function() {
var $this = this;
js.ajaxSubmit(ctxPath + '/account/getRegValidCode', {
validType: $('#reg_validType').val(),
corpCode_ : $('#reg_corpCode').val(),
corpName_ : $('#reg_corpName').val(),
loginCode : $('#reg_loginCode').val(),
userName : $('#reg_userName').val(),
email : $('#reg_email').val(),
mobile : $('#reg_mobile').val(),
userType: $('#reg_userType').val(),
validCode : $('#reg_validCode').val()
}, function(data){
js.showMessage(data.message);
if (data.result == 'true'){
sendTime($this);
$('#regValidCode').focus();
}else{
$('#reg_validCodeImg').click();
}
});
});
$('#registerForm').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);
$('#registerForm').reset();
}
});
}
});
$('#btnReset').click(function(){
location = ctx + '/login';
});
});

View File

@@ -16,28 +16,6 @@
<option value="email">使用电子邮箱找回您的密码</option> <option value="email">使用电子邮箱找回您的密码</option>
<option value="question">使用保密问题找回您的密码</option> <option value="question">使用保密问题找回您的密码</option>
</select> </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>
<div class="form-group has-feedback"> <div class="form-group has-feedback">
<span class="fa fa-user form-control-feedback"></span> <span class="fa fa-user form-control-feedback"></span>
@@ -54,54 +32,9 @@
<input type="button" id="sendFpValidCode" value="获取手机验证码" class="btn btn-flat"/> <input type="button" id="sendFpValidCode" value="获取手机验证码" class="btn btn-flat"/>
</span> </span>
</div> </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>
<div class="form-group has-feedback fp-element fp-question clearfix"> <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"/> <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>
<div class="form-group has-feedback fp-element fp-question"> <div class="form-group has-feedback fp-element fp-question">
问题1<span id="fp_q1"></span> 问题1<span id="fp_q1"></span>
@@ -167,22 +100,4 @@
<script>var secretKey = '${@Global.getConfig("shiro.loginSubmit.secretKey")}';</script> <script>var secretKey = '${@Global.getConfig("shiro.loginSubmit.secretKey")}';</script>
<script src="${ctxStatic}/jquery-toastr/2.1/toastr.min.js?${_version}"></script> <script src="${ctxStatic}/jquery-toastr/2.1/toastr.min.js?${_version}"></script>
<script src="${ctxStatic}/common/des.js?${_version}"></script> <script src="${ctxStatic}/common/des.js?${_version}"></script>
<script> <script src="${ctxStatic}/modules/sys/forgetPwd.js?${_version}"></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>

View File

@@ -15,28 +15,6 @@
<option value="email">使用电子邮箱注册账号</option> <option value="email">使用电子邮箱注册账号</option>
<option value="mobile">使用手机号码注册账号</option> <option value="mobile">使用手机号码注册账号</option>
</select> </select>
<script type="text/javascript">
$(document).ready(function(){
$('#reg_validType').change(function(){
var val = $(this).val(), action = '';
$('.reg-element').addClass('hide').removeClass('block');
$('.reg-'+val).addClass('block').removeClass('hide');
setTimeout(function(){
$('#reg_loginCode').focus();
}, 100);
if (val == 'mobile' || val == 'email'){
var txt = (val == 'mobile' ? '手机' : '邮箱')
$('#regValidCode').attr('placeholder', txt+'验证码')
.attr('data-msg-required', '请填写'+txt+'验证码.');
$('#sendRegValidCode').val('获取'+txt+'验证码');
action = '${ctxPath}/account/saveRegByValidCode';
}else if(val == 'question'){
action = '${ctxPath}/account/savePwdByPwdQuestion';
}
$('#registerForm').attr('action', action);
}).change();
});
</script>
</div> </div>
<% if(@Global.isUseCorpModel()){ %> <% if(@Global.isUseCorpModel()){ %>
<div class="form-group has-feedback"> <div class="form-group has-feedback">
@@ -75,42 +53,6 @@
<input type="button" id="sendRegValidCode" value="获取手机验证码" class="btn btn-flat"/> <input type="button" id="sendRegValidCode" value="获取手机验证码" class="btn btn-flat"/>
</span> </span>
</div> </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);
}
}
$('#sendRegValidCode').click(function() {
var $this = this;
js.ajaxSubmit('${ctxPath}/account/getRegValidCode', {
validType: $('#reg_validType').val(),
corpCode_ : $('#reg_corpCode').val(),
corpName_ : $('#reg_corpName').val(),
loginCode : $('#reg_loginCode').val(),
userName : $('#reg_userName').val(),
email : $('#reg_email').val(),
mobile : $('#reg_mobile').val(),
userType: $('#reg_userType').val(),
validCode : $('#reg_validCode').val()
}, function(data){
js.showMessage(data.message);
if (data.result == 'true'){
sendTime($this);
}
});
});
</script>
</div> </div>
<div class="form-group has-feedback"> <div class="form-group has-feedback">
<span class="fa fa-lock form-control-feedback"></span> <span class="fa fa-lock form-control-feedback"></span>
@@ -152,22 +94,4 @@
</div> </div>
<% } %> <% } %>
<script src="${ctxStatic}/jquery-toastr/2.1/toastr.min.js?${_version}"></script> <script src="${ctxStatic}/jquery-toastr/2.1/toastr.min.js?${_version}"></script>
<script> <script src="${ctxStatic}/modules/sys/registerUser.js?${_version}"></script>
$('#registerForm').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);
$('#registerForm').reset();
}
});
}
});
$('#btnReset').click(function(){
location = '${ctx}/login';
});
</script>

View File

@@ -9,10 +9,10 @@ var bodyClass = 'fixed noscroll2 sidebar-mini ' + sidebarCollapse;
<link rel="stylesheet" href="${ctxStatic}/jquery-toastr/2.1/toastr.min.css?${_version}"> <link rel="stylesheet" href="${ctxStatic}/jquery-toastr/2.1/toastr.min.css?${_version}">
<link rel="stylesheet" href="${ctxStatic}/modules/sys/sysIndex.css?${_version}"> <link rel="stylesheet" href="${ctxStatic}/modules/sys/sysIndex.css?${_version}">
<header class="main-header"> <header class="main-header">
<% include('sysIndex/topMenu.html'){} %> <% include('/include/sysIndex/topMenu.html'){} %>
</header> </header>
<aside class="main-sidebar"> <aside class="main-sidebar">
<% include('sysIndex/leftMenu.html'){} %> <% include('/include/sysIndex/leftMenu.html'){} %>
</aside> </aside>
<div class="content-wrapper"> <div class="content-wrapper">
<div id="tabpanel"></div> <div id="tabpanel"></div>

View File

@@ -9,27 +9,23 @@
<small>${@Global.getConfig('productVersion')}</small></a> <small>${@Global.getConfig('productVersion')}</small></a>
</div> </div>
<div class="login-box-body"> <div class="login-box-body">
<#form:form id="loginForm" model="${user!}" action="${ctx}/login" method="post"> <ul id="loginTab" class="nav nav-tabs ${@Global.getConfigToBoolean('user.loginByValidCode','true')?'':'hide'}">
<h4 class="login-box-msg">${isNotBlank(message!)?message:text('欢迎回来')+''}</h4> <li class="active"><a href="#tab-1" data-toggle="tab" action="${ctx}/login">${text('账号登录')}</a></li>
<!-- <ul class="nav nav-tabs mb20"> <li><a href="#tab-2" data-toggle="tab" action="${ctxPath}/account/loginByValidCode">${text('手机登录')}</a></li>
<li class="active"><a href="#tab-1" data-toggle="tab">账号登录</a></li> </ul>
<li><a href="#tab-2" data-toggle="tab">邮箱登录</a></li> <#form:form id="loginForm" model="${user!}" action="${ctx}/login" method="post" class="tab-content">
</ul> <% if(isNotBlank(message!)){ %>
<div class="tab-content"> <h5 class="login-box-msg text-red">${message}</h5>
<div class="tab-pane active" id="tab-1"></div> <% } %>
<div class="tab-pane" id="tab-2"></div>
</div> -->
<div class="form-group has-feedback"> <div class="form-group has-feedback">
<span class="icon-user form-control-feedback" title="${text('登录账号')}"></span> <span class="icon-user form-control-feedback" title="${text('登录账号')}"></span>
<#form:input type="text" name="username" class="form-control required" <#form:input type="text" name="username" class="form-control required"
data-msg-required="${text('请填写登录账号.')}" placeholder="${text('登录账号')}" data-msg-required="${text('请填写登录账号.')}" placeholder="${text('登录账号')}"
value="${cookie('rememberUserCode')}"/> value="${cookie('rememberUserCode')}"/>
</div> </div>
<div class="form-group has-feedback"> <div class="form-group has-feedback tab-pane tab-1 active">
<span class="icon-lock form-control-feedback" <span class="icon-lock form-control-feedback" title="${text('登录密码,鼠标按下显示密码')}"
title="${text('登录密码,鼠标按下显示密码')}" onmousedown="$('#password').attr('type','text')" onmouseup="$('#password').attr('type','password')"
onmousedown="$('#password').attr('type','text')"
onmouseup="$('#password').attr('type','password')"
onmouseenter="$(this).removeClass('icon-lock').addClass('icon-eye')" onmouseenter="$(this).removeClass('icon-lock').addClass('icon-eye')"
onmouseout="$(this).removeClass('icon-eye').addClass('icon-lock')"></span> onmouseout="$(this).removeClass('icon-eye').addClass('icon-lock')"></span>
<#form:input type="password" name="password" class="form-control required" <#form:input type="password" name="password" class="form-control required"
@@ -44,6 +40,15 @@
<div class="form-group has-feedback" id="isValidCodeLogin" style="display:${isValidCodeLogin?'blank':'none'}"> <div class="form-group has-feedback" id="isValidCodeLogin" style="display:${isValidCodeLogin?'blank':'none'}">
<#form:validcode name="validCode" isRequired="true" isRemote="true" isLazy="${!isValidCodeLogin}"/> <#form:validcode name="validCode" isRequired="true" isRemote="true" isLazy="${!isValidCodeLogin}"/>
</div> </div>
<div class="form-group has-feedback tab-pane tab-2">
<div class="input-group">
<input type="text" id="loginValidCode" name="loginValidCode" class="form-control required"
data-msg-required="${text('请填写手机验证码.')}" placeholder="${text('手机验证码')}" />
<span class="input-group-btn">
<input type="button" id="sendLoginValidCode" value="${text('获取手机验证码')}" class="btn btn-flat"/>
</span>
</div>
</div>
<div class="form-group"> <div class="form-group">
<div class="mt5 icheck"> <div class="mt5 icheck">
<label title="${text('公共场所慎用,下次不需要再填写帐号')}"><input type="checkbox" <label title="${text('公共场所慎用,下次不需要再填写帐号')}"><input type="checkbox"

View File

@@ -0,0 +1,21 @@
======= 说明:
当前目录:/src/main/resources/views/themes/default/modules/
是【default】主题的模块视图目录该目录下的文件会自动覆盖
/src/main/resources/views/modules/ 下的默认视图文件。
======= 举例:
拷贝文件:/src/main/resources/views/modules/sys/sysLogin.html
复制到:/src/main/resources/views/themes/default/modules/sys/sysLogin.html
这样写,默认的视图文件就会被替换为你自定义的视图文件,从而实现简单的自定义视图。
======= 更多资料:
http://jeesite.com/docs/custom-views/

View File

@@ -246,6 +246,9 @@ logging:
# # 登录账号是否租户内唯一,否则全局唯一 # # 登录账号是否租户内唯一,否则全局唯一
# loginCodeCorpUnique: false # loginCodeCorpUnique: false
# #
# # 是否启用验证码登录(手机、邮箱)
# loginByValidCode: true
#
# # 用户类型配置信息employee员工member会员btype往来单位persion个人expert专家...JSON 格式说明如下: # # 用户类型配置信息employee员工member会员btype往来单位persion个人expert专家...JSON 格式说明如下:
# # {"用户类型":{"beanName":"Service或Dao的Bean名称","loginView":"登录页面视图","indexView":"主框架页面视图,支持 redirect: 前缀"}} # # {"用户类型":{"beanName":"Service或Dao的Bean名称","loginView":"登录页面视图","indexView":"主框架页面视图,支持 redirect: 前缀"}}
# userTypeMap: > # userTypeMap: >