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.
authentications
Map was mutable you could at least add your own class (implementing Authentication interface) and authenticate anyway you want. e.g: calling a service to get current user username and password, and generating BasicAuth headers to add to the request. – Millner