Swagger Codegen CLI Java客户端 - 如何正确使用

22
我目前正在使用我的jersey2 rest服务进行实验。为了更好地了解所提供的服务(描述、类型等),我大量使用swagger(swagger-jersey2-jaxrs)。因此,我能够生成我的服务描述(swagger.json),并且可以通过swagger ui查看和探索它们。
现在我需要创建一些客户端来使用这些服务。我遇到了swagger codegen cli,这是一个很好的工具,可以用多种不同的语言(在我的情况下是java)生成客户端。我能够生成api客户端和使用的模型。
在这里,我面临着第一个问题。REST服务和swagger描述都受到http基本身份验证的保护。我阅读了文档documentation,它给了我一些提示,说明有可能使用基本身份验证。在这一点上,我必须提到,从我的角度来看,文档非常差。它说:
-a,--auth 在远程获取swagger定义时添加授权标头。传递一个URL编码的名称:带有逗号分隔多个值的标头字符串。

我首先想到的是像http头一样传递一个字符串,但这并不起作用,即使在谷歌上搜索如何使用swagger cli使用basic auth也没有得到清晰的答案。经过多次尝试和错误后,我(我使用CLI 2.1.2)终于找到了正确的格式:

java -jar swagger-codegen-cli-2.1.2.jar generate -a“Authorization: Basic YWRtaW46YWRtaW4 =” -i http://localhost:8080/webproject/restapi/swagger.json -l java -o restclient

其中YWRtaW46YWRtaW4=是我的情况下admin: admin的base64编码值。

到目前为止还不错。生成的java客户端也必须使用基本身份验证。我查看了ApiClient中的方法,发现了setUsername和setPassword。我认为这些方法赋予了客户端使用基本身份验证的能力,但没有成功。

因此,我深入研究了生成的类,特别是ApiClient和几个生成的ApiService类。我发现setUsername和setPassword没有效果,原因如下:

/**
   * Helper method to set username for the first HTTP basic authentication.
   */
  public void setUsername(String username) {
    for (Authentication auth : authentications.values()) {
      if (auth instanceof HttpBasicAuth) {
        ((HttpBasicAuth) auth).setUsername(username);
        return;
      }
    }
    throw new RuntimeException("No HTTP basic authentication configured!");
  }

同时,HashMap 定义如下:

// Setup authentications (key: authentication name, value: authentication).
authentications = new HashMap<String, Authentication>();
// Prevent the authentications from being modified.
authentications = Collections.unmodifiableMap(authentications);

认证哈希图变为不可变,但为什么?目的是什么?此外,ApiClinet内没有生成所需auth对象的帮助方法,因此我进行了以下操作:
1)注释掉“authentications Collections.unmodifiableMap(authentications)”这一行,使哈希图再次可修改
2)手动创建所需的auth对象
HttpBasicAuth authentication = new HttpBasicAuth(); 
authentication.setUsername("admin");
authentication.setPassword("admin");

3) 将auth对象添加到apiClients身份验证哈希映射中:

ApiClient apiClient = new ApiClient();
apiClient.setBasePath(basePath);
apiClient.getAuthentications().put("basic", authentication);

4) 修改invokeApi方法(ApiClient.java)

public String invokeAPI(String path, String method, Map<String, String> queryParams, Object body, Map<String, String> headerParams, Map<String, String> formParams, String accept, String contentType, String[] authNames) throws ApiException {
String authNames2[] = {"basic"};
updateParamsForAuth(authNames2, queryParams, headerParams);
//updateParamsForAuth(authNames, queryParams, headerParams);
...

第四步是必要的,因为 ApiServices 调用 apiClient 方法的方式如下:
String[] authNames = new String[] {  };
String response = apiClient.invokeAPI(path, "POST", queryParams, postBody, headerParams, formParams, accept, contentType, authNames);

另一种可能的解决方案是在每个apiService中定义认证哈希映射的键,例如:
String[] authNames = new String[] { "basic" };

在进行所有修改后,一切都按预期工作,但我认为这不是自动生成的REST客户端的思想所在。因此我的问题是:我是否错过了某些要点,或者应该将由Swagger生成的客户端(在这种情况下是Java)视为一种正在开发中的Beta解决方案?请理解我的意思,我认为整个Swagger框架(Jersey2支持,OpenAPI,SwaggerUI,Codegen)是一件好事,我很欣赏开发人员的努力,但我想正确使用codegen,并且不认为其背后的想法是必须以这种方式自定义生成的ApiClient和ApiServices。
3个回答

19
问题在于你的规范没有提及你想要使用哪些安全类型(也称为安全定义)或哪个安全定义适用于哪个端点。

Swagger规范在这里,但它并没有讲述全部内容。

你需要做的是1.设置安全定义。这是一个简单的基本HTTP身份验证定义:

securityDefinitions:
  basic:
    type: basic
    description: HTTP Basic Authentication. 

并且 2. 在端点中使用该安全定义。

paths:
  /:
    get:
      security:
       - basic: []
      responses:
        200:
          description:  OK

然后重新生成您的Swagger客户端代码,它应该正确设置不可变映射和authNames数组。


1
是的,但这样做生成的代码不是线程安全的,这意味着我们每个请求只有一个用户名/密码。如果您正在代理请求到另一个API并且需要根据当前用户动态更改身份验证,则无法使用该方法...如果“authentications”映射是可变的,您至少可以添加自己的类(实现Authentication接口)并以任何您想要的方式进行身份验证。 例如:调用服务以获取当前用户的用户名和密码,并生成BasicAuth标头以添加到请求中。 - Thomas Joeisseint
添加自己的身份验证类对我来说听起来不错。你有如何实现的示例吗? - Taylor
1
正如https://github.com/swagger-api/swagger-codegen/issues/1435中建议的那样,我相信为`APIClient`创建构造器方法可以解决这个问题。现在作为一个快速解决方法,我只是自定义了Mustache模板,并删除了authentications = Collections.unmodifiableMap(authentications);部分,然后在Map<String,Authentication>中替换基本身份验证实现。 - Thomas Joeisseint

3

如上所述,如果您不想修改现有的代码,您可以在自定义配置中扩展ApiClient,例如:

@Configuration
public class Config {

  @Value("${baseUrl}")
  private String baseUrl;

  protected class AuthApiClient extends ApiClient {

    public AuthApiClient() {
      super();
    }

    @Override
    public <T> T invokeAPI(final String path, final HttpMethod method,
            final MultiValueMap<String, String> queryParams, final Object body,
            final HttpHeaders headerParams, final MultiValueMap<String, Object> formParams,
            final List<MediaType> accept, final MediaType contentType,
            final String[] authNames, final ParameterizedTypeReference<T> returnType)
            throws RestClientException {

            final HttpBasicAuth auth = new HttpBasicAuth();
            auth.setUsername("myUsername");
            auth.setPassword("myPassword");
            auth.applyToParams(queryParams, headerParams);

      return super.invokeAPI(path, method, queryParams, body, headerParams, formParams,
                accept, contentType, authNames, returnType);
    }
  }

  @Bean
  @Primary
  @Qualifier("MyApiClient")
  public AuthApiClient myApiClient() {
    final AuthApiClient apiClient = new AuthApiClient();
    apiClient.setBasePath(this.baseUrl);
    return apiClient;
  }
}

1

对我来说,只需这样做:

 apiClient.addDefaultHeader("Authorization", "Basic " + base64Bearer);

我正在使用swagger-codegen版本3.0.35和没有定义任何安全定义的API规范


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接