1

I have a Spring Boot 3 project and I want to show the required role/permission in the OpenApi UI for my endpoints. I have had this working before, in the past when I used Swagger instead OpenApi, however I am not sure what changes I need to make to get it to work for this.

Here is what I have:

import java.util.Optional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.OperationBuilderPlugin;
import springfox.documentation.spi.service.contexts.OperationContext;
import springfox.documentation.spring.web.DescriptionResolver;
import springfox.documentation.swagger.common.SwaggerPluginSupport;

@Slf4j
@Component
@Order(SwaggerPluginSupport.OAS_PLUGIN_ORDER)// this used to be: SWAGGER_PLUGIN_ORDER for Swagger
@RequiredArgsConstructor
class MyOperationBuilderPlugin implements OperationBuilderPlugin {

  private final DescriptionResolver descriptionResolver;

  @Override
  public void apply(OperationContext context) {
    try {
      String apiRoleAccessNoteText = "Required role: None";
      Optional<PreAuthorize> preAuthorizeAnnotation = context.findAnnotation(PreAuthorize.class);
      if (preAuthorizeAnnotation.isPresent()) {
        apiRoleAccessNoteText =
            "Required role: "
                + preAuthorizeAnnotation
                    .get()
                    .value()
                    .replace("hasAuthority('", "")
                    .replace("')", "");
      }
      context.operationBuilder().notes(descriptionResolver.resolve(apiRoleAccessNoteText));
    } catch (Exception e) {
      log.error("Error when creating swagger documentation for security roles: " + e);
    }
  }

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

The UI looks like this

No role showing

However, it should show something like this (this is for a previous service using Swagger instead of OpenApi) Should look like this

mr nooby noob
  • 1,860
  • 5
  • 33
  • 56

2 Answers2

1

I use a custom annotation @UserRoleDescription with a controller's method as below:

@GetMapping("/bytin/{tin}")
@Secured("ROLE_ADMIN")
@Operation(summary = "Get company by Tax ID Number")
@UserRoleDescription
public CompanyDto getCompanyByTin(@PathVariable String tin) {
    ...
}

It results in a description containing an information about the required role: Swagger UI result here.

Annotation:

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

}

And description customizer:

@Component
public class UserRoleDescriptionCustomizer implements OperationCustomizer {
    @Override
    public Operation customize(Operation operation, HandlerMethod handlerMethod) {
        var annotation = handlerMethod.getMethodAnnotation(UserRoleDescription.class);
        if (annotation != null) {
            var securedAnnotation = handlerMethod.getMethodAnnotation(Secured.class);
            if(securedAnnotation != null) {
                String description = operation.getDescription()==null ? "" : (operation.getDescription()+"\n");
                operation.setDescription(description + "Required role: **"+ String.join("or", securedAnnotation.value()) + "**");
            }
        }
        return operation;
    }
}
0

I propose a workaround for that. As roles in applications are strictly defined we can create a special annotation for each role that will be used for authorization and include @PreAuthorize annotation together with @Operation annotation. @Operation enriches endpoint documentation by custom description.

So create annotation:

@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@PreAuthorize("hasRole('bar-admin')")
@Operation(description = "Required role: bar-admin")
public @interface AuthorizeBarAdmin {
}

Now you can use the @AuthorizeBarAdmin annotation instead @PreAuthorize("hasRole('bar-admin')") and the custom description will be included.

Note: @Operation can be annotated on REST endpoint next to the @Preauthorize anyway, but always you need to remember about the additional annotation and copy the description many times. That's why the above solution is better.

mahh
  • 1