Java 记录Spring';s在Swagger中的登录/注销API

Java 记录Spring';s在Swagger中的登录/注销API,java,spring-boot,swagger,swagger-ui,springfox,Java,Spring Boot,Swagger,Swagger Ui,Springfox,我正在使用springboot开发演示REST服务,其中用户必须登录才能执行某些操作子集。使用该简单配置添加Swagger UI(使用springfox库)后: @Bean public Docket docApi() { return new Docket(DocumentationType.SWAGGER_2) .select() .apis(any()) .paths(PathSelectors

我正在使用
springboot
开发演示REST服务,其中用户必须登录才能执行某些操作子集。使用该简单配置添加
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);
}
@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());
}
我最终得到了所有API,所有操作都列在
Swagger UI
页面上。不幸的是,其中没有列出登录/注销端点

问题是,部分操作无法通过内置表单执行(我发现这是一个非常好的功能,希望能够正常工作),因为用户未登录。那个问题有什么解决办法吗?我可以在
Swagger
中手动定义一些端点吗

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

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

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

派对有点晚了,但是由于SpringFox依赖Springbeans来构建文档,我们可以很容易地操纵它。希望这能帮助别人

将其注册为bean

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

只是加上一点修正。 如果您想发出真正的POST请求(例如,通过swagger ui的HTML页面),您需要对Morten的答案进行一些更改

的代码发出POST请求/登录,如下所示:

@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;
}
http:///api/login?username=&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;
}
@覆盖
公共多映射扫描(ApiListingScanningContext)
{
最终多重映射定义=super.scan(上下文);
最终列表API=新建LinkedList();
最终列表操作=新建ArrayList();
添加(新的OperationBuilder(新的CachingOperationNameGenerator())
.方法(HttpMethod.POST)
.uniqueId(“登录”)
.parameters(Arrays.asList)(新参数生成器()
.名称(“机构”)
。必需(正确)
.说明(“请求正文”)
.参数类型(“主体”)
.type(typeResolver.resolve(String.class))
.modelRef(新modelRef(“字符串”))
.build())
.摘要(“登录”)//
.notes(“您可以在此登录”)
.build());
add(新的apisdescription(“/api/login/”,“身份验证文档”,operations,false));
def.put(“身份验证”,新的ApiListingBuilder(context.getDocumentationContext().getApisDescriptionOrdering())
.API(API)
.说明(“自定义身份验证”)
.build());
返回def;
}
现在我们可以通过一个机构与我们的职位要求。主体可以是JSON,例如:


{“username”:“admin”,“password”:“admin”}

您可以在API中添加一个虚假的登录和注销方法来生成Swagger文档,它将被Spring安全过滤器自动覆盖

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

您可以使用描述身份验证API的接口。acutal实现由Spring Security提供。(这是的一个变体,其中使用接口而不是伪实现。)


非常感谢你的比赛!祝福你!