Post 对 sling servlet 的请求不适用于发布实例

Post request on sling servlet not working on publish instance

昨天,我在 AEM 6.3 中编写 sling post servlet 时遇到问题,注册资源类型 属性。实际上我的代码适用于作者实例,但不适用于发布实例。下面的代码使用用户给定的数据在项目的内容路径上创建一个节点和 属性。 (忽略下面代码中的导入语句和分号,写在groovy)

我不确定,通过使用 SlingHttpServletRequest 的实例并获取会话来解析资源是否是一个好习惯?

此外,我还能找到 session.save() 或 resolver.commit 之间的任何区别。

有人可以帮忙吗?

@SlingServlet(
    resourceTypes = ["app/project/components/formComp"],
    extensions = ['json'],
    methods = "POST")
@Properties([
    @Property(name = "Code ABC", value = 'Project ABC'),
    @Property(name = "Description ABC", value = 'servlet form')])
@CompileStatic
@Slf4j
class PostFormServlet extends SlingAllMethodsServlet {

    ResourceResolver resolver
    Session session

    @Override
    void doPost(SlingHttpServletRequest request, SlingHttpServletResponse response) {

        String myNodePath = 'content/project/en/mynode'
        String requestParam = 'param'
        try {
            resolver = request.getResourceResolver()
            session = resolver.adaptTo(Session)
            Node root = session.getRootNode()

            Node myDestinationNode
            if (rootNode.hasNode(myNodePath)) {
                myDestinationNode = rootNode.getNode(myNodePath)
            } else {
                myDestinationNode = rootNode.addNode(myNodePath, NT_UNSTRUCTURED)
            }
            String paramValue = request.getParameter(requestParam)

            if (myDestinationNode) {
                Date date = new Date();
                timeStamp = date.getTime() as String
                Node dateTimeNode = myDestinationNode.addNode(timeStamp, NT_UNSTRUCTURED)
                if (dateTimeNode) {
                    dateTimeNode.setProperty(requestParam, paramValue)
                }
            }
            session.save()
        } catch (Exception ex) {
           //log error
        }


        response.contentType = 'application/json'
        response.getWriter().write("Node Created")
    }
}

如果 POST 请求是以管理员用户身份发出的,则您的 Servlet 可以正常工作。所以脚本解析没问题,就是权限问题

分几层检查权限:

权限检查 - 调度员: Dispatcher 可能不允许 POST-请求某些路径,或者需要某种身份验证,或者只是从请求中删除一些查询参数或其他有效负载。但是很容易识别错误消息是来自 Apache 还是来自 Publisher。否则,您可以通过 Dispatcher 发送一次 POST 请求,一次直接发送给 Publisher。如果结果不同,那么你必须先处理 Dispatcher。

权限检查 - Apache Sling 身份验证服务: 这主要与作者相关,因为默认情况下发布者非常开放。该服务简单地说,哪些路径可以作为匿名访问(意味着无需任何身份验证),以及用户被转发到登录页面的哪些路径。可以通过 OSGi 或直接在您的 Servlet 中指定 sling.auth.requirements 属性 来配置此服务。身份验证服务将读取此类属性,并将其视为自己的配置。如前所述,它主要与作者相关——因为默认情况下只有登录页面无需身份验证即可访问。

权限检查 - 内容资源: 如果您通过 SlingResourceType 注册您的 Servlet,则脚本解析过程需要对所请求资源的读取权限。在 Publisher 中,/content/...-tree 对于 Anonymous 通常是可读的,但不是像 /app/... 这样的东西。如果您在路径上注册您的 Servlet,则可以轻松避免这种情况。对于这样的 servlet,没有 Sling 权限检查(好坏,取决于你想要什么)。


Servlet 的权限

以上权限检查是相关的,你的Servlet被调用了。但是如果被调用,Servlet 仍然只有调用用户的读写权限。这是非常非常少的匿名(读取/内容/...,没有写入权限)。

因此您需要与服务用户打开一个新会话。

public class TestServlet extends SlingAllMethodsServlet {

    @Reference
    private ResourceResolverFactory resolverFactory;

    @Override
    protected void doPost(@Nonnull SlingHttpServletRequest request, @Nonnull SlingHttpServletResponse response) throws ServletException, IOException {

        final Map<String, Object> authenticationInfo = Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, "testservlet");
        try (ResourceResolver resolver = resolverFactory.getServiceResourceResolver(authenticationInfo)) {

            Resource rootRes = resolver.getResource("/content/....");
            resolver.create(rootRes, "test", null);
            resolver.commit();

        } catch (Exception e) {
            response.setContentType("text/plain");
            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            e.printStackTrace(response.getWriter());
        }
    }
}

一开始会说org.apache.sling.api.resource.LoginException: Cannot derive user name for bundle ....

您需要:

  1. 创建系统用户(普通用户不行!)
  2. 添加服务用户映射 (OSGi-config)
  3. 授予您的系统用户read/write权限

要么按照这些说明操作:

https://helpx.adobe.com/experience-manager/6-3/sites/administering/using/security-service-users.html

或者使用工具创建服务用户并为他们授予权限。要么你已经使用了一个,要么我推荐 https://github.com/Netcentric/accesscontroltool。映射仍然需要 OSGi 配置。


如果第一次试用服务用户太复杂,您仍然可以使用已弃用的 ResourceResolver resolver = resolverFactory.getAdministrativeResourceResolver(null)。它不安全,因此已弃用。您只需通过 OSGi 配置将您的捆绑包列入白名单(Apache Sling 登录管理白名单 - 其他捆绑包)


最后一个问题:

difference between session.save() or resolver.commit

解析器是 Jackrabbit-Oak 会话周围的 Sling-wrapper。所以 resolver.commit() 正在自动调用 session.save() (但不是相反)。

强烈建议尽可能使用最高级别的 API,不要将高级与低级别的 API 混用。可能会出现例外情况 - 但不适合初学者。 (例如,PageManager-API 建立在 Slings Resource-API 之上,而 Slings Resource-API 建立在 Jackrabbit OAK 的 Node-API 之上。困难在于要知道哪个 API的存在)