类 LoginService
java.lang.Object
net.sohelp.boot.admin.core.service.login.LoginService
登录用户验证服务
依据网络安全等级保护(等保)相关要求实现:
- 身份鉴别:密码复杂度校验(长度、大小写、数字、特殊字符)
- 登录失败处理:连续失败 N 次锁定账户,超时后自动解锁
- 密码有效期:定期强制更换,默认 90 天
- 密码历史:禁止与最近 N 次历史密码重复
- 弱密码限制:密码不能与用户名相同
相关系统配置项(system.config.pwd_rules):
min_length 最小密码长度,默认 8(等保要求) contain_number 是否必须含数字,默认 false contain_lower 是否必须含小写字母,默认 false contain_upper 是否必须含大写字母,默认 false contain_special 是否必须含特殊字符,默认 false pwd_change_interval 密码有效天数,默认 90,0 表示不限制 max_login_fail 最大连续失败次数,默认 5,0 表示不限制 login_lock_minutes 锁定时长(分钟),默认 30 pwd_history_count 禁止重用的历史密码条数,默认 3,0 表示不限制
- 从以下版本开始:
- 2023/12/22
- 作者:
- AaronFung
-
构造器概要
构造器 -
方法概要
修饰符和类型方法说明booleanchangePassword(String loginName, String newPassword, String oldPassword) 修改用户密码voidcheckPasswordExpire(String loginName) 检查用户密码是否已过期,过期则强制要求更换(抛出异常)consumePwdExpiredToken(String pwdToken) 验证密码过期临时令牌并返回对应的登录名booleanforceChangePwd(String loginName, String newPassword) 强制修改用户密码(密码过期场景,无需提供旧密码)getLastLoginInfo(String username) 查询用户上一次成功登录的记录(跳过本次,取倒数第二条)签发访问令牌并记录登录成功日志(供第三方登录控制器统一调用)issuePwdExpiredToken(String loginName) 签发密码过期临时令牌(用于 code=601 的密码修改流程)voidrevokePwdToken(String pwdToken) 撤销密码过期临时令牌(密码修改成功后调用)voidsaveLoginLog(String username, String nickname, Integer type, String comments, Long tenantId, jakarta.servlet.http.HttpServletRequest request) 写入登录日志到 pb_login_record 表booleanupdateUser(Map<String, Object> userMap) 更新用户基本信息boolean根据用户、密码、租户验证用户身份,并执行等保登录失败处理机制boolean根据用户和密码验证用户身份(忽略租户),并执行等保登录失败处理机制voidvalidateByRule(String loginName, String password) 根据系统配置规则校验密码合法性,同时检查密码不能与用户名相同
-
构造器详细资料
-
LoginService
public LoginService()
-
-
方法详细资料
-
validate
@Comment("\u6839\u636e\u7528\u6237\u3001\u5bc6\u7801\u3001\u79df\u6237\u9a8c\u8bc1\u7528\u6237\u662f\u5426\u5b58\u5728") public boolean validate(long tenantID, String loginName, String password) throws SQLException, BusiException 根据用户、密码、租户验证用户身份,并执行等保登录失败处理机制- 参数:
tenantID- 租户 IDloginName- 登录名password- 明文密码- 返回:
- true 表示验证通过
- 抛出:
SQLException- 数据库异常BusiException- 账户锁定或凭据错误时抛出
-
validate
@Comment("\u6839\u636e\u7528\u6237\u548c\u5bc6\u7801\u9a8c\u8bc1\u7528\u6237\u662f\u5426\u5b58\u5728") public boolean validate(String loginName, String password) throws SQLException, BusiException 根据用户和密码验证用户身份(忽略租户),并执行等保登录失败处理机制- 参数:
loginName- 登录名password- 明文密码- 返回:
- true 表示验证通过
- 抛出:
SQLException- 数据库异常BusiException- 账户锁定或凭据错误时抛出
-
changePassword
@Comment("\u4fee\u6539\u7528\u6237\u5bc6\u7801") public boolean changePassword(String loginName, String newPassword, String oldPassword) throws SQLException, BusiException 修改用户密码执行顺序:
- 验证新密码符合复杂度规则
- 验证新密码不与用户名相同
- 验证旧密码正确
- 验证新密码不在历史密码记录中
- 更新密码并记录修改时间,同步写入历史
- 参数:
loginName- 登录名newPassword- 新密码(明文)oldPassword- 旧密码(明文),用于身份验证- 返回:
- true 表示修改成功
- 抛出:
SQLException- 数据库异常BusiException- 规则校验失败时抛出
-
forceChangePwd
@Comment("\u5f3a\u5236\u4fee\u6539\u7528\u6237\u5bc6\u7801\uff08\u5bc6\u7801\u8fc7\u671f\u65f6\u4f7f\u7528\uff0c\u4e0d\u9700\u8981\u65e7\u5bc6\u7801\uff09") public boolean forceChangePwd(String loginName, String newPassword) throws SQLException, BusiException 强制修改用户密码(密码过期场景,无需提供旧密码)执行顺序:
- 验证新密码符合复杂度规则
- 验证新密码不在历史密码记录中
- 保存当前密码到历史,更新密码与修改时间
- 参数:
loginName- 登录名newPassword- 新密码(明文)- 返回:
- true 表示修改成功
- 抛出:
SQLException- 数据库异常BusiException- 规则校验失败时抛出
-
checkPasswordExpire
@Comment("\u68c0\u67e5\u7528\u6237\u5bc6\u7801\u662f\u5426\u5df2\u8fc7\u671f\u9700\u8981\u5f3a\u5236\u66f4\u6362") public void checkPasswordExpire(String loginName) throws BusiException, SQLException 检查用户密码是否已过期,过期则强制要求更换(抛出异常)读取 pwd_change_interval 配置,pwd_change_time 为 null 时视为从未更换,直接判定过期。
- 参数:
loginName- 登录名- 抛出:
BusiException- 密码已过期时抛出SQLException- 数据库异常
-
validateByRule
根据系统配置规则校验密码合法性,同时检查密码不能与用户名相同校验项:非空、最小长度(等保默认 8)、数字、小写字母、大写字母、特殊字符、不与用户名相同。
- 参数:
loginName- 登录名(用于禁止密码与用户名相同)password- 待验证密码- 抛出:
BusiException- 不满足规则时抛出
-
updateUser
@Comment("\u4fee\u6539\u7528\u6237\u57fa\u672c\u4fe1\u606f") public boolean updateUser(Map<String, Object> userMap) throws BusiException, SQLException更新用户基本信息采用白名单字段策略,仅允许更新以下业务字段,系统字段(权限、凭据、状态流转等) 一律不在此方法处理,防止越权修改:
- nickname - 昵称
- user_name - 姓名
- email - 邮箱
- phone - 手机号
- address - 地址
- avatar - 头像
- introduction - 个人简介
- org_id - 所属组织
- 参数:
userMap- 包含 id(必填)及待更新字段的用户数据- 返回:
- true 表示更新成功
- 抛出:
BusiException- id 缺失或无可更新字段时抛出SQLException- 数据库异常
-
issuePwdExpiredToken
@Comment("\u7b7e\u53d1\u5bc6\u7801\u8fc7\u671f\u4e34\u65f6\u4ee4\u724c") public String issuePwdExpiredToken(String loginName) 签发密码过期临时令牌(用于 code=601 的密码修改流程)生成一个 UUID 令牌,以 pwd_exp:{token} 为 key 将登录名写入 VerifyCodeCache(5 分钟有效)。 前端携带此 token 调用 /changePwd 接口完成密码修改。
- 参数:
loginName- 登录名- 返回:
- 临时令牌字符串
-
consumePwdExpiredToken
@Comment("\u9a8c\u8bc1\u5bc6\u7801\u8fc7\u671f\u4e34\u65f6\u4ee4\u724c\uff0c\u8fd4\u56de\u767b\u5f55\u540d") public String consumePwdExpiredToken(String pwdToken) 验证密码过期临时令牌并返回对应的登录名- 参数:
pwdToken- 临时令牌- 返回:
- 登录名,令牌不存在或已过期时返回 null
-
revokePwdToken
@Comment("\u64a4\u9500\u5bc6\u7801\u8fc7\u671f\u4e34\u65f6\u4ee4\u724c") public void revokePwdToken(String pwdToken) 撤销密码过期临时令牌(密码修改成功后调用)- 参数:
pwdToken- 临时令牌
-
saveLoginLog
@Comment("\u5199\u5165\u767b\u5f55\u65e5\u5fd7") public void saveLoginLog(String username, String nickname, Integer type, String comments, Long tenantId, jakarta.servlet.http.HttpServletRequest request) throws SQLException 写入登录日志到 pb_login_record 表含 24 小时内累计失败次数(等保 1.2.2 暴力破解检测), 供 sohelp-dev / sohelp-demo 及其他模块共用。
- 参数:
username- 登录名nickname- 昵称type- 日志类型(0=成功, 1=失败, 2=退出)comments- 备注tenantId- 租户 IDrequest- HTTP 请求(用于获取 IP、UA 信息)- 抛出:
SQLException- 数据库异常
-
getLastLoginInfo
@Comment("\u67e5\u8be2\u7528\u6237\u4e0a\u6b21\u767b\u5f55\u4fe1\u606f\uff08IP / \u65f6\u95f4 / \u8bbe\u5907\uff09") public Map<String,Object> getLastLoginInfo(String username) 查询用户上一次成功登录的记录(跳过本次,取倒数第二条)在本次登录日志已写入后调用,返回上次登录的 ip、时间、平台(os)、设备(device)、浏览器(browser)。 若历史中只有本次登录或表不存在,则返回 null。
- 参数:
username- 登录名- 返回:
- 上次登录信息 Map,字段:ip / create_time / os / device / browser;首次登录返回 null
-
getToken
@Comment("\u7b7e\u53d1\u8bbf\u95ee\u4ee4\u724c\u5e76\u8bb0\u5f55\u767b\u5f55\u65e5\u5fd7\uff08\u7b2c\u4e09\u65b9\u767b\u5f55\u4f7f\u7528\uff09") public String getToken(Map<String, Object> userMap, jakarta.servlet.http.HttpServletRequest request) throws SQLException签发访问令牌并记录登录成功日志(供第三方登录控制器统一调用)按用户表 token_expired 字段(秒)设置令牌有效期,0 表示永不过期。 同时将 user_no 写入 Session 以兼容旧版会话读取逻辑。
- 参数:
userMap- 用户信息 Map(来自 pb_users select *)request- HTTP 请求,用于获取 IP/UA、写 Session- 返回:
- JWT/Sa-Token 访问令牌字符串
- 抛出:
SQLException- 数据库异常(写登录日志)
-