blog.broncotoxique.com

Juste another geek’s website

Howto: Swagger OIDC and Spring Boot 3

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.


Posted

in

by

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *