Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swagger Codegen CLI Java Client - How to use it right

I'm currently playing around with my jersey2 rest service. For a better overview of the given service (description, types and so on) I make heavily use of swagger (swagger-jersey2-jaxrs). So I'm able to genereate my service description (swagger.json) and I can view and explore them via swagger ui.

Now I'm at the point that I need to create some clients to make use of these services. I came across swagger codegen cli which is a nice tool to generate your client and many different languages (java in my case). I'm able to generate the api client and the model in use.

Here I face the first problem. The REST services and the swagger description are http basic auth protected. I read the documentation which gave me some hint that there is a possibility to use basic auth. At this point I have to mention that from my point of view the documentation is very poor. It says:

-a , --auth adds authorization headers when fetching the swagger definitions remotely. Pass in a URL-encoded string of name:header with a comma separating multiple values.

First thing I thought of is to pass a string like in an http header but that doesn't work and even googling how to use basic auth with swagger cli didn't result in some clear answer. After a lot of try and errors I (I'm using CLI 2.1.2) I finally came across the right format:

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

where YWRtaW46YWRtaW4= is the the base64 encoded value of admin:admin in my case.

So far so good. The generated java client have to make use of basic auth as well. I took a look at the methods from the ApiClient and found setUsername and setPassword. I thought that this methods empowers the client to use basic auth but no luck.

So I took a deeper look at the generated classes especially the ApiClient and the several generated ApiService Classes. I found out that the setUsername and setPassword have no effect because of the following:

/**
   * 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!");
  }

where at the same time the HashMap is defined as the following:

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

The authentication hashmap becomes immutable, but why? What is the purpose? Furthermore there are no helper methods inside the ApiClinet which generates the needed auth object so I did the following:

1) comment out the line authentications Collections.unmodifiableMap(authentications) so the hashmap becomes modifiable again

2) create needed auth object manually

HttpBasicAuth authentication = new HttpBasicAuth(); 
authentication.setUsername("admin");
authentication.setPassword("admin");

3) add the auth object to the apiClients authentication hashmap:

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

4) modifying invokeApi Method (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);
...

Step 4 is necessary because the ApiServices invoke the apiClient method like the following:

String[] authNames = new String[] {  };
String response = apiClient.invokeAPI(path, "POST", queryParams, postBody, headerParams, formParams, accept, contentType, authNames);

An other possible solution would be to define the Key of the authentications hashmap in every apiService like:

String[] authNames = new String[] { "basic" };

After doing all the modifications everything works as expected but I cant think that this is the idea behind an autogenerated rest client. So my question is: Am I missing some point or should I think of the swagger generated client (java in this case) more of a beta solution which is under development? Please get me right, I think the whole swagger framework (jersey2 support, openapi, swaggerui, codegen) is a great thing and I appreciate the developers effort but I want to use the codegen right and I don't think that the idea behind is so have to customize the generated ApiClient and ApiServices in such a way.

like image 787
Matthias Avatar asked Sep 27 '16 11:09

Matthias


People also ask

How do I check swagger codegen?

All versions of the Swagger Codegen project can be found on Maven Central. Visit this folder on Maven, and choose the appropriate version (we recommend the latest version). Alternatively, you could use the wget command as well.

How do I create a swagger client?

The most interactive way to create a client from a swagger file is using the online swagger editor. Go to https://editor.swagger.io/. Select file , import URL and type in the URL of the swagger endpoint you want to load. Alternatively you can select File , Import File and upload the downloaded swagger.


1 Answers

The issue is that your specification does not mention the types of Security you want to use (a.k.a. Security Definitions) or which Security Definition applies to which end point.

The swagger specification is here, but it doesn't tell the whole story.

What you need to do is 1. Set up the Security Definitions. Here is a simple basic http auth definition:

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

and 2. Use that security definition in the end point.

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

Then regenerate your swagger client code. It should set up the immutable map correctly and the authNames array.

like image 169
Taylor Avatar answered Oct 24 '22 09:10

Taylor