Alternative For OAuth2FeignRequestInterceptor as it is deprecated NOW - spring-boot

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>
}

Related

Webclient not wiring up kotlin Spring boot and returns null object

I have declared OAuthClientConfiguration setting for WebClient using ReactiveClientRegistrationRepository. But when I try to autowire the WebClient bean it does not pick up the configuration settings of OAuthClientConfiguration and returns null object.
OAuthClientConfiguration defines as follows:
#Configuration
class OAuthClientConfiguration {
#Bean
fun clientRegistrations(
#Value("\${spring.security.oauth2.client.provider.okta.access-token-url}") access_token: String?,
#Value("\${spring.security.oauth2.client.registration.okta.client-id}") client_id: String?,
#Value("\${spring.security.oauth2.client.registration.okta.client-secret}") client_secret: String?,
#Value("\${spring.security.oauth2.client.registration.okta.scope}") scope: String?,
#Value("\${spring.security.oauth2.client.registration.okta.authorization-grant-type}") authorizationGrantType: String?
): ReactiveClientRegistrationRepository? {
val registration = ClientRegistration
.withRegistrationId("okta")
.tokenUri(access_token)
.clientId(client_id)
.clientSecret(client_secret)
.scope(scope)
.authorizationGrantType(AuthorizationGrantType(authorizationGrantType))
.build()
return InMemoryReactiveClientRegistrationRepository(registration)
}
#Bean
fun webClient(clientRegistrations: ReactiveClientRegistrationRepository?): WebClient? {
val clientService = InMemoryReactiveOAuth2AuthorizedClientService(clientRegistrations)
val authorizedClientManager =
AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(clientRegistrations, clientService)
val oauth = ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager)
oauth.setDefaultClientRegistrationId("okta")
return WebClient.builder()
.filter(oauth)
.build()
}
}
And WebClient calling is defined as follows:
#Autowired
private var webClient: WebClient? = null
fun getAccessToken(): String? {
return webClient?.post()
?.retrieve()
?.bodyToFlux(String::class.java)
?.onErrorMap { e: Throwable? -> Exception("message", e) }
?.blockLast();
}
How to create WebClient with configuration defined in OAuthClientConfiguration class?
I want it to be wired up so that the configuration defined in OAuthClientConfiguration could be setup as I initialise it.
I believe the client is created correctly. However, the wiring seems wrong.
As a rule of thumb, it is better practice to wire on the constructor
#Service
class MyService(
val webClient: WebClient
)
Alternative, in Kotlin, I usually use:
#Autowired
lateinit var webClient: WebClient

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

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

Spring api gateway return empty response

i am getting empty response when i use spring security with spring api gateway.
I have a separate auth server.
These are my configurations.
APi Gateway yml config:
server:
port: 8080
spring:
mvc:
log-request-details: true
cloud:
gateway:
httpclient:
connect-timeout: 1000
response-timeout: 5s
wiretap: true
httpserver:
wiretap: true
routes:
- id: service1
uri: lb://example-service
predicates:
- Path=/first/**
- id: auth-server
uri: lb://auth-server
predicates:
- Path=/api/auth/**
default-filters:
#- TokenRelay=
#- SaveSession
security:
oauth2:
client:
registration:
gateway-password:
provider: spring
client-id: admin
client-secret: password
authorization-grant-type: password
scope: openid
client-name: gateway-password
gateway-client-creds:
provider: spring
client-id: user1
client-secret: password
authorization-grant-type: refresh_token
scope: openid
client-name: gateway-client-creds
provider:
spring:
token-uri: http://auth-server:9000/api/auth/token
client-registration-id: gateway-password
logging:
level:
root: debug
org.springframework: debug
reactor.netty.http.client: DEBUG
org.springframework.cloud.gateway: DEBUG
eureka:
client:
service-url:
defaultZone: http://localhost:8083/eureka
API Gateway OauthConfig.java
package com.heal.gateway.config.security;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.WebClientHttpRoutingFilter;
import org.springframework.cloud.gateway.filter.WebClientWriteResponseFilter;
import org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.client.*;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.DefaultReactiveOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.web.reactive.function.client.ServerOAuth2AuthorizedClientExchangeFilterFunction;
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.util.StringUtils;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.*;
import java.util.function.Function;
#Configuration
#Slf4j
public class OauthConfig {
#Value("${client-registration-id}")
private String clientRegistrationId;
#Bean
public WebClientHttpRoutingFilter webClientHttpRoutingFilter(
WebClient webClient, ObjectProvider<List<HttpHeadersFilter>> headersFilters) {
return new WebClientHttpRoutingFilter(webClient, headersFilters);
}
#Bean
public WebClientWriteResponseFilter webClientWriteResponseFilter() {
return new WebClientWriteResponseFilter();
}
#Bean
WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) {
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
oauth2Client.setDefaultOAuth2AuthorizedClient(true);
oauth2Client.setDefaultClientRegistrationId(clientRegistrationId);
return WebClient.builder()
.filter(oauth2Client)
//.clientConnector(new ReactorClientHttpConnector())
//.filter(oauth)
.build();
}
#Bean
public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
ReactiveClientRegistrationRepository clientRegistrationRepository,
ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
.password()
.refreshToken()
.build();
DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager =
new DefaultReactiveOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
authorizedClientManager.setContextAttributesMapper(contextAttributesMapper());
return authorizedClientManager;
}
private Function<OAuth2AuthorizeRequest, Mono<Map<String, Object>>> contextAttributesMapper() {
return authorizeRequest -> {
Map<String, Object> contextAttributes = Collections.emptyMap();
ServerWebExchange serverWebExchange = authorizeRequest.getAttribute(ServerWebExchange.class.getName());
String username = Objects.requireNonNull(serverWebExchange).
getRequest().getQueryParams().getFirst(OAuth2ParameterNames.USERNAME);
String password = serverWebExchange.getRequest().getQueryParams().getFirst(OAuth2ParameterNames.PASSWORD);
if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
contextAttributes = new HashMap<>();
contextAttributes.put(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, username);
contextAttributes.put(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, password);
}
return Mono.just(contextAttributes);
};
}
}
API Gateway - SecurityConfig.java
package com.heal.gateway.config.security;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;
#Slf4j
#Configuration
#EnableWebFluxSecurity
public class SecurityConfig {
#Bean
public SecurityWebFilterChain configure(ServerHttpSecurity http) {
http.headers().cache().disable().frameOptions().disable().xssProtection().disable().contentTypeOptions().disable().and()
.csrf().disable().authorizeExchange(exchanges -> exchanges.anyExchange().permitAll()).oauth2Client()
.and().
oauth2Login();
return http.build();
}
}
Now when i hit any api via api gateway i get 200 but every time the response is empty. Please help i am stuck on this forever.
Let me know if more details are needed.

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

Spring Boot Social Login and Google Calendar API

Problem
Reuse End-User Google Authentication via Spring Security OAuth2 to access Google Calendar API in Web Application
Description
I was able to create a small Spring Boot Web application with Login through Spring Security
application.yaml
spring:
security:
oauth2:
client:
registration:
google:
client-id: <id>
client-secret: <secret>
scope:
- email
- profile
- https://www.googleapis.com/auth/calendar.readonly
When application starts I can access http://localhost:8080/user and user is asked for google login. After successful login profile json is shown in a browser as the response from:
SecurityController
#RestController
class SecurityController {
#RequestMapping("/user")
fun user(principal: Principal): Principal {
return principal
}
}
SecurityConfiguration.kt
#Configuration
class SecurityConfiguration : WebSecurityConfigurerAdapter() {
#Throws(Exception::class)
override fun configure(http: HttpSecurity) {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.oauth2Login()
}
}
Question
I want to reuse this authentication to retrieve all user's Calendar Events. The following code is taken from google's tutorial on accessing calendar API but it creates a completely independent authorization flow and asks user to log in.
#Throws(IOException::class)
private fun getCredentials(httpTransport: NetHttpTransport): Credential {
val clientSecrets = loadClientSecrets()
return triggerUserAuthorization(httpTransport, clientSecrets)
}
private fun loadClientSecrets(): GoogleClientSecrets {
val `in` = CalendarQuickstart::class.java.getResourceAsStream(CREDENTIALS_FILE_PATH)
?: throw FileNotFoundException("Resource not found: $CREDENTIALS_FILE_PATH")
return GoogleClientSecrets.load(JSON_FACTORY, InputStreamReader(`in`))
}
private fun triggerUserAuthorization(httpTransport: NetHttpTransport, clientSecrets: GoogleClientSecrets): Credential {
val flow = GoogleAuthorizationCodeFlow.Builder(
httpTransport, JSON_FACTORY, clientSecrets, SCOPES)
.setDataStoreFactory(FileDataStoreFactory(File(TOKENS_DIRECTORY_PATH)))
.setAccessType("offline")
.build()
val receiver = LocalServerReceiver.Builder().setPort(8880).build()
return AuthorizationCodeInstalledApp(flow, receiver).authorize("user")
}
How can I reuse already done authentication to access end user's calendar events on Google account?
If I understand correctly, what you mean be reusing the authentication is that you want to use the access and refresh tokens Spring retrieved for you in order to use them for requests against Google API.
The user authentication details can be injected into an endpoint method like this:
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient
import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
#RestController
class FooController(val historyService: HistoryService) {
#GetMapping("/foo")
fun foo(#RegisteredOAuth2AuthorizedClient("google") user: OAuth2AuthorizedClient) {
user.accessToken
}
}
With the details in OAuth2AuthorizedClient you should be able to do anything you need with the google API.
If you need to access the API without a user making a request to your service, you can inject OAuth2AuthorizedClientService into a managed component, and use it like this:
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService
import org.springframework.stereotype.Service
#Service
class FooService(val clientService: OAuth2AuthorizedClientService) {
fun foo() {
val user = clientService.loadAuthorizedClient<OAuth2AuthorizedClient>("google", "principal-name")
user.accessToken
}
}

Resources