【问题标题】:How to display required user roles (access control information) in Swagger UI for Spring's endpoints?如何在 Spring 端点的 Swagger UI 中显示所需的用户角色(访问控制信息)?
【发布时间】:2017-09-17 08:56:58
【问题描述】:

我有一个在 Spring 中制作的 rest api,并且正在使用 Swagger 来获取文档。最近实现了基于令牌的身份验证。在令牌中,有(内部)用户的角色(权限)。每个控制器都带有几个 Swagger 注释和一个 @PreAuthorize(some roles..) 注释,如下所示:

@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();
}

现在,我不知道如何在我的 swagger-ui 中显示这些角色,所以每个端点都有信息,访问它需要什么用户角色。我浏览了互联网,发现只有一些非常模糊的信息,其中大部分与 Spring 无关。

注意: 我尝试使用注释:@ApiOperation(value = "Delete user", notes = "Required roles: ADMIN, USER") 来显示自定义文本,但这似乎不是正确的方法。

【问题讨论】:

标签: spring swagger access-control


【解决方案1】:

这是我从@Unni版本开始的个人版本,我只是玩了一点html标记。

@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);
    }
}

这是最终效果图:

【讨论】:

    【解决方案2】:

    如果您需要保留通过注解添加的现有注释并一起添加@Authorisation值,请执行以下操作..

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

    如下创建“OperationNotesResourcesReader”类

    @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);
    }
    }
    

    注意以下变化

    @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));
    

    【讨论】:

      【解决方案3】:

      如果您需要显示所有端点的角色,则无需在上面创建自定义注释等。

      只需创建“OperationNotesResourcesReader”类即可。

      示例控制器类:

      @PostMapping("/api/user")
      @ResponseStatus(HttpStatus.CREATED)
      @PreAuthorize(value = "hasAuthority('" + ROOT_ORG_ADMIN + "')")
      @ApiOperation("Create new Organisation")
      @Authorization(value = ROOT_ORG_ADMIN)
      public ResponseEntity<APIResponse<Data>> create(@Valid @RequestBody OrganisationCreateRequest createRequest, BindingResult bindingResult, HttpServletRequest request) {
      
          RequestContext requestContext = wiseconnectSecurityContextProvider.getRequestContext();
          LOGGER.debug("Create Organisation request received");
          if (bindingResult.hasErrors()) {
              LOGGER.error(INPUT_VALIDATION_ERROR);
              throw new ControllerException(bindingResult, INPUT_VALIDATION_ERROR);
          }
      
          OrganisationView organisationView = organisationService.createOrganisation(createRequest, requestContext);
      
          LOGGER.debug("Created Organisation {} successfully", organisationView.getId());
          APIResponse<Data> apiResponse = new APIResponse<>(CREATE_SUCCESS, "Organisation Created", new Data(organisationView.getId()));
          return new ResponseEntity<>(apiResponse, HttpStatus.CREATED);
      }
      

      然后如下创建“OperationNotesResourcesReader”类

      @Component
      @Order(SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER)
      @Slf4j // or LOGGER
      public class OperationNotesResourcesReader implements springfox.documentation.spi.service.OperationBuilderPlugin {
      
      private final DescriptionResolver descriptions;
      
      @Autowired
      public OperationNotesResourcesReader(DescriptionResolver descriptions) {
          this.descriptions = descriptions;
      }
      
      @Override
      public void apply(OperationContext context) {
          try {
      
              String apiRoleAccessNoteText = "Endpoint Access Privilieges & Ruels : Nil";
              Optional<PreAuthorize> preAuthorizeAnnotation = context.findAnnotation(PreAuthorize.class);
              if (preAuthorizeAnnotation.isPresent()) {
                  apiRoleAccessNoteText = "Endpoint Access Privilieges & Ruels : " + preAuthorizeAnnotation.get().value();
              }
              // 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);
      }
      

      }

      【讨论】:

        【解决方案4】:

        灵感来自 blog.codecentric.de/2018/11/springfoxswaggerextension 文章中的解决方案
        在我们的例子中,我们有由 @Secured 注解修饰的控制器 @Secured("ROLE_Admin")

        我们添加 OperationNotesResourcesReader 组件来为 @ApiRoleAccessNotes 添加评估。
        这里是完整的解决方案。

        控制器

          @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());
          }
        

        新注解

        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 {
        }
        

        然后是 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);
          }
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-01-22
          • 2015-03-10
          • 1970-01-01
          • 2020-07-19
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多