JAVA-17164 update config and discovery services to use Spring Boot 2.7.X (#13967)

This commit is contained in:
Kasra Madadipouya 2023-05-31 13:40:49 +02:00 committed by GitHub
parent ff30f4648f
commit 920214f38d
40 changed files with 329 additions and 485 deletions

View File

@ -18,3 +18,5 @@ spring.redis.port=6379
spring.sleuth.sampler.percentage=1.0
spring.sleuth.web.skipPattern=(^cleanup.*)
spring.zipkin.baseUrl=http://localhost:9411

View File

@ -6,25 +6,13 @@ eureka.client.registryFetchIntervalSeconds = 5
management.security.sessions=always
zuul.routes.book-service.path=/book-service/**
zuul.routes.book-service.sensitive-headers=Set-Cookie,Authorization
hystrix.command.book-service.execution.isolation.thread.timeoutInMilliseconds=600000
zuul.routes.rating-service.path=/rating-service/**
zuul.routes.rating-service.sensitive-headers=Set-Cookie,Authorization
hystrix.command.rating-service.execution.isolation.thread.timeoutInMilliseconds=600000
zuul.routes.discovery.path=/discovery/**
zuul.routes.discovery.sensitive-headers=Set-Cookie,Authorization
zuul.routes.discovery.url=http://localhost:8082
hystrix.command.discovery.execution.isolation.thread.timeoutInMilliseconds=600000
logging.level.org.springframework.web.=debug
logging.level.org.springframework.security=debug
logging.level.org.springframework.cloud.netflix.zuul=debug
spring.redis.host=localhost
spring.redis.port=6379
spring.sleuth.sampler.percentage=1.0
spring.sleuth.web.skipPattern=(^cleanup.*|.+favicon.*)
spring.sleuth.web.skipPattern=(^cleanup.*|.+favicon.*)
spring.zipkin.baseUrl=http://localhost:9411

View File

@ -18,3 +18,5 @@ spring.redis.port=6379
spring.sleuth.sampler.percentage=1.0
spring.sleuth.web.skipPattern=(^cleanup.*)
spring.zipkin.baseUrl=http://localhost:9411

View File

@ -9,9 +9,9 @@
<parent>
<groupId>com.baeldung</groupId>
<artifactId>parent-boot-1</artifactId>
<artifactId>parent-boot-2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../../../parent-boot-1</relativePath>
<relativePath>../../../parent-boot-2</relativePath>
</parent>
<dependencyManagement>
@ -33,7 +33,7 @@
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
@ -42,7 +42,7 @@
</dependencies>
<properties>
<spring-cloud-dependencies.version>Brixton.SR7</spring-cloud-dependencies.version>
<spring-cloud-dependencies.version>2021.0.7</spring-cloud-dependencies.version>
</properties>
</project>

View File

@ -2,12 +2,12 @@ package com.baeldung.spring.cloud.bootstrap.config;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.config.server.EnableConfigServer;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableConfigServer
@EnableEurekaClient
@EnableDiscoveryClient
public class ConfigApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigApplication.class, args);

View File

@ -1,23 +1,41 @@
package com.baeldung.spring.cloud.bootstrap.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.context.annotation.Bean;
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.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
public class SecurityConfig {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("configUser").password("configPassword").roles("SYSTEM");
@Bean
public InMemoryUserDetailsManager userDetailsService(BCryptPasswordEncoder bCryptPasswordEncoder) {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("configUser")
.password(bCryptPasswordEncoder.encode("configPassword"))
.roles("SYSTEM")
.build());
return manager;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().hasRole("SYSTEM").and().httpBasic().and().csrf().disable();
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest()
.hasRole("SYSTEM")
.and()
.httpBasic()
.and()
.csrf()
.disable();
return http.build();
}
@Bean
public BCryptPasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}
}

View File

@ -9,9 +9,9 @@
<parent>
<groupId>com.baeldung</groupId>
<artifactId>parent-boot-1</artifactId>
<artifactId>parent-boot-2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../../../parent-boot-1</relativePath>
<relativePath>../../../parent-boot-2</relativePath>
</parent>
<dependencyManagement>
@ -33,7 +33,11 @@
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
@ -41,7 +45,7 @@
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
@ -50,7 +54,7 @@
</dependencies>
<properties>
<spring-cloud-dependencies.version>Edgware.SR5</spring-cloud-dependencies.version>
<spring-cloud-dependencies.version>2021.0.7</spring-cloud-dependencies.version>
</properties>
</project>

View File

@ -17,7 +17,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("discUser").password("discPassword").roles("SYSTEM");
auth.inMemoryAuthentication().withUser("discUser").password("{noop}discPassword").roles("SYSTEM");
}
@Override

View File

@ -9,9 +9,9 @@
<parent>
<groupId>com.baeldung</groupId>
<artifactId>parent-boot-1</artifactId>
<artifactId>parent-boot-2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../../../parent-boot-1</relativePath>
<relativePath>../../../parent-boot-2</relativePath>
</parent>
<dependencyManagement>
@ -33,11 +33,15 @@
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
@ -45,7 +49,7 @@
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
@ -53,11 +57,15 @@
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
@ -97,7 +105,7 @@
</build>
<properties>
<spring-cloud-dependencies.version>Dalston.RELEASE</spring-cloud-dependencies.version>
<spring-cloud-dependencies.version>2021.0.7</spring-cloud-dependencies.version>
</properties>
</project>

View File

@ -1,8 +1,8 @@
package com.baeldung.spring.cloud.bootstrap.gateway;
import org.springframework.boot.web.servlet.ErrorPage;
import org.springframework.boot.web.servlet.ErrorPageRegistrar;
import org.springframework.boot.web.servlet.ErrorPageRegistry;
import org.springframework.boot.web.server.ErrorPage;
import org.springframework.boot.web.server.ErrorPageRegistrar;
import org.springframework.boot.web.server.ErrorPageRegistry;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

View File

@ -1,76 +1,15 @@
package com.baeldung.spring.cloud.bootstrap.gateway;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.discovery.EurekaClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
import org.springframework.cloud.netflix.ribbon.RibbonClientSpecification;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.cloud.sleuth.metric.SpanMetricReporter;
import org.springframework.cloud.sleuth.zipkin.HttpZipkinSpanReporter;
import org.springframework.cloud.sleuth.zipkin.ZipkinProperties;
import org.springframework.cloud.sleuth.zipkin.ZipkinSpanReporter;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
import zipkin.Span;
import java.util.ArrayList;
import java.util.List;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableZuulProxy
@EnableEurekaClient
@EnableFeignClients
@EnableDiscoveryClient
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
@Autowired(required = false)
private List<RibbonClientSpecification> configurations = new ArrayList<>();
@Autowired
private EurekaClient eurekaClient;
@Autowired
private SpanMetricReporter spanMetricReporter;
@Autowired
private ZipkinProperties zipkinProperties;
@Value("${spring.sleuth.web.skipPattern}")
private String skipPattern;
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
@Bean
public SpringClientFactory springClientFactory() {
SpringClientFactory factory = new SpringClientFactory();
factory.setConfigurations(this.configurations);
return factory;
}
@Bean
public ZipkinSpanReporter makeZipkinSpanReporter() {
return new ZipkinSpanReporter() {
private HttpZipkinSpanReporter delegate;
private String baseUrl;
@Override
public void report(Span span) {
InstanceInfo instance = eurekaClient.getNextServerFromEureka("zipkin", false);
if (baseUrl == null || !instance.getHomePageUrl().equals(baseUrl)) {
baseUrl = instance.getHomePageUrl();
}
delegate = new HttpZipkinSpanReporter(new RestTemplate(), baseUrl, zipkinProperties.getFlushInterval(), spanMetricReporter);
if (!span.name.matches(skipPattern)) delegate.report(span);
}
};
}
}

View File

@ -1,37 +1,59 @@
package com.baeldung.spring.cloud.bootstrap.gateway;
import org.springframework.beans.factory.annotation.Autowired;
import static org.springframework.security.config.Customizer.withDefaults;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.authentication.RedirectServerAuthenticationSuccessHandler;
@EnableWebSecurity
@EnableWebFluxSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
public class SecurityConfig {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("password").roles("USER")
.and()
.withUser("admin").password("admin").roles("ADMIN");
@Bean
public MapReactiveUserDetailsService userDetailsService() {
UserDetails user = User.withUsername("user")
.password(passwordEncoder().encode("password"))
.roles("USER")
.build();
UserDetails adminUser = User.withUsername("admin")
.password(passwordEncoder().encode("admin"))
.roles("ADMIN")
.build();
return new MapReactiveUserDetailsService(user, adminUser);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.defaultSuccessUrl("/home/index.html", true)
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http.formLogin()
.authenticationSuccessHandler(new RedirectServerAuthenticationSuccessHandler("/home/index.html"))
.and()
.authorizeRequests()
.antMatchers("/book-service/**", "/rating-service/**", "/login*", "/").permitAll()
.antMatchers("/eureka/**").hasRole("ADMIN")
.anyRequest().authenticated()
.authorizeExchange()
.pathMatchers("/book-service/**", "/rating-service/**", "/login*", "/")
.permitAll()
.pathMatchers("/eureka/**")
.hasRole("ADMIN")
.anyExchange()
.authenticated()
.and()
.logout()
.logout()
.and()
.csrf().disable();
.csrf()
.disable()
.httpBasic(withDefaults());
return http.build();
}
}

View File

@ -1,11 +1,10 @@
package com.baeldung.spring.cloud.bootstrap.gateway;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.data.redis.RedisFlushMode;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
import org.springframework.session.web.context.AbstractHttpSessionApplicationInitializer;
import org.springframework.session.data.redis.config.annotation.web.server.EnableRedisWebSession;
@Configuration
@EnableRedisHttpSession(redisFlushMode = RedisFlushMode.IMMEDIATE)
public class SessionConfig extends AbstractHttpSessionApplicationInitializer {
@EnableRedisWebSession
public class SessionConfig {
}

View File

@ -1,7 +1,6 @@
package com.baeldung.spring.cloud.bootstrap.gateway.client.book;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@ -9,6 +8,6 @@ import org.springframework.web.bind.annotation.RequestMethod;
@FeignClient(value = "book-service")
public interface BooksClient {
@RequestMapping(value = "/books/{bookId}", method = {RequestMethod.GET})
@RequestMapping(value = "/books/{bookId}", method = { RequestMethod.GET })
Book getBookById(@PathVariable("bookId") Long bookId);
}

View File

@ -1,17 +1,16 @@
package com.baeldung.spring.cloud.bootstrap.gateway.client.rating;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import java.util.List;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
@FeignClient(value = "rating-service")
public interface RatingsClient {
@RequestMapping(value = "/ratings", method = {RequestMethod.GET})
@RequestMapping(value = "/ratings", method = { RequestMethod.GET })
List<Rating> getRatingsByBookId(@RequestParam("bookId") Long bookId, @RequestHeader("Cookie") String session);
}

View File

@ -0,0 +1,25 @@
package com.baeldung.spring.cloud.bootstrap.gateway.filter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class SessionSavingPreFilter implements GlobalFilter {
private static final Logger logger = LoggerFactory.getLogger(SessionSavingPreFilter.class);
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return exchange.getSession()
.flatMap(session -> {
logger.debug("SessionId: {}", session.getId());
return chain.filter(exchange);
});
}
}

View File

@ -1,47 +0,0 @@
package com.baeldung.spring.cloud.bootstrap.gateway.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpSession;
@Component
public class SessionSavingZuulPreFilter extends ZuulFilter {
private Logger log = LoggerFactory.getLogger(this.getClass());
@Autowired
private SessionRepository repository;
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
HttpSession httpSession = context.getRequest().getSession();
Session session = repository.getSession(httpSession.getId());
context.addZuulRequestHeader("Cookie", "SESSION=" + httpSession.getId());
log.info("ZuulPreFilter session proxy: {}", session.getId());
return null;
}
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 0;
}
}

View File

@ -3,5 +3,7 @@ spring.cloud.config.discovery.service-id=config
spring.cloud.config.discovery.enabled=true
spring.cloud.config.username=configUser
spring.cloud.config.password=configPassword
spring.cloud.gateway.discovery.locator.enabled=true
spring.cloud.gateway.discovery.locator.lowerCaseServiceId=true
eureka.client.serviceUrl.defaultZone=http://discUser:discPassword@localhost:8082/eureka/

View File

@ -20,7 +20,6 @@
<module>gateway</module>
<module>svc-book</module>
<module>svc-rating</module>
<module>zipkin</module>
<module>customer-service</module>
<module>order-service</module>
</modules>

View File

@ -10,9 +10,9 @@
<parent>
<groupId>com.baeldung</groupId>
<artifactId>parent-boot-1</artifactId>
<artifactId>parent-boot-2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../../../parent-boot-1</relativePath>
<relativePath>../../../parent-boot-2</relativePath>
</parent>
<dependencyManagement>
@ -34,7 +34,11 @@
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
@ -46,7 +50,7 @@
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
@ -63,12 +67,16 @@
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>
</dependencies>
<properties>
<spring-cloud-dependencies.version>Dalston.RELEASE</spring-cloud-dependencies.version>
<spring-cloud-dependencies.version>2021.0.7</spring-cloud-dependencies.version>
</properties>
</project>

View File

@ -1,53 +1,14 @@
package com.baeldung.spring.cloud.bootstrap.svcbook;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.discovery.EurekaClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.sleuth.metric.SpanMetricReporter;
import org.springframework.cloud.sleuth.zipkin.HttpZipkinSpanReporter;
import org.springframework.cloud.sleuth.zipkin.ZipkinProperties;
import org.springframework.cloud.sleuth.zipkin.ZipkinSpanReporter;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
import zipkin.Span;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
public class BookServiceApplication {
@Autowired
private EurekaClient eurekaClient;
@Autowired
private SpanMetricReporter spanMetricReporter;
@Autowired
private ZipkinProperties zipkinProperties;
@Value("${spring.sleuth.web.skipPattern}")
private String skipPattern;
public static void main(String[] args) {
SpringApplication.run(BookServiceApplication.class, args);
}
@Bean
public ZipkinSpanReporter makeZipkinSpanReporter() {
return new ZipkinSpanReporter() {
private HttpZipkinSpanReporter delegate;
private String baseUrl;
@Override
public void report(Span span) {
InstanceInfo instance = eurekaClient.getNextServerFromEureka("zipkin", false);
if (baseUrl == null || !instance.getHomePageUrl().equals(baseUrl)) {
baseUrl = instance.getHomePageUrl();
}
delegate = new HttpZipkinSpanReporter(new RestTemplate(), baseUrl, zipkinProperties.getFlushInterval(), spanMetricReporter);
if (!span.name.matches(skipPattern)) delegate.report(span);
}
};
}
}

View File

@ -0,0 +1,16 @@
package com.baeldung.spring.cloud.bootstrap.svcbook;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.web.http.DefaultCookieSerializer;
@Configuration
public class CookieConfig {
@Bean
public DefaultCookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setUseBase64Encoding(false);
return serializer;
}
}

View File

@ -1,36 +1,37 @@
package com.baeldung.spring.cloud.bootstrap.svcbook;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.SecurityFilterChain;
@EnableWebSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
public class SecurityConfig {
@Autowired
public void configureGlobal1(AuthenticationManagerBuilder auth) throws Exception {
//try in memory auth with no users to support the case that this will allow for users that are logged in to go anywhere
public void registerAuthProvider(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic()
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http.authorizeHttpRequests((auth) -> auth.antMatchers(HttpMethod.GET, "/books")
.permitAll()
.antMatchers(HttpMethod.GET, "/books/*")
.permitAll()
.antMatchers(HttpMethod.POST, "/books")
.hasRole("ADMIN")
.antMatchers(HttpMethod.PATCH, "/books/*")
.hasRole("ADMIN")
.antMatchers(HttpMethod.DELETE, "/books/*")
.hasRole("ADMIN"))
.csrf()
.disable()
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/books").permitAll()
.antMatchers(HttpMethod.GET, "/books/*").permitAll()
.antMatchers(HttpMethod.POST, "/books").hasRole("ADMIN")
.antMatchers(HttpMethod.PATCH, "/books/*").hasRole("ADMIN")
.antMatchers(HttpMethod.DELETE, "/books/*").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.csrf()
.disable();
.build();
}
}

View File

@ -2,7 +2,6 @@ package com.baeldung.spring.cloud.bootstrap.svcbook.book;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@ -23,8 +22,8 @@ public class BookService {
}
public Book findBookById(Long bookId) {
return Optional.ofNullable(bookRepository.findOne(bookId))
.orElseThrow(() -> new BookNotFoundException("Book not found. ID: " + bookId));
return bookRepository.findById(bookId)
.orElseThrow(() -> new BookNotFoundException(String.format("Book not found. ID: %s", bookId)));
}
@Transactional(propagation = Propagation.REQUIRED)
@ -37,7 +36,7 @@ public class BookService {
@Transactional(propagation = Propagation.REQUIRED)
public void deleteBook(Long bookId) {
bookRepository.delete(bookId);
bookRepository.deleteById(bookId);
}
@Transactional(propagation = Propagation.REQUIRED)
@ -60,7 +59,8 @@ public class BookService {
public Book updateBook(Book book, Long bookId) {
Preconditions.checkNotNull(book);
Preconditions.checkState(book.getId() == bookId);
Preconditions.checkNotNull(bookRepository.findOne(bookId));
Preconditions.checkArgument(bookRepository.findById(bookId)
.isPresent());
return bookRepository.save(book);
}
}

View File

@ -3,5 +3,7 @@ spring.cloud.config.discovery.service-id=config
spring.cloud.config.discovery.enabled=true
spring.cloud.config.username=configUser
spring.cloud.config.password=configPassword
spring.cloud.gateway.discovery.locator.enabled=true
spring.cloud.gateway.discovery.locator.lowerCaseServiceId=true
eureka.client.serviceUrl.defaultZone=http://discUser:discPassword@localhost:8082/eureka/

View File

@ -10,9 +10,9 @@
<parent>
<groupId>com.baeldung</groupId>
<artifactId>parent-boot-1</artifactId>
<artifactId>parent-boot-2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../../../parent-boot-1</relativePath>
<relativePath>../../../parent-boot-2</relativePath>
</parent>
<dependencyManagement>
@ -34,7 +34,11 @@
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
@ -46,7 +50,7 @@
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
@ -58,25 +62,29 @@
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
</dependencies>
<properties>
<spring-cloud-dependencies.version>Dalston.RELEASE</spring-cloud-dependencies.version>
<spring-cloud-dependencies.version>2021.0.7</spring-cloud-dependencies.version>
</properties>
</project>

View File

@ -0,0 +1,16 @@
package com.baeldung.spring.cloud.bootstrap.svcrating;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.web.http.DefaultCookieSerializer;
@Configuration
public class CookieConfig {
@Bean
public DefaultCookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setUseBase64Encoding(false);
return serializer;
}
}

View File

@ -1,69 +1,18 @@
package com.baeldung.spring.cloud.bootstrap.svcrating;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.sleuth.metric.SpanMetricReporter;
import org.springframework.cloud.sleuth.zipkin.HttpZipkinSpanReporter;
import org.springframework.cloud.sleuth.zipkin.ZipkinProperties;
import org.springframework.cloud.sleuth.zipkin.ZipkinSpanReporter;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.AdviceMode;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.web.client.RestTemplate;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.discovery.EurekaClient;
import com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect;
import zipkin.Span;
@SpringBootApplication
@EnableEurekaClient
@EnableHystrix
@EnableTransactionManagement(order=Ordered.LOWEST_PRECEDENCE, mode=AdviceMode.ASPECTJ)
@EnableDiscoveryClient
@EnableTransactionManagement(order = Ordered.LOWEST_PRECEDENCE, mode = AdviceMode.ASPECTJ)
public class RatingServiceApplication {
@Autowired
private EurekaClient eurekaClient;
@Autowired
private SpanMetricReporter spanMetricReporter;
@Autowired
private ZipkinProperties zipkinProperties;
@Value("${spring.sleuth.web.skipPattern}")
private String skipPattern;
public static void main(String[] args) {
SpringApplication.run(RatingServiceApplication.class, args);
}
@Bean
public ZipkinSpanReporter makeZipkinSpanReporter() {
return new ZipkinSpanReporter() {
private HttpZipkinSpanReporter delegate;
private String baseUrl;
@Override
public void report(Span span) {
InstanceInfo instance = eurekaClient.getNextServerFromEureka("zipkin", false);
if (baseUrl == null || !instance.getHomePageUrl().equals(baseUrl)) {
baseUrl = instance.getHomePageUrl();
}
delegate = new HttpZipkinSpanReporter(new RestTemplate(), baseUrl, zipkinProperties.getFlushInterval(), spanMetricReporter);
if (!span.name.matches(skipPattern)) delegate.report(span);
}
};
}
@Bean
@Primary
@Order(value=Ordered.HIGHEST_PRECEDENCE)
public HystrixCommandAspect hystrixAspect() {
return new HystrixCommandAspect();
}
}

View File

@ -1,39 +1,41 @@
package com.baeldung.spring.cloud.bootstrap.svcrating;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
@EnableWebSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
public class SecurityConfig {
@Autowired
public void configureGlobal1(AuthenticationManagerBuilder auth) throws Exception {
//try in memory auth with no users to support the case that this will allow for users that are logged in to go anywhere
auth.inMemoryAuthentication();
@Bean
public UserDetailsService users() {
return new InMemoryUserDetailsManager();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.regexMatchers("^/ratings\\?bookId.*$").authenticated()
.antMatchers(HttpMethod.POST,"/ratings").authenticated()
.antMatchers(HttpMethod.PATCH,"/ratings/*").hasRole("ADMIN")
.antMatchers(HttpMethod.DELETE,"/ratings/*").hasRole("ADMIN")
.antMatchers(HttpMethod.GET,"/ratings").hasRole("ADMIN")
.antMatchers(HttpMethod.GET,"/hystrix").authenticated()
.anyRequest().authenticated()
@Bean
public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity.authorizeHttpRequests((auth) -> auth.regexMatchers("^/ratings\\?bookId.*$")
.authenticated()
.antMatchers(HttpMethod.POST, "/ratings")
.authenticated()
.antMatchers(HttpMethod.PATCH, "/ratings/*")
.hasRole("ADMIN")
.antMatchers(HttpMethod.DELETE, "/ratings/*")
.hasRole("ADMIN")
.antMatchers(HttpMethod.GET, "/ratings")
.hasRole("ADMIN")
.anyRequest()
.authenticated())
.httpBasic()
.and()
.httpBasic().and()
.csrf()
.disable();
.csrf()
.disable()
.build();
}
}

View File

@ -5,24 +5,24 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
import org.springframework.session.web.context.AbstractHttpSessionApplicationInitializer;
@Configuration
@EnableRedisHttpSession
public class SessionConfig extends AbstractHttpSessionApplicationInitializer {
@Autowired
Environment properties;
private Environment properties;
@Bean
@Primary
public JedisConnectionFactory connectionFactory() {
JedisConnectionFactory factory = new JedisConnectionFactory();
factory.setHostName(properties.getProperty("spring.redis.host","localhost"));
factory.setPort(properties.getProperty("spring.redis.port", Integer.TYPE,6379));
factory.afterPropertiesSet();
factory.setUsePool(true);
return factory;
public LettuceConnectionFactory redisConnectionFactory() {
RedisStandaloneConfiguration redisConfiguration = new RedisStandaloneConfiguration();
redisConfiguration.setHostName(properties.getProperty("spring.redis.host", "localhost"));
redisConfiguration.setPort(properties.getProperty("spring.redis.port", Integer.TYPE, 6379));
return new LettuceConnectionFactory(redisConfiguration);
}
}

View File

@ -6,21 +6,21 @@ import java.util.stream.Collectors;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.SetOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Repository;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.stereotype.Repository;
@Repository
public class RatingCacheRepository implements InitializingBean {
@Autowired
private JedisConnectionFactory cacheConnectionFactory;
private LettuceConnectionFactory cacheConnectionFactory;
private StringRedisTemplate redisTemplate;
private ValueOperations<String, String> valueOps;

View File

@ -2,7 +2,6 @@ package com.baeldung.spring.cloud.bootstrap.svcrating.rating;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@ -10,7 +9,8 @@ import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import com.google.common.base.Preconditions;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
@Service
@Transactional(readOnly = true)
@ -22,31 +22,31 @@ public class RatingService {
@Autowired
private RatingCacheRepository cacheRepository;
@HystrixCommand(commandKey = "ratingsByBookIdFromDB", fallbackMethod = "findCachedRatingsByBookId")
@CircuitBreaker(name = "ratingsByBookIdFromDB", fallbackMethod = "findCachedRatingsByBookId")
public List<Rating> findRatingsByBookId(Long bookId) {
return ratingRepository.findRatingsByBookId(bookId);
}
public List<Rating> findCachedRatingsByBookId(Long bookId) {
public List<Rating> findCachedRatingsByBookId(Long bookId, Exception exception) {
return cacheRepository.findCachedRatingsByBookId(bookId);
}
@HystrixCommand(commandKey = "ratingsFromDB", fallbackMethod = "findAllCachedRatings")
@CircuitBreaker(name = "ratingsFromDB", fallbackMethod = "findAllCachedRatings")
public List<Rating> findAllRatings() {
return ratingRepository.findAll();
}
public List<Rating> findAllCachedRatings() {
public List<Rating> findAllCachedRatings(Exception exception) {
return cacheRepository.findAllCachedRatings();
}
@HystrixCommand(commandKey = "ratingsByIdFromDB", fallbackMethod = "findCachedRatingById", ignoreExceptions = { RatingNotFoundException.class })
@CircuitBreaker(name = "ratingsByIdFromDB", fallbackMethod = "findCachedRatingById")
public Rating findRatingById(Long ratingId) {
return Optional.ofNullable(ratingRepository.findOne(ratingId))
return ratingRepository.findById(ratingId)
.orElseThrow(() -> new RatingNotFoundException("Rating not found. ID: " + ratingId));
}
public Rating findCachedRatingById(Long ratingId) {
public Rating findCachedRatingById(Long ratingId, Exception exception) {
return cacheRepository.findCachedRatingById(ratingId);
}
@ -62,7 +62,7 @@ public class RatingService {
@Transactional(propagation = Propagation.REQUIRED)
public void deleteRating(Long ratingId) {
ratingRepository.delete(ratingId);
ratingRepository.deleteById(ratingId);
cacheRepository.deleteRating(ratingId);
}
@ -86,7 +86,7 @@ public class RatingService {
public Rating updateRating(Rating rating, Long ratingId) {
Preconditions.checkNotNull(rating);
Preconditions.checkState(rating.getId() == ratingId);
Preconditions.checkNotNull(ratingRepository.findOne(ratingId));
Preconditions.checkNotNull(ratingRepository.findById(ratingId));
return ratingRepository.save(rating);
}

View File

@ -3,5 +3,7 @@ spring.cloud.config.discovery.service-id=config
spring.cloud.config.discovery.enabled=true
spring.cloud.config.username=configUser
spring.cloud.config.password=configPassword
spring.cloud.gateway.discovery.locator.enabled=true
spring.cloud.gateway.discovery.locator.lowerCaseServiceId=true
eureka.client.serviceUrl.defaultZone=http://discUser:discPassword@localhost:8082/eureka/

View File

@ -0,0 +1,19 @@
# Zipkin server
Zipkin project [deprecated custom server](https://github.com/openzipkin/zipkin/tree/master/zipkin-server).
It's no longer possible to run a custom Zipkin server compatible with Spring Cloud or even Spring Boot.
The best approach to run a Zipkin server is to use docker. We provided a docker-compose file that you can run:
```bash
$ docker compose up -d
```
After that Zipkin is accessible via [http://localhost:9411](http://localhost:9411)
Alternatively, you can run the Zipkin Jar file,
```bash
$ curl -sSL https://zipkin.io/quickstart.sh | bash -s
$ java -jar zipkin.jar
```

View File

@ -0,0 +1,6 @@
version: "3.9"
services:
zipkin:
image: openzipkin/zipkin
ports:
- 9411:9411

View File

@ -1,53 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>zipkin</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>zipkin</name>
<parent>
<groupId>com.baeldung</groupId>
<artifactId>parent-boot-1</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../../../parent-boot-1</relativePath>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud-dependencies.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-server</artifactId>
</dependency>
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-autoconfigure-ui</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
<properties>
<spring-cloud-dependencies.version>Brixton.SR7</spring-cloud-dependencies.version>
</properties>
</project>

View File

@ -1,15 +0,0 @@
package com.baeldung.spring.cloud.bootstrap.zipkin;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import zipkin.server.EnableZipkinServer;
@SpringBootApplication
@EnableEurekaClient
@EnableZipkinServer
public class ZipkinApplication {
public static void main(String[] args) {
SpringApplication.run(ZipkinApplication.class, args);
}
}

View File

@ -1,7 +0,0 @@
spring.cloud.config.name=zipkin
spring.cloud.config.discovery.service-id=config
spring.cloud.config.discovery.enabled=true
spring.cloud.config.username=configUser
spring.cloud.config.password=configPassword
eureka.client.serviceUrl.defaultZone=http://discUser:discPassword@localhost:8082/eureka/

View File

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>

View File

@ -1,17 +0,0 @@
package com.baeldung;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import com.baeldung.spring.cloud.bootstrap.zipkin.ZipkinApplication;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ZipkinApplication.class)
public class SpringContextTest {
@Test
public void whenSpringContextIsBootstrapped_thenNoExceptions() {
}
}