如何实现支持用户名(User表)或邮箱(ContactPerson表)的登录功能?
实现用户名/邮箱双方式登录的正确方案
原HQL语句的问题
你的HQL存在两个核心错误:
- 逻辑运算符优先级混乱:AND优先级高于OR,原语句会被解析成
(usr.name = :username AND usr.pass = :password) OR ctp.email = :username,导致只要输入内容匹配任意联系人邮箱,无需验证密码就可能返回结果,完全不符合登录逻辑。 - 表关联逻辑错误:JOIN的ON条件应该是User和ContactPerson之间的外键关联关系(比如
usr.id = ctp.userId),而非用输入的用户名来关联两张表,这会导致关联逻辑完全失效。
正确实现方案
方案一:使用关联查询的HQL语句
首先明确User和ContactPerson的关联关系(假设ContactPerson表存在userId字段,关联User表的id主键),通过括号包裹OR条件确保逻辑优先级正确:
已映射实体关联的情况(推荐)
如果User实体类已通过注解/XML映射了与ContactPerson的关联(比如@OneToOne private ContactPerson contactPerson;),可以直接用属性关联:
FROM User usr LEFT JOIN usr.contactPerson ctp WHERE (usr.name = :loginValue OR ctp.email = :loginValue) AND usr.pass = :password
未映射实体关联的情况
如果没有实体关联,需显式指定JOIN的外键条件:
FROM User usr LEFT JOIN ContactPerson ctp ON usr.id = ctp.userId WHERE (usr.name = :loginValue OR (ctp.email = :loginValue AND ctp IS NOT NULL)) AND usr.pass = :password
使用LEFT JOIN可确保未关联ContactPerson的用户依然能通过用户名登录。
修改后的Java代码:
public LoginUsr logInUsr(RequestLoginUsr request) { List<User> userList = getCurrentSession() .createQuery("FROM User usr LEFT JOIN ContactPerson ctp ON usr.id = ctp.userId WHERE (usr.name = :loginValue OR (ctp.email = :loginValue AND ctp IS NOT NULL)) AND usr.pass = :password", User.class) .setParameter("loginValue", request.getUsername()) .setParameter("password", request.getPassword()) .getResultList(); if (userList.size() == 1) { User u = userList.get(0); return new LoginUsr(u.getSurname(), u.getId(), u.getFkUsrPrc(), null); } else { return null; } }
方案二:分两次查询(更直观)
如果觉得关联查询逻辑复杂,可拆分两次查询:先尝试用户名+密码登录,失败后再用邮箱查找对应用户并验证密码:
public LoginUsr logInUsr(RequestLoginUsr request) { // 1. 尝试用户名+密码登录 List<User> userNameMatch = getCurrentSession() .createQuery("from User where name = :loginValue and pass = :password", User.class) .setParameter("loginValue", request.getUsername()) .setParameter("password", request.getPassword()) .getResultList(); if (!userNameMatch.isEmpty()) { User u = userNameMatch.get(0); return new LoginUsr(u.getSurname(), u.getId(), u.getFkUsrPrc(), null); } // 2. 尝试邮箱+密码登录 List<User> emailMatch = getCurrentSession() .createQuery("select usr from User usr join ContactPerson ctp on usr.id = ctp.userId where ctp.email = :loginValue and usr.pass = :password", User.class) .setParameter("loginValue", request.getUsername()) .setParameter("password", request.getPassword()) .getResultList(); if (!emailMatch.isEmpty()) { User u = emailMatch.get(0); return new LoginUsr(u.getSurname(), u.getId(), u.getFkUsrPrc(), null); } // 两种方式都未匹配 return null; }
额外注意事项
- 密码安全:当前代码明文比较密码存在严重风险,建议使用BCrypt、Argon2等哈希算法加密存储密码,登录时对输入密码哈希后再对比。
- 唯一性约束:需在数据库中给
ContactPerson.email添加唯一约束,避免多个用户关联同一邮箱导致登录逻辑混乱。 - 结果去重:如果存在用户名和邮箱重复的极端情况,需在业务逻辑中避免冲突,或在查询中添加
distinct关键字确保返回唯一用户。
内容的提问来源于stack exchange,提问作者treumiton
相关产品推荐
相关产品推荐

