私はデモを開発していますRESTユーザーが特定の操作のサブセットを実行するためにログインする必要があるSpring Boot
を使用したサービス。その単純な構成で(springfox
ライブラリを使用して)Swagger UI
を追加した後:
@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
ページにリストされているすべての操作を含むすべてのAPIが作成されます。残念ながら、それらの中にリストされているログイン/ログアウトエンドポイントはありません。
問題は、ユーザーがログインしていないため、その操作の一部が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());
}
パーティーには少し遅れますが、SpringFoxはドキュメントの構築をSpring Beanに依存しているため、簡単に操作できます。これが誰かを助けることを願っています!
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;
}
}
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"
} ]
}
}
Swaggerドキュメントを生成するためだけに、偽のログインおよびログアウトメソッドをAPIに追加できます。これは、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 のコードは、POST/loginに次のように要求します:
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によって提供されます。 (これは イタロの答え のバリエーションであり、偽の実装の代わりにインターフェースが使用されます。)
/**
* 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");
}
}