Shiro INI配置如何添加额外参数 实现数据库多字段查询认证
问题原因
Shiro原生JdbcRealm执行authenticationQuery认证查询时,逻辑固定仅向SQL传入1个绑定参数:即登录时UsernamePasswordToken中存储的登录标识(前端传入的username字段值),仅会对第一个?占位符赋值。你写的SQL包含2个占位符,第二个占位符没有传入参数,自然会抛出参数未配置的错误,这个问题无法直接通过编写多占位符的SQL在原生配置下解决。
解决方案
方案1:零代码修改,调整SQL适配原生JdbcRealm逻辑
不需要自定义任何Java类,仅修改ini中的SQL语句,通过子查询将单个传入的参数复用给多字段匹配条件,整个SQL仅保留1个?占位符即可。
修改后的认证SQL配置示例:
jdbcRealm.authenticationQuery = SELECT user_password FROM user, (SELECT ? AS login_key) tmp WHERE ( user_name = tmp.login_key OR CAST(user_id AS CHAR) = tmp.login_key OR user_email = tmp.login_key ) AND user_status = 1
注意事项:
- 如果
user_id是数值类型,必须通过数据库的类型转换函数转为字符串,避免和传入的字符串类型登录值做匹配时出现类型错误,MySQL用CAST(user_id AS CHAR)、Oracle用TO_CHAR(user_id)即可。 - 原有的
userRolesQuery角色查询、权限查询也需要做相同改造,否则用用户ID/邮箱登录时,无法查询到对应角色、权限,会导致授权失败。角色查询改造示例:
jdbcRealm.userRolesQuery = SELECT t.login_type_name FROM login_type t INNER JOIN schooler_user_rel r ON r.login_type_id = t.login_type_id INNER JOIN user u ON u.user_id = r.user_id, (SELECT ? AS login_key) tmp WHERE ( u.user_name = tmp.login_key OR CAST(u.user_id AS CHAR) = tmp.login_key OR u.user_email = tmp.login_key )
方案2:自定义Realm扩展逻辑(适合后续有更多登录扩展需求的场景)
如果后续需要增加更多登录校验逻辑(比如不同登录类型走不同校验规则、加额外的账号状态判断),可以自定义Realm继承原生JdbcRealm,重写认证查询的参数绑定逻辑,手动给多个占位符赋值。
- 编写自定义Realm类:
import org.apache.shiro.authc.*; import org.apache.shiro.realm.jdbc.JdbcRealm; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class MultiFieldAuthJdbcRealm extends JdbcRealm { @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { UsernamePasswordToken upToken = (UsernamePasswordToken) token; String loginValue = upToken.getUsername(); String dbPassword = null; try (Connection conn = getDataSource().getConnection(); PreparedStatement ps = conn.prepareStatement(getAuthenticationQuery())) { // 按你SQL里的占位符数量,全部绑定同一个登录入参 ps.setString(1, loginValue); ps.setString(2, loginValue); ps.setString(3, loginValue); try (ResultSet rs = ps.executeQuery()) { if (rs.next()) { dbPassword = rs.getString(1); } } } catch (SQLException e) { throw new AuthenticationException("账号认证查询执行失败", e); } if (dbPassword == null) { throw new UnknownAccountException("账号不存在"); } return new SimpleAuthenticationInfo(loginValue, dbPassword.toCharArray(), getName()); } }
- 修改shiro.ini中的Realm配置为自定义类,其余原有配置保持不变即可:
jdbcRealm = 你的类全限定名.MultiFieldAuthJdbcRealm jdbcRealm.dataSource = $ds jdbcRealm.credentialsMatcher = $passwordMatcher jdbcRealm.permissionsLookupEnabled = false # 这里可以直接写多占位符的SQL,自定义Realm会给所有占位符赋值 jdbcRealm.authenticationQuery = SELECT user_password FROM user WHERE (user_name = ? OR CAST(user_id AS CHAR) = ? OR user_email = ?) AND user_status = 1 # 角色、权限查询如果也需要支持多字段登录,也可以通过重写对应查询方法实现多参数绑定 jdbcRealm.userRolesQuery = SELECT login_type_name FROM login_type t INNER JOIN schooler_user_rel r ON r.login_type_id = t.login_type_id INNER JOIN user u ON u.user_id = r.user_id WHERE u.user_name = ? OR CAST(u.user_id AS CHAR) = ? OR u.user_email = ? authc.usernameParam = username authc.passwordParam = password
内容的提问来源于stack exchange,提问作者Vijay Kumar Kanta
相关产品推荐
相关产品推荐

