Spring api gateway return empty response - spring

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.

Related

SpringBoot security allowed path permit all not working in Spring Reactive application

I have an SpringBoot reactive[Webflux] application which has the below mentioned security configuration. Recently I have integrated a module which uses spring-boot-starter-web dependency. After integrating the API's mentioned in the security.config.allowed-paths configuration is not accessible. could you please help me with the solution.
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.util.matcher.NegatedServerWebExchangeMatcher;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsConfigurationSource;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import java.util.Arrays;
#Configuration
#EnableWebFluxSecurity
#EnableReactiveMethodSecurity
public class SecurityConfig {
#Value("${security.config.allowed-paths}")
private String[] allowedPaths;
#Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http.csrf().requireCsrfProtectionMatcher(getURLsForIgnoredCSRF())
.and().cors().configurationSource(corsConfigSource()).and()
.authorizeExchange()
.pathMatchers(allowedPaths).permitAll()
.pathMatchers(HttpMethod.OPTIONS).permitAll();
http.headers().frameOptions().disable();
return http.build();
}
public NegatedServerWebExchangeMatcher getURLsForIgnoredCSRF() {
return new NegatedServerWebExchangeMatcher(exchange -> ServerWebExchangeMatchers.pathMatchers(allowedPaths).matches(exchange));
}
#Bean
CorsConfigurationSource corsConfigSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOriginPatterns(Arrays.asList("https://*.sampleexmple.com"));
configuration.setAllowedMethods(Arrays.asList("GET","POST","OPTIONS"));
configuration.setAllowCredentials(true);
configuration.setAllowedHeaders(Arrays.asList("*"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
Thanks in advance.
Suraj Jannu

Parameter 1 of constructor in required a bean of type that could not be found

I've been stuck for a while now. I'm modifying my Spring Security project by adding Jwt. Currently, I'm trying to make the JwtEncoder and JwtDecoder work in SecurityConfig, I need RSAPrivateKey and RSAPublicKey for these methods. To get these Key-values I'm using a Record with #ConfigurationProperties annotation. But Getting this Record into the SecurtyConfig gives me some problems:
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 1 of constructor in com.ssl.app.security.config.SecurityConfig required a bean of type 'com.ssl.app.security.config.RsaKeyProperties' that could not be found.
Action:
Consider defining a bean of type 'com.ssl.app.security.config.RsaKeyProperties' in your configuration.
This is my SecurtyConfig
import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import com.ssl.app.security.filters.LoginAuthFilter;
import com.ssl.app.utility.ConsoleUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
#Configuration
//#AllArgsConstructor
#EnableWebSecurity
//#EnableConfigurationProperties
public class SecurityConfig {
private final LoginAuthFilter loginAuthFilter;
private final RsaKeyProperties rsaKeyProperties;
public SecurityConfig(LoginAuthFilter loginAuthFilter, RsaKeyProperties rsaKeyProperties) {
this.loginAuthFilter = loginAuthFilter;
this.rsaKeyProperties = rsaKeyProperties;
}
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.csrf(csrf -> csrf.disable())
.authorizeRequests(auth -> auth
.anyRequest().authenticated()
)
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt) // get config_class :: method
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.addFilterBefore(loginAuthFilter, UsernamePasswordAuthenticationFilter.class)
.build();
}
#Bean
JwtDecoder jwtDecoder() {
ConsoleUtil.PrintRow(this.getClass().getSimpleName(),"Decode publicKey", "true");
// Get public key and decode and return
return NimbusJwtDecoder.withPublicKey(rsaKeyProperties.publicKey()).build();
}
#Bean
JwtEncoder jwtEncoder() {
ConsoleUtil.PrintRow(this.getClass().getSimpleName(),"Encode jwt", true);
JWK jwk = new RSAKey.Builder(rsaKeyProperties.publicKey()).privateKey(rsaKeyProperties.privateKey()).build();
JWKSource<SecurityContext> jwks = new ImmutableJWKSet<>(new JWKSet(jwk));
return new NimbusJwtEncoder(jwks);
}
}
Record
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
#ConfigurationProperties(prefix ="rsa")
public record RsaKeyProperties(RSAPublicKey publicKey, RSAPrivateKey privateKey) {
}
I tried adding #EnableConfigurationProperties, and EnableAutoConfiguration to the SecurtyConfig would work, but it has no effect. #Value annotation don't work either. The SecurityConfig required a bean, but what bean?
Maybe you should refer to this article
spring-security-jwt

Spring Cloud Gateway is not routing (only 404)

I have a Spring Cloud Gateway, a eureka service registry and two microservives. I have no problems with sending the request directely to the sevice with: http://localhost:8081/auth. When i want to use the Gateway http://localhost:8080/auth, i always get a 404 error response. The services and the gateway all connect to the eureka server.
Here is my code:
Gateway
application.properties:
server.port=8080
spring.application.name=gateway-service
spring.cloud.gateway.discovery.locator.enabled=true
spring.cloud.gateway.discovery.locator.lower-case-service-id=true
spring.cloud.gateway.routes[0].id=auth-service
spring.cloud.gateway.routes[0].uri=lb://AUTH-SERVICE
spring.cloud.gateway.routes[0].predicates[0]=Path=/auth/**
spring.cloud.gateway.routes[1].id=songs-service
spring.cloud.gateway.routes[1].uri=lb://SONGS-SERVICE
spring.cloud.gateway.routes[1].predicates[0]=Path=/songs/**
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
Main:
package htw.kbe_beleg
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.cloud.netflix.eureka.EnableEurekaClient
#SpringBootApplication
#EnableEurekaClient
class GatewayApplication {
companion object {
#JvmStatic
fun main(args: Array<String>) {
SpringApplication.run(GatewayApplication::class.java, *args)
}
}
}
Eureka
application properties:
server.port= 8761
spring.application.name=discovery-service
eureka.instance.hostname=localhost
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
eureka.client.service-url.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/
Main:
package htw.kbe_beleg
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer
#SpringBootApplication
#EnableEurekaServer
class EurekaServiceRegistration {
companion object {
#JvmStatic
fun main(args: Array<String>) {
SpringApplication.run(EurekaServiceRegistration::class.java, *args)
}
}
}
Microservice
application.properties:
spring.datasource.url= jdbc:mysql://localhost:3306/authdb
spring.datasource.username= admin
server.port= 8081
spring.jpa.properties.hibernate.dialect= org.hibernate.dialect.MySQLDialect
spring.jpa.properties.hibernate.show_sql= true
spring.jpa.hibernate.naming.implicit-strategy= org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl
spring.jpa.hibernate.naming.physical-strategy= org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
spring.application.name= auth-service
eureka.client.service-url.defaultZone= http://localhost:8761/eureka/
Controller :
package htw.kbe_beleg.controller
import htw.kbe_beleg.model.Auth
import htw.kbe_beleg.repository.AuthRepository
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
#RestController
class AuthController(val authRepository: AuthRepository) {
#PostMapping("/auth", consumes = [MediaType.APPLICATION_JSON_VALUE], produces = [MediaType.TEXT_PLAIN_VALUE])
fun authenticateUser(#RequestBody authUser: Auth): ResponseEntity<String> {
...........
}
}
Main:
package htw.kbe_beleg
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.cloud.netflix.eureka.EnableEurekaClient
#SpringBootApplication
#EnableEurekaClient
class AuthMain {
companion object {
#JvmStatic
fun main(args: Array<String>) {
SpringApplication.run(AuthMain::class.java, *args)
}
}
}
I already gone through nearly every related stackoverflow question, changed a lot with no result and reverted it. I just cant see what am i missing. Hope you can help me.
Shame on me. Problem was that i had the wrong dependencies for my gateway. I only had spring-cloud-starter-config, but i needed spring-cloud-starter-gateway... Took me over a week.

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

Why does feign client timeout setting (as in documentation) not works?

I try to configure feign client connect timeout, as defined on official documentation, but it does not work.
The app is just simple demo app.
MyFeign.java
package com.example.demo;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
#FeignClient(name = "myFeign", url = "localhost:9047/payroll", configuration = FeignConfig.class)
public interface MyFeign {
#GetMapping(value = "/api/master/{employee_id}")
ResponseEntity<String> disableMasterPayroll(#PathVariable(name = "employee_id") String employeeId);
}
1st attempt : using configuration class
FeignConfig.java
package com.example.demo;
import org.springframework.context.annotation.Configuration;
import feign.Request;
#Configuration
public class FeignConfig {
public Request.Options feignRequestOptionsCustom() {
return new Request.Options(5000, 5000);
}
}
2nd attempt, using application.yml as defined on spring doc (copy-paste it)
feign:
hystrix:
enabled: false
client:
config:
default:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: basic
But none works.
Found several question on stackoverflow (this, this), but none resolve it.
I did not use hystrix. This is my gradle file
dependencies {
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
}
I'm using boot 2.2 with spring cloud Hoxton.RELEASE.
Any idea how to solve this?

Resources