This is an automated email from the ASF dual-hosted git repository.
voidmatcha pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/zeppelin.git
The following commit(s) were added to refs/heads/master by this push:
new 614baac846 [ZEPPELIN-6731] Fix LdapRealm login failure caused by
unsupported SHA-1 hashing after Shiro 2.0.6 upgrade
614baac846 is described below
commit 614baac84668a7c880161b8df8f1d575be4d8aa2
Author: JangAyeon <[email protected]>
AuthorDate: Sat Oct 24 10:59:52 2026 +0900
[ZEPPELIN-6731] Fix LdapRealm login failure caused by unsupported SHA-1
hashing after Shiro 2.0.6 upgrade
### What is this PR for?
Since the Shiro 2.0.6 upgrade (ZEPPELIN-6528), logging in through
`LdapRealm` fails with HTTP 403 even with the correct password, because
`createAuthenticationInfo()` hashes the credentials with `SHA-1`, which Shiro
2's `SimpleHashProvider` no longer supports (only SHA-256/384/512).
Switching the algorithm alone is not enough. In Shiro 2,
`DefaultHashService` defaults to 50,000 iterations with a random salt, while
`HashedCredentialsMatcher` defaults to 1 iteration. With only the algorithm
changed, the exception disappears but the hashes never match, so login still
fails silently with 403.
This PR:
- Switches `HASHING_ALGORITHM` from `SHA-1` to `SHA-256`.
- Introduces `HASH_ITERATIONS` (Shiro's
`SimpleHashProvider.Parameters.DEFAULT_ITERATIONS`) and uses it on both sides:
the hash request in `createAuthenticationInfo()` and the
`HashedCredentialsMatcher`.
- Adds unit tests covering the `createAuthenticationInfo()` → credentials
matcher path, which previously had no test coverage.
### What type of PR is it?
Bug Fix
### Todos
* [x] Switch `LdapRealm` hashing algorithm from `SHA-1` to `SHA-256`
* [x] Align hash iterations between `DefaultHashService` and
`HashedCredentialsMatcher`
* [x] Add unit tests for the `createAuthenticationInfo()` → credentials
matcher path
* [x] Verify login manually against a real LDAP server (389 Directory
Server)
### What is the Jira issue?
[ZEPPELIN-6731](https://issues.apache.org/jira/browse/ZEPPELIN-6731)
### How should this be tested?
Unit tests:
- `LdapRealmTest#testCreateAuthenticationInfoMatchesSubmittedCredentials`
- `LdapRealmTest#testCreateAuthenticationInfoRejectsDifferentCredentials`
| Code state | Result |
|---|---|
| Before fix (`SHA-1`) | Both tests error: `UnsupportedOperationException:
Cannot create a hash with the given algorithm: SHA-1` |
| Algorithm changed to `SHA-256` only | Match test fails: `expected: <true>
but was: <false>` (iteration mismatch) |
| This PR | All tests in `LdapRealmTest`, `RoleMappingLdapRealmTest`,
`LdapRealmFilterInjectionTest`, `LdapRealmDnInjectionTest` pass |
Manual test, following the reproduction steps in the Jira issue (389
Directory Server in Docker, `LdapRealm` in `shiro.ini`):
| Password | Before | After |
|---|---|---|
| `alice123` (correct) | HTTP 403 + SHA-1 exception in server log | HTTP
200 |
| `wrong-pw` | HTTP 403 | HTTP 403 |
### Questions:
* Does the license files need to update? No
* Is there breaking changes for older versions? No
* Does this needs documentation? No
Closes #5585 from JangAyeon/ZEPPELIN-6731.
Signed-off-by: YONGJAE LEE <[email protected]>
---
.../java/org/apache/zeppelin/realm/LdapRealm.java | 11 +++++++--
.../org/apache/zeppelin/realm/LdapRealmTest.java | 27 ++++++++++++++++++++++
2 files changed, 36 insertions(+), 2 deletions(-)
diff --git
a/zeppelin-server/src/main/java/org/apache/zeppelin/realm/LdapRealm.java
b/zeppelin-server/src/main/java/org/apache/zeppelin/realm/LdapRealm.java
index e4c0df1496..46edfbd459 100644
--- a/zeppelin-server/src/main/java/org/apache/zeppelin/realm/LdapRealm.java
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/realm/LdapRealm.java
@@ -60,6 +60,7 @@ import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.DefaultHashService;
import org.apache.shiro.crypto.hash.Hash;
import org.apache.shiro.crypto.hash.HashRequest;
+import org.apache.shiro.crypto.hash.SimpleHashProvider;
import org.apache.shiro.crypto.hash.HashService;
import org.apache.shiro.realm.ldap.DefaultLdapRealm;
import org.apache.shiro.realm.ldap.JndiLdapContextFactory;
@@ -145,7 +146,10 @@ public class LdapRealm extends DefaultLdapRealm {
private static final String DEFAULT_PRINCIPAL_REGEX = "(.*)";
private static final String MEMBER_SUBSTITUTION_TOKEN = "{0}";
- private static final String HASHING_ALGORITHM = "SHA-1";
+ private static final String HASHING_ALGORITHM = "SHA-256";
+ // The hash service and the credentials matcher must use the same iteration
count,
+ // otherwise the cached credentials never match the submitted ones
(ZEPPELIN-6731).
+ private static final int HASH_ITERATIONS =
SimpleHashProvider.Parameters.DEFAULT_ITERATIONS;
private static final Logger LOGGER =
LoggerFactory.getLogger(LdapRealm.class);
static {
@@ -204,6 +208,7 @@ public class LdapRealm extends DefaultLdapRealm {
public LdapRealm() {
HashedCredentialsMatcher credentialsMatcher = new
HashedCredentialsMatcher(HASHING_ALGORITHM);
+ credentialsMatcher.setHashIterations(HASH_ITERATIONS);
setCredentialsMatcher(credentialsMatcher);
}
@@ -1185,7 +1190,9 @@ public class LdapRealm extends DefaultLdapRealm {
HashRequest.Builder builder = new HashRequest.Builder();
Hash credentialsHash = hashService
.computeHash(builder.setSource(token.getCredentials())
- .setAlgorithmName(HASHING_ALGORITHM).build());
+ .setAlgorithmName(HASHING_ALGORITHM)
+ .addParameter(SimpleHashProvider.Parameters.PARAMETER_ITERATIONS,
HASH_ITERATIONS)
+ .build());
return new SimpleAuthenticationInfo(token.getPrincipal(),
credentialsHash.toHex(), credentialsHash.getSalt(),
getName());
diff --git
a/zeppelin-server/src/test/java/org/apache/zeppelin/realm/LdapRealmTest.java
b/zeppelin-server/src/test/java/org/apache/zeppelin/realm/LdapRealmTest.java
index 415d80e6ec..f2df2322e3 100644
--- a/zeppelin-server/src/test/java/org/apache/zeppelin/realm/LdapRealmTest.java
+++ b/zeppelin-server/src/test/java/org/apache/zeppelin/realm/LdapRealmTest.java
@@ -19,7 +19,9 @@
package org.apache.zeppelin.realm;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
@@ -28,6 +30,8 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.realm.ldap.LdapContextFactory;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.SimplePrincipalCollection;
@@ -48,6 +52,29 @@ import javax.naming.directory.SearchResult;
import javax.naming.ldap.LdapContext;
class LdapRealmTest {
+ @Test
+ void testCreateAuthenticationInfoMatchesSubmittedCredentials() throws
NamingException {
+ // ZEPPELIN-6731: after a successful LDAP bind, the cached credentials
must be
+ // hashable with the installed Shiro hash providers and must match the
same password.
+ LdapRealm realm = new LdapRealm();
+ UsernamePasswordToken token = new UsernamePasswordToken("alice",
"alice123");
+
+ AuthenticationInfo info = realm.createAuthenticationInfo(token, "alice",
"alice123", null);
+
+ assertTrue(realm.getCredentialsMatcher().doCredentialsMatch(token, info));
+ }
+
+ @Test
+ void testCreateAuthenticationInfoRejectsDifferentCredentials() throws
NamingException {
+ LdapRealm realm = new LdapRealm();
+ UsernamePasswordToken token = new UsernamePasswordToken("alice",
"alice123");
+ AuthenticationInfo info = realm.createAuthenticationInfo(token, "alice",
"alice123", null);
+
+ UsernamePasswordToken wrongToken = new UsernamePasswordToken("alice",
"wrong-pw");
+
+ assertFalse(realm.getCredentialsMatcher().doCredentialsMatch(wrongToken,
info));
+ }
+
@Test
void testGetUserDn() {
LdapRealm realm = new LdapRealm();