CXF Swagger2Feature adding securityDefinitions

2019-07-13 17:35发布

问题:

I want to add Security Definition to my rest service using org.apache.cxf.jaxrs.swagger.Swagger2Feature. However I can not see any related method or any resource on how to do it. Below is the swagger doc which I want to generate using swagger2feature. How can I do it?

swagger: '2.0'
info:
  version: 1.0.0
  title: Based on "Basic Auth Example"
  description: >
    An example for how to use Auth with Swagger.

host: basic-auth-server.herokuapp.com
schemes:
  - http
  - https
securityDefinitions:
  Bearer:
    type: apiKey
    name: Authorization
    in: header
paths:
  /:
    get:
      security:
        - Bearer: []
      responses:
        '200':
          description: 'Will send `Authenticated`'
        '403': 
          description: 'You do not have necessary permissions for the resource'

回答1:

I was facing the same problem and I couldn't find a suitable solution with CXF and its api. My solution is the following, create a class that extends the Swagger2Feature of CXF in order to override the addSwaggerResource method, to bound the security definition:

/** Name of the security definition */
public static final String SECURITY_NAME = "Bearer";

/** Extends the Swagger2Feature to use the security definition of Swagger */
@Provider(value = Provider.Type.Feature, scope = Provider.Scope.Server)
public class ExtendedSwagger2Feature extends Swagger2Feature {
    @Override
    protected void addSwaggerResource(Server server, Bus bus) {
        super.addSwaggerResource(server, bus);

        BeanConfig config = (BeanConfig) ScannerFactory.getScanner();
        Swagger swagger = config.getSwagger();
        swagger.securityDefinition(SECURITY_NAME, new ApiKeyAuthDefinition("authorization", In.HEADER));
    }
}

Then, as the Swagger instance has been modified after it has been loaded by the swagger api, you should "re-register" it in the context of the servlet (as I understand when I browsed the code of swagger). Have a look at io.swagger.jaxrs.config.SwaggerContextService. To do this, I had to create a new ServletContextInitializer in my servlet context:

return servletContext -> {
    BeanConfig scanner = (BeanConfig) ScannerFactory.getScanner();
    Swagger swagger = scanner.getSwagger();
    servletContext.setAttribute("swagger", swagger);
};

Putting in the context the Swagger configuration previously modified with the security definition allows the swagger api to take it into account correctly. Without this, our extended Swagger2Feature would not work.

With this changes, I was able to get a swagger.yaml file as the one you are expecting, especially the following part:

securityDefinitions:
  Bearer:
    type: apiKey
    name: Authorization
    in: header

I am using this solution in a Spring Boot application, here is my complete swagger configuration class, in case it helps someone:

package my.package.configuration;

import io.swagger.config.ScannerFactory;
import io.swagger.core.filter.AbstractSpecFilter;
import io.swagger.jaxrs.config.BeanConfig;
import io.swagger.model.ApiDescription;
import io.swagger.models.Operation;
import io.swagger.models.Swagger;
import io.swagger.models.auth.ApiKeyAuthDefinition;
import io.swagger.models.auth.In;
import org.apache.cxf.Bus;
import org.apache.cxf.annotations.Provider;
import org.apache.cxf.endpoint.Server;
import org.apache.cxf.jaxrs.swagger.Swagger2Feature;
import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;

import java.util.List;
import java.util.Map;

/**
 * Configuration of the Swagger API to enable it with CXF.
 */
@Configuration
public class SwaggerConfiguration {

    /** Name of the security definition */
    public static final String SECURITY_NAME = "Bearer";

    @Bean
    public Swagger2Feature swagger() {
        Swagger2Feature feature = new ExtendedSwagger2Feature();
        // Do your stuff with the configuration
        return feature;
    }

    /**
     * Register a custom {@link ServletContextInitializer} in the cxf servlet to expose the custom {@link Swagger2Feature}
     * otherwise the security definition added in the {@link ExtendedSwagger2Feature#addSwaggerResource} will not be
     * used by the swagger api because the original hook occurs during the super call.
     *
     * @see io.swagger.jaxrs.config.SwaggerContextService
     * @see org.apache.cxf.jaxrs.spring.SpringComponentScanServer
     *
     * @return a new instance of the {@link ServletContextInitializer}
     */
    @Bean
    @DependsOn("jaxRsServer")
    public ServletContextInitializer initializer() {
        return servletContext -> {
            BeanConfig scanner = (BeanConfig) ScannerFactory.getScanner();
            Swagger swagger = scanner.getSwagger();
            servletContext.setAttribute("swagger", swagger);
        };
    }

    /**
     * Extension of the {@link Swagger2Feature} because the one provided by CXF doesn't allow to use
     * feature of the Swagger API such as the security definition. This feature use the {@link ApiKeyAuthDefinition}
     * to transport the authorization header required by the application.
     */
    @Provider(value = Provider.Type.Feature, scope = Provider.Scope.Server)
    public static class ExtendedSwagger2Feature extends Swagger2Feature {
        @Override
        protected void addSwaggerResource(Server server, Bus bus) {
            super.addSwaggerResource(server, bus);

            BeanConfig config = (BeanConfig) ScannerFactory.getScanner();
            Swagger swagger = config.getSwagger();
            swagger.securityDefinition(SECURITY_NAME, new ApiKeyAuthDefinition("authorization", In.HEADER));
        }
    }
}


回答2:

I'm not using Spring Boot, but I copied @Naoj's approach. (Thanks!)

For those not on Spring Boot, you can accomplish this in a startup servlet that loads after the CXF servlet. You can also avoid extending the class if you just modify the Swagger instance when you grab it.

So in web.xml:

<servlet>
    <servlet-name>SwaggerServlet</servlet-name>
    <servlet-class>my.package.configuration.SwaggerStartupServlet</servlet-class>
    <load-on-startup>2</load-on-startup>
</servlet>

Then the servlet code:

/** Name of the security definition */
public static final String SECURITY_NAME = "Bearer";

@Override
public void init(final ServletConfig config) throws ServletException
{
    BeanConfig scanner = (BeanConfig) ScannerFactory.getScanner();
    Swagger swagger = scanner.getSwagger();
    swagger.securityDefinition(SECURITY_NAME, new ApiKeyAuthDefinition("Authorization", In.HEADER));
    config.getServletContext().setAttribute("swagger", swagger);
}


标签: java cxf swagger