Custom Authentication not working as expected with Java Client SDK

Hello,

I have created a custom authentication with a JWT token in nuxeo for both, WebUI and Rest API. For the second one, I am using Java Client SDK 3.2. The WebUI authentication works perfectly, but in the Rest API the behaviour is a little strange.

This is what I have done to create my authentication (named MY_CUSTOM_AUTH):

1) I have created the new authentication for the general authentication chain (for WebUI) and also for the Automation/Rest API specific chains:

<require>org.nuxeo.ecm.restapi.server.auth.config</require>
<require>org.nuxeo.ecm.automation.server.auth.config</require>

<extension target="org.nuxeo.ecm.platform.ui.web.auth.service.PluggableAuthenticationService" point="chain">
    <authenticationChain>
        <plugins>
            <plugin>BASIC_AUTH</plugin>
            <plugin>MY_CUSTOM_AUTH</plugin>
            <plugin>FORM_AUTH</plugin>
        </plugins>
    </authenticationChain>
</extension>

<extension target="org.nuxeo.ecm.platform.ui.web.auth.service.PluggableAuthenticationService" point="specificChains">
    <specificAuthenticationChain name="RestAPI">
        <urlPatterns>
            <url>(.*)/api/v.*</url>
        </urlPatterns>
        <replacementChain>
        <plugin>MY_CUSTOM_AUTH</plugin>
           <plugin>AUTOMATION_BASIC_AUTH</plugin>
           <plugin>TOKEN_AUTH</plugin>
           <plugin>OAUTH2_AUTH</plugin>
           <plugin>JWT_AUTH</plugin>
    </replacementChain>
    </specificAuthenticationChain>

    <specificAuthenticationChain name="Automation">
        <urlPatterns>
            <url>(.*)/automation.*</url>
        </urlPatterns>
        <replacementChain>
        <plugin>MY_CUSTOM_AUTH</plugin>
        <plugin>AUTOMATION_BASIC_AUTH</plugin>
        </replacementChain>
    </specificAuthenticationChain>
</extension>

I have put both RestAPI and Automation specific chains because Java Client seems to use the automation one to do the login, and then the rest API for the other calls.

2) I have defined MY_CUSTOM_AUTH as follows:

<extension target="org.nuxeo.ecm.platform.ui.web.auth.service.PluggableAuthenticationService" point="authenticators">
    <authenticationPlugin name="MY_CUSTOM_AUTH" enabled="true" class="my.custom.auth.service.CustomAuthenticator">
        <loginModulePlugin>Trusting_LM</loginModulePlugin>
        <parameters>
            <parameter name="headerToken">token</parameter>
        </parameters>
    </authenticationPlugin>
</extension>

And this is part of the Java class CustomAuthenticator.java:

public class CustomAuthenticator implements NuxeoAuthenticationPlugin {

    @Override
    public Boolean handleLoginPrompt(HttpServletRequest httpRequest, HttpServletResponse httpResponse, String baseURL) {
        return false;
    }

@Override
    public UserIdentificationInfo handleRetrieveIdentity(HttpServletRequest request, HttpServletResponse response) {
        UserIdentificationInfo uui = null;

        // I create the session to get a JSESSIONID
        HttpSession session = request.getSession(false);
        if (session == null) {
            session = request.getSession(true);
        }

        // If the authentication is in WebUI, token is a parameter. Via Rest API, I send it as a header.
        String token = request.getParameter(userIdHeaderToken);
        if(token == null)
            token = request.getHeader(userIdHeaderToken);   

        // Redirection only applied for WebUI. Rest API calls will have a "redirect" header set to false.
        boolean redirect = true;
        String redirectHeader = request.getHeader("redirect");
        if(redirectHeader != null && !redirectHeader.equals(""))
            redirect = false;

        if (token == null) {
            return null;
        }

        /**************************
        * Here I do the token validation, calling an external service.
        * If the token is valid, it returns the username in nuxeo. If not, null.
        ***************************/

        String username = TokenValidation.validate(token);
        if (username == null) {
            return null;
        }

        if(redirect)
            handleRedirectToValidStartPage(request, response);

        uui = new UserIdentificationInfo(username, "");
        if (uui != null) {
            uui.setToken(token);
        }
        return uui;
    }

    protected void handleRedirectToValidStartPage(HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
        // The code here is the same as TokenAuthenticator, with redirection.
    }

    @Override
    public Boolean needLoginPrompt(HttpServletRequest httpRequest) {
        return false;
    }

    @Override
    public void initPlugin(Map<String, String> parameters) {
        if (parameters.containsKey(HEADER_TOKEN_KEY)) {
            userIdHeaderToken = parameters.get(HEADER_TOKEN_KEY);
        }
    }

    @Override
    public List<String> getUnAuthenticatedURLPrefix() {
        return null;
    }

With this configuration, the authentication works perfectly in WebUI, and it also works by using Java Client. However, in Java Client, the token validation is made in every method called, even after creating the NuxeClient object.

I mean, when I create the NuxeoClient object, the token validation is made. However, when I call a NuxeoClient method, the validation is done again!

// Connexion, token validation is done (OK)
NuxeoClient nuxeoClient = new NuxeoClient.Builder().url(url).authentication(new CustomAuthInterceptor(token)).connect();
// I retrieve a binary, token validation is done again!! (BAD)
StreamBlob blob = nuxeoClient.repository(repository).streamBlobById(id, Document.DEFAULT_FILE_CONTENT);

This is my CustomAuthInterceptor:

public class CustomAuthInterceptor implements Interceptor {

    protected String token;

    public CustomAuthInterceptor (String token) {
        this.token = token;
    }

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request()
                               .newBuilder()
                               .addHeader("redirect", "false")
                               .addHeader("token", token)
                               .build();
        Response response = chain.proceed(request);
    // Check if cookies have been sent
        List<String> cookieHeaders = response.headers("set-cookie");
        return response;
    }
}

Token validation involves a REST call to another application, and this is time I have to wait (around 200-300ms). The problem is that this validation is done always, and I only want to do that validation when creating the “NuxeoClient” object. I think I am missing some configuration to store the session cookie inside NuxeoClient or similar. Can you help me?

The cookieHeaders has the JSESSIONID, but it seems is not persisted.

Thank you.

0 votes

1 answers

1265 views

ANSWER



Hello,

after some research, I have chosen to store the cookie manually. To do so, I have modified my interceptor like follows:

public class CustomAuthInterceptor implements Interceptor {

    protected String token;
    protected String cookie;

    public CustomAuthInterceptor (String token) {
        this.token = token;
    }

    @Override
    public Response intercept(Chain chain) throws IOException {
            Request original = chain.request();
            Builder builder = chain.request()
                                   .newBuilder()
                                   .method(original.method(), original.body())
                                   .addHeader("redirect", "false")
                                   .addHeader("token", token);

            // If there is a cookie saved, I include it in the request
            if(cookie != null)
                builder.addHeader("Cookie", cookie);

            Request request = builder.build();
            Response response = chain.proceed(request);

            // I get the headers, and I check for the cookie. If there is a cookie, I store it
            List<String> cookies = response.headers("set-cookie");
            if(!cookies.isEmpty())
                cookie = cookies.get(0);

            return response;
    }
}

Now it works, and it only checks the token in the first call.

Regards.

0 votes