"No converter found capable of converting from type String to OAuth2ClientProperties$Provider" - Spring Boot Oauth 2.0 - spring-boot

My application needs to be an API client, using Spring Security, Oauth 2.0, and OpenID, in Spring Boot. For OAuthClientConfiguration I followed this tutorial (starting with the "Creating Web Client-Based Application" header): https://developer.okta.com/blog/2021/05/05/client-credentials-spring-security#create-a-webclient-based-application
I'm getting this error upon starting the app:
Failed to bind properties under 'spring.security.oauth2.client.provider.authorization-uri' to org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties$Provider:
Reason: org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [java.lang.String] to type [org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties$Provider]
My OAuthClientConfiguration class
#Configuration
public class OAuthClientConfiguration
{
#Bean
ReactiveClientRegistrationRepository clientRegistrations(
#Value(value = "${spring.security.oauth2.client.provider.token-uri}") String tokenUri,
#Value(value = "${spring.security.oauth2.client.registration.IdOfMyApp.client-id}") String clientId,
#Value(value = "${spring.security.oauth2.client.registration.IdOfMyApp.client-secret}") String clientSecret,
#Value(value = "${spring.security.oauth2.client.registration.IdOfMyApp.authorization-grant-type}") String authorizationGrantType,
#Value(value = "${spring.security.oauth2.client.registration.IdOfMyApp.redirect-uri}") String redirectUri,
#Value(value = "${spring.security.oauth2.client.provider.authorization-uri}") String authorizationUri)
{
ClientRegistration registration = ClientRegistration
.withRegistrationId("IdOfMyApp")
.tokenUri(tokenUri)
.clientId(clientId)
.clientSecret(clientSecret)
.scope("pr.pro", "pr.act", "openid", "offline")
.authorizationGrantType(new AuthorizationGrantType(authorizationGrantType))
.redirectUri(redirectUri)
.authorizationUri(authorizationUri)
.build();
return new InMemoryReactiveClientRegistrationRepository(registration);
}
#Bean
WebClient webClient(ReactiveClientRegistrationRepository clientRegistrations)
{
InMemoryReactiveOAuth2AuthorizedClientService clientService = new InMemoryReactiveOAuth2AuthorizedClientService(clientRegistrations);
AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager authorizedClientManager = new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(clientRegistrations, clientService);
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
oauth.setDefaultClientRegistrationId("MarvelGuru");
return WebClient.builder().filter(oauth).build();
}
}
application.yaml file:
spring:
security:
oauth2:
client:
registration:
IdOfMyApp:
provider: https://api.provider.guys.com
client-id: [my id here]
client-secret: [my secret here]
client-authentication-method: basic
authorization-grant-type: authorization_code
scope:
- pr.pro
- pr.act
- openid
- offline
redirect-uri: https://my.domain.com/fallback
client-name: My App Name
provider:
authorization-uri: https://api.provider.guys.com/oauth2/auth
token-uri: https://api.provider.guys.com/oauth2/token
issuer-uri: https://api.provider.guys.com
resourceserver:
jwt:
issuer-uri: https://api.provider.guys.com
logging:
level:
'[org.springframework.web]': DEBUG

You need to have a provider id key in the properties:
provider:
IdOfMyApp:
authorization-uri: https://api.provider.guys.com/oauth2/auth
token-uri: https://api.provider.guys.com/oauth2/token
issuer-uri: https://api.provider.guys.com

Related

Spring Cloud Config - Vault and JDBC backend with JDBC creds in Vault

I am attempting to modify our current Spring Cloud Config server which has only a JDBC backend to include a Vault backend in order make the JDBC connection credentials secret.
VAULT:
Listener 1: tcp (addr: "127.0.0.1:8400", cluster address: "127.0.0.1:8401", max_request_duration: "1m30s", max_request_size: "33554432", tls: "disabled")
C:\apps\HashiCorp>vault kv get secret/my-secrets
=============== Data ===============
Key Value
--- -----
spring.datasource.password yadayadayada
spring.datasource.username cobar
bootstrap.yml
server:
port: 8888
spring:
application:
name: config-server
cloud:
config:
allowOverride: true
server:
jdbc:
sql: SELECT prop_key, prop_value from CloudProperties where application=? and profile=? and label=?
order: 2
#https://cloud.spring.io/spring-cloud-config/reference/html/#vault-backend
vault:
scheme: http
host: localhost
port: 8400
defaultKey: my-secrets
order: 1
application.yml
spring:
main:
banner-mode: off
allow-bean-definition-overriding: true
datasource:
url: jdbc:mysql://localhost/bootdb?createDatabaseIfNotExist=true&autoReconnect=true&useSSL=false
#username: cobar
#password: yadayadayada
driverClassName: com.mysql.jdbc.Driver
hikari:
connection-timeout: 60000
maximum-pool-size: 5
cloud:
vault:
scheme: http
host: localhost
port: 8400
defaultKey: my-secrets
token: root.RIJQjZ4jRZUS8mskzfCON88K
The spring.datasource username and password are not being retrieved from the vault.
2021-12-01 12:43:39.927 INFO 5992 --- [ restartedMain]: The following profiles are active: jdbc,vault
2021-12-01 12:43:46.123 ERROR 5992 --- [ restartedMain] com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Exception during pool initialization.
Login failed for user ''. ClientConnectionId:a32
Move properties from bootstrap to application context.
Call Vault endpoint to obtain secrets and use these to configure Datasource to JDBC backend.
#Slf4j
#SpringBootApplication
#EnableConfigServer
public class ConfigServerApplication {
public static final String VAULT_URL_FRMT = "%s://%s:%s/v1/secret/%s";
#Autowired
private Environment env;
public static void main(String[] args) {
SpringApplication app = new SpringApplication(ConfigServerApplication.class);
app.addListeners(new ApplicationPidFileWriter());
app.addListeners(new WebServerPortFileWriter());
app.run(args);
}
#Order(1)
#Bean("restTemplate")
public RestTemplate restTemplate() {
return new RestTemplate();
}
#Configuration
public class JdbcConfig {
#Autowired
private RestTemplate restTemplate;
#Bean
public DataSource getDataSource() {
Secrets secrets = findSecrets();
DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
dataSourceBuilder.url(secrets.getData().get("spring.datasource.url"));
dataSourceBuilder.username(secrets.getData().get("spring.datasource.username"));
dataSourceBuilder.password(secrets.getData().get("spring.datasource.password"));
return dataSourceBuilder.build();
}
private Secrets findSecrets() {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.set("X-Vault-Token", env.getProperty("spring.cloud.vault.token"));
HttpEntity request = new HttpEntity(httpHeaders);
String url = String.format(VAULT_URL_FRMT,
env.getProperty("spring.cloud.vault.scheme"),
env.getProperty("spring.cloud.vault.host"),
env.getProperty("spring.cloud.vault.port"),
env.getProperty("spring.cloud.vault.defaultKey")
);
return restTemplate.exchange(url, HttpMethod.GET, request, Secrets.class, 1).getBody();
}
}
}
#Getter
#Setter
public class Secrets implements Serializable {
private String request_id;
private String lease_id;
private boolean renewable;
private Duration lease_duration;
private Map<String, String> data;
}
Now you have a Cloud Config with a JDBC backend you can keep the Database properties secret.

Error while using custom loadbalancer for spring cloud loadbalancer with healthcheck configuration

I am using a static list (SimpleDiscoveryClient) to loadbalance using spring cloud loadbalancer. Using StickySession Loadbalancer rule in https://github.com/fitzoh/spring-cloud-commons/blob/e10997b6141ff560479ef7065c3547f1f59360c8/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/StickySessionLoadBalancer.java.
My WebClientConfig class:
#Configuration
#LoadBalancerClient(name = "testservice", configuration = CustomLoadBalancerConfiguration.class)
public class WebClientConfig {
#LoadBalanced
#Bean
WebClient.Builder webClientBuilder() {
return WebClient.builder();
}
}
Custom LoadBalancer Configuration class:
public class CustomLoadBalancerConfiguration {
#Bean
ReactorLoadBalancer<ServiceInstance> StickySessionLoadBalancer(Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new StickySessionLoadBalancer(loadBalancerClientFactory
.getLazyProvider(name, ServiceInstanceListSupplier.class),
name);
}
#Bean
public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier(
ConfigurableApplicationContext context) {
return ServiceInstanceListSupplier.builder()
.withDiscoveryClient()
.withHealthChecks()
.build(context);
}
}
Posting my yml here :
spring:
application:
name: sample
cloud:
discovery:
client:
health-indicator:
enabled: false
simple:
instances:
testservice:
- uri: http://localhost:8082
- uri: http://localhost:8081
loadbalancer:
configurations: health-check
cache:
enabled: false
health-check:
path:
default: /actuator/health
interval: 10000
gateway:
routes:
- id: testrouting
path: /user/*
uri: lb://testservice
predicates:
- Method=GET,POST
- Path=/user/**
It's all according to the official documentation. But with the customloadbalancer rule (Stickysession Loadbalancer), the healthchecks to servers are not happening to checkif the servers are alive or not. The server list is always empty (all servers are marked as not alive).

Spring + OpenApi 3 - How to set the clientId and the clientSecret for displaying automatically on the swagger-ui Authorization page?

On my Spring Boot application, I am trying to replace Swagger 2 with OpenApi 3.
In the current implementation of SwaggerConfiguration class,
#Configuration
#EnableSwagger2
public class SwaggerConfig {
...
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build()
.securitySchemes(Collections.singletonList(securityScheme()))
.host(host)
.securityContexts(Collections.singletonList(securityContext()));
}
#Bean
public SecurityConfiguration security() {
return SecurityConfigurationBuilder.builder()
.clientId(swaggerCredentialsProvider.getClientId())
.clientSecret(swaggerCredentialsProvider.getClientSecret())
.scopeSeparator(" ")
.useBasicAuthenticationWithAccessCodeGrant(true)
.build();
}
private SecurityScheme securityScheme() {
GrantType grantType = new AuthorizationCodeGrantBuilder()
.tokenEndpoint(new TokenEndpoint(tokenEndpoint, "code"))
.tokenRequestEndpoint(new TokenRequestEndpoint(tokenRequestEndpoint,
swaggerCredentialsProvider.getClientId(),
swaggerCredentialsProvider.getClientSecret()))
.build();
return new OAuthBuilder().name("spring_oauth")
.grantTypes(Collections.singletonList(grantType))
.scopes(Arrays.asList(scopes())).build();
}
...
}
In this sample code, I give the clientId and the clientSecret and it will display automatically on the swagger-ui Authorization page:
In my new implementation of OpenApi Configuration
#Bean
public OpenAPI customOpenAPI() {
OAuthFlow oAuthFlow = new OAuthFlow()
.tokenUrl(tokenEndpoint)
.authorizationUrl(tokenRequestEndpoint)
.scopes(new Scopes().addString(scope, ""));
return new OpenAPI()
.components(new Components()
.addSecuritySchemes("security_auth", new SecurityScheme()
.flows(new OAuthFlows().authorizationCode(oAuthFlow))
.type(SecurityScheme.Type.OAUTH2).scheme("oauth2")))
.info(new Info()
.title(appName)
.version(appVersion)
.description(appDescription));
}
I do not find a way to set theses information. I tried to set springdoc.swagger-ui.oauth.clientId in the application.property file, but the clientId did not display.
How to set the clientId and the clientSecret with OpenApi 3 for displaying automatically on the Authorization Page?
You can set the client-id and client-secret via application properties. I think you might have the path to the client-id and client-secret wrong. The properties are 'client-id' and 'client-secret' not 'clientId' and 'clientSecret'
springdoc:
swagger-ui:
oauth:
client-id: your-client-id-value
client-secret: your-client-secret-value

Alternative For OAuth2FeignRequestInterceptor as it is deprecated NOW

In my previous implementation I was using OAuth2FeignRequestInterceptor. But from Spring security 5 onwards, OAuth2FeignRequestInterceptor seems to be deprecated. What is the alternative to achieve the same ?. I searched lot of blogs and threads, but couldn't find any answer.
build.gradle.kts
implementation("org.springframework.security:spring-security-oauth2-client")
application.yml
spring:
security:
oauth2:
client:
registration:
keycloak: // <- replace with your custom oauth2 client details
provider: keycloak
client-id: [keycloak-client-id]
client-secret: [keycloak-client-secret]
authorization-grant-type: client_credentials
scope: openid
provider:
keycloak: // <- replace with your custom oauth2 provider details
authorization-uri: http://localhost:8080/auth/realms/yourealm/protocol/openid-connect/auth
token-uri: http://localhost:8080/auth/realms/yourealm/protocol/openid-connect/token
Oauth2Config
#Configuration
class Oauth2Config {
#Bean
fun authorizedClientManager(
clientRegistrationRepository: ClientRegistrationRepository?,
authorizedClientRepository: OAuth2AuthorizedClientRepository?
): OAuth2AuthorizedClientManager? {
val authorizedClientProvider: OAuth2AuthorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
.authorizationCode()
.clientCredentials()
.build()
val authorizedClientManager = DefaultOAuth2AuthorizedClientManager(clientRegistrationRepository, authorizedClientRepository)
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
return authorizedClientManager
}
}
FeignOauth2Configuration
class FeignOauth2Configuration (private val authorizedClientManager: OAuth2AuthorizedClientManager) {
#Bean
fun oauth2HttpRequestInterceptor(): RequestInterceptor {
return RequestInterceptor { request ->
request.headers()["Authorization"] = listOf("Bearer ${getAccessToken()?.tokenValue}")
}
}
private fun getAccessToken(): OAuth2AccessToken? {
val request = OAuth2AuthorizeRequest
.withClientRegistrationId("keycloak")
.principal("client-id")
.build()
return authorizedClientManager.authorize(request)?.accessToken
}
}
UserClient
#FeignClient(name="user-service", configuration = [FeignOauth2Configuration::class])
interface UserClient {
#GetMapping("/users")
fun getAllUsers(): List<UserDto>
}

Spring Cloud Vault Configuration without YAML file

I have mentioned the Spring Cloud Vault Configuration in my bootstrap.ymlfile
spring:
cloud:
vault:
authentication: APPROLE
app-role:
role-id: *****
secret-id: ****
host: ****
port: 80
scheme: http
But i dont want to have these in my YML file, rather i would like to have these configured as a bean
#configuration / #bean
Please help. Thanks
I was able to do this successfully by configuring a Bean of type VaultProperties. Below is the code snippet which completely eliminated the need for maintaining the same in bootstrap.yml
#Configuration
public class VaultConfiguration {
#Bean
public VaultProperties vaultProperties() {
VaultProperties vaultProperties = new VaultProperties();
vaultProperties.setAuthentication(VaultProperties.AuthenticationMethod.APPROLE);
VaultProperties.AppRoleProperties appRoleProperties = new VaultProperties.AppRoleProperties();
appRoleProperties.setRoleId("****");
appRoleProperties.setSecretId("****");
vaultProperties.setAppRole(appRoleProperties);
vaultProperties.setHost("***");
vaultProperties.setPort(80);
vaultProperties.setScheme("http");
return vaultProperties;
}
}
Note : When you are having a configuration that should be treated as bootstrap-configuration, then you need to mention the class name under src/main/resources/META-INF/spring.factories
The content in spring.factories is
org.springframework.cloud.bootstrap.BootstrapConfiguration=com.arun.local.cloudconfig.VaultConfiguration

Resources