Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to display required user roles (access control information) in Swagger UI for Spring's endpoints?

I have a rest api made in Spring and am using Swagger for documentation. Recently a token based authentication was implemented. In the token, there are (internal) user's roles (authorities). Each controller is annotated with a couple of Swagger annotations and a @PreAuthorize(some roles..) like so:

@ApiOperation("Delete user")
@ApiResponses(value = {
        @ApiResponse(code = 404, message = "User not found", response = ErrorResponse.class)
})
@PreAuthorize("hasAuthority('ADMIN')")
@DeleteMapping(value = "/{id}")
public ResponseEntity<?> deleteUser(@PathVariable UUID id) {
    userService.delete(id);
    return ResponseEntity.ok().build();
}

Now, I have no idea how I can display in my swagger-ui those roles, so each endpoint has information, what user role is required to access it. I have browsed the internet and found only some really vague information, most of it not concerning Spring at all.

Note: I tried using notes: @ApiOperation(value = "Delete user", notes = "Required roles: ADMIN, USER") to display custom text, but this doesn't seem like a right way to go.

like image 974
Jakub Kvba Avatar asked Apr 20 '17 15:04

Jakub Kvba


People also ask

How do I access Swagger endpoints?

In Swagger, click on region : region CRUD operations to list all the available endpoints for accessing regions. In the list of region endpoints, click on the GET /v1 endpoint link. The page displays additional request and response information about the API. Click the Try it out!

Which functionality of Swagger should be used to display in the documentation?

Swagger is a set of open-source tools built around the OpenAPI Specification that can help you design, build, document and consume REST APIs. The major Swagger tools include: Swagger Editor – browser-based editor where you can write OpenAPI specs. Swagger UI – renders OpenAPI specs as interactive API documentation.

What is @API annotation in Swagger?

The Springfox library provides @Api annotation to configure a class as a Swagger resource. Previously, the @Api annotation provided a description attribute to customize the API documentation: @Api(value = "", description = "") However, as mentioned earlier, the description attribute is deprecated.


3 Answers

If you need to keep the existing notes added through annotation and add @Authorisation value together, then do the following..

@ApiOperation(value = "Create new Organisation", notes = "Notes added through annotation")
@Authorization(value = ROOT_ORG_ADMIN)

create "OperationNotesResourcesReader" class as follows

@Component
@Order(SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER + 1)
@Slf4j
public class OperationNotesResourcesReader implements OperationBuilderPlugin {

private final DescriptionResolver descriptions;

@Autowired
public OperationNotesResourcesReader(DescriptionResolver descriptions) {
    this.descriptions = descriptions;
}

@Override
public void apply(OperationContext context) {
    try {
        Optional<ApiOperation> annotation = context.findAnnotation(ApiOperation.class);
        String notes = (annotation.isPresent() && StringUtils.hasText(annotation.get().notes())) ? (annotation.get().notes()) : "";
        String apiRoleAccessNoteText = "<b>Access Privilieges & Ruels : </b> Nil";
        Optional<PreAuthorize> preAuthorizeAnnotation = context.findAnnotation(PreAuthorize.class);
        if (preAuthorizeAnnotation.isPresent()) {
            apiRoleAccessNoteText = "<b>Access Privilieges & Ruels : </b>" + preAuthorizeAnnotation.get().value();
        }
        notes = apiRoleAccessNoteText + " \n\n " + notes;
        // add the note text to the Swagger UI
        context.operationBuilder().notes(descriptions.resolve(notes));
    } catch (Exception e) {
        LOGGER.error("Error when creating swagger documentation for security roles: " + e);
    }
}

@Override
public boolean supports(DocumentationType delimiter) {
    return SwaggerPluginSupport.pluginDoesApply(delimiter);
}
}

Note the following changes

@Order(SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER + 1)

notes = apiRoleAccessNoteText + " \n\n " + notes;
        // add the note text to the Swagger UI
        context.operationBuilder().notes(descriptions.resolve(notes));
like image 63
Unni Avatar answered Nov 08 '22 07:11

Unni


Inspired by solution in the artical blog.codecentric.de/2018/11/springfoxswaggerextension
In our case, we have controller decorated by @Secured annotation @Secured("ROLE_Admin")

We add the OperationNotesResourcesReader component to add evaluation for @ApiRoleAccessNotes.
Here the complete solution.

The Controller

  @ApiRoleAccessNotes
  @Secured("ROLE_Admin")
  @PostMapping
  public ResponseEntity<ResponseRestDTO> createVersion(@RequestBody DraftVersionCreateDTO dto) {
    return ResponseEntity.ok()
        .body(ResponseRestDTO.builder().data(versionService.createVersion(dto))
            .message(messageService.get("version.create.ok")).build());
  }

The new annotation

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiRoleAccessNotes {
}

Then the OperationNotesResourcesReader

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.security.access.annotation.Secured;
import org.springframework.stereotype.Component;
import com.google.common.base.Optional;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.OperationContext;
import springfox.documentation.spring.web.DescriptionResolver;
import springfox.documentation.swagger.common.SwaggerPluginSupport;

@Component
@Order(SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER)
public class OperationNotesResourcesReader
    implements springfox.documentation.spi.service.OperationBuilderPlugin {
  private final DescriptionResolver descriptions;

  final static Logger logger = LoggerFactory.getLogger(OperationNotesResourcesReader.class);

  @Autowired
  public OperationNotesResourcesReader(DescriptionResolver descriptions) {
    this.descriptions = descriptions;
  }

  @Override
  public void apply(OperationContext context) {
    try {
      Optional<ApiRoleAccessNotes> methodAnnotation =
          context.findAnnotation(ApiRoleAccessNotes.class);
      if (!methodAnnotation.isPresent()) {
        // the REST Resource does not have the @ApiRoleAccessNotes annotation -> ignore
        return;
      }

      String apiRoleAccessNoteText;
      // get @Secured annotation
      Optional<Secured> securedAnnotation = context.findAnnotation(Secured.class);
      if (!securedAnnotation.isPresent()) {
        apiRoleAccessNoteText = "Accessible by all user roles";
      } else {
        apiRoleAccessNoteText = "Accessible by users having one of the following roles: ";
        Secured secure = securedAnnotation.get();
        for (String role : secure.value()) {
          // add the roles to the notes. Use Markdown notation to create a list
          apiRoleAccessNoteText = apiRoleAccessNoteText + "\n * " + String.join("\n * ", role);
        }
      }
      // add the note text to the Swagger UI
      context.operationBuilder().notes(descriptions.resolve(apiRoleAccessNoteText));
    } catch (Exception e) {
      logger.error("Error when creating swagger documentation for security roles: " + e);
    }
  }

  @Override
  public boolean supports(DocumentationType delimiter) {
    return SwaggerPluginSupport.pluginDoesApply(delimiter);
  }
}
like image 33
cuongvm Avatar answered Nov 08 '22 06:11

cuongvm


This is my personal version started from @Unni version, I just played a little bit with html markup.

@Component
@Order(SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER + 1)
public class OperationNotesResourcesReader implements OperationBuilderPlugin {
    private static final Logger LOG = Logger.getLogger(OperationNotesResourcesReader.class.getName());

    private final DescriptionResolver descriptions;

    @Autowired
    public OperationNotesResourcesReader(DescriptionResolver descriptions) {
        this.descriptions = descriptions;
    }

    @Override
    public void apply(OperationContext context) {
        try {
            StringBuilder sb = new StringBuilder();

            // Check authorization
            Optional<PreAuthorize> preAuthorizeAnnotation = context.findAnnotation(PreAuthorize.class);
            sb.append("<b>Access Privileges & Rules</b>: ");
            if (preAuthorizeAnnotation.isPresent()) {
                sb.append("<em>" + preAuthorizeAnnotation.get().value() + "</em>");
            } else {
                sb.append("<em>NOT_FOUND</em>");
            }

            // Check notes
            Optional<ApiOperation> annotation = context.findAnnotation(ApiOperation.class);
            if (annotation.isPresent() && StringUtils.hasText(annotation.get().notes())) {
                sb.append("<br /><br />");
                sb.append(annotation.get().notes());
            }

            // Add the note text to the Swagger UI
            context.operationBuilder().notes(descriptions.resolve(sb.toString()));
        } catch (Exception e) {
            LOG.log(Level.SEVERE, "Error when creating swagger documentation for security roles: ", e);
        }
    }

    @Override
    public boolean supports(DocumentationType delimiter) {
        return SwaggerPluginSupport.pluginDoesApply(delimiter);
    }
}

This is the final rendering: enter image description here

enter image description here

like image 43
Deviling Master Avatar answered Nov 08 '22 08:11

Deviling Master