richard-scott commented on issue #216:
URL: https://github.com/apache/bifromq/issues/216#issuecomment-3707256614
Thank you for the helpful feedback! You were absolutely correct - the plugin
JAR was missing the required PF4J metadata files.
## Root Cause Confirmed
After checking the actual running configuration and verifying the plugin JAR
contents, we confirmed:
1. ✅ **Configuration was correct**: `authProviderFQN:
"com.bifromq.plugin.authprovider.CustomAuthProvider"` was properly set in
`standalone.yml`
2. ❌ **Missing `META-INF/extensions.idx`**: This file was not present in the
plugin JAR
3. ❌ **Missing PF4J manifest entries**: The `META-INF/MANIFEST.MF` file was
missing:
- `Plugin-Class`
- `Plugin-Id`
- `Plugin-Version`
## Documentation Gap
While implementing this fix, we noticed that these PF4J metadata
requirements are **not explicitly documented** in the BifroMQ plugin
documentation:
- [Plugin Overview](https://bifromq.apache.org/docs/plugin/intro/)
- [Auth Provider
Documentation](https://bifromq.apache.org/docs/plugin/auth_provider/)
- [Plugin Practice and
Notice](https://bifromq.apache.org/docs/plugin/practice/)
The documentation references PF4J but doesn't detail the required metadata
files (`extensions.idx` and manifest entries) that must be present in plugin
JARs. This information is documented in PF4J's own documentation
(https://pf4j.org/doc/extensions.html), but developers creating BifroMQ plugins
may not realize they need to consult PF4J documentation for packaging
requirements.
**Suggestion**: It would be helpful if the BifroMQ plugin documentation
included a section on plugin packaging requirements, specifically mentioning:
- The need for `META-INF/extensions.idx` file (or how to generate it)
- Required manifest entries (`Plugin-Class`, `Plugin-Id`, `Plugin-Version`)
- The need for a PF4J Plugin wrapper class when using FQN configuration
This would help prevent similar issues for other developers.
## Solution Implemented
We've fixed the issue by adding the required PF4J metadata:
### 1. Created PF4J Plugin Wrapper Class
```java
package com.bifromq.plugin.authprovider;
import org.pf4j.Plugin;
import org.pf4j.PluginWrapper;
public class AuthProviderPlugin extends Plugin {
public AuthProviderPlugin(PluginWrapper wrapper) {
super(wrapper);
}
}
```
### 2. Updated Maven POM Configuration
Added PF4J dependency and configured Maven plugins. **Important**: We also
added `maven-shade-plugin` to bundle all dependencies (including BCrypt
library) into the plugin JAR, as plugins need to be self-contained:
```xml
<dependencies>
<!-- BifroMQ Plugin API -->
<dependency>
<groupId>org.apache.bifromq</groupId>
<artifactId>bifromq-plugin-auth-provider</artifactId>
<version>4.0.0-incubating</version>
</dependency>
<!-- PF4J for plugin framework -->
<dependency>
<groupId>org.pf4j</groupId>
<artifactId>pf4j</artifactId>
<version>3.9.0</version>
</dependency>
<!-- BCrypt library - Using at.favre.lib:bcrypt which supports $2a$,
$2b$, and $2y$ hash formats -->
<dependency>
<groupId>at.favre.lib</groupId>
<artifactId>bcrypt</artifactId>
<version>0.9.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Maven Compiler Plugin - Configure PF4J annotation processor -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>17</source>
<target>17</target>
<annotationProcessorPaths>
<path>
<groupId>org.pf4j</groupId>
<artifactId>pf4j</artifactId>
<version>3.9.0</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<!-- Maven JAR Plugin - Add PF4J manifest entries -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<archive>
<manifestEntries>
<Plugin-Class>com.bifromq.plugin.authprovider.AuthProviderPlugin</Plugin-Class>
<Plugin-Id>auth-provider</Plugin-Id>
<Plugin-Version>${project.version}</Plugin-Version>
</manifestEntries>
</archive>
</configuration>
</plugin>
<!-- Maven Shade Plugin - Bundle all dependencies into plugin JAR -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Plugin-Class>com.bifromq.plugin.authprovider.AuthProviderPlugin</Plugin-Class>
<Plugin-Id>auth-provider</Plugin-Id>
<Plugin-Version>${project.version}</Plugin-Version>
</manifestEntries>
</transformer>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
```
### 3. Created `META-INF/extensions.idx` File
Created `src/main/resources/META-INF/extensions.idx` containing:
```
com.bifromq.plugin.authprovider.CustomAuthProvider
```
### 4. Additional Implementation Fixes
After fixing the PF4J metadata, we encountered and resolved two additional
issues:
**a) BCrypt Library Compatibility**: The `org.mindrot:jbcrypt` library
(version 0.4) doesn't fully support `$2b$` salt revisions commonly used in
modern password hashes. We switched to `at.favre.lib:bcrypt` (version 0.9.0)
which provides full support for `$2a$`, `$2b$`, and `$2y$` hash formats.
**b) MQTT3AuthResult Builder Usage**: The `MQTT3AuthResult` protobuf uses a
nested `Ok` message structure. The correct way to build an authentication
result is:
```java
// Build MQTT3AuthResult using protobuf builder
// MQTT3AuthResult uses a nested Ok message that contains tenantId and userId
MQTT3AuthResult.Ok okMessage = MQTT3AuthResult.Ok.newBuilder()
.setTenantId(result.tenantId())
.setUserId(result.userId())
.build();
MQTT3AuthResult authResult = MQTT3AuthResult.newBuilder()
.setOk(okMessage)
.build();
```
**c) Dependency Bundling**: Plugins must bundle all their dependencies (like
BCrypt) into the JAR using `maven-shade-plugin`, otherwise you'll get
`NoClassDefFoundError` at runtime. The shade plugin creates a "fat JAR" that
includes all required classes.
## Verification & Testing
We've tested the fix and confirmed it works:
✅ **JAR Verification**:
- `META-INF/extensions.idx` exists and contains:
`com.bifromq.plugin.authprovider.CustomAuthProvider`
- `META-INF/MANIFEST.MF` contains all required entries:
- `Plugin-Class: com.bifromq.plugin.authprovider.AuthProviderPlugin`
- `Plugin-Id: auth-provider`
- `Plugin-Version: 1.0.0`
- Plugin wrapper class (`AuthProviderPlugin.class`) is present in JAR
✅ **Runtime Verification**:
- Plugin loads successfully (confirmed via startup logs)
- Configuration is correct (`authProviderFQN:
"com.bifromq.plugin.authprovider.CustomAuthProvider"`)
- Authentication works correctly (tested with MQTT clients)
- BCrypt password verification works with `$2b$` hash format
- All plugin dependencies are bundled and available at runtime
✅ **End-to-End Testing**:
- All test suites pass (100% success rate)
- MQTT client connections authenticate successfully
- Password verification works with BCrypt hashes
- Plugin is fully functional
## Status
✅ **Issue resolved** - plugin now loads correctly with proper PF4J metadata
files and all dependencies bundled. The plugin is fully functional and tested.
## Additional Recommendations
For developers creating BifroMQ plugins, we recommend:
1. **Always use `maven-shade-plugin`** to bundle dependencies into plugin
JARs
2. **Use modern BCrypt libraries** (like `at.favre.lib:bcrypt`) if password
hashing is needed
3. **Test plugin loading** by checking startup logs and verifying the plugin
is actually being used (not falling back to Demo plugins)
4. **Verify JAR contents** using `jar tf plugin.jar | grep META-INF` to
ensure all metadata files are present
Thank you again for pointing us in the right direction!
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]