How to develop an HTTP POST method in Java Classes - REST Resources (Extend BasePluginResource)?

Which IIQ version are you inquiring about?

8.4

Problem Description

We have a simple plugin that exposes several APIs, the problem is to build the POST method correctly.
We have:

	@POST
	@Path("/search/paginatedResults")
	@Produces(MediaType.APPLICATION_JSON)
	@Consumes(MediaType.APPLICATION_JSON)
    public Response getPaginatedJobRuns(PaginatedJobRequestDto requestBody) {
        try {
            // Ottieni i parametri dal DTO che rappresenta il body JSON
            int page = requestBody.getPagination().getPage();
            int itemsxPage = requestBody.getPagination().getItemsxPage();

            String startDate = requestBody.getFilters() != null ? requestBody.getFilters().getStartDate() : null;
            String endDate = requestBody.getFilters() != null ? requestBody.getFilters().getEndDate() : null;
            String jobName = requestBody.getFilters() != null ? requestBody.getFilters().getId() : null;
            String column = requestBody.getOrders() != null ? requestBody.getOrders().getColumn() : null;
            String sort = requestBody.getOrders() != null ? requestBody.getOrders().getSort() : null;

            // Chiamata al service per elaborare la richiesta
            PaginatedJobRunsDto jsonResponse = getSearchService().getPaginatedJobRuns(jobName, page, 
            		itemsxPage, startDate, endDate, column, sort);
            
         // Recupero i dati dal servizio
//            PaginatedJobRunsDto response = getSearchService().getPaginatedJobRuns(
//                    searchRequest.getJobName(),X->5
//                    searchRequest.getPagination().getPage(),X
//                    searchRequest.getPagination().getItemsPerPage(),X
//                    searchRequest.getFilters().getStartDate(),X
//                    searchRequest.getFilters().getEndDate(),X
//                    searchRequest.getOrders().getColumn(),X
//                    searchRequest.getOrders().getSort()X
//            );

            // Ritorna una risposta con status OK e il risultato JSON
            return Response.status(Response.Status.OK).entity(jsonResponse).build();
        } catch (Exception e) {
            e.printStackTrace();

            // Gestione dell'errore e risposta con status 500
            return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
                    .entity("{\"error\":\"Errore durante la gestione della richiesta.\"}").build();
        }
    }

This method is in my class with the com.acme.training.rest.SearchResource package and extends BasePluginResource

`import sailpoint.rest.plugin.BasePluginResource;

@Path(“DashboardPlugin”)
@Produces(“application/json”)
@Consumes(“application/json”)
@AllowAll
public class SearchResource extends BasePluginResource{
…
}`

The API returns “405 Method Not Allowed” when testing in Postman as follows:
URL:
POST: http://localhost:8080/iiq/plugin/rest/DashboardPlugin/search/paginatedResults
HEADERS:
Content-Type: application/json
SCREEN BODY JSON:

Unlike all the other APIs (GET) that work fine when installed on IdentityIQ.
The path indicated is correct, I don’t know what else to do to make my HTTP POST method work.

Could you help me?

The issue you’re encountering is likely due to the leading slash (/) in your method-level @Path annotation. In JAX-RS, when a method-level @Path starts with a /, it is considered an absolute path and replaces the class-level @Path. This means your method is being mapped to /search/paginatedResults instead of /DashboardPlugin/search/paginatedResults.

Here’s how you can fix it:

  1. Remove the Leading Slash in the Method-Level @Path AnnotationChange your method annotation from:
@POST
@Path("/search/paginatedResults")

To:

@POST
@Path("search/paginatedResults")

This ensures the method-level path is appended to the class-level path, resulting in the correct endpoint.
2. Verify the Class-Level @Path AnnotationEnsure your class-level @Path does not start with a leading slash, as it should be relative:

@Path("DashboardPlugin")
@Produces("application/json")
@Consumes("application/json")
@AllowAll
public class SearchResource extends BasePluginResource {
    // ...
}
  1. Confirm the Full Endpoint URLWith these changes, your full endpoint URL becomes:
http://localhost:8080/iiq/plugin/rest/DashboardPlugin/search/paginatedResults

This matches the URL you’re using in Postman.
4. Double-Check Server ConfigurationEnsure that your server or web application (e.g., SailPoint IdentityIQ) is not restricting the POST method for this endpoint. Sometimes, server configurations or security settings can prevent certain HTTP methods from being used.
5. Test the Endpoint AgainAfter making these changes, redeploy your application and test the endpoint in Postman:

  • URL: http://localhost:8080/iiq/plugin/rest/DashboardPlugin/search/paginatedResults
  • Method: POST
  • Headers: Content-Type: application/json
  • Body: Include a valid JSON body that matches PaginatedJobRequestDto

By removing the leading slash, your method should now correctly handle POST requests, and the 405 error should be resolved.

1 Like

I removed the leading slash (/) in the @Path annotation at the method level as shown in the figure:


The call keeps returning “405 Method Not Allowed” , the configurations to test the POST call in Postman are exactly the shared ones, I don’t think it’s a problem with the JSON body, I explicitly report what I pass to it in the call:

{
	"pagination": {
		"page": 2,
		"itemsxPage": 10
	},
	"filters": {
		"startDate": "2024-01-02",
		"endDate": "2024-01-03",
		"id": "XM3B10"
	},
	"orders": {
		"column": "APP_NAME",
		"sort": "ASC"
	}
}

we defined the Request in code as follows:

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonPropertyOrder({
"pagination",
"filters",
"orders"
})
public class PaginatedJobRequestDto extends BaseDto implements Serializable {

private static final long serialVersionUID = 1L;

@JsonProperty("pagination")
private PaginationDto pagination;

@JsonProperty("results")
private PaginationFiltersDto filter; // Questo può essere null

@JsonProperty("orders")
private PaginationOrdersDto orders; // Questo può essere null

}

where PaginationDto is:

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonPropertyOrder({
"page",
"itemsxPage"
})
public class PaginationDto extends BaseDto implements Serializable {

private static final long serialVersionUID = 1L;

@JsonProperty("page")
private int page;

@JsonProperty("itemsxPage")
private int itemsxPage;

}

where PaginationFiltersDto is:

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonPropertyOrder({
"startDate",
"endDate",
"id"
})
public class PaginationFiltersDto extends BaseDto implements Serializable {

private static final long serialVersionUID = 1L;

@JsonProperty("startDate")
private String startDate;

@JsonProperty("endDate")
private String endDate;

@JsonProperty("id")
private String id;

}

where PaginationOrdersDto is:

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonPropertyOrder({
"column",
"sort"
})
public class PaginationOrdersDto extends BaseDto implements Serializable {

private static final long serialVersionUID = 1L;

@JsonProperty("column")
private String column;

@JsonProperty("sort")
private String sort;

}

otherwise I don’t know where it can generate this kind of error code, note that I generated a “similar” GET call, with more or less the same behavior, and it correctly returns 200 ok. The related GET call is the following:

@GET
	@Path("search/paginatedResults/jobName/{jobName}")
	@Produces({MediaType.APPLICATION_JSON})
	@AllowAll
	public Response getPaginatedJobRuns(
	        @PathParam("jobName") String jobName,
	        @QueryParam("page") @DefaultValue("1") int page,
	        @QueryParam("pageSize") @DefaultValue("10") int pageSize,
	        @QueryParam("startDate") @DefaultValue("") String startDate,
	        @QueryParam("endDate") @DefaultValue("") String endDate) throws GeneralException {

	    try {
	        logger.info("##### START method 'getPaginatedJobRuns' #####");

	        // Otteniamo i dati paginati dal servizio
	        PaginatedJobRunsDto response = getSearchService().getPaginatedJobRuns(jobName, page, pageSize, startDate, endDate);

	        response.setStatus(StatusConstants.OK);
	        logger.info("##### END method 'getPaginatedJobRuns' - SUCCESS #####");

	        return Response.status(Status.OK).entity(response).build();
	    } catch (RuntimeException e) {
	        PaginatedJobRunsDto response = new PaginatedJobRunsDto();
	        logger.error("RuntimeException: " + e);
	        response.setMsg(e.getMessage());
	        response.setStatus(StatusConstants.KO);

	        return Response.status(Status.BAD_REQUEST).entity(response).build();
	    } catch (Exception e) {
	        PaginatedJobRunsDto response = new PaginatedJobRunsDto();
	        logger.error("Exception: " + e);
	        response.setMsg("Problema durante l'esecuzione: " + e);
	        response.setStatus(StatusConstants.KO);

	        return Response.status(Status.SERVICE_UNAVAILABLE).entity(response).build();
	    }
	}

otherwise I don’t know where it can generate this kind of error code, note that I generated a “similar” GET call, with more or less the same behavior, and it correctly returns 200 ok. The related GET call is the following.

Besides this I don’t know where to verify that the server or web application (for example, SailPoint IdentityIQ) is not limiting the POST method for this endpoint. It’s the only point to check the other details seem aligned to me.

I add that I noticed on the “web.xml” file of Tomcat 9.0 that there are the following lines of filters that limit the blocks of POST requests such as CORS/CSRF filters:

<!--
  REST CSRF validation filter. Remove the following filter and filter mappings to disable CSRF validation for 
  REST apis.
-->
  
  <filter>
    <filter-name>restCsrfValidationFilter</filter-name>
    <filter-class>
      sailpoint.rest.RestCsrfValidationFilter
    </filter-class>
    <!-- The list of GET paths to ignore in Csrf validation. Note: POST and PUT paths are never ignored. -->
    <init-param>
      <param-name>ignoredPaths</param-name>
      <param-value>/rest/report, /rest/image, /ui/rest/redirect, /ui/rest/certifications/export, ui/rest/redirect/hash, /ui/rest/accessHistory/export</param-value>
    </init-param>
  </filter>

  <filter-mapping>
    <filter-name>restCsrfValidationFilter</filter-name>
    <url-pattern>/rest/*</url-pattern>
  </filter-mapping>

  <filter-mapping>
    <filter-name>restCsrfValidationFilter</filter-name>
    <url-pattern>/ui/rest/*</url-pattern>
  </filter-mapping>

  <filter-mapping>
    <filter-name>restCsrfValidationFilter</filter-name>
    <url-pattern>/plugin/rest/*</url-pattern>
  </filter-mapping>

Reading the following comment “” these lines could continue to block only POST (and PUT) calls ?? what should be added to the filters to be able to correctly perform POST calls on the Sailpoint plugin without blocks? I noticed online the following suggestion, to add the following filters or just add a specific parameter on the filters that I already have on the web.xml file.

<filter>
    <filter-name>CSRFProtectionFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    <init-param>
        <param-name>enabled</param-name>
        <param-value>false</param-value>
    </init-param>
</filter>

@ffalcitelli -
This may be due to the CSRF (Cross-Site Request Forgery) protection enforced by SailPoint IdentityIQ’s RestCsrfValidationFilter. This filter is designed to prevent CSRF attacks by validating requests to REST endpoints, and it can block POST and PUT requests if they do not include a valid CSRF token.

Understanding the Issue:

  • GET Requests Work: Your GET methods work fine because the CSRF filter allows GET requests without validation or they are ignored via the ignoredPaths parameter.
  • POST Requests Fail with 405: Your POST request returns a 405 Method Not Allowed
    may be because the CSRF filter is blocking it due to a missing or invalid CSRF token.

Why the CSRF Filter is Blocking Your Request:

In your web.xml, you have the following configuration:

<!--
  REST CSRF validation filter. Remove the following filter and filter mappings to disable CSRF validation for 
  REST apis.
-->

<filter>
  <filter-name>restCsrfValidationFilter</filter-name>
  <filter-class>
    sailpoint.rest.RestCsrfValidationFilter
  </filter-class>
  <!-- The list of GET paths to ignore in Csrf validation. Note: POST and PUT paths are never ignored. -->
  <init-param>
    <param-name>ignoredPaths</param-name>
    <param-value>/rest/report, /rest/image, /ui/rest/redirect, /ui/rest/certifications/export, ui/rest/redirect/hash, /ui/rest/accessHistory/export</param-value>
  </init-param>
</filter>

<filter-mapping>
  <filter-name>restCsrfValidationFilter</filter-name>
  <url-pattern>/rest/*</url-pattern>
</filter-mapping>

<filter-mapping>
  <filter-name>restCsrfValidationFilter</filter-name>
  <url-pattern>/ui/rest/*</url-pattern>
</filter-mapping>

<filter-mapping>
  <filter-name>restCsrfValidationFilter</filter-name>
  <url-pattern>/plugin/rest/*</url-pattern>
</filter-mapping>
  • The filter applies to /plugin/rest/*, which includes your endpoint.
  • According to the comment, POST and PUT methods are never ignored by the ignoredPaths parameter.
  • Therefore, any POST request to your REST endpoints must pass CSRF validation.

For Testing purpose, we can Include a Valid CSRF Token in Your POST Request

To satisfy the CSRF filter, you need to include a valid CSRF token in your POST request.

Steps to Obtain and Include the CSRF Token:

  1. Make an Initial GET Request to Obtain the CSRF Token:
  • Perform a GET request to any endpoint (e.g., your login endpoint or any public API).
  • The CSRF token is typically provided in a cookie named CSRF-TOKEN or in a response header. For Testing purpose you can use chrome developer tool.
  1. Extract the CSRF Token:
  • In Postman, after the GET request, go to the Cookies tab and look for CSRF-TOKEN.
  • Alternatively, check the Headers tab for any CSRF token information.
  1. Include the CSRF Token in Your POST Request:
  • Add a Header to Your POST Request:
CSRF-TOKEN: [your_csrf_token_value]
  • Ensure Cookies are Sent:
    • In Postman, enable the option to Automatically follow redirects and Send cookies.
  • Include the CSRF Token as a Cookie (if required):
    • Add the CSRF-TOKEN cookie with the obtained value.

Let me know the Outcome.

1 Like

This topic was automatically closed 60 days after the last reply. New replies are no longer allowed.