一、前言
任何一个管理信息系统都会有登录功能。我们简单可以通过用户名加密码加验证码进行登录。但是就是一个这样的简单功能却涉及的要求很多。
比如对账号的要求,对密码复杂度的要求,对登录时长的要求,对密码有效期的要求,对登录用户登录日志的记录,登录用户的权限等等。非常非常多。本文就介绍简单的登录功能。
二、登录功能数据库表设计
-- Drop table
-- DROP TABLE public.t_user;
CREATE TABLE public.t_user (
id varchar(32) NOT NULL,
user_name varchar(255) NOT NULL,
login_name varchar(255) NOT NULL,
"password" varchar(255) NOT NULL,
create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
last_login_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted int4 NOT NULL DEFAULT 0,
pwd_val_time timestamp NOT NULL,
belong_code varchar(8) NULL,
belong_name varchar(255) NULL,
data_type varchar(255) NULL,
phone varchar(255) NULL,
CONSTRAINT t_user_pkey PRIMARY KEY (id)
);
三、注册用户
注册用户分以下几步
1、判断新增的用户是否存在,如果存在不允许注册同账号的用户
2、判断用户名和密码是否是空
3、校验密码复杂度是否复合要求
4、对密码进行加密处理
5、报错新注册管理员账号
/**
* 保存后台管理用户信息
* @param tUser
* @return
*/
@ApiOperation(value = "保存后台管理用户信息", notes = "保存后台管理用户信息")
@PostMapping("save")
public ResponseData<Boolean> save(@RequestBody TUser tUser) {
//先校验用户是否存在
if(tUserService.userIsExists(tUser)) {
log.error(TUserConstant.SAVE_USER_EXISTS);
return ResponseData.error(TUserConstant.SAVE_USER_EXISTS);
}
if(StringUtils.isEmpty(tUser.getPassword()) || StringUtils.isEmpty(tUser.getLoginName())) {
log.error(TUserConstant.SAVE_USER_EMP);
return ResponseData.error(TUserConstant.SAVE_USER_EMP);
}
//校验密码复杂度
Boolean checkPWD = tUserService.checkPWD(tUser.getPassword());
if (!checkPWD) {
log.error(TUserConstant.PWD_CHECK_ERROR);
return ResponseData.error(TUserConstant.PWD_CHECK_ERROR);
}
tUser.setPassword(Des3Utils.get3DESEncryptECB(tUser.getPassword(), AES_KEY));
tUser.setPwdValTime(new Date());
boolean res = tUserService.save(tUser);
if(res) {
return ResponseData.success(true);
}else {
log.error(TUserConstant.SAVE_USER_ERROR);
return ResponseData.error(TUserConstant.SAVE_USER_ERROR);
}
}
密码复杂度函数:
/**
* 校验复杂度
*/
public Boolean checkPWD(String PWD) {
// 规定的正则表达式
// (?![a-zA-Z]+$) 表示 字符串不能完全由大小写字母组成
// (?![A-Z0-9]+$) 表示 字符串不能完全由大写字母和数字组成
// (?![A-Z\W_]+$) 表示 字符串不能完全由大写字母和特殊字符组成
// (?![a-z0-9]+$) 表示 字符串不能完全由小写字母和数字组成
// (?![a-z\W_]+$) 表示 字符串不能完全由小写字母和特殊字符组成
// (?![0-9\W_]+$) 表示 字符串不能完全由数字和特殊字符组成
// [a-zA-Z0-9\W_]{8,} 表示 字符串应该匹配大小写字母、数字和特殊字符,至少匹配8次
String regex = "^(?![a-zA-Z]+$)(?![A-Z0-9]+$)(?![a-z0-9]+$)(?![A-Z\\W_]+$)(?![a-z\\W_]+$)(?![0-9\\W_]+$)[a-zA-Z0-9\\W_]{8,}$";
return ReUtil.isMatch(regex, PWD);
}
四、用户登录
1、限制频繁刷登录操作
2、校验码校验
3、校验用户和密码是否正确
4、判断密码是否过期是否需要重置密码
5、将登录信息写到缓存,设置不操作2小时过期
6、获取登录用户的权限
代码示例:
/**
* 后台管理用户登录
* @param tUser
* @return
*/
@ApiOperation(value = "后台管理用户登录", notes = "后台管理用户登录")
@PostMapping("login")
public ResponseData<String> login(@RequestBody Map<String,Object> map,HttpServletRequest request,HttpServletResponse response) {
//防刷登录限制,一分钟内刷连续30次,限制访问
if (!tUserService.isLimit(request,30)) {
ResponseData<Object> responseData = ResponseData.error(ResponseCode.IS_LIMIT_ACC.getCode(),
ResponseCode.IS_LIMIT_ACC.getMessage());
tUserService.resNoPermiss(response, responseData);
return ResponseData.error(TUserConstant.IS_LIMIT_ACC);
}
String verCode = "";
if(map.containsKey("verCode")) {
verCode = map.get("verCode").toString();
}else {
return ResponseData.error(TUserConstant.VERCODE_EMP);
}
log.info("前端传入验证码:"+verCode);
/**登录验证码(暂时屏蔽)**/
if (!CaptchaUtil.ver(verCode, request)) {
log.info("验证码验证失败");
return ResponseData.error(TUserConstant.VERCODE_ERROE);
}
TUser tUser = new TUser();
if(map.containsKey("loginName")) {
tUser.setLoginName(map.get("loginName").toString());
}
if(map.containsKey("password")) {
tUser.setPassword(map.get("password").toString());
}
String pwdError = redisUtils.get(RedisKeys.getLoginPwdError(tUser.getLoginName()));
if(!StringUtils.isEmpty(pwdError)) {
if(Integer.parseInt(pwdError) > 4) {
return ResponseData.error(TUserConstant.LOGIN_PERMISS_ERROR);
}
}
log.info("获取到后台登录的密码为:"+ tUser.getPassword());
log.info("获取到后台登录的加密后的密码为:"+ Des3Utils.get3DESEncryptECB(tUser.getPassword(),AES_KEY));
tUser.setPassword(Des3Utils.get3DESEncryptECB(tUser.getPassword(),AES_KEY));
TUser tUserRes = tUserService.getByLoginName(tUser);
if(null != tUserRes) {
//判断密码更新时间是否超过有效期
if (((new Date().getTime() - tUserRes.getPwdValTime().getTime())/(1000*3600*24)) > pwdValTime) {
return ResponseData.error(-1,TUserConstant.PWD_VAL_TIME_ERROR);
}
//更新最后登录时间
tUserService.updateUserLastLoginTime(tUser.getLoginName(), DateTimeUtils.getDateStr());
// 添加session
HttpSession session = request.getSession();
try {
session.setAttribute(Constant.CTG_ADMIN_USER_NAME,AesUtil.aesEncrypt(tUserRes.getUserName(),AES_KEY));
session.setAttribute(Constant.CTG_ADMIN_USER_ID,AesUtil.aesEncrypt(tUserRes.getLoginName(),AES_KEY));
} catch (Exception e) {
e.printStackTrace();
}
//两个小时的有效期
session.setMaxInactiveInterval(7200);
//获取最新权限缓存
tMenuService.getMenuUrlByloginName(tUserRes.getLoginName());
return ResponseData.success(tUserRes.getUserName());
}else {
log.error(TUserConstant.LOGIN_PSW_ERROR);
return ResponseData.error(TUserConstant.LOGIN_PSW_ERROR);
}
}
五、单点登录
作为登录功能因为所有的系统都需要登录,往往我们就可以把登录功能封装处理供所有的系统使用。我们可以把它作为一个登录系统或者认证中心,也可以作为单点登录。
单点登录的原理:
关于单点登录,我会上传一份源码到资源。开箱即用。