Create Service User in AEM As Cloud Service

Learn how to create and use Service Users or System Users in AEM as cloud service environment. In this tutorial we will be using Repository initialization (repoinit) feature and below mentioned best practices are valid for AEM 6.5.4+ or AEM as a Cloud Service version.

The purpose for writing this tutorial is to show the best practices that we should follow when creating a service user in AEM (Adobe Experience Manager). After completing this tutorial you should be able to answer below questions :-

Create Repo Init Configuration for service user:-

Sling Repository Initialiser factory allows us to create paths, users, groups, assign users to groups and provide privileges and restrictions to the users, ensuring that required structure, user & permissions are available before they are referenced in our code during deployment.

Before writing any java code, we need to make sure that we have required service-user available. So let’s define one and for this we will use sling repository initialization, or repoinit. We will use a repoinit script defined in an OSGI configuration that declares our service-user and its permissions.

We will create a new OSGI configuration file for the sling repository initializer and this follows the usual conventions. So the file name will start off with org.Apache.sling.JCR.repoinit.repositoryInitializer, and since this is a factory configuration, we’ll follow that up with a tilde, and then postfix this configuration with a unique name. Let’s say wknd-examples-statistics.

Note:- It is recommended to use Tilde sign between OOTB class name org.Apache.sling.JCR.repoinit.repositoryInitializer and our custom unique name, because Adobe uses the same and I personally also use same for older version of AEM you can also use dash (-) as separator. If we don’t add this unique seperator (tilda or dash) configurations will get deployed properly but it will not be picked in our code base. using tilde sign or dash only adobe distinguish between factory configuration and sub service name.

org.apache.sling.jcr.repoinit.RepositoryInitializer~aemcq5tutorials-examples.cfg.json

{
    "scripts": [
"create service user aemcq5tutorials-examples-service with forced path system/cq:services/aemcq5tutorials",
"set principal ACL for aemcq5tutorials-examples-service \nallow jcr:read on /content/dam \nend"
]
}

Note:- Service Users MUST be created under system/cq:services to be compatible with AEMaaCS (AEMaaCS supports principal-based authorization by default only for all users below /home/users/system/cq:services). 

Note:- We are setting principal ACL’s, instead of regular repository ACL’s. And what this does, is it sets the ACL’s for /content/dam under the service-user’s node, rather than polluting the AEM repository’s /content dam rep policy node with more allow and deny roles. Also, in order to use the set principal ACL, the user must be created under system/cq:services. If it’s not under system/cq:services, the principal ACL’s will not be set

Create Service User Mapper configuration for service user

Our service user is created, Now let’s map the service-user with sling, so it can be referenceable under Java code. For this, we need another OSGI config, and we’ll place this config.author as well, since that’s where the service-user’s defined. This’ll be an OSGI config for sling’s service-user mapper. So, let’s go ahead and name the file org.Apache.sling.ServiceUserMapping.impl.

Since this OSGI configuration files in the cfg.json format, we’ll create a json object with the key user.mapping, whose value is a string array. In each element in the array, we’ll map a service and sub-service ID to a set of service-users. The format of each mapping is the service name, which is the OSGI bundle symbolic name that will use this service-user, and is usually the core project’s Maven artifact ID. So for us, it will be aemcq5tutorials-examples.core.

Create file org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended-aemcq5tutorials-examples.cfg.json and place it under /ui.config/src/main/content/jcr_root/apps/aemcq5tutorials-examples/osgiconfig/config.author/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended-aemcq5tutorials-examples.cfg.json

{
  "user.mapping": [
    "aemcq5tutorials-examples.core:aemcq5tutorials-examples=[aemcq5tutorials-examples-service]"
  ]
}

In above code sample:-

aemcq5tutorials-examples.core = Service [Bundle symbolic name] , You might not have .core in your maven artifact Id, i have used .core just to differentiate and show that this is my aemcq5tutorials-examples.core or Maven artifact ID.

aemcq5tutorials-examples , This is our Sub Service ID

aemcq5tutorials-examples-service, Service User

Note:- You can have same Sub Service ID and Service User, so that it is easy to use in our code. I am using different so that i can explain where which ID we have to refer.

Access service User in basic servlet

Let’s create our servlet , where we are going to use service user.

Note:- On Line Number 42, name can be any unique name but I recommend just making it the subservice ID for clarity.

Note:- Please update resourceType and extension as per your code structure. You can also register a servlet based on path instead of resourceType.

Let’s create our SimpleServiceUserExampleServlet under /core/src/main/java/com/aemcq5tutorials-examples/core/servlets/SimpleServiceUserExampleServlet.java

package com.aemcq5tutorials-examples.core.servlets;

import com.day.cq.commons.jcr.JcrConstants;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.LoginException;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.api.servlets.HttpConstants;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import org.apache.sling.api.resource.ValueMap;
import org.osgi.framework.Constants;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.metatype.annotations.Designate;
import org.apache.sling.serviceusermapping.ServiceUserMapped;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import java.io.IOException;

/**
 * Servlet that writes some sample content into the response. It is mounted for
 * all resources of a specific Sling resource type. The
 * {@link SlingSafeMethodsServlet} shall be used for HTTP methods that are
 * idempotent. For write operations use the {@link SlingAllMethodsServlet}.
 */
@Component(service=Servlet.class,
           property={
                   Constants.SERVICE_DESCRIPTION + "=Simple Demo Servlet",
                   "sling.servlet.methods=" + HttpConstants.METHOD_GET,
                   "sling.servlet.resourceTypes="+ "aemcq5tutorials-examples/components/structure/page",
                   "sling.servlet.extensions=" + "txt"
           },
@Reference(
            name = "aemcq5tutorials-examples",
            service = ServiceUserMapped.class,
            target = "(subServiceName=aemcq5tutorials-examples)")))
public class SimpleServiceUserExampleServlet extends SlingSafeMethodsServlet {

    private static final long serialVersionUID = 1L;

    @Override
    protected void doGet(final SlingHttpServletRequest req,
            final SlingHttpServletResponse resp) throws ServletException, IOException {
        // Create the Map that specifies the SubService ID
        final Map<String, Object> authInfo = Collections.singletonMap(
                ResourceResolverFactory.SUBSERVICE,
                "wknd-examples-statistics");

        // Get the auto-closing Service resource resolver
        // Remember, any ResourceResolver you get, you must ensure is closed!
        try (ResourceResolver serviceResolver = resourceResolverFactory.getServiceResourceResolver(authInfo)) {
            // Do some work with the service user's resource resolver and underlying resources.
            // Make sure to do all work that relies on the AEM repository access in the try-block, since the serviceResolver will auto-close when it's left
             final Resource resource = req.getResource();
        resp.setContentType("text/plain");
        resp.getWriter().write("Title = " + resource.getValueMap().get(JcrConstants.JCR_TITLE));
        } catch (LoginException e) {
            log.error("Login Exception when obtaining a User for the Bundle Service: {} ", e);
        }
    }
}

Repository Initializer Examples:-

Create paths in AEM using Repository Initializer

{
    "scripts": [
        "create path (sling:OrderedFolder) /content/dam/aemcq5tutorials",
        "create path (nt:unstructured) /content/dam/aemcq5tutorials/jcr:content"
]
}

Create User Groups in AEM using Repository Initializer

{
    "scripts": [
        "create group aemcq5tutorialsgroup with path aemcq5tutorials",
        "set ACL for aemcq5tutorialsgroup \ndeny jcr:all on /content/dam restriction(rep:glob,/*) \nallow rep:readNodes,rep:readProperties on /content/dam restriction(rep:glob,/content/dam/jcr:content*)  \nend"
]
}

Create NameSpace in AEM using Repository Initializer

We will create a new OSGI configuration file for the sling repository initializer and this follows the usual conventions. So the file name will start off with org.Apache.sling.JCR.repoinit.repositoryInitializer, and since this is a factory configuration, we’ll follow that up with a tilde, and then postfix this configuration with a unique name. Let’s say aemcq5tutorials-namespace.

So, the final name will look like org.Apache.sling.JCR.repoinit.repositoryInitializer~aemcq5tutorials-namespace

{
    "scripts": [
        "register namespace (customnamespace) https://aemcq5tutorials.com/custom.namespace/"
    ]
}
Spread the love

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.