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.
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!
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.
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.
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));
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);
}
}
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:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With