When working as a backend engineer, it is essential to document the REST APIs.
It also helps in providing a UI(swagger-ui) to test the REST calls. Let us try to integrate
springdoc-openapi to provide swagger documentation for a spring boot project
using spring-security(OAuth2).
Add the dependencies to build.gradle¶
1
2
3
| implementation("org.springdoc:springdoc-openapi-ui:1.6.8")
implementation("org.springdoc:springdoc-openapi-security:1.6.8")
implementation("org.springdoc:springdoc-openapi-kotlin:1.6.8")
|
Kotlin code example¶
The following example showcases spring configuration to create OpenAPI bean which supports two authentication mechanism:
- OAuth2 ClientCredentials flow
- Bearer Token
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
| import io.swagger.v3.oas.models.Components
import io.swagger.v3.oas.models.OpenAPI
import io.swagger.v3.oas.models.info.Contact
import io.swagger.v3.oas.models.info.Info
import io.swagger.v3.oas.models.security.OAuthFlow
import io.swagger.v3.oas.models.security.OAuthFlows
import io.swagger.v3.oas.models.security.SecurityRequirement
import io.swagger.v3.oas.models.security.SecurityScheme
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Profile
@Configuration
class OpenApiConfig {
companion object {
private const val OAUTH2_CLIENT_CREDENTIALS_KEY = "oauth2ClientCredentials"
private const val BEARER_TOKEN_KEY = "bearerToken"
}
@Value("\${springdoc.version}")
private lateinit var appVersion: String
@Value("\${spring.application.name}")
private lateinit var applicationName: String
@Value("\${springdoc.oAuthFlow.authorizationUrl}")
private lateinit var authorizationUrl: String
@Value("\${springdoc.oAuthFlow.tokenUrl}")
private lateinit var tokenUrl: String
@Bean
fun customOpenAPI(): OpenAPI {
return OpenAPI()
.info(
Info()
.title(applicationName)
.version(appVersion)
.contact(Contact().name("Team").email("test@test.com"))
.termsOfService("https://springdoc.org/")
)
.components(
Components()
.addSecuritySchemes(OAUTH2_CLIENT_CREDENTIALS_KEY, clientCredentialSecurityScheme())
.addSecuritySchemes(BEARER_TOKEN_KEY, bearerTokenSecurityScheme())
)
.addSecurityItem(SecurityRequirement().addList(OAUTH2_CLIENT_CREDENTIALS_KEY))
.addSecurityItem(SecurityRequirement().addList(BEARER_TOKEN_KEY))
}
private fun bearerTokenSecurityScheme() =
SecurityScheme().type(SecurityScheme.Type.HTTP).scheme("bearer").bearerFormat("JWT")
private fun clientCredentialSecurityScheme() = SecurityScheme().type(SecurityScheme.Type.OAUTH2).flows(
OAuthFlows().clientCredentials(OAuthFlow().authorizationUrl(authorizationUrl).tokenUrl(tokenUrl))
)
}
|
In the above example no scopes have been configured. Lets add read and write scopes:
1
2
3
4
5
| private fun clientCredentialSecurityScheme() = SecurityScheme().type(SecurityScheme.Type.OAUTH2).flows(
OAuthFlows().clientCredentials(OAuthFlow().authorizationUrl(authorizationUrl).tokenUrl(tokenUrl).scopes(readWriteScope()))
)
private fun readWriteScope() = Scopes().addString("read", "Can Read").addString("write", "Can Write")
|
Note that springdoc also supports other authentication mechanism like basic auth, implicit grant, etc.
When using spring-security, dont forget to whitelist following urls:
- “/v3/api-docs/**”
- “/swagger-ui/**”
- “/swagger-ui.html”
Spring application configuration¶
Let’s configure spring’s application.yaml with authorization and token urls
1
2
3
4
5
| springdoc:
version: v1
oAuthFlow:
authorizationUrl: https://dev-88823233.okta.com/oauth2/default/v1/authorize
tokenUrl: https://dev-88823233.okta.com/oauth2/default/v1/token
|
Note that the okta urls don’t exist anymore 😁. But you may replace them with your authorization and token url.
Usually these urls are exposed at well known endpoints by your Authorization Server
- oidc provider:
/.well-known/openid-configuration
url - oauth2: okta provides the url configurations under
https://dev-88823233.okta.com/oauth2/default/.well-known/oauth-authorization-server
Sometimes, it might be useful to disable swagger in production.
1
2
3
4
5
| springdoc:
api-docs:
enabled: false
swagger-ui:
enabled: false
|
Swagger of Swaggers¶
When working with multiple services, we should ideally have a single swagger ui which could act as a swagger aggregate to host
all the swagger docs under a single service.
Lets say we have following two services:
user
service: Java based spring boot service & springdoc exposing only swagger api docs at https://ravikrs.com/users/v3/api-docs
.
1
2
3
4
5
| springdoc:
api-docs:
enabled: true
swagger-ui:
enabled: false
|
ui would be used from aggregate swagger service, so its disabled in user
service.
api
service: Go service using swaggo
for exposing swagger api docs at https://ravikrs.com/api/swagger/doc.json
We may combine multiple such services inside a single swagger service(https://ravikrs.com/swagger/swagger-ui.html
) which could be a simple spring boot service aggregating all
swagger api docs and exposing in a single Swagger UI.
1
2
3
4
5
6
7
8
9
10
11
| springdoc:
version: v1
api-docs:
enabled: true
swagger-ui:
enabled: true
urls:
- name: Api
url: https://ravikrs.com/api/swagger/doc.json
- name: User
url: https://ravikrs.com/user/v3/api-docs
|
CORS issue when using multiple domain for swagger¶
When working with multiple domains, we may need to enable CORS for accessing /v3/api-docs
url to retrieve openapi3 docs.
Following code snippet showd how to declare a cors filter for spring apps.
1
2
3
4
5
6
7
8
9
10
11
12
13
| @Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/v3/api-docs", config);
return new CorsFilter(source);
}
|
Links¶