programing

Swagger에서 Spring의 로그인/로그아웃 API 문서화

codeshow 2023. 7. 31. 22:03
반응형

Swagger에서 Spring의 로그인/로그아웃 API 문서화

저는 다음을 이용하여 데모 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);
}

나는 모든 작업이 나열된 모든 아피스와 함께 끝납니다.Swagger UI페이지. 유감스럽게도 로그인/로그아웃 엔드포인트가 목록에 없습니다.

문제는 해당 작업의 일부가 다음을 통해 수행될 수 없다는 것입니다.Swagger UI사용자가 로그인하지 않았기 때문에 내장된 양식(나는 그것이 정말 좋은 기능이라고 생각하고 그것을 작동시키고 싶다).그 문제에 대한 해결책이 있습니까?에서 일부 엔드포인트를 수동으로 정의할 수 있습니까?Swagger?

자격 증명을 제출할 양식(예: 로그인/로그아웃 엔드포인트)이 있으면 해당 보안 엔드포인트를 사용하기 전에 권한 부여를 수행할 수 있습니다.그리고나서,Swagger사용자가 추출할 수 있음token/sessionid응답에서 다음을 통해 정의된 사용자 지정 쿼리 매개 변수에 붙여넣습니다.@ApiImplicitParams.

아래에서 내 보안 구성을 찾을 수 있습니다.

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

파티에 약간 늦었지만, 스프링 폭스가 문서를 만드는 데 스프링 콩에 의존하기 때문에, 우리는 그것을 쉽게 조작할 수 있습니다.이것이 누군가를 도울 수 있기를 바랍니다!

콩으로 등록합니다.

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

수동으로 작업을 추가하는 데 사용되는 클래스:

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

Swaggerjson 렌더링 중:

"/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"
        } ]
      }
    }

가짜 로그인 및 로그아웃 방법을 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.");
}

약간의 수정만 더하면 됩니다.(예를 들어, swagger-ui의 HTML 페이지를 통해) 실제 POST 요청을 하려면 Morten의 응답을 약간 변경해야 합니다.

Morten의 코드는 /login에 대한 POST 요청을 다음과 같이 수행합니다.

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

그러나 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;
}

이제 우리는 우리의 POST 요청으로 시체를 통과시킬 수 있습니다.본문은 JSON일 수 있습니다. 예:

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

인증 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");
    }
}

springdoc-openapi에 대한 나의 솔루션:

@Bean
public OpenAPI customOpenAPI() {
    return new OpenAPI()
            .components(new Components())
            .tags(List.of(
                    new Tag()
                            .name("Authentication")
                            .description("Login/logout controller")
            ))
            .path("/logout", new PathItem()
                    .post(new Operation()
                            .tags(List.of(
                                    "Authentication"
                            ))
                            .summary("Logout")
                            .description("Logout the current user.")
                            .operationId("logout")
                            .responses(new ApiResponses()
                                    .addApiResponse("200", new ApiResponse().description("OK"))
                            )
                    )
            )
            .path("/login", new PathItem()
                    .post(new Operation()
                            .tags(List.of(
                                    "Authentication"
                            ))
                            .summary("Login")
                            .description("Login with the given credentials.")
                            .operationId("login")
                            .parameters(List.of(
                                    new Parameter().name("username").in("query").required(true).schema(new Schema().type("string")),
                                    new Parameter().name("password").in("query").required(true).schema(new Schema().type("string"))
                            ))
                            .responses(new ApiResponses()
                                    .addApiResponse("200", new ApiResponse().description("OK"))
                            )
                    )
            );
}

언급URL : https://stackoverflow.com/questions/34386337/documenting-springs-login-logout-api-in-swagger

반응형