一、前言
任何一个管理信息系统都会有登录功能。我们简单可以通过用户名加密码加验证码进行登录。但是就是一个这样的简单功能却涉及的要求很多。
比如对账号的要求,对密码复杂度的要求,对登录时长的要求,对密码有效期的要求,对登录用户登录日志的记录,登录用户的权限等等。非常非常多。本文就介绍简单的登录功能。
二、登录功能数据库表设计
-- 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);
}
}
五、单点登录
作为登录功能因为所有的系统都需要登录,往往我们就可以把登录功能封装处理供所有的系统使用。我们可以把它作为一个登录系统或者认证中心,也可以作为单点登录。
单点登录的原理:
关于单点登录,我会上传一份源码到资源。开箱即用。
Java系统登录功能设计
一、需求分析
系统登录功能是任何应用程序的重要组成部分,它为用户提供了一个安全的方式来验证其身份并保护系统资源。在Java应用程序中,设计一个高效且安全的登录系统需要考虑以下几个关键需求:
- 用户注册与登录:允许用户注册账号并登录系统。
- 密码加密存储:使用安全的方法存储用户密码,如使用哈希加盐(Hashing with Salting)。
- 防止暴力破解:增加对连续登录失败的限制,以防止暴力破解攻击。
- 会话管理:为用户创建一个安全的会话,并在用户注销或会话过期后终止会话。
- 支持多用户类型:可能需要为不同用户类型(如管理员、普通用户)提供不同的权限级别。
- 验证码功能:为增强安全性,可以考虑使用验证码功能来防止机器人攻击。
- 记录与审计:记录所有登录活动,以便进行审计和监控。
- 国际化与本地化:支持多种语言,以满足不同地区用户的需求。
- 易用性与用户体验:设计简单、直观的界面和流程,方便用户快速完成登录操作。
- 支持社交登录:集成第三方认证服务,允许用户使用社交媒体账号登录。
- 安全性更新与通知:当系统有安全更新或风险时,能够及时通知用户。
二、开发实现
实现Java系统登录功能需要多个组件和技术的配合,以下是关键步骤和考虑因素:
- 设计数据库结构:为存储用户信息(如用户名、密码、邮箱等)设计数据库表结构。
- 选择认证方式:根据需求选择合适的认证方式,如基本身份验证、摘要认证等。
- 密码加密存储:使用如bcrypt、scrypt等算法对用户密码进行哈希加密存储,并加盐增加安全性。
- 实现注册与登录功能:创建注册和登录页面,处理表单提交,验证用户输入并与数据库进行交互。
- 处理会话管理:使用Java的会话管理机制(如HttpSession)来跟踪用户的登录状态和会话信息。
- 实现权限控制:根据用户类型或角色,使用Java的访问控制机制(如Java Security)来限制对特定资源的访问。
- 记录与审计日志:使用Java日志框架(如Log4j、SLF4J)记录所有登录活动,以便进行审计和监控。
- 集成验证码服务:如果需要验证码功能,可以考虑使用第三方验证码服务或自己实现。
- 支持多语言:使用Java的国际化和本地化API来支持多种语言环境。
- 集成第三方认证服务:使用OAuth、OpenID Connect等协议集成第三方认证服务,如Google、Facebook等。
- 前端与后端交互:使用Java的Web框架(如Spring MVC)处理前端请求并与后端服务进行交互。
- 测试与调试:对整个登录系统进行全面的测试,确保所有功能正常工作并修复潜在问题。
- 部署与监控:将应用程序部署到生产环境,并设置监控和告警以确保系统的稳定性与安全性。
三、安全考虑
设计Java系统登录功能时,安全性是最重要的考虑因素之一。以下是一些关键的安全建议和最佳实践:
- 使用HTTPS协议:确保应用程序之间的通信使用HTTPS协议进行加密,以防止数据泄露和中间人攻击。
- 防止SQL注入和跨站脚本攻击(XSS):对用户输入进行验证和清理,并使用预编译的SQL语句或ORM框架来防止SQL注入攻击;对输出进行适当的编码以防止XSS攻击。
- 防止跨站请求伪造(CSRF)攻击:在处理敏感操作时实施CSRF保护机制。