This small post will show you how to have an OIDC authentication on the Swagger interface of your Spring-Boot backend. The important part is, in the example the “client” used for the Swagger authent will be different than the client used for the “main” authent, that will allow you to turn of one authent channel separately from the other. Also keep in mind that Swagger is a development tool, it shouldn’t be used in production.
First of all the technical situation:
– Spring-Boot 3.3.3
– SpringDoc 2.6.0
– Keycloak as OIDC provider
First you’ll have to build a backend exposing at least one service, and setup the authentification.
Configuration
@EnableWebSecurity(debug = true) // Feel free to turn off the debug
public class SecurityConf {
@Autowired(requierd = false) // This Bean isn't mandatory because your app have to start even if the Swagger is turned OFF
private SpringDocConfigProperties swaggerProps;
// https://docs.spring.io/spring-security/reference/reactive/oauth2/resource-server/jwt.html
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(authz -> {
// Open Swagger IHM if Swagger is enabled
if (nonNull(swaggerProps) && swaggerProps.getApiDocs().isEnabled()) {
authz.requestMatchers("/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html").permitAll();
}
// Lock api request
authz.requestMatchers("/**").hasAnyAuthority("ROLE_realm_coom_user");
});
http.sessionManagement(
session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
http.oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(
token -> new JwtAuthenticationToken(token, authoritiesConverter.convert(token)))))
.cors(Customizer.withDefaults());
return http.build();
}
DelegatingJwtGrantedAuthoritiesConverter authoritiesConverter =
// Using the delegating converter multiple converters can be combined
new DelegatingJwtGrantedAuthoritiesConverter(
// First add the default converter
new JwtGrantedAuthoritiesConverter(),
// Second add our custom Keycloak specific converter
new KeycloakJwtRolesConverter()); // This will be seen in another post
}
After that setup the OIDC properties:
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: ${KEYCLOAK_URL}/realms/${APP_REAML_NAME}
jwk-set-uri: ${KEYCLOAK_URL}/realms/${APP_REAML_NAME}/protocol/openid-connect/certs
After that, add SpringDoc dependencies to your project:
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.6.0</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
<version>2.6.0</version>
</dependency>
Add some SpringDoc properties:
springdoc:
version: '@springdoc.version@' # Retrive the value from the POM xml
show-actuator: true
api-docs:
version: openapi-3-0
enabled: true
swagger-ui:
oauth:
realm: ${APP_REAML_NAME}
app-name: My-App-Swagger
clientId: ${SWAGGER_OAUTH_CLIENT_ID}
clientSecret: ${SWAGGER_OAUTH_CLIENT_SECRET}
o-auth-flow:
authorization-url: ${KEYCLOAK_URL}/realms/${APP_REAML_NAME}/protocol/openid-connect/auth
token-url: ${KEYCLOAK_URL}/realms/${APP_REAML_NAME}/protocol/openid-connect/token
open-id-connect-url: ${KEYCLOAK_URL}/realms/${APP_REAML_NAME}/.well-known/openid-configuration
Create an OpenAPI configuration class:
@Configuration
@ConditionalOnProperty(prefix = "springdoc.api-docs", name = "enabled", havingValue = "true", matchIfMissing = false)
@OpenAPIDefinition(
info = @Info(
title = "My App API",
description = "Here you can see all the API endpoints and try them",
version = "${info.app.version}"
),
servers = {
@Server(
url = "${springdoc.api-docs.url}",
description = "${springdoc.api-docs.description}"
)
}
)
@SecurityScheme(
name = "Swagger-OIDC",
type = SecuritySchemeType.OAUTH2,
flows = @OAuthFlows(
implicit = @OAuthFlow(
authorizationUrl = "${springdoc.o-auth-flow.authorization-url}",
tokenUrl = "${springdoc.o-auth-flow.token-url}",
refreshUrl = "${springdoc.o-auth-flow.token-url}",
scopes = {
@OAuthScope(name = "springdoc.read", description = "read scope"),
@OAuthScope(name = "springdoc.write", description = "write scope")}
)
)
)
public class OpenAPIConf {}
To make sure that the endpoint require a token from Swagger, your endpoint have to carry this annotation @SecurityRequirement(name = “Coom-Swagger OIDC”), the name value should be the same value of the @SecurityScheme(name = “Swagger-OIDC”, …) from the OpenAPIConf.class.
Example of endpoint :
@RestController
@RequestMapping("/test")
public class TestController {
@SecurityRequirement(name = "Coom-Swagger OIDC")
@GetMapping("/endpoint")
public void endpoint() {
...
}
}
Now the Keycloak configuration:
This part will be handled into another incomming blog post.
Leave a Reply