2015-12-20 11 views
11

Rozwijam usługę demo REST, używając Spring Boot, gdzie użytkownik musi się zalogować, aby wykonać określony podzbiór operacji. Po dodaniu Swagger UI (używając springfox biblioteki) z tej prostej konfiguracji:Dokumentowanie interfejsu API logowania/wylogowania wiosennego w aplikacji Swagger

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

skończę ze wszystkimi API z wszystkich operacji wymienionych na Swagger UI stronie. Niestety nie mam wśród nich punktów końcowych logowania/wylogowania.

Problem polega na tym, że część tych operacji nie może zostać wykonana za pomocą wbudowanej postaci Swagger UI (uważam, że jest to naprawdę fajna funkcja i chciałaby, aby działała), ponieważ użytkownik nie jest zalogowany. Czy istnieje rozwiązanie tego problemu ? Czy mogę ręcznie zdefiniować niektóre punkty końcowe w Swagger?

Jeśli nie było formą do składania poświadczeń (czyli punktów końcowych logowanie/wylogowanie) Mógłbym wykonać upoważnienie, które przed użyciem zabezpieczonych punktów końcowych. Następnie użytkownik może wyodrębnić z odpowiedzi odpowiedź token/sessionid i wkleić ją do niestandardowego parametru zapytania zdefiniowanego przez @ApiImplicitParams.

Poniżej znajdziesz moją konfigurację zabezpieczeń:

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

Odpowiedz

6

Trochę późno na imprezę, ale od SpringFox opiera się na wiosnę fasoli na budowę dokumentację, możemy łatwo manipulować. Mam nadzieję, że to może komuś pomóc!

zarejestrować go jako fasoli

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

klasy używane do dodawania jakichkolwiek operacji ręcznie:

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:

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

wystarczy dodać trochę korekty. Jeśli chcesz zrobić prawdziwe post-żądanie (za pośrednictwem strony HTML Swagger-ui na przykład), musisz dokonać małych zmian, aby odpowiedzieć Morten za.

kod Morten sprawia, że ​​żądanie POST/login tak:

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

Ale jeśli chcesz złożyć ofertę kupna trzeba przejść przez ciało z nim, nie tylko parametry kwerendy. Aby tak się stało, trzeba dodać parametr o nazwie body i parametr typu body tak:

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

Teraz możemy przekazać ciało nasze żądanie POST. Ciało może być JSON, na przykład:

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

0

Możesz dodać fałszywy login i metody wylogowania w API tylko do generowania dokumentacji Swagger, to zostaniesz automatycznie nadpisane przez filtry Wiosna 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."); 
} 
1

Można użyć interfejsu opisującego interfejs API uwierzytelniania.Egzotyczną implementację zapewnia Spring Security. (Jest to odmiana odpowiedzi Italo, w której używany jest interfejs zamiast fałszywej implementacji.)

/** 
* 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"); 
    } 
} 
Powiązane problemy