Microservices router locator dont work with euroka urls - spring

everyone
I just making a microservices application with gateway and eureka
(I used spring 3.0.2)
Eureka server
#SpringBootApplication
#EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
application.yml
server:
port: 8761
eureka:
client:
fetch-registry: false
register-with-eureka: false
Gateway
#SpringBootApplication
#EnableDiscoveryClient
public class GateWayApplication {
public static void main(String[] args) {
SpringApplication.run(GateWayApplication.class, args);
}
}
Cloud config
#Configuration
#Slf4j
#AllArgsConstructor
public class CloudConfig {
private final DiscoveryClient discoveryClient;
private final APIRouter APIRouter;
private URI getURI(String service) throws URISyntaxException {
// log.info("service url - {}", discoveryClient.getInstances(service).get(0).getUri());
// return discoveryClient.getInstances(service).get(0).getUri();
return new URI("lb://" + service + '/');
}
// #Bean
// #LoadBalanced
// public RestTemplate restTemplate() {
// return new RestTemplate();
// }
#Bean
#LoadBalanced
public WebClient.Builder loadBalancedWebClientBuilder() {
return WebClient.builder();
}
#Bean
public RouteLocator gatewayRoutes(RouteLocatorBuilder builder) {
var builderRoutes = builder.routes();
for (var api : APIRouter.getApis()) {
for (var path : api.getRoutes()) {
builderRoutes.route(r ->
{
try {
return r.path("/api" + "/" + api.getVersion() + "/" + path.getUrl() + "/**")
.filters(f -> f.stripPrefix(3))
.uri(getURI(path.getServiceName()));
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
});
}
}
return builderRoutes.build();
}
}
application.yml
server:
port: 5000
spring:
main:
web-application-type: reactive
allow-bean-definition-overriding: true
mvc:
pathmatch:
matching-strategy: ant_path_matcher
application:
name: gateway
datasource:
url: jdbc:postgresql://localhost:5432/jwt_security
username: postgres
password: 123456
driver-class-name: org.postgresql.Driver
jpa:
hibernate:
ddl-auto: update
show-sql: true
properties:
hibernate:
format_sql: true
database: postgresql
database-platform: org.hibernate.dialect.PostgreSQLDialect
api-router:
apis:
- version: v1
routes:
- url: kleiderkammer
service-name: KLEIDER-KAMMER
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
Kleiderkammer
#RestController
#RequestMapping("public/")
public class PublicController {
#GetMapping("/test")
String test() {
return "Hello from public";
}
}
#SpringBootApplication
#EnableDiscoveryClient
public class KleiderkammerApplication {
public static void main(String[] args) {
SpringApplication.run(KleiderkammerApplication.class, args);
}
}
server:
port: 8700
spring:
application:
name: kleider-kammer
datasource:
url: jdbc:postgresql://localhost:5432/kleiderkammer
username: postgres
password: 123456
driver-class-name: org.postgresql.Driver
jpa:
hibernate:
ddl-auto: update
show-sql: true
properties:
hibernate:
format_sql: true
database: postgresql
database-platform: org.hibernate.dialect.PostgreSQLDialect
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
instance:
prefer-ip-address: true
But when i make request
http://localhost:5000/api/v1/kleiderkammer/public/test
i just get nothing
enter image description here
And i have not an error message
I have tried to change lb to http(than i have an error)
I tried to use RestTemplate instead of WebClient.Builder(for load balancer)

Related

Struggled to find a cause of redirecting on the error page while requesting a token from Spring Cloud Authentication Server

I've got a problem with a spring cloud auth server. I sent a request from spring cloud gateway service and auth server received and authenticated user but it's redirected me on the error page.
Here is a spring cloud gateway application.yaml file
server:
port: 9090
spring:
application:
name: api-gateway
jackson:
deserialization:
FAIL_ON_IGNORED_PROPERTIES: false
serialization:
INDENT_OUTPUT: false
WRITE_DATES_AS_TIMESTAMPS: false
WRITE_BIGDECIMAL_AS_PLAIN: true
security:
oauth2:
client:
registration:
insta-client-oidc:
client-id: insta-client
client-secret: secret
scope: openid
authorization-grant-type: authorization_code
redirect-uri-template: '{baseUrl}/login/oauth2/code/insta-client-oidc'
client-authentication-method: client_secret_post
client-name: insta-client-oidc
provider:
insta-client-oidc:
issuer-uri: http://localhost:9000
token-uri: http://localhost:9000/oauth2/token
authorization-uri: http://localhost:9000/oauth2/authorize
user-info-uri: http://localhost:9000/userinfo
user-name-attribute: user_name
user-info-authentication-method: form
cloud:
gateway:
discovery:
locator:
enabled: true
routes:
- id: user-service-route
uri: http://localhost:8080 #${USERS_SERVICE_URL:lb://user-service}
predicates:
- Path=/public/v1/users
- Path=/public/v1/users/{username}
- Path=/public/v1/users/search
- Path=/public/v1/users/{username}/followers/**
- Path=/public/v1/users/{username}/followings/**
- Path=/public/v1/users/{username}/password
- Path=/public/v1/users/{username}/photo
- Path=/public/v1/users/{username}/profile
default-filters:
- RemoveRequestHeader=Cookie
- TokenRelay=
eureka:
instance:
instance-id: ${spring.application.name}:${instanceId:${random.value}}
client:
service-url:
defaultZone: http://localhost:8761/eureka
fetch-registry: true
register-with-eureka: true
enabled: false
management:
endpoint:
gateway:
enabled: true
endpoints:
web:
exposure:
include: gateway
logging:
level:
com: TRACE
org: TRACE
Here is a spring authentication server config:
#Configuration
#RequiredArgsConstructor
public class AuthorizationServerConfig {
private final SecurityProperties securityProperties;
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(10);
}
#Bean
#Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
return http.formLogin(Customizer.withDefaults()).build();
}
#Bean
public SecurityFilterChain nextSecurityFilterChain(HttpSecurity http) throws Exception {
return http
.csrf().disable()
.authorizeRequests(authorizeRequests -> authorizeRequests.anyRequest().authenticated())
.formLogin(Customizer.withDefaults())
.build();
}
#Bean
static BeanFactoryPostProcessor removeErrorSecurityFilter() {
return (beanFactory) ->
((DefaultListableBeanFactory)beanFactory).removeBeanDefinition("errorPageSecurityInterceptor");
}
#Bean
public RegisteredClientRepository registeredClientRepository(PasswordEncoder passwordEncoder) {
RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId(securityProperties.getClientId())
.clientSecret(passwordEncoder.encode(securityProperties.getClientSecret()))
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.redirectUri("http://localhost:9090/login/oauth2/code/insta-client-oidc")
.redirectUri("http://localhost:9090/authorized")
.scope(OidcScopes.OPENID)
.tokenSettings(tokenSettings())
//.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
.build();
return new InMemoryRegisteredClientRepository(registeredClient);
}
#Bean
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
}
#Bean
public JWKSource<SecurityContext> jwkSource() throws NoSuchAlgorithmException {
RSAKey rsaKey = generateRsa();
JWKSet jwkSet = new JWKSet(rsaKey);
return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
}
private static RSAKey generateRsa() throws NoSuchAlgorithmException {
KeyPair keyPair = generateRsaKey();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
return new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyID(UUID.randomUUID().toString())
.build();
}
private static KeyPair generateRsaKey() throws NoSuchAlgorithmException {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
return keyPairGenerator.generateKeyPair();
}
#Bean
public ProviderSettings providerSettings() {
return ProviderSettings.builder().issuer("http://localhost:9000").build();
}
#Bean
public OAuth2TokenCustomizer<JwtEncodingContext> tokenCustomizer() {
return context -> {
Authentication principal = context.getPrincipal();
if (Objects.equals(context.getTokenType().getValue(), "access_token") && principal instanceof UsernamePasswordAuthenticationToken) {
Set<String> authorities = principal.getAuthorities().stream()
.map(GrantedAuthority::getAuthority).collect(Collectors.toSet());
context.getClaims().claim("authorities", authorities);
}
};
}
private TokenSettings tokenSettings() {
return TokenSettings.builder()
.accessTokenFormat(OAuth2TokenFormat.SELF_CONTAINED)
.accessTokenTimeToLive(Duration.ofMinutes(securityProperties.getExpirationTimeInMinutes()))
.idTokenSignatureAlgorithm(SignatureAlgorithm.RS256)
.build();
}
}
Whole project you can find here: https://github.com/ZhenyaShvyrkov/insta-copy
log file you also can find in github repository
Thank you for any help in advance. Have a good one!

actuator/refresh path not updating #value variables when use spring cloud vault

When I update a property in the vault and call actuator / refresh, it still shows an older value.
I use spring boot and spring cloud.
<spring-cloud-dependencies.version>2020.0.3</spring-cloud-dependencies.version>
<spring-boot-dependencies.version>2.5.3</spring-boot-dependencies.version>
#SpringBootApplication
#EnableEurekaClient
#VaultPropertySource(value = "secret/services/${spring.application.name}/config", propertyNamePrefix = "", renewal = VaultPropertySource.Renewal.RENEW)
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
#RestController
public class ErrorControllerImpl implements ErrorController {
#Value("${gateway.prefix}")
private String prefixPath;
#Override
public Mono<String> errorPost(String serviceName) {
return errorResponse(serviceName);
}
#Override
public Mono<String> errorGet(String serviceName) {
System.out.println(prefixPath);
return errorResponse(serviceName);
}
Yml
spring:
application:
name: ${APP_NAME}
profiles:
active: ${PROFILE:dev}
cloud:
config:
enabled: ${CONFIG_SERVER_ENABLED:false}
vault:
uri: https://localhost:8200
authentication: APPROLE
app-role:
role-id: ${VAULT_SERVICE_ROLE_ID}
secret-id: ${VAULT_SERVICE_SECRET_ID}
scheme: http
fail-fast: true
kv:
enabled: false
The prefixPath in the controller does not change after refresh.
What would be the reason ?
Thanks for your help.

Tests not working for multiple datasource with spring boot liquibase

I am creating unittesting for spring boot application having multiple datasources. (Configuration is heavily inspired from this answer)
#Configuration
public class DatasourceConfig {
#Primary
#Bean
#ConfigurationProperties(prefix = "datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
#Bean
#ConfigurationProperties(prefix = "datasource.primary.liquibase")
public LiquibaseProperties primaryLiquibaseProperties() {
return new LiquibaseProperties();
}
#Bean
public SpringLiquibase primaryLiquibase() {
return springLiquibase(primaryDataSource(), primaryLiquibaseProperties());
}
#Bean
#ConfigurationProperties(prefix = "datasource.secondary")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
#Bean
#ConfigurationProperties(prefix = "datasource.secondary.liquibase")
public LiquibaseProperties secondaryLiquibaseProperties() {
return new LiquibaseProperties();
}
#Bean
public SpringLiquibase secondaryLiquibase() {
return springLiquibase(secondaryDataSource(), secondaryLiquibaseProperties());
}
private static SpringLiquibase springLiquibase(DataSource dataSource, LiquibaseProperties properties) {
SpringLiquibase liquibase = new SpringLiquibase();
liquibase.setDataSource(dataSource);
liquibase.setChangeLog(properties.getChangeLog());
liquibase.setContexts(properties.getContexts());
liquibase.setDefaultSchema(properties.getDefaultSchema());
liquibase.setDropFirst(properties.isDropFirst());
liquibase.setShouldRun(properties.isEnabled());
liquibase.setLabels(properties.getLabels());
liquibase.setChangeLogParameters(properties.getParameters());
liquibase.setRollbackFile(properties.getRollbackFile());
return liquibase;
}
...
}
and
datasource:
primary:
url: jdbc:mysql://localhost/primary
username: username
password: password
liquibase:
change-log: classpath:/db/changelog/db.primary.changelog-master.xml
secondary:
url: jdbc:mysql://localhost/secondary
username: username
password: password
liquibase:
change-log: classpath:/db/changelog/db.secondary.changelog-master.xml
The application is running properly.
The problem is unit testing is failing with this configuration. I have used h2 as embedded DB. Added h2 as test dependency and added h2 in datasource URL as well in application.yaml for testing.
application.yaml for testing
management:
endpoints.web.exposure.include: "*"
security.enabled: false
spring:
zipkin:
discoveryClientEnabled: false
sender:
type: kafka
#type: web
liquibase:
enabled: false
datasource:
url: "jdbc:h2:mem:testdb"
jdbc-url: "jdbc:h2:mem:testdb"
username: sa
password:
secondary-datasource:
url: "jdbc:h2:mem:testdb"
jdbc-url: "jdbc:h2:mem:testdb"
username: sa
password:
datasource:
primary-liquibase:
liquibase:
url: "jdbc:h2:mem:testdb"
username: sa
password:
secondary-liquibase:
liquibase:
url: "jdbc:h2:mem:testdb"
username: sa
password:
liquibase:
enable: false
Unit testing file
package com.foo.bar.car;
import com.foo.bar.car.utils.KSUIDGenerator;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.boot.jdbc.EmbeddedDatabaseConnection;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
#SpringBootTest
#AutoConfigureMockMvc
#AutoConfigureTestDatabase
// https://stackoverflow.com/a/58786742/1534925
//#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
//#AutoConfigureTestDatabase(connection = EmbeddedDatabaseConnection.H2)
class CarApplicationTests {
#Test
public void contextLoads() {
String h = "Hello World!";
Assertions.assertEquals(h, "Hello World!");
}
#Test
public void testKSUIDGeneration() {
Assertions.assertNotNull(KSUIDGenerator.generateKSUID());
}
}
I have created a repository to demonstrate this.
When I do gradlew clean check it gives me following error
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [liquibase.integration.spring.SpringLiquibase]: Factory method 'primaryLiquibase' threw exception; nested exception is java.lang.IllegalArgumentException: No visible constructors in class org.springframework.boot.test.autoconfigure.jdbc.TestDatabaseAutoConfiguration$EmbeddedDataSourceFactoryBean
Not sure what configuration change I am missing.
There are several adjustments to be done to make it work:
Exclude Autoconfiguration for DB and Liquibase in your CarApplication.java
#SpringBootApplication(exclude = { DataSourceAutoConfiguration.class,
LiquibaseAutoConfiguration.class })
Get rid of sping prefix in your configuration (both in app and test properties)
Add liquibase changelog location to test properties
Double check with the setup below in case it wont work after all listed above adjustments (I changed things here and there the way I usually code to make it comfortable to me):
CarApplication.java
#SpringBootApplication(exclude = { DataSourceAutoConfiguration.class,
LiquibaseAutoConfiguration.class })
public class CarApplication {
public static void main(String[] args) {
SpringApplication.run(CarApplication.class, args);
}
}
application.yaml
server:
port: 9999
datasource:
url: jdbc:postgresql://localhost:8888/foo
jdbcUrl: jdbc:postgresql://localhost:5432/foo
username: username
password: password
driverClassName: org.postgresql.Driver
liquibase:
change-log: classpath:db-changelog/primary.xml
secondary-datasource:
url: jdbc:postgresql://localhost:8888/bar
jdbcUrl: jdbc:postgresql://localhost:5432/bar
username: username
password: password
driverClassName: org.postgresql.Driver
liquibase:
change-log: classpath:db-changelog/secondary.xml
DatabaseConfig.java
#Configuration
public class DatabaseConfig {
#Bean
#Primary
#ConfigurationProperties(prefix = "datasource")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
#Bean
#ConfigurationProperties(prefix = "secondary-datasource")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
#Bean
#ConfigurationProperties(prefix = "datasource.liquibase")
public LiquibaseProperties primaryLiquibaseProperties() {
return new LiquibaseProperties();
}
#Bean("liquibase")
public SpringLiquibase primaryLiquibase() {
return springLiquibase(primaryDataSource(), primaryLiquibaseProperties());
}
#Bean
#ConfigurationProperties(prefix = "secondary-datasource.liquibase")
public LiquibaseProperties secondaryLiquibaseProperties() {
return new LiquibaseProperties();
}
#Bean
public SpringLiquibase secondaryLiquibase() {
return springLiquibase(secondaryDataSource(), secondaryLiquibaseProperties());
}
private static SpringLiquibase springLiquibase(DataSource dataSource, LiquibaseProperties properties) {
SpringLiquibase liquibase = new SpringLiquibase();
liquibase.setDataSource(dataSource);
liquibase.setChangeLog(properties.getChangeLog());
liquibase.setContexts(properties.getContexts());
liquibase.setDefaultSchema(properties.getDefaultSchema());
liquibase.setDropFirst(properties.isDropFirst());
liquibase.setShouldRun(properties.isEnabled());
liquibase.setLabels(properties.getLabels());
liquibase.setChangeLogParameters(properties.getParameters());
liquibase.setRollbackFile(properties.getRollbackFile());
return liquibase;
}
}
test application.yaml
datasource:
url: "jdbc:h2:mem:testdb"
jdbc-url: "jdbc:h2:mem:testdb"
username: sa
password:
liquibase:
change-log: classpath:db-changelog/primary.xml
secondary-datasource:
url: "jdbc:h2:mem:testdb"
jdbc-url: "jdbc:h2:mem:testdb"
username: sa
password:
liquibase:
change-log: classpath:db-changelog/secondary.xml
CarApplicationTest.java
#SpringBootTest
class CarApplicationTests {
#Test
public void contextLoads() {
String h = "Hello World!";
Assertions.assertEquals(h, "Hello World!");
}
#Test
public void testKSUIDGeneration() {
Assertions.assertNotNull(KSUIDGenerator.generateKSUID());
}
}

#RefreshScope not working. Properties are not updated after being modified the second time in Consul

I am working with Consul using spring-cloud-consul and currently have this problem. I create a key in consul as config/ConsulServer/my/username with value as "bob". In my controller, i return this value if a API call "/foo" is made. When i modified it the first time to "steve" the value is updated and i get "steve". However, it does not work the second time. The value is still "steve". Could anyone please help on what I did wrong ? My codes are
#SpringBootApplication
#EnableDiscoveryClient
public class ConsulServer {
public static void main(String[] args) {
SpringApplication.run(ConsulServer.class, args);
}
}
#Component
#RefreshScope
#ConfigurationProperties("my")
public class SampleProperties {
private String username;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username= username;
}
}
#RestController
public class ConsulController {
#Autowired
private SampleProperties consulConfig;
#GetMapping("/foo")
public String prop() {
return this.consulConfig.getUsername();
}
}
My bootstrap.yml is
spring:
application:
name: ConsulServer
cloud:
consul:
host: localhost
port: 8500
config:
enabled: true
fail-fast: true
watch:
enabled: true
discovery:
register: true
My application.yml is
spring:
application:
name: ConsulServer
I am using spring-cloud-starter-consul-all version 2.1.0.RC3

Spring data - If primary datasource fails the second does not come up

I am using Spring boot and Spring data and I want to use primarily a MySQL datasource but if fails to connect go to a H2 datasource.
So far, I do the change just moving the #Primary in the configurations, but if I put the #Primary in the MySQL (main data source) and stop the MySQL server in my pc, the other bean does not come up... What do I need?
application.yml:
# Main properties
spring:
application:
name: app
jpa:
database: default
show-sql: false
hibernate:
ddl-auto: update
properties:
hibernate:
format_sql: false
current_session_context_class: org.springframework.orm.hibernate5.SpringSessionContext
# Main database: MySQL
main.datasource:
url: jdbc:mysql://localhost:3306/app?useSSL=false
driver-class-name: com.mysql.jdbc.Driver
username: sa
password: sa
# Backup database: H2
backup.datasource:
url: jdbc:h2:${project.directory}/app;DB_CLOSE_ON_EXIT=FALSE
driver-class-name: org.h2.Driver
username: sa
password: sa
Main data source
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories("org.app")
#EntityScan("org.app")
public class MainDataSourceConfig {
#Primary
#Bean(name = "mainDataSource")
#ConfigurationProperties(prefix = "main.datasource")
public DataSource mainDataSource() {
return DataSourceBuilder.create().build();
}
}
Backup data source:
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories("org.app")
#EntityScan("org.app")
public class BackupDataSourceConfig {
#Bean(name = "backupDataSource")
#ConfigurationProperties(prefix = "backup.datasource")
public DataSource backupDataSource() {
return DataSourceBuilder.create().build();
}
}
Thanks!
I figure out how to do it. Hope this can help anyone:
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories("org.app")
#EntityScan("org.app")
public class DataSourceConfig {
private static final String USERNAME = "sa";
private static final String PASSWORD = "sa";
#Bean
#Primary
public DataSource dataSource() {
DataSource dataSource;
try {
dataSource = getMainDataSource();
dataSource.getConnection().isValid(500);
} catch (Exception e) {
log.error("Main database not valid.", e);
dataSource =getBackupDataSource();
}
return dataSource;
}
private DataSource getMainDataSource() {
return DataSourceBuilder.create()
.driverClassName("com.mysql.jdbc.Driver")
.username(USERNAME)
.password(PASSWORD)
.url("jdbc:mysql://localhost:3306/app?useSSL=false")
.build();
}
private DataSource getBackupDataSource() {
return DataSourceBuilder.create()
.driverClassName("org.h2.Driver")
.username(USERNAME)
.password(PASSWORD)
.url("jdbc:h2:/app;DB_CLOSE_ON_EXIT=FALSE")
.build();
}
}
Just on bean.

Resources