FX-HAO / keycloak-phone-authenticator

Obtain token and reset password via SMS
130 stars 43 forks source link

Failed to link com/hfx/keycloak/spi/ResetCredentialWithPhone #6

Closed CalistaIo closed 3 years ago

CalistaIo commented 3 years ago

I am using Keycloak version 13.0.1. I added the following classes to the project to implement my own SMS provider with AWS SNS:

(In package com.hfx.keycloak.spi.impl)

public class SmsServiceImpl implements SmsService<Object> {
    private final KeycloakSession session;
    public SmsServiceImpl(KeycloakSession session) {
        this.session = session;
    }
    @Override
    public void close() {
    }
    @Override
    public boolean send(String phoneNumber, Map<String, ? super Object> params) throws SmsException {
        String templateId = (String) params.get("templateId");
        String accessKey = "...";
        String secretKey = "...";
        BasicAWSCredentials awsCreds = new BasicAWSCredentials(accessKey, secretKey);
        AmazonSNS snsClient = AmazonSNSClient
                .builder()
                .withRegion(Regions.AP_SOUTHEAST_1)
                .withCredentials(new AWSStaticCredentialsProvider(awsCreds))
                .build();
        String SMSMessage = templateId;
        snsClient.publish(new PublishRequest()
                .withMessage(SMSMessage)
                .withPhoneNumber(phoneNumber));
        return true;
    }

    @Override
    public boolean sendVerificationCode(VerificationCodeRepresentation rep, Map<String, ? super Object> params)
            throws SmsException {
        String code = rep.getCode();
        String accessKey = "...";
        String secretKey = "...";
        BasicAWSCredentials awsCreds = new BasicAWSCredentials(accessKey, secretKey);
        AmazonSNS snsClient = AmazonSNSClient
                .builder()
                .withRegion(Regions.AP_SOUTHEAST_1)
                .withCredentials(new AWSStaticCredentialsProvider(awsCreds))
                .build();
        String SMSMessage = code;
        snsClient.publish(new PublishRequest()
                .withMessage(SMSMessage)
                .withPhoneNumber(rep.getPhoneNumber()));
        return true;
    }
}

(In package com.hfx.keycloak.spi.impl)

public class SmsServiceProviderFactoryImpl implements SmsServiceProviderFactory {
    @Override
    public SmsService create(KeycloakSession session) {
        return new SmsServiceImpl(session);
    }
    @Override
    public void init(Config.Scope config) {

    }
    @Override
    public void postInit(KeycloakSessionFactory factory) {

    }
    @Override
    public void close() {

    }
    @Override
    public String getId() {
        return "SmsServiceProviderFactoryImpl";
    }
}

I also added the following service file to META-INF/services, called com.hfx.keycloak.spi.SmsServiceProviderFactory, with the following content:

com.hfx.keycloak.spi.impl.SmsServiceProviderFactoryImpl

The only dependency I added to the pom.xml is this:

    <dependency>
      <groupId>com.amazonaws</groupId>
      <artifactId>aws-java-sdk</artifactId>
      <version>1.11.1030</version>
    </dependency>

However, adding the jar to standalone/deployments and deploying, I still get the following error: {"WFLYCTL0080: Failed services" => {"jboss.deployment.unit.\"keycloak-phone-authenticator-1.0.0-SNAPSHOT.jar\".POST_MODULE" => "WFLYSRV0153: Failed to process phase POST_MODULE of deployment \"keycloak-phone-authenticator-1.0.0-SNAPSHOT.jar\" Caused by: java.lang.NoClassDefFoundError: Failed to link com/hfx/keycloak/spi/ResetCredentialWithPhone (Module \"deployment.keycloak-phone-authenticator-1.0.0-SNAPSHOT.jar\" from Service Module Loader): org/keycloak/authentication/authenticators/resetcred/ResetCredentialChooseUser"}}

which is the same error as that faced in issue #1. May I know if there is an issue with the way I added the SMS service implementation?

FX-HAO commented 3 years ago

Sorry for the delayed response.

adding the jar to standalone/deployments and deploying

I think you cannot just put your jars into standalone/deployments(hot deployment), since my SPIs make some changes to the database and something like that. You need to use jboss-cli script to register your libraries. If you're not familiar with that, you could take a look at here. Or you can just take a look at the example and the Dockerfile under the directory. if you know docker, it's more clear and easy to know the workflow of how to register your libraries into keycloak. And you just need to replace the name of my SMS implementation(keycloak-phone-authenticator-yuntongxun-sms) with yours. Then build the docker image and run it.

Please let me know if you have any questions.

FX-HAO commented 3 years ago

BTW, since you introduce the library aws-java-sdk, you also need to check if the libraries it depends on are imported in Keycloak. otherwise, you also need to register them into keycloak. For instance, if aws-java-sdk depends on library B, then you also need to register B. if B depends on C, you need to register C as well, and so on.

CalistaIo commented 3 years ago

Hi, thank you for the response. To resolve the issue, I added a jboss-deployment-structure.xml file to META-INF and added the jar to standalone/deployments folder, and then registered the module (that is, the same jar with my SmsService implementation included) and edited the standalone.xml config file. However, when I run standalone.bat in cmd line, I get the following error:

Error during startup: java.util.ServiceConfigurationError: com.hfx.keycloak.spi.VerificationCodeServiceProviderFactory: com.hfx.keycloak.spi.impl.VerificationCodeServiceProviderFactoryImpl not a subtype

I have not made any changes to the service files/Java classes other than my own Sms Service class, so I am very puzzled about why I am getting this issue. I suspect that Keycloak is not detecting the providers properly. I was wondering if you have encountered this issue before?

Additionally, just an extra observation, but when I do not register the providers in the modules folder, I can deploy and see all the extra configurations, but the issue of the custom providers (like VerificationCodeService) not being detected pops up when I go through the entire authentication flow.

FX-HAO commented 3 years ago

and added the jar to standalone/deployments folder

As I mentioned before, you cannot put your jar into the standalone/deployments folder, it's called hot deployment. you need to take a look at https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.3/html/server_developer_guide/providers#register_a_provider_using_modules

FX-HAO commented 3 years ago

My example Dockerfile:

# base image
FROM jboss/keycloak:9.0.2

# copy the jars into the current folder
COPY --from=builder /app/keycloak-phone-authenticator/target/keycloak-phone-authenticator-1.0.0-SNAPSHOT.jar .
COPY --from=builder /app/keycloak-phone-authenticator-yuntongxun-sms/target/keycloak-phone-authenticator-yuntongxun-sms-1.0.0-SNAPSHOT.jar .
COPY --from=builder /app/yuntongxun4j/target/yuntongxun4j-1.0-SNAPSHOT.jar .
COPY examples/cli/ cli/

# register the jars
RUN $JBOSS_HOME/bin/jboss-cli.sh --file=cli/keycloak-phone-authenticator-yuntongxun-sms-config.cli
RUN $JBOSS_HOME/bin/jboss-cli.sh --file=cli/module-add.cli

# copy the resource files into the current folder
COPY --from=builder /app/keycloak-phone-authenticator/target/classes/theme-resources/templates/ $JBOSS_HOME/themes/base/login/
COPY ./messages/ messages/

# fix a bug
RUN rm -rf /opt/jboss/keycloak/standalone/configuration/standalone_xml_history/current

# register the resource files
RUN cat messages/messages_en.properties >> $JBOSS_HOME/themes/base/login/messages/messages_en.properties && \
    cat messages/messages_zh_CN.properties >> $JBOSS_HOME/themes/base/login/messages/messages_zh_CN.properties && \
    cat messages/messages_en.properties >> $JBOSS_HOME/themes/base/account/messages/messages_en.properties && \
    cat messages/messages_zh_CN.properties >> $JBOSS_HOME/themes/base/account/messages/messages_zh_CN.properties
CalistaIo commented 3 years ago

Ok, I placed the jar in providers instead. As for the AWS dependency, I used Maven's shade plugin to create an uber jar so there are no more issues about the dependency not being found. It is now working and I can set up the additional configs. Thanks for the help.