使用 SAP Cloud SDK 时如何处理对 CSRF 令牌的需求?
How do I handle the need for CSFR token when using SAP Cloud SDK?
我正在使用 Java 的 SAP Cloud SDK 对 S/4 中的 SalesOrder API 执行 CRUD。一切正常,因为我可以从 Postman 执行这些操作。但是,只有当我包含一个预请求脚本以获取 csrf 令牌时,Postman 的这些请求才有效,如 this blog post
中所述
如果我 运行 没有博客 post 中概述的预请求脚本的请求,我会收到“403 Forbidden”。正如我所说,它适用于 Postman,但我想了解如何在不需要此脚本的情况下处理它,例如,如果我从另一个应用程序发出请求。 SDK 是否允许我以某种方式从应用程序代码中处理此问题。也许我遗漏了什么。
感谢您的宝贵时间。
编辑:
我不是直接从 Postman 向 S/4 发出请求。我部署了一个应用程序,它使用 Cloud SDK 向 S/4 发出请求。如果我使用预请求脚本获取 CSFR 令牌并将其附加到请求,然后再发送它,它就会工作,但如果我不这样做,则会返回 403。所以,如果我们想象我不是在使用 Postman,而是在某个地方 ui 填写表格并发送此请求,我的理解是我不应该像你建议的那样担心这个令牌,我的服务在使用 SDK 和 VDM 的中间层应该为我处理这个问题。这就是我很难理解的。
这是 servlet 代码:
@Override
protected void doPost(final HttpServletRequest request, final HttpServletResponse response)
throws ServletException, IOException {
String body = IOUtils.toString(request.getReader());
JSONObject so = new JSONObject(body);
String distributionChannel = so.get("DistributionChannel").toString();
String salesOrderType = so.get("SalesOrderType").toString();
String salesOrganization = so.get("SalesOrganization").toString();
String soldToParty = so.get("SoldToParty").toString();
String organizationDivision = so.get("OrganizationDivision").toString();
String material = so.get("Material").toString();
String requestedQuantityUnit = so.get("RequestedQuantityUnit").toString();
SalesOrderItem salesOrderItem = SalesOrderItem.builder()
.material(material)
.requestedQuantityUnit(requestedQuantityUnit).build();
SalesOrder salesOrder = SalesOrder.builder()
.salesOrderType(salesOrderType)
.distributionChannel(distributionChannel)
.salesOrganization(salesOrganization)
.soldToParty(soldToParty)
.organizationDivision(organizationDivision)
.item(salesOrderItem)
.build();
try {
final ErpHttpDestination destination = DestinationAccessor.getDestination(DESTINATION_NAME).asHttp()
.decorate(DefaultErpHttpDestination::new);
final SalesOrder storedSalesOrder = new CreateSalesOrderCommand(destination, new DefaultSalesOrderService(),
salesOrder).execute();
response.setStatus(HttpServletResponse.SC_CREATED);
response.setContentType("application/json");
response.getWriter().write(new Gson().toJson(storedSalesOrder));
logger.info("Succeeded to CREATE {} sales order", storedSalesOrder);
} catch (final Exception e) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
logger.error(e.getMessage(), e);
logger.error("Failed to CREATE sales order", e);
}
}
以及 CreateSalesOrder 命令:
public SalesOrder execute() {
return ResilienceDecorator.executeSupplier(this::run, myResilienceConfig);
}
protected SalesOrder run() {
try {
return salesOrderService.createSalesOrder(salesOrder).execute(destination);
} catch (final ODataException e) {
throw new ResilienceRuntimeException(e);
}
}
我使用的是 3.16.1 版本的 SDK,并且在清单中将 SDK 的日志记录级别设置为 DEBUG:
SET_LOGGING_LEVEL: '{ROOT: INFO, com.sap.cloud.sdk: DEBUG}'
logback 中的日志记录级别为 DEBUG
如果我从请求中删除预请求脚本并发送它,我会收到 403 响应并且日志显示以下消息:
"logger":"com.sap.cloud.sdk.service.prov.api.security.AuthorizationListener","thread":"http-nio-0.0.0.0-8080-exec-4","level":"DEBUG","categories":[],"msg":"Reading
user principal"
"logger":"com.sap.cloud.sdk.service.prov.api.security.AuthorizationListener","thread":"http-nio-0.0.0.0-8080-exec-4","level":"DEBUG","categories":[],"msg":"Destroying Authorization as it is end of request." }
"logger":"com.sap.cloud.sdk.service.prov.api.security.AuthorizationService","thread":"http-nio-0.0.0.0-8080-exec-4","level":"DEBUG","categories":[],"msg":"Destroying Authorization JWT Token." }
墨客
你是对的,使用像 Postman
这样的 API
工具,你必须先发出 HEAD
请求才能获得 CSRF
令牌。
但是,在 Cloud SDK for Java
中,我们 会在您制作任何 时为您获取和刷新 CSRF
令牌CRUD 请求.
下面是一个读取 Saler Oder 项目并在之后更新它的示例:
// Create a new sales order item
SalesOrderItem item = new SalesOrderItem();
item.setSalesOrder(SALES_ORDER);
item.setNetAmount(new BigDecimal(NET_VALUE));
item = service.createSalesOrderItem(item).execute(destination).getResponseEntity().get();
// Modify it with a PATCH update to 9000 net value
item.setNetAmount(new BigDecimal(NET_VALUE_UPDATED));
ModificationResponse<SalesOrderItem> response = service.updateSalesOrderItem(item).modifyingEntity().execute(destination);
尝试一下,如果它适合你,请告诉我们。如果您遇到任何困难,我们很乐意提供帮助。
SDK 尝试在 execute(destination)
内自动获取 CSRF 令牌。这发生在发出实际请求之前。如果尝试成功,令牌将包含在请求中。如果没有,请求将被发送。
如果您认为这没有正确发生,请提高日志级别以调试所有 com.sap.cloud.sdk
包。也很高兴看到实际进出的 HTTP 请求,您可以通过将 org.apache.http.wire
的日志级别也设置为调试来启用这些请求。然后在此处附上堆栈跟踪以及您正在使用的 SDK 版本和您正在调用的确切代码。
由于其他答案侧重于应用程序与 S/4 的沟通,您调整了问题以明确您指的是用户(例如邮递员)与应用程序的沟通,我将提供一些额外的信息。
如其他答案所述,对 S/4 系统(或任何 OData 端点)的 CSRF 处理是在 OData VDM 端自动处理的。
您现在遇到的是 SAP Cloud SDK Maven 原型的安全默认配置,默认情况下 RestCsrfPreventionFilter
处于激活状态。
此过滤器通过要求您在您随后提供的请求之前获取 CSRF 令牌,自动保护所有非 GET
端点免受 CSRF 攻击。
这与后台对 S/4 系统的 OData VDM 调用完全无关。
要解决您的问题,现在需要执行三个后续步骤:
- 使用
GET
端点而不是 POST
- 可能只是临时解决方法
- 暂时从您的
web.xml
中删除 RestCsrfPreventionFilter
- 这应该不用于生产用途,但可能会使您在初始评估中的生活更轻松。
- "Live with it"
- 由于这是保护您的应用程序免受 CSRF 攻击的常用模式,因此建议保留过滤器并根据需要执行 CSRF-Token "flow"。
进一步阅读
- CSRF 的 OWASP 描述:https://owasp.org/www-community/attacks/csrf
- OWASP 在 CSRF 保护上作弊 sheet(链接到过滤器使用的方法):https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#use-of-custom-request-headers
RestCsrfPreventionFilter
的 JavaDoc:https://tomcat.apache.org/tomcat-8.5-doc/api/org/apache/catalina/filters/RestCsrfPreventionFilter.html
我正在使用 Java 的 SAP Cloud SDK 对 S/4 中的 SalesOrder API 执行 CRUD。一切正常,因为我可以从 Postman 执行这些操作。但是,只有当我包含一个预请求脚本以获取 csrf 令牌时,Postman 的这些请求才有效,如 this blog post
中所述如果我 运行 没有博客 post 中概述的预请求脚本的请求,我会收到“403 Forbidden”。正如我所说,它适用于 Postman,但我想了解如何在不需要此脚本的情况下处理它,例如,如果我从另一个应用程序发出请求。 SDK 是否允许我以某种方式从应用程序代码中处理此问题。也许我遗漏了什么。
感谢您的宝贵时间。
编辑:
我不是直接从 Postman 向 S/4 发出请求。我部署了一个应用程序,它使用 Cloud SDK 向 S/4 发出请求。如果我使用预请求脚本获取 CSFR 令牌并将其附加到请求,然后再发送它,它就会工作,但如果我不这样做,则会返回 403。所以,如果我们想象我不是在使用 Postman,而是在某个地方 ui 填写表格并发送此请求,我的理解是我不应该像你建议的那样担心这个令牌,我的服务在使用 SDK 和 VDM 的中间层应该为我处理这个问题。这就是我很难理解的。
这是 servlet 代码:
@Override
protected void doPost(final HttpServletRequest request, final HttpServletResponse response)
throws ServletException, IOException {
String body = IOUtils.toString(request.getReader());
JSONObject so = new JSONObject(body);
String distributionChannel = so.get("DistributionChannel").toString();
String salesOrderType = so.get("SalesOrderType").toString();
String salesOrganization = so.get("SalesOrganization").toString();
String soldToParty = so.get("SoldToParty").toString();
String organizationDivision = so.get("OrganizationDivision").toString();
String material = so.get("Material").toString();
String requestedQuantityUnit = so.get("RequestedQuantityUnit").toString();
SalesOrderItem salesOrderItem = SalesOrderItem.builder()
.material(material)
.requestedQuantityUnit(requestedQuantityUnit).build();
SalesOrder salesOrder = SalesOrder.builder()
.salesOrderType(salesOrderType)
.distributionChannel(distributionChannel)
.salesOrganization(salesOrganization)
.soldToParty(soldToParty)
.organizationDivision(organizationDivision)
.item(salesOrderItem)
.build();
try {
final ErpHttpDestination destination = DestinationAccessor.getDestination(DESTINATION_NAME).asHttp()
.decorate(DefaultErpHttpDestination::new);
final SalesOrder storedSalesOrder = new CreateSalesOrderCommand(destination, new DefaultSalesOrderService(),
salesOrder).execute();
response.setStatus(HttpServletResponse.SC_CREATED);
response.setContentType("application/json");
response.getWriter().write(new Gson().toJson(storedSalesOrder));
logger.info("Succeeded to CREATE {} sales order", storedSalesOrder);
} catch (final Exception e) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
logger.error(e.getMessage(), e);
logger.error("Failed to CREATE sales order", e);
}
}
以及 CreateSalesOrder 命令:
public SalesOrder execute() {
return ResilienceDecorator.executeSupplier(this::run, myResilienceConfig);
}
protected SalesOrder run() {
try {
return salesOrderService.createSalesOrder(salesOrder).execute(destination);
} catch (final ODataException e) {
throw new ResilienceRuntimeException(e);
}
}
我使用的是 3.16.1 版本的 SDK,并且在清单中将 SDK 的日志记录级别设置为 DEBUG:
SET_LOGGING_LEVEL: '{ROOT: INFO, com.sap.cloud.sdk: DEBUG}'
logback 中的日志记录级别为 DEBUG
如果我从请求中删除预请求脚本并发送它,我会收到 403 响应并且日志显示以下消息:
"logger":"com.sap.cloud.sdk.service.prov.api.security.AuthorizationListener","thread":"http-nio-0.0.0.0-8080-exec-4","level":"DEBUG","categories":[],"msg":"Reading user principal"
"logger":"com.sap.cloud.sdk.service.prov.api.security.AuthorizationListener","thread":"http-nio-0.0.0.0-8080-exec-4","level":"DEBUG","categories":[],"msg":"Destroying Authorization as it is end of request." }
"logger":"com.sap.cloud.sdk.service.prov.api.security.AuthorizationService","thread":"http-nio-0.0.0.0-8080-exec-4","level":"DEBUG","categories":[],"msg":"Destroying Authorization JWT Token." }
墨客
你是对的,使用像 Postman
这样的 API
工具,你必须先发出 HEAD
请求才能获得 CSRF
令牌。
但是,在 Cloud SDK for Java
中,我们 会在您制作任何 时为您获取和刷新 CSRF
令牌CRUD 请求.
下面是一个读取 Saler Oder 项目并在之后更新它的示例:
// Create a new sales order item
SalesOrderItem item = new SalesOrderItem();
item.setSalesOrder(SALES_ORDER);
item.setNetAmount(new BigDecimal(NET_VALUE));
item = service.createSalesOrderItem(item).execute(destination).getResponseEntity().get();
// Modify it with a PATCH update to 9000 net value
item.setNetAmount(new BigDecimal(NET_VALUE_UPDATED));
ModificationResponse<SalesOrderItem> response = service.updateSalesOrderItem(item).modifyingEntity().execute(destination);
尝试一下,如果它适合你,请告诉我们。如果您遇到任何困难,我们很乐意提供帮助。
SDK 尝试在 execute(destination)
内自动获取 CSRF 令牌。这发生在发出实际请求之前。如果尝试成功,令牌将包含在请求中。如果没有,请求将被发送。
如果您认为这没有正确发生,请提高日志级别以调试所有 com.sap.cloud.sdk
包。也很高兴看到实际进出的 HTTP 请求,您可以通过将 org.apache.http.wire
的日志级别也设置为调试来启用这些请求。然后在此处附上堆栈跟踪以及您正在使用的 SDK 版本和您正在调用的确切代码。
由于其他答案侧重于应用程序与 S/4 的沟通,您调整了问题以明确您指的是用户(例如邮递员)与应用程序的沟通,我将提供一些额外的信息。
如其他答案所述,对 S/4 系统(或任何 OData 端点)的 CSRF 处理是在 OData VDM 端自动处理的。
您现在遇到的是 SAP Cloud SDK Maven 原型的安全默认配置,默认情况下 RestCsrfPreventionFilter
处于激活状态。
此过滤器通过要求您在您随后提供的请求之前获取 CSRF 令牌,自动保护所有非 GET
端点免受 CSRF 攻击。
这与后台对 S/4 系统的 OData VDM 调用完全无关。
要解决您的问题,现在需要执行三个后续步骤:
- 使用
GET
端点而不是POST
- 可能只是临时解决方法
- 暂时从您的
web.xml
中删除RestCsrfPreventionFilter
- 这应该不用于生产用途,但可能会使您在初始评估中的生活更轻松。
- "Live with it"
- 由于这是保护您的应用程序免受 CSRF 攻击的常用模式,因此建议保留过滤器并根据需要执行 CSRF-Token "flow"。
进一步阅读
- CSRF 的 OWASP 描述:https://owasp.org/www-community/attacks/csrf
- OWASP 在 CSRF 保护上作弊 sheet(链接到过滤器使用的方法):https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#use-of-custom-request-headers
RestCsrfPreventionFilter
的 JavaDoc:https://tomcat.apache.org/tomcat-8.5-doc/api/org/apache/catalina/filters/RestCsrfPreventionFilter.html