Create Microservices with Spring Boot and deploy them into Cloud Foundry
Time: 35 minutes.
Cloud Foundry
This lab is just to get familiar with some of the common Cloud Foundry command using the CF CLI.
-
Make sure you have an account in PWS if not, create a trial account in Pivotal Web Services: https://run.pivotal.io
-
Install Cloud Foundry CLI. You can get it from: https://github.com/cloudfoundry/cli#installers-and-compressed-binaries
-
Login into Cloud Foundry
cf login$ cf login -a api.run.pivotal.io
Useful CF CLI commands
$ cf help -a
$ cf marketplace
$ cf services
$ cf create-service cloudamqp lemur rabbitmq
$ cf bind-service myapp rabbitmq
$ cf push myapp
$ cf set-env myapp MY_VARIABLE ITS_VALUE
Deploy Spring Boot Microservices to Cloud Foundry
For this lab you will deploy two projects, the directory-web-security and the code-snippet-manager-security, both must be completed with OAuth2 implementation. Remember that the directory-web-security is the Authorization Server and the code-snippet-manager-security is the Resource Server. If you haven’t finish the challenges for both projects, the it’s time to do it.
Deploying directory-web-security project
-
Open the direcoty-web-security app in your favorite IDE. Make sure your io.pivotal.workshop.directory.config.DirectorySecurityConfig class in the configure(HttpSecurity http) is based in the Http Basic Security (httpBasic()). This will allow our code-snippet-manager-security app to connect passing credentials using a basic authentication,
-
Open a terminal window in the directory-web-security project and create the JAR file.
maven./mvnw clean package -DskipTests=true
gradle./gradlew build -x test
-
Deploy the directory-web-security JAR.
If you used Maven, it creates the JAR in the target/ folder. If you used Gradle, the JAR is in the build/libs folder.cf push directory-web -p target/directory-web-security-0.0.1-SNAPSHOT.jar --random-route -b java_buildpack
-
Once deployed make sure is working by accessing the /api endpoint using the administrator credentials.
Deploying code-snippet-manager-security project
You will be using the code-snippet-manager-security project.
Remember that you had a Challenge for this project? Modify the code-snippet-manager-security project and use the directory-web-security project as authentication authority.
Well, this is the solution to that Challenge!!
-
Open your code-snippet-manager-security project and open the io.pivotal.workshop.snippet.config.SnippetConfiguration class. Modify it to look like the following code:
src/main/java/io/pivotal/workshop/snippet/config/SnippetConfiguration.javapackage io.pivotal.workshop.snippet.config; import io.pivotal.workshop.snippet.domain.Code; import io.pivotal.workshop.snippet.domain.Language; import io.pivotal.workshop.snippet.domain.Snippet; import io.pivotal.workshop.snippet.repository.SnippetRepository; import org.springframework.boot.CommandLineRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; @Configuration public class SnippetConfiguration { @Bean @Profile("local") public CommandLineRunner runner(SnippetRepository snippetRepo) { return args -> { @SuppressWarnings("serial") List<Snippet> snippets = new ArrayList<Snippet>() { { add(new Snippet("Hello World", new Language("HTML", "xml"),new Code(new String(Files.readAllBytes(Paths.get("code/html-code.txt")))))); add(new Snippet("Hello World", new Language("C#", "c#"),new Code(new String(Files.readAllBytes(Paths.get("code/cs-code.txt")))))); add(new Snippet("Hello World", new Language("Pascal", "py"),new Code(new String(Files.readAllBytes(Paths.get("code/pas-code.txt")))))); add(new Snippet("Hello World", new Language("Erlang", "erl"),new Code(new String(Files.readAllBytes(Paths.get("code/erl-code.txt")))))); add(new Snippet("Hello World", new Language("JavaScript", "js"),new Code(new String(Files.readAllBytes(Paths.get("code/js-code.txt")))))); add(new Snippet("Hello World", new Language("Groovy", "groovy"),new Code("println 'Hello World'"))); } }; snippetRepo.saveAll(snippets); }; } @Bean @Profile("cloud") //(1) public CommandLineRunner runnerCloud(SnippetRepository snippetRepo) { return args -> { @SuppressWarnings("serial") List<Snippet> snippets = new ArrayList<Snippet>() {{ add(new Snippet("Hello World", new Language("JavaScript", "js"),new Code("console.log(\"Hello World\");"))); add(new Snippet("Hello World", new Language("Groovy", "groovy"),new Code("println 'Hello World'"))); }}; snippetRepo.saveAll(snippets); }; } }
-
As you can see we are using the @Profile annotation, and we adding it to the runner method, making it a local profile, but also we are adding a new method runnerCloud and using the cloud profile. Why do we need to do this? well we are not deploying the code/ folder, so there are no initial snippets.
TipBy default Cloud Foundry activates the cloud profile. -
-
This is part of the Challenge: Use the directory-web-security project as authentication-authority for the code-snippet-manager project. Here we are going to see the solution. Create the io.pivotal.workshop.snippet.config.SnippetSecurityConfig class, this class will reach out to the directory-web-security app.
src/main/java/io/pivotal/workshop/snippet/config/SnippetSecurityConfig.javapackage io.pivotal.workshop.snippet.config; import io.pivotal.workshop.snippet.domain.Person; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.actuate.autoconfigure.security.EndpointRequest; import org.springframework.boot.autoconfigure.security.StaticResourceRequest; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.context.annotation.Configuration; import org.springframework.core.ParameterizedTypeReference; import org.springframework.hateoas.MediaTypes; import org.springframework.hateoas.Resource; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; 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.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; import java.net.URI; import java.util.Collections; @EnableConfigurationProperties(SnippetProperties.class) @Configuration public class SnippetSecurityConfig extends WebSecurityConfigurerAdapter { private final Logger log = LoggerFactory.getLogger(SnippetSecurityConfig.class); private RestTemplate restTemplate; private UriComponentsBuilder builder; private SnippetProperties properties; public SnippetSecurityConfig(RestTemplateBuilder restTemplateBuilder,SnippetProperties properties){ this.restTemplate = restTemplateBuilder.basicAuthorization(properties.getAuthenticationUsername(),properties.getAuthenticationPassword()).build(); this.properties = properties; } @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .requestMatchers(EndpointRequest.to("status", "info")) .permitAll() .requestMatchers(EndpointRequest.toAnyEndpoint()) .hasRole("ACTUATOR") .requestMatchers(StaticResourceRequest.toCommonLocations()) .permitAll() .antMatchers("/api/**").hasRole("ADMIN") .antMatchers("/").hasRole("USER") .and() .httpBasic(); } @Override public void configure(AuthenticationManagerBuilder auth) throws Exception { //(1) auth.userDetailsService(new UserDetailsService(){ @Override public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { try { builder = UriComponentsBuilder.fromUriString(properties.getAuthenticationUri()) .queryParam("email", email); log.info("Querying: " + builder.toUriString()); ResponseEntity<Resource<Person>> responseEntity = restTemplate.exchange( //(2) RequestEntity.get(URI.create(builder.toUriString())) .accept(MediaTypes.HAL_JSON) .build() , new ParameterizedTypeReference<Resource<Person>>() { }); if (responseEntity.getStatusCode() == HttpStatus.OK) { Resource<Person> resource = responseEntity.getBody(); Person person = resource.getContent(); return User.withDefaultPasswordEncoder().username(person.getEmail()).password(person.getPassword()).roles(person.getRole()).build(); } }catch(Exception ex) { ex.printStackTrace(); } throw new UsernameNotFoundException(email); } }); } }
-
We are configuring the AuthenticationManagerBuilder and the UserDetailsService that will use a restTemplate to contact the directory-web-security-app.
-
We are going to reach the /api/persons/search/findByEmailIgnoreCase endpoint. This endpoint returns a HAL+JSON, meaning that we need a Resource that will get email we are looking for.
-
-
From the previous class, you’ve already saw that we need the SnippetProperties. Create/Modify the io.pivotal.workshop.snippet.config.SnippetProperties class.
src/main/java/io/pivotal/workshop/snippet/config/SnippetProperties.javapackage io.pivotal.workshop.snippet.config; import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "snippet") public class SnippetProperties { private String path; private String authenticationUri; private String authenticationUsername; private String authenticationPassword; public String getPath() { return path; } public void setPath(String path) { this.path = path; } public String getAuthenticationUri() { return authenticationUri; } public void setAuthenticationUri(String authenticationUri) { this.authenticationUri = authenticationUri; } public String getAuthenticationUsername() { return authenticationUsername; } public void setAuthenticationUsername(String authenticationUsername) { this.authenticationUsername = authenticationUsername; } public String getAuthenticationPassword() { return authenticationPassword; } public void setAuthenticationPassword(String authenticationPassword) { this.authenticationPassword = authenticationPassword; } }
-
Open the src/main/resources/application.properties file and add the following configuration.
src/main/resources/application.properties## Web App Port server.port=${port:8081} ## JPA # spring.jpa.show-sql=true spring.jpa.generate-ddl=true spring.jpa.hibernate.ddl-auto=create-drop ## REST spring.data.rest.base-path=api ## ACTUATOR management.endpoints.web.base-path=/admin endpoints.default.web.enabled=true ## Snippet snippet.path=/tmp/snippets ## URL snippet.authentication-uri=http://localhost:8585/api/persons/search/findByEmailIgnoreCase snippet.authentication-username=admin snippet.authentication-password=admin
As you can see we are adding the snippet.authentication* properties, with the credentials and uri of the directory-web-security app.
-
Next, open a terminal window in the code-snippet-manager-security project and create the JAR file.
maven./mvnw clean package -DskipTests=true
gradle./gradlew build -x test
-
Deploy the code-snippet-manager-security JAR but don’t start the application just yet.
If you used Maven, it creates the JAR in the target/ folder. If you used Gradle, the JAR is in the build/libs folder.cf push code-snippet-manager -p target/code-snippet-manager-security-0.0.1-SNAPSHOT.jar --random-route -b java_buildpack --no-start
-
Remember that this application relies on the application.properties because it was added the credentials and the uri, so we need to alter at least the snippet.authentication-uri, so we can add the directory-web's uri. to Modify this we are going to use environment variables:
cf set-env code-snippet-manager SNIPPET_AUTHENTICATION_URI http://directory-web-unapprehended-pluralism.cfapps.io/api/persons/search/findByEmailIgnoreCase
The URI is the one from the directory-web application you have just deployed.
-
Start the code-snippet-manager application.
cf start code-snippet-manager
-
Once started make sure is working by accessing the code-snippet-manager app /api using the credentials.
Challenges
-
Add the code from the code-snippet-manager-amqp to use a RabbitMQ service.
-
Instead of using an H2 as persistence engine, use a MySQL service, what changes do you need to do in your code?