java 在 Swagger 中记录 Spring 的登录/注销 API

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/34386337/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 22:50:12  来源:igfitidea点击:

Documenting Spring's login/logout API in Swagger

javaspring-bootswaggerswagger-uispringfox

提问by Maciej Dobrowolski

I am developing demo REST service using Spring Bootwhere user has to login in order to to perform certain subset of operations. After adding Swagger UI(using springfoxlibrary) with that simple configuration:

我正在开发演示 REST 服务,使用Spring Boot用户必须登录的位置才能执行某些操作子集。Swagger UI使用springfox该简单配置添加(使用库)后:

@Bean
public Docket docApi() {
    return new Docket(DocumentationType.SWAGGER_2)
            .select()
                .apis(any())
                .paths(PathSelectors.ant("/api/**"))
                .build()
            .pathMapping("/")
            .apiInfo(apiInfo())
            .directModelSubstitute(LocalDate.class, String.class)
            .useDefaultResponseMessages(true)
            .enableUrlTemplating(true);
}

I end up with all apis with all operations listed on Swagger UIpage. Unfortunately I don't have login/logout endpoints listed among them.

我最终得到了Swagger UI页面上列出的所有操作的所有 api 。不幸的是,我没有在其中列出登录/注销端点。

The problem is that part of that operations cannot be performed via Swagger UIbuilt-in form (I find it really nice feature and would like make it work), because user is not logged in. Is there any solution to that problem? Can I define manually some endpoints in Swagger?

问题是该操作的一部分无法通过Swagger UI内置表单执行(我发现它非常好的功能并想让它工作),因为用户没有登录。有没有办法解决这个问题?我可以手动定义一些端点Swagger吗?

If there was a form to submit credentials (i.e. login/logout endpoints) I could perform authorization before using that secured endpoints. Then, Swaggeruser could extract token/sessionidfrom response and paste it to custom query parameter defined via @ApiImplicitParams.

如果有提交凭据的表单(即登录/注销端点),我可以在使用该安全端点之前执行授权。然后,Swagger用户可以token/sessionid从响应中提取并将其粘贴到通过@ApiImplicitParams.

Below you can find my security configuration:

您可以在下面找到我的安全配置:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
            .formLogin()
                .loginProcessingUrl("/api/login")
                .usernameParameter("username")
                .passwordParameter("password")
                .successHandler(new CustomAuthenticationSuccessHandler())
                .failureHandler(new CustomAuthenticationFailureHandler())
                .permitAll()
                .and()
            .logout()
                .logoutUrl("/api/logout")
                .logoutSuccessHandler(new CustomLogoutSuccessHandler())
                .deleteCookies("JSESSIONID")
                .permitAll()
                .and()
            .csrf()
                .disable()
            .exceptionHandling()
                .authenticationEntryPoint(new CustomAuthenticationEntryPoint())
                .and()
            .authorizeRequests()
            .and()
                .headers()
                .frameOptions()
                .disable();
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth
            .userDetailsService(userDetailsService)
            .passwordEncoder(passwordEncoder());
}

采纳答案by Morten Haraldsen

A bit late for the party, but since SpringFox relies on Spring beans for building the documentation, we can easily manipulate it. Hope this can help someone!

聚会有点晚了,但由于 SpringFox 依赖 Spring bean 来构建文档,我们可以轻松地对其进行操作。希望这可以帮助某人!

Register it as a bean

将其注册为 bean

@Primary
@Bean
public ApiListingScanner addExtraOperations(ApiDescriptionReader apiDescriptionReader, ApiModelReader apiModelReader, DocumentationPluginsManager pluginsManager)
{
    return new FormLoginOperations(apiDescriptionReader, apiModelReader, pluginsManager);
}

The class used to add any operation manually:

用于手动添加任何操作的类:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;

import com.fasterxml.classmate.TypeResolver;
import com.google.common.collect.Multimap;

import springfox.documentation.builders.ApiListingBuilder;
import springfox.documentation.builders.OperationBuilder;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ApiDescription;
import springfox.documentation.service.ApiListing;
import springfox.documentation.service.Operation;
import springfox.documentation.spring.web.plugins.DocumentationPluginsManager;
import springfox.documentation.spring.web.readers.operation.CachingOperationNameGenerator;
import springfox.documentation.spring.web.scanners.ApiDescriptionReader;
import springfox.documentation.spring.web.scanners.ApiListingScanner;
import springfox.documentation.spring.web.scanners.ApiListingScanningContext;
import springfox.documentation.spring.web.scanners.ApiModelReader;

public class FormLoginOperations extends ApiListingScanner
{
    @Autowired
    private TypeResolver typeResolver;

    @Autowired
    public FormLoginOperations(ApiDescriptionReader apiDescriptionReader, ApiModelReader apiModelReader, DocumentationPluginsManager pluginsManager)
    {
        super(apiDescriptionReader, apiModelReader, pluginsManager);
    }

    @Override
    public Multimap<String, ApiListing> scan(ApiListingScanningContext context)
    {
        final Multimap<String, ApiListing> def = super.scan(context);

        final List<ApiDescription> apis = new LinkedList<>();

        final List<Operation> operations = new ArrayList<>();
        operations.add(new OperationBuilder(new CachingOperationNameGenerator())
            .method(HttpMethod.POST)
            .uniqueId("login")
            .parameters(Arrays.asList(new ParameterBuilder()
                .name("username")
                .description("The username")
                .parameterType("query")            
                .type(typeResolver.resolve(String.class))
                .modelRef(new ModelRef("string"))
                .build(), 
                new ParameterBuilder()
                .name("password")
                .description("The password")
                .parameterType("query")            
                .type(typeResolver.resolve(String.class))
                .modelRef(new ModelRef("string"))
                .build()))
            .summary("Log in") // 
            .notes("Here you can log in")
            .build());
        apis.add(new ApiDescription("/api/login/", "Authentication documentation", operations, false));

        def.put("authentication", new ApiListingBuilder(context.getDocumentationContext().getApiDescriptionOrdering())
            .apis(apis)
            .description("Custom authentication")
            .build());

        return def;
    }
}

Rendering Swagger json:

渲染 Swagger json:

"/api/login/" : {
      "post" : {
        "summary" : "Log in",
        "description" : "Here you can log in",
        "operationId" : "loginUsingPOST",
        "parameters" : [ {
          "name" : "username",
          "in" : "query",
          "description" : "The username",
          "required" : false,
          "type" : "string"
        }, {
          "name" : "password",
          "in" : "query",
          "description" : "The password",
          "required" : false,
          "type" : "string"
        } ]
      }
    }

回答by Italo Borssatto

You can add a fake login and logout method in your API just to generate the Swagger documentation, it'll be automatically overriden by Spring Security filters.

你可以在你的 API 中添加一个假的登录和注销方法来生成 Swagger 文档,它会被 Spring Security 过滤器自动覆盖。

@ApiOperation("Login.")
@PostMapping("/login")
public void fakeLogin(@ApiParam("User") @RequestParam String email, @ApiParam("Password") @RequestParam String password) {
    throw new IllegalStateException("This method shouldn't be called. It's implemented by Spring Security filters.");
}

@ApiOperation("Logout.")
@PostMapping("/logout")
public void fakeLogout() {
    throw new IllegalStateException("This method shouldn't be called. It's implemented by Spring Security filters.");
}

回答by Lennier

Just adding a little correction. If you want to make real POST-request (through HTML page of swagger-ui for example), you need to make little changes to Morten's answer.

只是添加了一点修正。如果您想发出真正的 POST 请求(例如,通过 swagger-ui 的 HTML 页面),您需要对 Morten 的回答稍作改动。

Morten's code makes POST request to /login like this:

Morten的代码像这样向 /login 发出 POST 请求:

http://<hostname>/api/login?username=<user>&password=<password>

http://<hostname>/api/login?username=<user>&password=<password>

But if you want to make a POST request you need to pass a body with it, not just query parameters. To make that happen, you need to add parameter with name bodyand parameter type bodylike this:

但是如果你想发出一个 POST 请求,你需要传递一个主体,而不仅仅是查询参数。要实现这一点,您需要添加具有名称body和参数类型的参数,body如下所示:

@Override
public Multimap<String, ApiListing> scan(ApiListingScanningContext context)
{
    final Multimap<String, ApiListing> def = super.scan(context);

    final List<ApiDescription> apis = new LinkedList<>();

    final List<Operation> operations = new ArrayList<>();
    operations.add(new OperationBuilder(new CachingOperationNameGenerator())
        .method(HttpMethod.POST)
        .uniqueId("login")
        .parameters(Arrays.asList(new ParameterBuilder()
            .name("body")
            .required(true)
            .description("The body of request")
            .parameterType("body")            
            .type(typeResolver.resolve(String.class))
            .modelRef(new ModelRef("string"))
            .build()))
        .summary("Log in") // 
        .notes("Here you can log in")
        .build());
    apis.add(new ApiDescription("/api/login/", "Authentication documentation", operations, false));

    def.put("authentication", new ApiListingBuilder(context.getDocumentationContext().getApiDescriptionOrdering())
        .apis(apis)
        .description("Custom authentication")
        .build());

    return def;
}

Now we can pass a body with our POST request. A body could be JSON, for example:

现在我们可以通过 POST 请求传递一个正文。正文可以是 JSON,例如:

{"username":"admin","password":"admin"}

{"username":"admin","password":"admin"}

回答by spike

You can use an interface describing the authentication API. The acutal implementation is provided by Spring Security. (This a variation of Italo's answer, where an interface is used instead of a fake implementation.)

您可以使用描述身份验证 API 的接口。实际实现由 Spring Security 提供。(这是Italo 答案的变体,其中使用接口而不是虚假实现。)

/**
 * Authentication API specification for Swagger documentation and Code Generation.
 * Implemented by Spring Security.
 */
@Api("Authentication")
@RequestMapping(value = "/", produces = MediaType.APPLICATION_JSON_VALUE)
public interface AuthApi {
    /**
     * Implemented by Spring Security
     */
    @ApiOperation(value = "Login", notes = "Login with the given credentials.")
    @ApiResponses({@ApiResponse(code = 200, message = "", response = Authentication.class)})
    @RequestMapping(value = "/login", method = RequestMethod.POST)
    default void login(
        @RequestParam("username") String username,
        @RequestParam("password") String password
    ) {
        throw new IllegalStateException("Add Spring Security to handle authentication");
    }

    /**
     * Implemented by Spring Security
     */
    @ApiOperation(value = "Logout", notes = "Logout the current user.")
    @ApiResponses({@ApiResponse(code = 200, message = "")})
    @RequestMapping(value = "/logout", method = RequestMethod.POST)
    default void logout() {
        throw new IllegalStateException("Add Spring Security to handle authentication");
    }
}