├── auth-service ├── src │ ├── main │ │ ├── resources │ │ │ ├── application.properties │ │ │ ├── bootstrap.properties │ │ │ └── application-cloud.properties │ │ └── java │ │ │ └── auth │ │ │ ├── clients │ │ │ ├── ClientRepository.java │ │ │ ├── Client.java │ │ │ └── ClientConfiguration.java │ │ │ ├── accounts │ │ │ ├── AccountRepository.java │ │ │ ├── Account.java │ │ │ └── AccountConfiguration.java │ │ │ ├── PrincipalRestController.java │ │ │ ├── AuthServiceApplication.java │ │ │ ├── DataCommandLineRunner.java │ │ │ └── AuthorizationServerConfiguration.java │ └── test │ │ └── java │ │ └── edge │ │ └── AuthServiceApplicationTests.java ├── manifest.yml └── pom.xml ├── edge-service ├── src │ └── main │ │ ├── resources │ │ ├── bootstrap-cors.properties │ │ ├── bootstrap-sso.properties │ │ ├── bootstrap.properties │ │ ├── application-cloud.properties │ │ └── static │ │ │ ├── index.html │ │ │ └── app.js │ │ └── java │ │ └── greetings │ │ ├── FeignConfiguration.java │ │ ├── PrincipalRestController.java │ │ ├── ThrottlingConfiguration.java │ │ ├── GreetingsClientApplication.java │ │ ├── GreetingsClient.java │ │ ├── FeignGreetingsClientApiGateway.java │ │ ├── ZuulConfiguration.java │ │ ├── SecureResourceConfiguration.java │ │ ├── SsoConfiguration.java │ │ ├── RestTemplateGreetingsClientApiGateway.java │ │ ├── RoutesListener.java │ │ ├── ThrottlingZuulFilter.java │ │ └── CorsFilter.java ├── manifest.yml └── pom.xml ├── html5-client ├── src │ └── main │ │ ├── resources │ │ ├── bootstrap-default.properties │ │ ├── bootstrap.properties │ │ ├── application-cloud.properties │ │ └── static │ │ │ └── index.html │ │ └── java │ │ └── client │ │ └── Html5Client.java ├── manifest.yml └── pom.xml ├── greetings-service ├── src │ └── main │ │ ├── resources │ │ ├── bootstrap-default.properties │ │ ├── bootstrap-insecure.properties │ │ ├── bootstrap.properties │ │ └── application-cloud.properties │ │ └── java │ │ └── greetings │ │ ├── GreetingsServiceApplication.java │ │ ├── OAuthResourceConfiguration.java │ │ ├── DefaultGreetingsRestController.java │ │ ├── SecureGreetingsRestController.java │ │ └── ProxyAwareGreetingsRestController.java ├── manifest.yml └── pom.xml ├── service-registry ├── src │ └── main │ │ ├── resources │ │ ├── application.properties │ │ └── bootstrap.properties │ │ └── java │ │ └── demo │ │ └── EurekaServiceApplication.java ├── manifest.yml └── pom.xml ├── security-autoconfiguration ├── src │ └── main │ │ ├── resources │ │ └── META-INF │ │ │ └── spring.factories │ │ └── java │ │ └── relay │ │ └── TokenRelayAutoConfiguration.java └── pom.xml ├── social-auth-service ├── src │ └── main │ │ ├── java │ │ └── auth │ │ │ ├── clients │ │ │ ├── ClientRepository.java │ │ │ ├── ClientConfiguration.java │ │ │ └── Client.java │ │ │ ├── accounts │ │ │ ├── AccountRepository.java │ │ │ ├── Account.java │ │ │ └── AccountConfiguration.java │ │ │ └── SocialAuthApplication.java │ │ └── resources │ │ ├── bootstrap.yml │ │ └── static │ │ └── index.html └── pom.xml ├── .travis.yml ├── deploy.sh ├── edge-it ├── src │ └── integration-test │ │ └── java │ │ └── edge │ │ ├── Config.java │ │ └── EdgeIT.java └── pom.xml └── pom.xml /auth-service/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=${PORT:9191} 2 | -------------------------------------------------------------------------------- /edge-service/src/main/resources/bootstrap-cors.properties: -------------------------------------------------------------------------------- 1 | security.basic.enabled=false -------------------------------------------------------------------------------- /html5-client/src/main/resources/bootstrap-default.properties: -------------------------------------------------------------------------------- 1 | security.basic.enabled=false -------------------------------------------------------------------------------- /greetings-service/src/main/resources/bootstrap-default.properties: -------------------------------------------------------------------------------- 1 | security.basic.enabled=false -------------------------------------------------------------------------------- /service-registry/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=${PORT:8761} 2 | 3 | -------------------------------------------------------------------------------- /greetings-service/src/main/resources/bootstrap-insecure.properties: -------------------------------------------------------------------------------- 1 | security.basic.enabled=false -------------------------------------------------------------------------------- /html5-client/src/main/resources/bootstrap.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=html5-client 2 | server.port=${PORT:8083} 3 | -------------------------------------------------------------------------------- /security-autoconfiguration/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=relay.TokenRelayAutoConfiguration -------------------------------------------------------------------------------- /service-registry/manifest.yml: -------------------------------------------------------------------------------- 1 | --- 2 | applications: 3 | - name: service-registry 4 | host: service-registry-${random-word} 5 | buildpack: https://github.com/cloudfoundry/java-buildpack.git 6 | path: target/service-registry.jar 7 | -------------------------------------------------------------------------------- /service-registry/src/main/resources/bootstrap.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=eureka-service 2 | # <1> 3 | eureka.client.register-with-eureka=false 4 | eureka.client.fetch-registry=false 5 | # <2> 6 | eureka.server.enable-self-preservation=false 7 | 8 | 9 | -------------------------------------------------------------------------------- /auth-service/src/main/resources/bootstrap.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=auth-service 2 | # <1> 3 | server.context-path=/uaa 4 | security.sessions=if_required 5 | logging.level.org.springframework.security=DEBUG 6 | spring.jpa.hibernate.ddl-auto=create 7 | spring.jpa.generate-ddl=true -------------------------------------------------------------------------------- /greetings-service/src/main/resources/bootstrap.properties: -------------------------------------------------------------------------------- 1 | # <1> 2 | spring.application.name=greetings-service 3 | # <2> 4 | server.port=${PORT:8081} 5 | hystrix.command.default.execution.isolation.strategy=SEMAPHORE 6 | security.oauth2.resource.userInfoUri=http://auth-service/uaa/user 7 | 8 | -------------------------------------------------------------------------------- /edge-service/src/main/java/greetings/FeignConfiguration.java: -------------------------------------------------------------------------------- 1 | package greetings; 2 | 3 | import org.springframework.cloud.netflix.feign.EnableFeignClients; 4 | import org.springframework.context.annotation.Configuration; 5 | 6 | @Configuration 7 | @EnableFeignClients 8 | class FeignConfiguration { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /auth-service/src/main/java/auth/clients/ClientRepository.java: -------------------------------------------------------------------------------- 1 | package auth.clients; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | import java.util.Optional; 6 | 7 | public interface ClientRepository extends JpaRepository { 8 | 9 | Optional findByClientId(String clientId); // <1> 10 | } 11 | -------------------------------------------------------------------------------- /social-auth-service/src/main/java/auth/clients/ClientRepository.java: -------------------------------------------------------------------------------- 1 | package auth.clients; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | import java.util.Optional; 6 | 7 | public interface ClientRepository extends JpaRepository { 8 | 9 | Optional findByClientId(String clientId); 10 | } 11 | -------------------------------------------------------------------------------- /social-auth-service/src/main/java/auth/accounts/AccountRepository.java: -------------------------------------------------------------------------------- 1 | package auth.accounts; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | import java.util.Optional; 6 | 7 | public interface AccountRepository extends JpaRepository { 8 | 9 | Optional findByUsername(String username); 10 | } 11 | -------------------------------------------------------------------------------- /auth-service/src/main/java/auth/accounts/AccountRepository.java: -------------------------------------------------------------------------------- 1 | package auth.accounts; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | import java.util.Optional; 6 | 7 | public interface AccountRepository extends JpaRepository { 8 | 9 | // <1> 10 | Optional findByUsername(String username); 11 | } 12 | -------------------------------------------------------------------------------- /greetings-service/manifest.yml: -------------------------------------------------------------------------------- 1 | --- 2 | applications: 3 | - name: greetings-service 4 | host: greetings-service-${random-word} 5 | timeout: 80 6 | buildpack: https://github.com/cloudfoundry/java-buildpack.git 7 | path: target/greetings-service.jar 8 | services: 9 | - service-registry 10 | env: 11 | DEBUG: "true" 12 | SPRING_PROFILES_ACTIVE: cloud 13 | # ,secure 14 | -------------------------------------------------------------------------------- /edge-service/src/main/resources/bootstrap-sso.properties: -------------------------------------------------------------------------------- 1 | 2 | # <1> 3 | security.oauth2.client.client-id=html5 4 | security.oauth2.client.client-secret=password 5 | 6 | # <2> 7 | security.oauth2.client.access-token-uri=http://localhost:9191/uaa/oauth/token 8 | security.oauth2.client.user-authorization-uri=\ 9 | http://localhost:9191/uaa/oauth/authorize 10 | 11 | security.basic.enabled=false 12 | -------------------------------------------------------------------------------- /html5-client/manifest.yml: -------------------------------------------------------------------------------- 1 | --- 2 | applications: 3 | - name: html5-client 4 | memory: 512M 5 | host: html5-client-${random-word} 6 | instances: 1 7 | random-route: true 8 | buildpack: https://github.com/cloudfoundry/java-buildpack.git 9 | path: target/html5-client.jar 10 | services: 11 | - service-registry 12 | env: 13 | DEBUG: "true" 14 | SPRING_PROFILES_ACTIVE: cloud 15 | -------------------------------------------------------------------------------- /edge-service/manifest.yml: -------------------------------------------------------------------------------- 1 | --- 2 | applications: 3 | - name: edge-service 4 | host: edge-service-${random-word} 5 | timeout: 180 6 | buildpack: https://github.com/cloudfoundry/java-buildpack.git 7 | path: target/edge-service.jar 8 | services: 9 | - service-registry 10 | - auth-service 11 | env: 12 | DEBUG: "true" 13 | SPRING_PROFILES_ACTIVE: cloud 14 | # cloud,secure,sso,feign 15 | -------------------------------------------------------------------------------- /auth-service/manifest.yml: -------------------------------------------------------------------------------- 1 | --- 2 | applications: 3 | - name: auth-service 4 | memory: 1G 5 | host: auth-service-${random-word} 6 | instances: 1 7 | random-route: true 8 | buildpack: https://github.com/cloudfoundry/java-buildpack.git 9 | path: target/auth-service.jar 10 | services: 11 | - service-registry 12 | - auth-service-pgsql 13 | env: 14 | DEBUG: "true" 15 | SPRING_PROFILES_ACTIVE: cloud 16 | -------------------------------------------------------------------------------- /auth-service/src/main/java/auth/PrincipalRestController.java: -------------------------------------------------------------------------------- 1 | package auth; 2 | 3 | import org.springframework.web.bind.annotation.RequestMapping; 4 | import org.springframework.web.bind.annotation.RestController; 5 | 6 | import java.security.Principal; 7 | 8 | @RestController 9 | class PrincipalRestController { 10 | 11 | // <1> 12 | @RequestMapping("/user") 13 | Principal principal(Principal p) { 14 | return p; 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /edge-service/src/main/resources/bootstrap.properties: -------------------------------------------------------------------------------- 1 | # <1> 2 | spring.application.name=greetings-client 3 | # <2> 4 | server.port=${PORT:8082} 5 | security.oauth2.resource.userInfoUri=http://auth-service/uaa/user 6 | #security.oauth2.resource.loadBalanced=false 7 | 8 | hystrix.command.default.execution.isolation.strategy=SEMAPHORE 9 | spring.mvc.dispatch-options-request=true 10 | 11 | zuul.routes.hi.path=/lets/** 12 | zuul.routes.hi.serviceId=greetings-service 13 | -------------------------------------------------------------------------------- /auth-service/src/main/resources/application-cloud.properties: -------------------------------------------------------------------------------- 1 | eureka.instance.hostname=${vcap.application.uris[0]:localhost} 2 | eureka.instance.nonSecurePort=80 3 | eureka.instance.metadataMap.instanceId=${vcap.application.instance_id:${spring.application.name}:${spring.application.instance_id:${server.port}}} 4 | eureka.instance.leaseRenewalIntervalInSeconds=5 5 | eureka.client.region=default 6 | eureka.client.registryFetchIntervalSeconds=5 7 | eureka.client.serviceUrl.defaultZone=${vcap.services.service-registry.credentials.uri}/eureka/ 8 | -------------------------------------------------------------------------------- /edge-service/src/main/java/greetings/PrincipalRestController.java: -------------------------------------------------------------------------------- 1 | package greetings; 2 | 3 | import org.springframework.context.annotation.Profile; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | import org.springframework.web.bind.annotation.RestController; 6 | 7 | import java.security.Principal; 8 | 9 | @Profile("secure") 10 | @RestController 11 | class PrincipalRestController { 12 | 13 | @RequestMapping("/user") 14 | public Principal user(Principal principal) { 15 | return principal; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /edge-service/src/main/java/greetings/ThrottlingConfiguration.java: -------------------------------------------------------------------------------- 1 | package greetings; 2 | 3 | import com.google.common.util.concurrent.RateLimiter; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.context.annotation.Profile; 7 | 8 | @Profile("throttled") 9 | @Configuration 10 | class ThrottlingConfiguration { 11 | 12 | @Bean //<1> 13 | RateLimiter rateLimiter() { 14 | return RateLimiter.create(1.0D / 10.0D); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /html5-client/src/main/resources/application-cloud.properties: -------------------------------------------------------------------------------- 1 | eureka.instance.hostname=${vcap.application.uris[0]:localhost} 2 | eureka.instance.nonSecurePort=80 3 | eureka.instance.metadataMap.instanceId=${vcap.application.instance_id:${spring.application.name}:${spring.application.instance_id:${server.port}}} 4 | eureka.instance.leaseRenewalIntervalInSeconds=5 5 | eureka.client.region=default 6 | eureka.client.registryFetchIntervalSeconds=5 7 | eureka.client.serviceUrl.defaultZone=${vcap.services.service-registry.credentials\ 8 | .uri}/eureka/ -------------------------------------------------------------------------------- /service-registry/src/main/java/demo/EurekaServiceApplication.java: -------------------------------------------------------------------------------- 1 | package demo; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; 6 | 7 | // <1> 8 | @EnableEurekaServer 9 | @SpringBootApplication 10 | public class EurekaServiceApplication { 11 | 12 | public static void main(String args[]) { 13 | SpringApplication.run(EurekaServiceApplication.class, args); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /greetings-service/src/main/resources/application-cloud.properties: -------------------------------------------------------------------------------- 1 | eureka.instance.hostname=${vcap.application.uris[0]:localhost} 2 | eureka.instance.nonSecurePort=80 3 | eureka.instance.metadataMap.instanceId=${vcap.application.instance_id:${spring.application.name}:${spring.application.instance_id:${server.port}}} 4 | eureka.instance.leaseRenewalIntervalInSeconds=5 5 | eureka.client.region=default 6 | eureka.client.registryFetchIntervalSeconds=5 7 | eureka.client.serviceUrl.defaultZone=${vcap.services.service-registry.credentials\ 8 | .uri}/eureka/ -------------------------------------------------------------------------------- /edge-service/src/main/java/greetings/GreetingsClientApplication.java: -------------------------------------------------------------------------------- 1 | package greetings; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 6 | 7 | // <1> 8 | @EnableDiscoveryClient 9 | @SpringBootApplication 10 | public class GreetingsClientApplication { 11 | 12 | public static void main(String[] args) { 13 | SpringApplication.run(GreetingsClientApplication.class, args); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /greetings-service/src/main/java/greetings/GreetingsServiceApplication.java: -------------------------------------------------------------------------------- 1 | package greetings; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 6 | 7 | // <1> 8 | @EnableDiscoveryClient 9 | @SpringBootApplication 10 | public class GreetingsServiceApplication { 11 | 12 | public static void main(String[] args) { 13 | SpringApplication.run(GreetingsServiceApplication.class, args); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | sudo: required 4 | 5 | jdk: 6 | - oraclejdk8 7 | 8 | #cache: 9 | # directories: 10 | # - $HOME/.m2/repository/ 11 | 12 | script: 13 | - mvn -e verify deploy 14 | 15 | #language: java 16 | # 17 | #jdk: 18 | # - oraclejdk8 19 | # 20 | #cache: 21 | # directories: 22 | # - $HOME/.m2/repository/ 23 | # 24 | #install: 25 | # - git clone https://github.com/cloud-native-java/build.git 26 | # - ./build/install.sh $CF_USER $CF_PASSWORD $CF_ORG $CF_SPACE 27 | # 28 | #script: 29 | # - ./build/script.sh $CF_USER $CF_PASSWORD $CF_ORG $CF_SPACE 30 | -------------------------------------------------------------------------------- /auth-service/src/main/java/auth/AuthServiceApplication.java: -------------------------------------------------------------------------------- 1 | package auth; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 6 | import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; 7 | 8 | @EnableDiscoveryClient 9 | @EnableResourceServer 10 | @SpringBootApplication 11 | public class AuthServiceApplication { 12 | 13 | public static void main(String[] args) { 14 | SpringApplication.run(AuthServiceApplication.class, args); 15 | } 16 | } -------------------------------------------------------------------------------- /edge-service/src/main/java/greetings/GreetingsClient.java: -------------------------------------------------------------------------------- 1 | package greetings; 2 | 3 | import org.springframework.cloud.netflix.feign.FeignClient; 4 | import org.springframework.web.bind.annotation.PathVariable; 5 | import org.springframework.web.bind.annotation.RequestMapping; 6 | import org.springframework.web.bind.annotation.RequestMethod; 7 | 8 | import java.util.Map; 9 | 10 | // <1> 11 | @FeignClient(serviceId = "greetings-service") 12 | interface GreetingsClient { 13 | 14 | // <2> 15 | @RequestMapping(method = RequestMethod.GET, value = "/greet/{name}") 16 | Map greet(@PathVariable("name") String name); // <3> 17 | } 18 | -------------------------------------------------------------------------------- /greetings-service/src/main/java/greetings/OAuthResourceConfiguration.java: -------------------------------------------------------------------------------- 1 | package greetings; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.context.annotation.Profile; 5 | //@formatter:off 6 | import org.springframework.security.oauth2.config.annotation 7 | .web.configuration.EnableOAuth2Client; 8 | import org.springframework.security.oauth2.config.annotation 9 | .web.configuration.EnableResourceServer; 10 | //@formatter:on 11 | 12 | @Configuration 13 | // <1> 14 | @Profile("secure") 15 | // <2> 16 | @EnableResourceServer 17 | // <3> 18 | @EnableOAuth2Client 19 | class OAuthResourceConfiguration { 20 | } 21 | -------------------------------------------------------------------------------- /auth-service/src/main/java/auth/accounts/Account.java: -------------------------------------------------------------------------------- 1 | package auth.accounts; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import javax.persistence.Entity; 8 | import javax.persistence.GeneratedValue; 9 | import javax.persistence.Id; 10 | 11 | @Data 12 | @AllArgsConstructor 13 | @NoArgsConstructor 14 | @Entity 15 | public class Account { 16 | 17 | @Id 18 | @GeneratedValue 19 | private Long id; 20 | 21 | private String username, password; // <1> 22 | 23 | private boolean active; // <2> 24 | 25 | public Account(String username, String password, boolean active) { 26 | this.username = username; 27 | this.password = password; 28 | this.active = active; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /edge-service/src/main/java/greetings/FeignGreetingsClientApiGateway.java: -------------------------------------------------------------------------------- 1 | package greetings; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.annotation.Profile; 5 | import org.springframework.web.bind.annotation.*; 6 | 7 | import java.util.Map; 8 | 9 | // <1> 10 | @Profile("feign") 11 | @RestController 12 | @RequestMapping("/api") 13 | class FeignGreetingsClientApiGateway { 14 | 15 | private final GreetingsClient greetingsClient; 16 | 17 | @Autowired 18 | FeignGreetingsClientApiGateway(GreetingsClient greetingsClient) { 19 | this.greetingsClient = greetingsClient; 20 | } 21 | 22 | // <2> 23 | @GetMapping("/feign/{name}") 24 | Map feign(@PathVariable String name) { 25 | return this.greetingsClient.greet(name); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /greetings-service/src/main/java/greetings/DefaultGreetingsRestController.java: -------------------------------------------------------------------------------- 1 | package greetings; 2 | 3 | import org.springframework.context.annotation.Profile; 4 | import org.springframework.web.bind.annotation.PathVariable; 5 | import org.springframework.web.bind.annotation.RequestMapping; 6 | import org.springframework.web.bind.annotation.RequestMethod; 7 | import org.springframework.web.bind.annotation.RestController; 8 | 9 | import java.util.Collections; 10 | import java.util.Map; 11 | 12 | @Profile({ "default", "insecure" }) 13 | @RestController 14 | @RequestMapping(method = RequestMethod.GET, value = "/greet/{name}") 15 | class DefaultGreetingsRestController { 16 | 17 | @RequestMapping 18 | Map hi(@PathVariable String name) { 19 | return Collections.singletonMap("greeting", "Hello, " + name + "!"); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /edge-service/src/main/resources/application-cloud.properties: -------------------------------------------------------------------------------- 1 | eureka.instance.hostname=${vcap.application.uris[0]:localhost} 2 | eureka.instance.nonSecurePort=80 3 | eureka.instance.metadataMap.instanceId=${vcap.application.instance_id:${spring.application.name}:${spring.application.instance_id:${server.port}}} 4 | eureka.instance.leaseRenewalIntervalInSeconds=5 5 | eureka.client.region=default 6 | eureka.client.registryFetchIntervalSeconds=5 7 | eureka.client.serviceUrl.defaultZone=${vcap.services.service-registry.credentials.uri}/eureka/ 8 | # 9 | security.oauth2.client.client-id=html5 10 | security.oauth2.client.client-secret=secret 11 | security.oauth2.client.access-token-uri=${vcap.services.auth-service.credentials.uri}/uaa/oauth/token 12 | security.oauth2.client.user-authorization-uri=${vcap.services.auth-service.credentials.uri}/uaa/oauth/authorize 13 | 14 | 15 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | source $BUILD_DIRECTORY/utils/cf-common.sh 6 | 7 | mvn -DskipTests clean install 8 | 9 | as=auth-service 10 | res=service-registry 11 | gs=greetings-service 12 | gc=edge-service 13 | h5=html5-client 14 | 15 | cf d -f $res 16 | cf d -f $as 17 | cf d -f $gs 18 | cf d -f $gc 19 | cf d -f $h5 20 | 21 | # EUREKA SERVICE 22 | cf ds -f $res 23 | cf a | grep $res && cf d -f $res 24 | deploy_app $res 25 | deploy_service $res 26 | 27 | # AUTH SERVICE 28 | as_db=auth-service-pgsql 29 | 30 | cf ds -f $as 31 | cf d -f $as 32 | cf ds -f ${as_db} 33 | 34 | cf cs elephantsql turtle ${as_db} 35 | 36 | deploy_app $as 37 | deploy_service $as 38 | 39 | ## GREETINGS SERVICE 40 | cf d -f $gs 41 | deploy_app $gs 42 | 43 | ## GREETINGS CLIENT 44 | cf d -f $gc 45 | deploy_app $gc 46 | 47 | ## HTML5 CLIENT 48 | cf d -f $h5 49 | deploy_app $h5 50 | -------------------------------------------------------------------------------- /edge-service/src/main/java/greetings/ZuulConfiguration.java: -------------------------------------------------------------------------------- 1 | package greetings; 2 | 3 | import org.apache.commons.logging.Log; 4 | import org.apache.commons.logging.LogFactory; 5 | import org.springframework.boot.CommandLineRunner; 6 | import org.springframework.cloud.netflix.zuul.EnableZuulProxy; 7 | import org.springframework.cloud.netflix.zuul.filters.RouteLocator; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | 11 | @Configuration 12 | @EnableZuulProxy 13 | class ZuulConfiguration { 14 | 15 | @Bean 16 | CommandLineRunner commandLineRunner(RouteLocator routeLocator) { // <1> 17 | Log log = LogFactory.getLog(getClass()); 18 | return args -> routeLocator.getRoutes().forEach( 19 | r -> log.info(String.format("%s (%s) %s", r.getId(), r.getLocation(), 20 | r.getFullPath()))); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /social-auth-service/src/main/java/auth/accounts/Account.java: -------------------------------------------------------------------------------- 1 | package auth.accounts; 2 | 3 | import javax.persistence.Entity; 4 | import javax.persistence.GeneratedValue; 5 | import javax.persistence.Id; 6 | 7 | @Entity 8 | public class Account { 9 | 10 | @Id 11 | @GeneratedValue 12 | private Long id; 13 | 14 | private String username, password; // <1> 15 | 16 | private boolean active; // <2> 17 | 18 | Account() { 19 | } 20 | 21 | public Account(String username, String password, boolean active) { 22 | this.username = username; 23 | this.password = password; 24 | this.active = active; 25 | } 26 | 27 | public Long getId() { 28 | return id; 29 | } 30 | 31 | public String getUsername() { 32 | return username; 33 | } 34 | 35 | public String getPassword() { 36 | return password; 37 | } 38 | 39 | public boolean isActive() { 40 | return active; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /greetings-service/src/main/java/greetings/SecureGreetingsRestController.java: -------------------------------------------------------------------------------- 1 | package greetings; 2 | 3 | import org.springframework.context.annotation.Profile; 4 | import org.springframework.web.bind.annotation.PathVariable; 5 | import org.springframework.web.bind.annotation.RequestMapping; 6 | import org.springframework.web.bind.annotation.RequestMethod; 7 | import org.springframework.web.bind.annotation.RestController; 8 | 9 | import java.security.Principal; 10 | import java.util.Collections; 11 | import java.util.Map; 12 | 13 | @Profile("secure") 14 | @RestController 15 | @RequestMapping(method = RequestMethod.GET, value = "/greet/{name}") 16 | public class SecureGreetingsRestController { 17 | 18 | @RequestMapping 19 | Map hi(@PathVariable String name, Principal p) { 20 | return Collections.singletonMap("greeting", 21 | "Hello, " + name + " from " + p.getName() + "!"); 22 | } 23 | } -------------------------------------------------------------------------------- /edge-service/src/main/java/greetings/SecureResourceConfiguration.java: -------------------------------------------------------------------------------- 1 | package greetings; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.context.annotation.Profile; 5 | //@formatter:off 6 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 7 | import org.springframework.security.oauth2.config.annotation.web 8 | .configuration.EnableResourceServer; 9 | import org.springframework.security.oauth2.config.annotation.web 10 | .configuration.ResourceServerConfigurerAdapter; 11 | //@formatter:on 12 | 13 | // <1> 14 | @Profile("secure") 15 | @Configuration 16 | @EnableResourceServer 17 | class SecureResourceConfiguration extends ResourceServerConfigurerAdapter { 18 | 19 | @Override 20 | public void configure(HttpSecurity http) throws Exception { 21 | http.antMatcher("/api/**").authorizeRequests() // <2> 22 | .anyRequest().authenticated(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /edge-it/src/integration-test/java/edge/Config.java: -------------------------------------------------------------------------------- 1 | package edge; 2 | 3 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.retry.backoff.ExponentialBackOffPolicy; 7 | import org.springframework.retry.support.RetryTemplate; 8 | import org.springframework.web.client.RestTemplate; 9 | 10 | @Configuration 11 | @EnableAutoConfiguration 12 | public class Config { 13 | 14 | @Bean 15 | RestTemplate restTemplate() { 16 | return new RestTemplate(); 17 | } 18 | 19 | @Bean 20 | RetryTemplate retryTemplate() { 21 | RetryTemplate retryTemplate = new RetryTemplate(); 22 | ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy(); 23 | backOffPolicy.setInitialInterval(30 * 1000); 24 | backOffPolicy.setMaxInterval(180 * 1000); 25 | retryTemplate.setBackOffPolicy(backOffPolicy); 26 | return retryTemplate; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /html5-client/src/main/resources/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Demo 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 31 | 32 | -------------------------------------------------------------------------------- /social-auth-service/src/main/java/auth/accounts/AccountConfiguration.java: -------------------------------------------------------------------------------- 1 | package auth.accounts; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.security.core.authority.AuthorityUtils; 6 | import org.springframework.security.core.userdetails.User; 7 | import org.springframework.security.core.userdetails.UserDetailsService; 8 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 9 | 10 | @Configuration 11 | public class AccountConfiguration { 12 | 13 | @Bean 14 | UserDetailsService userDetailsService(AccountRepository accountRepository) { 15 | return username -> accountRepository 16 | .findByUsername(username) 17 | .map( 18 | account -> { 19 | boolean active = account.isActive(); 20 | return new User(account.getUsername(), account.getPassword(), active, 21 | active, active, active, AuthorityUtils.createAuthorityList("ROLE_ADMIN", 22 | "ROLE_USER")); 23 | }) 24 | .orElseThrow( 25 | () -> new UsernameNotFoundException(String.format("username %s not found!", 26 | username))); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /auth-service/src/main/java/auth/accounts/AccountConfiguration.java: -------------------------------------------------------------------------------- 1 | package auth.accounts; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.security.core.authority.AuthorityUtils; 6 | import org.springframework.security.core.userdetails.User; 7 | import org.springframework.security.core.userdetails.UserDetailsService; 8 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 9 | 10 | @Configuration 11 | public class AccountConfiguration { 12 | 13 | @Bean 14 | UserDetailsService userDetailsService(AccountRepository accountRepository) { 15 | // <1> 16 | return username -> accountRepository 17 | .findByUsername(username) 18 | .map( 19 | account -> { 20 | boolean active = account.isActive(); 21 | return new User(account.getUsername(), account.getPassword(), active, 22 | active, active, active, AuthorityUtils.createAuthorityList("ROLE_ADMIN", 23 | "ROLE_USER")); 24 | }) 25 | .orElseThrow( 26 | () -> new UsernameNotFoundException(String.format("username %s not found!", 27 | username))); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /auth-service/src/main/java/auth/clients/Client.java: -------------------------------------------------------------------------------- 1 | package auth.clients; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import org.springframework.util.StringUtils; 7 | 8 | import javax.persistence.Entity; 9 | import javax.persistence.GeneratedValue; 10 | import javax.persistence.Id; 11 | 12 | @Data 13 | @AllArgsConstructor 14 | @NoArgsConstructor 15 | @Entity 16 | public class Client { 17 | 18 | @Id 19 | @GeneratedValue 20 | private Long id; 21 | 22 | private String clientId; 23 | 24 | private String secret; 25 | 26 | private String scopes = StringUtils 27 | .arrayToCommaDelimitedString(new String[] { "openid" }); 28 | 29 | private String authorizedGrantTypes = StringUtils 30 | .arrayToCommaDelimitedString(new String[] { "authorization_code", 31 | "refresh_token", "password" }); 32 | 33 | private String authorities = StringUtils 34 | .arrayToCommaDelimitedString(new String[] { "ROLE_USER", "ROLE_ADMIN" }); 35 | 36 | private String autoApproveScopes = StringUtils 37 | .arrayToCommaDelimitedString(new String[] { ".*" }); 38 | 39 | public Client(String clientId, String clientSecret) { 40 | this.clientId = clientId; 41 | this.secret = clientSecret; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /social-auth-service/src/main/resources/bootstrap.yml: -------------------------------------------------------------------------------- 1 | #security: 2 | # oauth2: 3 | # client: 4 | # client-id: html5 5 | # client-secret: secret 6 | # scope: read,write 7 | # auto-approve-scopes: '.*' 8 | # 9 | 10 | 11 | facebook: 12 | client: 13 | clientId: 233668646673605 14 | clientSecret: 33b17e044ee6a4fa383f46ec6e28ea1d 15 | accessTokenUri: https://graph.facebook.com/oauth/access_token 16 | userAuthorizationUri: https://www.facebook.com/dialog/oauth 17 | tokenName: oauth_token 18 | authenticationScheme: query 19 | clientAuthenticationScheme: form 20 | resource: 21 | userInfoUri: https://graph.facebook.com/me 22 | 23 | github: 24 | client: 25 | client-id: 83aea5adb9b172f14260 26 | client-secret: 4f03278cbe8b2f3df3b6fbbff0382f7740a6b3ce 27 | accessTokenUri: https://github.com/login/oauth/access_token 28 | userAuthorizationUri: https://github.com/login/oauth/authorize 29 | clientAuthenticationScheme: form 30 | resource: 31 | userInfoUri: https://api.github.com/user 32 | 33 | 34 | logging: 35 | level: 36 | org.springframework.security: DEBUG 37 | 38 | server: 39 | port: ${PORT:9191} 40 | context-path: /uaa 41 | spring: 42 | application: 43 | name: auth-service 44 | -------------------------------------------------------------------------------- /greetings-service/src/main/java/greetings/ProxyAwareGreetingsRestController.java: -------------------------------------------------------------------------------- 1 | package greetings; 2 | 3 | import org.apache.commons.logging.Log; 4 | import org.apache.commons.logging.LogFactory; 5 | import org.springframework.context.annotation.Profile; 6 | import org.springframework.web.bind.annotation.*; 7 | 8 | import java.util.Collections; 9 | import java.util.Map; 10 | 11 | @Profile("zuul") 12 | @RestController 13 | @RequestMapping(method = RequestMethod.GET, value = "/greet/{name}") 14 | public class ProxyAwareGreetingsRestController { 15 | 16 | private Log log = LogFactory.getLog(getClass()); 17 | 18 | @RequestMapping(headers = "x-forwarded-for") 19 | Map hi(@PathVariable String name, 20 | @RequestHeader("x-forwarded-for") String forwardedFor, 21 | @RequestHeader("x-forwarded-proto") String proto, 22 | @RequestHeader("x-forwarded-host") String host, 23 | @RequestHeader("x-forwarded-port") int port, 24 | @RequestHeader("x-forwarded-prefix") String prefix) { 25 | 26 | log 27 | .info(String.format( 28 | "responded to a proxied request debugPrincipal %s://%s:%s " 29 | + "with prefix %s for service %s.", proto, host, port, prefix, 30 | forwardedFor)); 31 | 32 | return Collections.singletonMap("greeting", "Hello, " + name + "!"); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /edge-service/src/main/java/greetings/SsoConfiguration.java: -------------------------------------------------------------------------------- 1 | package greetings; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.context.annotation.Profile; 5 | 6 | //@formatter:off 7 | import org.springframework.boot.autoconfigure 8 | .security.oauth2.client.EnableOAuth2Sso; 9 | import org.springframework.security.config.annotation 10 | .web.builders.HttpSecurity; 11 | import org.springframework.security.config.annotation 12 | .web.configuration.WebSecurityConfigurerAdapter; 13 | //@formatter:on 14 | 15 | import org.springframework.security.web.csrf.CookieCsrfTokenRepository; 16 | 17 | // <1> 18 | @Profile("sso") 19 | @Configuration 20 | // <2> 21 | @EnableOAuth2Sso 22 | class SsoConfiguration extends WebSecurityConfigurerAdapter { 23 | 24 | @Override 25 | protected void configure(HttpSecurity http) throws Exception { 26 | // @formatter:off 27 | http.antMatcher("/**").authorizeRequests() // <3> 28 | .antMatchers( "/", "/app.js", "/login**", "/webjars/**").permitAll().anyRequest() 29 | .authenticated().and().logout().logoutSuccessUrl("/").permitAll().and().csrf() 30 | .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()); 31 | // @formatter:on 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /service-registry/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | jar 7 | 8 | cnj 9 | edge 10 | 1.0.0-SNAPSHOT 11 | 12 | service-registry 13 | edge/service-registry 14 | 15 | 16 | org.springframework.cloud 17 | spring-cloud-starter-eureka-server 18 | 19 | 20 | org.springframework.boot 21 | spring-boot-starter-test 22 | test 23 | 24 | 25 | 26 | 27 | 28 | org.springframework.boot 29 | spring-boot-maven-plugin 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /edge-it/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | cnj 9 | edge 10 | 1.0.0-SNAPSHOT 11 | 12 | edge-it 13 | 14 | 15 | 16 | cnj 17 | it-support 18 | ${project.version} 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-starter-web 23 | test 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-test 28 | 29 | 30 | org.cloudfoundry 31 | cloudfoundry-client-lib 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /auth-service/src/main/java/auth/DataCommandLineRunner.java: -------------------------------------------------------------------------------- 1 | package auth; 2 | 3 | import auth.accounts.Account; 4 | import auth.accounts.AccountRepository; 5 | import auth.clients.Client; 6 | import auth.clients.ClientRepository; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.CommandLineRunner; 9 | import org.springframework.stereotype.Component; 10 | 11 | import java.util.stream.Stream; 12 | 13 | @Component 14 | class DataCommandLineRunner implements CommandLineRunner { 15 | 16 | private final AccountRepository accountRepository; 17 | 18 | private final ClientRepository clientRepository; 19 | 20 | @Autowired 21 | public DataCommandLineRunner(AccountRepository accountRepository, 22 | ClientRepository clientRepository) { 23 | this.accountRepository = accountRepository; 24 | this.clientRepository = clientRepository; 25 | } 26 | 27 | @Override 28 | public void run(String... args) throws Exception { 29 | 30 | Stream 31 | .of("dsyer,cloud", "pwebb,boot", "mminella,batch", "rwinch,security", 32 | "jlong,spring") 33 | .map(s -> s.split(",")) 34 | .forEach( 35 | tuple -> accountRepository.save(new Account(tuple[0], tuple[1], true))); 36 | 37 | Stream.of("html5,password", "android,secret").map(x -> x.split(",")) 38 | .forEach(x -> clientRepository.save(new Client(x[0], x[1]))); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /social-auth-service/src/main/java/auth/clients/ClientConfiguration.java: -------------------------------------------------------------------------------- 1 | package auth.clients; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.security.oauth2.provider.ClientDetailsService; 6 | import org.springframework.security.oauth2.provider.ClientRegistrationException; 7 | import org.springframework.security.oauth2.provider.client.BaseClientDetails; 8 | 9 | import java.util.Arrays; 10 | import java.util.Collections; 11 | 12 | @Configuration 13 | public class ClientConfiguration { 14 | 15 | @Bean 16 | ClientDetailsService clientDetailsService(ClientRepository clientRepository) { 17 | return clientId -> clientRepository 18 | .findByClientId(clientId) 19 | .map( 20 | client -> { 21 | BaseClientDetails details = new BaseClientDetails(client.getClientId(), 22 | null, client.getScopes(), client.getAuthorizedGrantTypes(), client 23 | .getAuthorities()); 24 | details.setClientSecret(client.getSecret()); 25 | details.setAutoApproveScopes(Arrays.asList(client.getAutoApproveScopes() 26 | .split(","))); 27 | details.setRegisteredRedirectUri(Collections 28 | .singleton("http://localhost:8082")); 29 | return details; 30 | }) 31 | .orElseThrow( 32 | () -> new ClientRegistrationException(String.format( 33 | "no client %s registered", clientId))); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /edge-service/src/main/resources/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Edge Service 7 | 8 | 9 | 10 | 12 | 14 | 16 | 17 | 18 | 19 | 20 |
21 | Login 22 |
23 | 24 |
25 | 26 | 27 | Logged in as: 28 |
29 | 30 | Token: 31 |
32 | 33 | Greeting from Zuul Route: 34 |
35 | 36 | Greeting from Edge Service (Feign): 37 |
38 |
39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /edge-service/src/main/java/greetings/RestTemplateGreetingsClientApiGateway.java: -------------------------------------------------------------------------------- 1 | package greetings; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.cloud.client.loadbalancer.LoadBalanced; 5 | import org.springframework.context.annotation.Profile; 6 | import org.springframework.core.ParameterizedTypeReference; 7 | import org.springframework.http.HttpMethod; 8 | import org.springframework.http.ResponseEntity; 9 | import org.springframework.web.bind.annotation.*; 10 | import org.springframework.web.client.RestTemplate; 11 | 12 | import java.util.Map; 13 | 14 | @Profile({ "default", "insecure" }) 15 | @RestController 16 | @RequestMapping("/api") 17 | class RestTemplateGreetingsClientApiGateway { 18 | 19 | private final RestTemplate restTemplate; 20 | 21 | @Autowired 22 | RestTemplateGreetingsClientApiGateway( 23 | @LoadBalanced RestTemplate restTemplate) { // <1> 24 | this.restTemplate = restTemplate; 25 | } 26 | 27 | @GetMapping("/resttemplate/{name}") 28 | Map restTemplate(@PathVariable String name) { 29 | 30 | //@formatter:off 31 | ParameterizedTypeReference> type = 32 | new ParameterizedTypeReference>() {}; 33 | //@formatter:on 34 | 35 | ResponseEntity> responseEntity = this.restTemplate 36 | .exchange("http://greetings-service/greet/{name}", HttpMethod.GET, null, 37 | type, name); 38 | return responseEntity.getBody(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /social-auth-service/src/main/java/auth/clients/Client.java: -------------------------------------------------------------------------------- 1 | package auth.clients; 2 | 3 | import javax.persistence.Entity; 4 | import javax.persistence.GeneratedValue; 5 | import javax.persistence.Id; 6 | import java.util.Arrays; 7 | import java.util.stream.Collectors; 8 | 9 | @Entity 10 | public class Client { 11 | 12 | @Id 13 | @GeneratedValue 14 | private Long id; 15 | 16 | private String clientId; 17 | 18 | private String secret; 19 | 20 | private String scopes = from("openid"); 21 | 22 | private String authorizedGrantTypes = from("authorization_code", 23 | "refresh_token", "password"); 24 | 25 | private String authorities = from("ROLE_USER", "ROLE_ADMIN"); 26 | 27 | private String autoApproveScopes = from(".*"); 28 | 29 | public Client(String clientId, String clientSecret) { 30 | this.clientId = clientId; 31 | this.secret = clientSecret; 32 | } 33 | 34 | Client() { 35 | } 36 | 37 | private static String from(String... arr) { 38 | return Arrays.stream(arr).collect(Collectors.joining(",")); 39 | } 40 | 41 | public String getScopes() { 42 | return scopes; 43 | } 44 | 45 | public String getAuthorizedGrantTypes() { 46 | return authorizedGrantTypes; 47 | } 48 | 49 | public String getAuthorities() { 50 | return authorities; 51 | } 52 | 53 | public String getAutoApproveScopes() { 54 | return autoApproveScopes; 55 | } 56 | 57 | public Long getId() { 58 | return id; 59 | } 60 | 61 | public String getClientId() { 62 | return clientId; 63 | } 64 | 65 | public String getSecret() { 66 | return secret; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /edge-service/src/main/java/greetings/RoutesListener.java: -------------------------------------------------------------------------------- 1 | package greetings; 2 | 3 | import org.apache.commons.logging.Log; 4 | import org.apache.commons.logging.LogFactory; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.cloud.client.discovery.DiscoveryClient; 7 | import org.springframework.cloud.client.discovery.event.HeartbeatEvent; 8 | import org.springframework.cloud.netflix.zuul.RoutesRefreshedEvent; 9 | import org.springframework.cloud.netflix.zuul.filters.RouteLocator; 10 | import org.springframework.context.event.EventListener; 11 | import org.springframework.stereotype.Component; 12 | 13 | @Component 14 | class RoutesListener { 15 | 16 | private final RouteLocator routeLocator; 17 | 18 | private final DiscoveryClient discoveryClient; 19 | 20 | private Log log = LogFactory.getLog(getClass()); 21 | 22 | @Autowired 23 | public RoutesListener(DiscoveryClient dc, RouteLocator rl) { 24 | this.routeLocator = rl; 25 | this.discoveryClient = dc; 26 | } 27 | 28 | // <1> 29 | @EventListener(HeartbeatEvent.class) 30 | public void onHeartbeatEvent(HeartbeatEvent event) { 31 | this.log.info("onHeartbeatEvent()"); 32 | this.discoveryClient.getServices().stream().map(x -> " " + x) 33 | .forEach(this.log::info); 34 | } 35 | 36 | // <2> 37 | @EventListener(RoutesRefreshedEvent.class) 38 | public void onRoutesRefreshedEvent(RoutesRefreshedEvent event) { 39 | this.log.info("onRoutesRefreshedEvent()"); 40 | this.routeLocator.getRoutes().stream().map(x -> " " + x) 41 | .forEach(this.log::info); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /html5-client/src/main/java/client/Html5Client.java: -------------------------------------------------------------------------------- 1 | package client; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 7 | import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; 8 | import org.springframework.http.MediaType; 9 | import org.springframework.web.bind.annotation.GetMapping; 10 | import org.springframework.web.bind.annotation.RequestMapping; 11 | import org.springframework.web.bind.annotation.RequestMethod; 12 | import org.springframework.web.bind.annotation.RestController; 13 | 14 | import java.util.Collections; 15 | import java.util.Map; 16 | import java.util.Optional; 17 | 18 | @RestController 19 | @EnableDiscoveryClient 20 | @SpringBootApplication 21 | public class Html5Client { 22 | 23 | private final LoadBalancerClient loadBalancerClient; 24 | 25 | @Autowired 26 | Html5Client(LoadBalancerClient loadBalancerClient) { 27 | this.loadBalancerClient = loadBalancerClient; 28 | } 29 | 30 | public static void main(String[] args) { 31 | SpringApplication.run(Html5Client.class, args); 32 | } 33 | 34 | // <1> 35 | //@formatter:off 36 | @GetMapping(value = "/greetings-client-uri", 37 | produces = MediaType.APPLICATION_JSON_VALUE) 38 | //@formatter:on 39 | Map greetingsClientURI() throws Exception { 40 | return Optional 41 | .ofNullable(this.loadBalancerClient.choose("greetings-client")) 42 | .map(si -> Collections.singletonMap("uri", si.getUri().toString())) 43 | .orElse(null); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /security-autoconfiguration/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 4.0.0 7 | 8 | 9 | cnj 10 | edge 11 | 1.0.0-SNAPSHOT 12 | 13 | security-autoconfiguration 14 | edge/security-autoconfiguration 15 | 16 | 17 | org.springframework.cloud 18 | spring-cloud-starter-oauth2 19 | provided 20 | 21 | 22 | org.springframework.cloud 23 | spring-cloud-starter-hystrix 24 | provided 25 | 26 | 27 | org.springframework.cloud 28 | spring-cloud-starter-feign 29 | provided 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-starter-web 34 | 35 | 36 | org.springframework.boot 37 | spring-boot-starter-test 38 | test 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /edge-service/src/main/resources/static/app.js: -------------------------------------------------------------------------------- 1 | var app = angular.module("app", []); 2 | 3 | //<1> 4 | app.factory('oauth', function () { 5 | return {details: null, name: null, token: null}; 6 | }); 7 | 8 | app.run(['$http', '$rootScope', 'oauth', function ($http, $rootScope, oauth) { 9 | 10 | $http.get("/user").success(function (data) { 11 | 12 | oauth.details = data.userAuthentication.details; 13 | oauth.name = oauth.details.name; 14 | oauth.token = data.details.tokenValue; 15 | 16 | // <2> 17 | $http.defaults.headers.common['Authorization'] = 'bearer ' + oauth.token; 18 | 19 | // <3> 20 | $rootScope.$broadcast('auth-event', oauth.token); 21 | }); 22 | }]); 23 | 24 | app.controller("home", function ($http, $rootScope, oauth) { 25 | 26 | var self = this; 27 | 28 | self.authenticated = false; 29 | 30 | // <4> 31 | $rootScope.$on('auth-event', function (evt, ctx) { 32 | self.user = oauth.details.name; 33 | self.token = oauth.token; 34 | self.authenticated = true; 35 | 36 | var name = window.prompt('who would you like to greet?'); 37 | 38 | // <5> 39 | $http.get('/greetings-service/greet/' + name) 40 | .success(function (greetingData) { 41 | self.greetingFromZuulRoute = greetingData.greeting; 42 | }) 43 | .error(function (e) { 44 | console.log('oops!' + JSON.stringify(e)); 45 | }); 46 | 47 | // <6> 48 | $http.get('/lets/greet/' + name) 49 | .success(function (greetingData) { 50 | self.greetingFromEdgeService = greetingData.greeting; 51 | }) 52 | .error(function (e) { 53 | console.log('oops!' + JSON.stringify(e)); 54 | }); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /html5-client/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 4.0.0 7 | 8 | 9 | cnj 10 | edge 11 | 1.0.0-SNAPSHOT 12 | 13 | 14 | 15 | html5-client 16 | edge/html5-client 17 | 18 | 19 | 20 | 21 | 22 | org.webjars 23 | jquery 24 | 2.1.1 25 | 26 | 27 | org.webjars 28 | webjars-locator 29 | 30 | 31 | 32 | org.springframework.cloud 33 | spring-cloud-starter-eureka 34 | 35 | 36 | org.springframework.boot 37 | spring-boot-starter-web 38 | 39 | 40 | org.springframework.boot 41 | spring-boot-starter-test 42 | test 43 | 44 | 45 | 46 | 47 | 48 | org.springframework.boot 49 | spring-boot-maven-plugin 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /greetings-service/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 4.0.0 7 | 8 | 9 | cnj 10 | edge 11 | 1.0.0-SNAPSHOT 12 | 13 | 14 | greetings-service 15 | edge/greetings-service 16 | 17 | 18 | 19 | cnj 20 | security-autoconfiguration 21 | ${project.version} 22 | 23 | 24 | org.springframework.cloud 25 | spring-cloud-starter-oauth2 26 | 27 | 28 | org.springframework.cloud 29 | spring-cloud-starter-eureka 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-starter-web 34 | 35 | 36 | org.springframework.boot 37 | spring-boot-starter-test 38 | test 39 | 40 | 41 | 42 | 43 | 44 | org.springframework.boot 45 | spring-boot-maven-plugin 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /auth-service/src/main/java/auth/AuthorizationServerConfiguration.java: -------------------------------------------------------------------------------- 1 | package auth; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.security.authentication.AuthenticationManager; 6 | 7 | //@formatter:off 8 | import org.springframework.security.oauth2 9 | .config.annotation.configurers.ClientDetailsServiceConfigurer; 10 | import org.springframework.security.oauth2 11 | .config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; 12 | import org.springframework.security.oauth2 13 | .config.annotation.web.configuration.EnableAuthorizationServer; 14 | import org.springframework.security.oauth2 15 | .config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; 16 | import org.springframework.security.oauth2 17 | .provider.ClientDetailsService; 18 | //@formatter:on 19 | 20 | @Configuration 21 | @EnableAuthorizationServer 22 | class AuthorizationServerConfiguration extends 23 | AuthorizationServerConfigurerAdapter { 24 | 25 | private final AuthenticationManager authenticationManager; 26 | 27 | private final ClientDetailsService clientDetailsService; 28 | 29 | @Autowired 30 | public AuthorizationServerConfiguration( 31 | AuthenticationManager authenticationManager, 32 | ClientDetailsService clientDetailsService) { 33 | this.authenticationManager = authenticationManager; 34 | this.clientDetailsService = clientDetailsService; 35 | } 36 | 37 | @Override 38 | public void configure(ClientDetailsServiceConfigurer clients) throws Exception { 39 | // <1> 40 | clients.withClientDetails(this.clientDetailsService); 41 | } 42 | 43 | @Override 44 | public void configure(AuthorizationServerEndpointsConfigurer endpoints) 45 | throws Exception { 46 | // <2> 47 | endpoints.authenticationManager(this.authenticationManager); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /edge-service/src/main/java/greetings/ThrottlingZuulFilter.java: -------------------------------------------------------------------------------- 1 | package greetings; 2 | 3 | import com.google.common.util.concurrent.RateLimiter; 4 | import com.netflix.zuul.ZuulFilter; 5 | import com.netflix.zuul.context.RequestContext; 6 | import com.netflix.zuul.exception.ZuulException; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.context.annotation.Profile; 9 | import org.springframework.core.Ordered; 10 | import org.springframework.http.HttpStatus; 11 | import org.springframework.http.MediaType; 12 | import org.springframework.stereotype.Component; 13 | import org.springframework.util.ReflectionUtils; 14 | 15 | import javax.servlet.http.HttpServletResponse; 16 | 17 | @Profile("throttled") 18 | @Component 19 | class ThrottlingZuulFilter extends ZuulFilter { 20 | 21 | private final HttpStatus tooManyRequests = HttpStatus.TOO_MANY_REQUESTS; 22 | 23 | private final RateLimiter rateLimiter; 24 | 25 | @Autowired 26 | public ThrottlingZuulFilter(RateLimiter rateLimiter) { 27 | this.rateLimiter = rateLimiter; 28 | } 29 | 30 | // <1> 31 | @Override 32 | public String filterType() { 33 | return "pre"; 34 | } 35 | 36 | // <2> 37 | @Override 38 | public int filterOrder() { 39 | return Ordered.HIGHEST_PRECEDENCE; 40 | } 41 | 42 | // <3> 43 | @Override 44 | public boolean shouldFilter() { 45 | return true; 46 | } 47 | 48 | // <4> 49 | @Override 50 | public Object run() { 51 | try { 52 | RequestContext currentContext = RequestContext.getCurrentContext(); 53 | HttpServletResponse response = currentContext.getResponse(); 54 | 55 | if (!rateLimiter.tryAcquire()) { 56 | 57 | // <5> 58 | response.setContentType(MediaType.TEXT_PLAIN_VALUE); 59 | response.setStatus(this.tooManyRequests.value()); 60 | response.getWriter().append(this.tooManyRequests.getReasonPhrase()); 61 | 62 | // <6> 63 | currentContext.setSendZuulResponse(false); 64 | 65 | throw new ZuulException(this.tooManyRequests.getReasonPhrase(), 66 | this.tooManyRequests.value(), this.tooManyRequests.getReasonPhrase()); 67 | } 68 | } 69 | catch (Exception e) { 70 | ReflectionUtils.rethrowRuntimeException(e); 71 | } 72 | return null; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /social-auth-service/src/main/resources/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Demo 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

Login

16 |
17 |
18 | With Facebook: click here 19 |
20 |
21 | With Github: click here 22 |
23 |
24 |
25 | Logged in as: 26 |
27 | 28 |
29 |
30 | 31 | 62 | 63 | -------------------------------------------------------------------------------- /auth-service/src/main/java/auth/clients/ClientConfiguration.java: -------------------------------------------------------------------------------- 1 | package auth.clients; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.cloud.client.discovery.DiscoveryClient; 5 | import org.springframework.cloud.client.discovery.event.HeartbeatEvent; 6 | import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.context.event.EventListener; 10 | import org.springframework.security.oauth2.provider.ClientDetailsService; 11 | import org.springframework.security.oauth2.provider.ClientRegistrationException; 12 | import org.springframework.security.oauth2.provider.client.BaseClientDetails; 13 | 14 | import java.util.Collections; 15 | import java.util.List; 16 | import java.util.Optional; 17 | import java.util.Set; 18 | import java.util.concurrent.ConcurrentSkipListSet; 19 | import java.util.stream.Collectors; 20 | 21 | @Configuration 22 | public class ClientConfiguration { 23 | 24 | private final LoadBalancerClient loadBalancerClient; 25 | 26 | @Autowired 27 | public ClientConfiguration(LoadBalancerClient client) { 28 | this.loadBalancerClient = client; 29 | } 30 | 31 | @Bean 32 | ClientDetailsService clientDetailsService(ClientRepository clientRepository) { 33 | return clientId -> clientRepository 34 | .findByClientId(clientId) 35 | .map( 36 | client -> { 37 | 38 | BaseClientDetails details = new BaseClientDetails(client.getClientId(), 39 | null, client.getScopes(), client.getAuthorizedGrantTypes(), client 40 | .getAuthorities()); 41 | details.setClientSecret(client.getSecret()); 42 | 43 | // <1> 44 | // details.setAutoApproveScopes 45 | // (Arrays.asList(client.getAutoApproveScopes().split(","))); 46 | 47 | // <2> 48 | String greetingsClientRedirectUri = Optional 49 | .ofNullable(this.loadBalancerClient.choose("greetings-client")) 50 | .map(si -> "http://" + si.getHost() + ':' + si.getPort() + '/') 51 | .orElseThrow( 52 | () -> new ClientRegistrationException( 53 | "couldn't find and bind a greetings-client IP")); 54 | 55 | details.setRegisteredRedirectUri(Collections 56 | .singleton(greetingsClientRedirectUri)); 57 | return details; 58 | }) 59 | .orElseThrow( 60 | () -> new ClientRegistrationException(String.format( 61 | "no client %s registered", clientId))); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /security-autoconfiguration/src/main/java/relay/TokenRelayAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | 2 | package relay; 3 | 4 | import feign.RequestInterceptor; 5 | 6 | //@formatter:on 7 | import org.springframework.boot.autoconfigure 8 | .condition.ConditionalOnBean; 9 | import org.springframework.boot.autoconfigure 10 | .condition.ConditionalOnClass; 11 | import org.springframework.boot.autoconfigure 12 | .condition.ConditionalOnWebApplication; 13 | import org.springframework.boot.autoconfigure 14 | .security.oauth2.resource.UserInfoRestTemplateFactory; 15 | //@formatter:off 16 | 17 | import org.springframework.cloud.client.loadbalancer.LoadBalanced; 18 | import org.springframework.context.annotation.Bean; 19 | import org.springframework.context.annotation.Configuration; 20 | import org.springframework.context.annotation.Lazy; 21 | import org.springframework.context.annotation.Profile; 22 | import org.springframework.http.HttpHeaders; 23 | 24 | //@formatter:on 25 | import org.springframework.security 26 | .oauth2.client.OAuth2ClientContext; 27 | import org.springframework.security 28 | .oauth2.client.OAuth2RestTemplate; 29 | import org.springframework.security 30 | .oauth2.client.filter.OAuth2ClientContextFilter; 31 | import org.springframework.security 32 | .oauth2.config.annotation.web.configuration.EnableResourceServer; 33 | import org.springframework.web.client.RestTemplate; 34 | //@formatter:off 35 | 36 | @Configuration 37 | @ConditionalOnWebApplication 38 | @ConditionalOnClass(EnableResourceServer.class) 39 | public class TokenRelayAutoConfiguration { 40 | 41 | public static final String SECURE_PROFILE = "secure"; 42 | 43 | @Configuration 44 | @Profile("!" + SECURE_PROFILE) 45 | public static class RestTemplateConfiguration { 46 | 47 | // <1> 48 | @Bean 49 | @LoadBalanced 50 | RestTemplate simpleRestTemplate() { 51 | return new RestTemplate(); 52 | } 53 | } 54 | 55 | @Configuration 56 | @Profile(SECURE_PROFILE) 57 | public static class SecureRestTemplateConfiguration { 58 | 59 | // <2> 60 | @Bean 61 | @Lazy 62 | @LoadBalanced 63 | OAuth2RestTemplate anOAuth2RestTemplate( UserInfoRestTemplateFactory factory) { 64 | return factory.getUserInfoRestTemplate(); 65 | } 66 | } 67 | 68 | @Configuration 69 | @Profile(SECURE_PROFILE) 70 | @ConditionalOnClass(RequestInterceptor.class) 71 | @ConditionalOnBean(OAuth2ClientContextFilter.class) 72 | public static class FeignAutoConfiguration { 73 | 74 | // <3> 75 | @Bean 76 | RequestInterceptor requestInterceptor(OAuth2ClientContext clientContext ) { 77 | return requestTemplate -> requestTemplate.header(HttpHeaders.AUTHORIZATION, 78 | clientContext.getAccessToken().getTokenType() + ' ' 79 | + clientContext.getAccessToken().getValue()); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /auth-service/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | cnj 8 | edge 9 | 1.0.0-SNAPSHOT 10 | 11 | auth-service 12 | edge/auth-service 13 | 14 | 15 | cnj 16 | service-registry 17 | 1.0.0-SNAPSHOT 18 | test 19 | 20 | 21 | org.springframework.cloud 22 | spring-cloud-starter-eureka 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-actuator 27 | 28 | 29 | org.projectlombok 30 | lombok 31 | 32 | 33 | org.springframework.cloud 34 | spring-cloud-starter-oauth2 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-starter-data-jpa 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-starter-web 43 | 44 | 45 | org.springframework.cloud 46 | spring-cloud-starter-config 47 | 48 | 49 | org.postgresql 50 | postgresql 51 | runtime 52 | 53 | 54 | com.h2database 55 | h2 56 | runtime 57 | 58 | 59 | org.springframework.boot 60 | spring-boot-starter-test 61 | test 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | org.springframework.boot 70 | spring-boot-maven-plugin 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /social-auth-service/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | cnj 8 | edge 9 | 1.0.0-SNAPSHOT 10 | 11 | social-auth-service 12 | edge/social-auth-service 13 | 14 | 15 | org.springframework.cloud 16 | spring-cloud-starter-eureka 17 | 18 | 19 | org.springframework.boot 20 | spring-boot-starter-actuator 21 | 22 | 23 | org.springframework.cloud 24 | spring-cloud-starter-oauth2 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-data-jpa 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-web 33 | 34 | 35 | org.springframework.cloud 36 | spring-cloud-starter-config 37 | 38 | 39 | 40 | org.webjars 41 | angularjs 42 | 1.4.3 43 | 44 | 45 | org.webjars 46 | jquery 47 | 2.1.1 48 | 49 | 50 | org.webjars 51 | bootstrap 52 | 3.2.0 53 | 54 | 55 | org.webjars 56 | webjars-locator 57 | 58 | 59 | 60 | com.h2database 61 | h2 62 | runtime 63 | 64 | 65 | org.springframework.boot 66 | spring-boot-starter-test 67 | test 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | org.springframework.boot 76 | spring-boot-maven-plugin 77 | 78 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /edge-service/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 4.0.0 7 | 8 | 9 | cnj 10 | edge 11 | 1.0.0-SNAPSHOT 12 | 13 | 14 | 15 | edge-service 16 | edge/edge-service 17 | 18 | 19 | 20 | 21 | org.webjars 22 | angularjs 23 | 1.4.3 24 | 25 | 26 | org.webjars 27 | jquery 28 | 2.1.1 29 | 30 | 31 | org.webjars 32 | bootstrap 33 | 3.2.0 34 | 35 | 36 | org.webjars 37 | webjars-locator 38 | 39 | 40 | 41 | 42 | 43 | 44 | cnj 45 | security-autoconfiguration 46 | ${project.version} 47 | 48 | 49 | org.springframework.cloud 50 | spring-cloud-starter-zuul 51 | 52 | 53 | com.google.guava 54 | guava 55 | 56 | 57 | org.springframework.boot 58 | spring-boot-devtools 59 | true 60 | 61 | 62 | org.springframework.cloud 63 | spring-cloud-starter-oauth2 64 | 65 | 66 | org.springframework.cloud 67 | spring-cloud-starter-feign 68 | 69 | 70 | org.springframework.cloud 71 | spring-cloud-starter-eureka 72 | 73 | 74 | org.springframework.boot 75 | spring-boot-starter-web 76 | 77 | 78 | org.springframework.boot 79 | spring-boot-starter-actuator 80 | 81 | 82 | org.springframework.boot 83 | spring-boot-starter-test 84 | test 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | org.springframework.boot 93 | spring-boot-maven-plugin 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /auth-service/src/test/java/edge/AuthServiceApplicationTests.java: -------------------------------------------------------------------------------- 1 | package edge; 2 | 3 | import auth.AuthServiceApplication; 4 | import org.apache.commons.logging.Log; 5 | import org.apache.commons.logging.LogFactory; 6 | import org.junit.Before; 7 | import org.junit.Ignore; 8 | import org.junit.Test; 9 | import org.springframework.boot.SpringApplication; 10 | import org.springframework.boot.context.embedded.EmbeddedServletContainerInitializedEvent; 11 | import org.springframework.boot.web.client.RestTemplateBuilder; 12 | import org.springframework.context.ApplicationContext; 13 | import org.springframework.context.annotation.Configuration; 14 | import org.springframework.context.annotation.Import; 15 | import org.springframework.context.event.EventListener; 16 | import org.springframework.core.ParameterizedTypeReference; 17 | import org.springframework.http.MediaType; 18 | import org.springframework.http.RequestEntity; 19 | import org.springframework.http.ResponseEntity; 20 | import org.springframework.util.Base64Utils; 21 | import org.springframework.util.LinkedMultiValueMap; 22 | import org.springframework.web.client.RestTemplate; 23 | 24 | import java.net.URI; 25 | import java.nio.charset.Charset; 26 | import java.util.Map; 27 | import java.util.concurrent.atomic.AtomicInteger; 28 | 29 | @Ignore 30 | public class AuthServiceApplicationTests { 31 | 32 | private static AtomicInteger PORT = new AtomicInteger(); 33 | 34 | private final Log log = LogFactory.getLog(getClass()); 35 | 36 | private ApplicationContext applicationContext; 37 | 38 | private RestTemplate restTemplate; 39 | 40 | private int port = 0; 41 | 42 | @Before 43 | public void setUp() throws Exception { 44 | this.restTemplate = new RestTemplate(); 45 | this.applicationContext = SpringApplication.run(AuthConfig.class); 46 | this.port = PORT.get(); 47 | } 48 | 49 | @Test 50 | public void generateToken() throws Exception { 51 | // <1> 52 | URI uri = URI.create("http://localhost:" + this.port + "/uaa/oauth/token"); 53 | String username = "jlong"; 54 | String password = "spring"; 55 | String clientSecret = "password"; 56 | String client = "html5"; 57 | 58 | // <2> 59 | LinkedMultiValueMap map = new LinkedMultiValueMap() { 60 | 61 | { 62 | add("client_secret", clientSecret); 63 | add("client_id", client); 64 | add("scope", "openid"); 65 | add("grant_type", "password"); 66 | add("username", username); 67 | add("password", password); 68 | } 69 | }; 70 | 71 | // <3> 72 | String token = Base64Utils.encodeToString((client + ":" + clientSecret) 73 | .getBytes(Charset.forName("UTF-8"))); 74 | 75 | RequestEntity> requestEntity = RequestEntity 76 | .post(uri).accept(MediaType.APPLICATION_JSON) 77 | .header("Authorization", "Basic " + token).body(map); 78 | 79 | ParameterizedTypeReference> type = new ParameterizedTypeReference>() { 80 | }; 81 | 82 | ResponseEntity> responseEntity = this.restTemplate 83 | .exchange(requestEntity, type); 84 | 85 | // <4> 86 | Map body = responseEntity.getBody(); 87 | 88 | log.info("access_token: " + body.get("access_token")); 89 | } 90 | 91 | @Configuration 92 | @Import(AuthServiceApplication.class) 93 | public static class AuthConfig { 94 | 95 | @EventListener(EmbeddedServletContainerInitializedEvent.class) 96 | public void ready(EmbeddedServletContainerInitializedEvent evt) { 97 | PORT.set(evt.getEmbeddedServletContainer().getPort()); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /edge-service/src/main/java/greetings/CorsFilter.java: -------------------------------------------------------------------------------- 1 | package greetings; 2 | 3 | import org.apache.commons.logging.Log; 4 | import org.apache.commons.logging.LogFactory; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.cloud.client.ServiceInstance; 7 | import org.springframework.cloud.client.discovery.DiscoveryClient; 8 | import org.springframework.cloud.client.discovery.event.HeartbeatEvent; 9 | import org.springframework.context.annotation.Profile; 10 | import org.springframework.context.event.EventListener; 11 | import org.springframework.core.Ordered; 12 | import org.springframework.core.annotation.Order; 13 | import org.springframework.http.HttpHeaders; 14 | import org.springframework.stereotype.Component; 15 | import org.springframework.util.StringUtils; 16 | 17 | import javax.servlet.*; 18 | import javax.servlet.http.HttpServletRequest; 19 | import javax.servlet.http.HttpServletResponse; 20 | import java.io.IOException; 21 | import java.net.URI; 22 | import java.util.Collections; 23 | import java.util.List; 24 | import java.util.Map; 25 | import java.util.concurrent.ConcurrentHashMap; 26 | import java.util.stream.Collectors; 27 | 28 | @Profile("cors") 29 | @Component 30 | @Order(Ordered.HIGHEST_PRECEDENCE + 10) 31 | class CorsFilter implements Filter { 32 | 33 | private final Log log = LogFactory.getLog(getClass()); 34 | 35 | private final Map> catalog = new ConcurrentHashMap<>(); 36 | 37 | private final DiscoveryClient discoveryClient; 38 | 39 | // <1> 40 | @Autowired 41 | public CorsFilter(DiscoveryClient discoveryClient) { 42 | this.discoveryClient = discoveryClient; 43 | this.refreshCatalog(); 44 | } 45 | 46 | // <2> 47 | @Override 48 | public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) 49 | throws IOException, ServletException { 50 | HttpServletResponse response = HttpServletResponse.class.cast(res); 51 | HttpServletRequest request = HttpServletRequest.class.cast(req); 52 | String originHeaderValue = originFor(request); 53 | boolean clientAllowed = isClientAllowed(originHeaderValue); 54 | 55 | if (clientAllowed) { 56 | response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, 57 | originHeaderValue); 58 | } 59 | 60 | chain.doFilter(req, res); 61 | } 62 | 63 | // <3> 64 | private boolean isClientAllowed(String origin) { 65 | if (StringUtils.hasText(origin)) { 66 | URI originUri = URI.create(origin); 67 | int port = originUri.getPort(); 68 | String match = originUri.getHost() + ':' + (port <= 0 ? 80 : port); 69 | 70 | this.catalog.forEach((k, v) -> { 71 | String collect = v 72 | .stream() 73 | .map( 74 | si -> si.getHost() + ':' + si.getPort() + '(' + si.getServiceId() + ')') 75 | .collect(Collectors.joining()); 76 | }); 77 | 78 | boolean svcMatch = this.catalog 79 | .keySet() 80 | .stream() 81 | .anyMatch( 82 | serviceId -> this.catalog.get(serviceId).stream() 83 | .map(si -> si.getHost() + ':' + si.getPort()) 84 | .anyMatch(hp -> hp.equalsIgnoreCase(match))); 85 | return svcMatch; 86 | } 87 | return false; 88 | } 89 | 90 | // <4> 91 | @EventListener(HeartbeatEvent.class) 92 | public void onHeartbeatEvent(HeartbeatEvent e) { 93 | this.refreshCatalog(); 94 | } 95 | 96 | private void refreshCatalog() { 97 | discoveryClient.getServices().forEach( 98 | svc -> this.catalog.put(svc, this.discoveryClient.getInstances(svc))); 99 | } 100 | 101 | @Override 102 | public void init(FilterConfig filterConfig) throws ServletException { 103 | } 104 | 105 | @Override 106 | public void destroy() { 107 | } 108 | 109 | private String originFor(HttpServletRequest request) { 110 | return StringUtils.hasText(request.getHeader(HttpHeaders.ORIGIN)) ? request 111 | .getHeader(HttpHeaders.ORIGIN) : request.getHeader(HttpHeaders.REFERER); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | edge 7 | pom 8 | 9 | cnj 10 | parent 11 | 1.0.0-SNAPSHOT 12 | 13 | 14 | 15 | 16 | 19 | security-autoconfiguration 20 | 21 | 24 | service-registry 25 | 26 | 29 | auth-service 30 | social-auth-service 31 | greetings-service 32 | edge-service 33 | 34 | 37 | html5-client 38 | 39 | 42 | edge-it 43 | 44 | 45 | 46 | 47 | 48 | org.jfrog.buildinfo 49 | artifactory-maven-plugin 50 | 2.4.0 51 | false 52 | 53 | 54 | build-info 55 | 56 | publish 57 | 58 | 59 | 60 | {{TRAVIS_COMMIT}} 61 | 62 | 63 | 64 | https://cloudnativejava.artifactoryonline.com/cloudnativejava 65 | 66 | ${env.ARTIFACTORY_USERNAME} 67 | ${env.ARTIFACTORY_PASSWORD} 68 | libs-release-local 69 | libs-snapshot-local 70 | 71 | 72 | Travis CI 73 | {{TRAVIS_BUILD_NUMBER}} 74 | 75 | http://travis-ci.org/{{TRAVIS_REPO_SLUG}}/builds/{{TRAVIS_BUILD_ID}} 76 | 77 | {{USER}} 78 | {{TRAVIS_COMMIT}} 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | false 91 | 92 | central 93 | libs-release 94 | https://cloudnativejava.artifactoryonline.com/cloudnativejava/libs-release 95 | 96 | 97 | 98 | snapshots 99 | libs-snapshot 100 | https://cloudnativejava.artifactoryonline.com/cloudnativejava/libs-snapshot 101 | 102 | 103 | 104 | 105 | 106 | false 107 | 108 | central 109 | plugins-release 110 | https://cloudnativejava.artifactoryonline.com/cloudnativejava/plugins-release 111 | 112 | 113 | 114 | snapshots 115 | plugins-snapshot 116 | https://cloudnativejava.artifactoryonline.com/cloudnativejava/plugins-snapshot 117 | 118 | 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /social-auth-service/src/main/java/auth/SocialAuthApplication.java: -------------------------------------------------------------------------------- 1 | package auth; 2 | 3 | import auth.accounts.Account; 4 | import auth.accounts.AccountRepository; 5 | import auth.clients.Client; 6 | import auth.clients.ClientRepository; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.CommandLineRunner; 9 | import org.springframework.boot.SpringApplication; 10 | import org.springframework.boot.autoconfigure.SpringBootApplication; 11 | import org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerProperties; 12 | import org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoTokenServices; 13 | import org.springframework.boot.context.properties.ConfigurationProperties; 14 | import org.springframework.boot.context.properties.NestedConfigurationProperty; 15 | import org.springframework.boot.web.servlet.FilterRegistrationBean; 16 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 17 | import org.springframework.context.annotation.Bean; 18 | import org.springframework.context.annotation.Configuration; 19 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 20 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 21 | import org.springframework.security.oauth2.client.OAuth2ClientContext; 22 | import org.springframework.security.oauth2.client.OAuth2RestTemplate; 23 | import org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter; 24 | import org.springframework.security.oauth2.client.filter.OAuth2ClientContextFilter; 25 | import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails; 26 | import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; 27 | import org.springframework.security.oauth2.config.annotation.web.configuration.*; 28 | import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; 29 | import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; 30 | import org.springframework.security.oauth2.provider.ClientDetailsService; 31 | import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; 32 | import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; 33 | import org.springframework.security.web.csrf.CookieCsrfTokenRepository; 34 | import org.springframework.stereotype.Component; 35 | import org.springframework.web.bind.annotation.RequestMapping; 36 | import org.springframework.web.bind.annotation.RestController; 37 | import org.springframework.web.filter.CompositeFilter; 38 | 39 | import javax.servlet.Filter; 40 | import java.security.Principal; 41 | import java.util.Arrays; 42 | import java.util.LinkedHashMap; 43 | import java.util.List; 44 | import java.util.Map; 45 | import java.util.stream.Stream; 46 | 47 | @EnableDiscoveryClient 48 | @EnableOAuth2Client 49 | @SpringBootApplication 50 | public class SocialAuthApplication { 51 | 52 | public static void main(String[] args) { 53 | SpringApplication.run(SocialAuthApplication.class, args); 54 | } 55 | } 56 | 57 | @RestController 58 | class PrincipalRestController { 59 | 60 | @RequestMapping({ "/user", "/me" }) 61 | Map user(Principal principal) { 62 | Map map = new LinkedHashMap<>(); 63 | map.put("name", principal.getName()); 64 | return map; 65 | } 66 | } 67 | 68 | @Configuration 69 | @EnableResourceServer 70 | class ResourceServerConfiguration extends ResourceServerConfigurerAdapter { 71 | 72 | @Override 73 | public void configure(HttpSecurity http) throws Exception { 74 | // @formatter:off 75 | http.antMatcher("/me").authorizeRequests().anyRequest().authenticated(); 76 | http.antMatcher("/user").authorizeRequests().anyRequest().authenticated(); 77 | // @formatter:on 78 | } 79 | } 80 | 81 | @Configuration 82 | @EnableAuthorizationServer 83 | class AuthorizationServerConfiguration extends WebSecurityConfigurerAdapter 84 | implements AuthorizationServerConfigurer { 85 | 86 | private final OAuth2ClientContext oauth2ClientContext; 87 | 88 | private final ClientDetailsService clientDetailsService; 89 | 90 | @Autowired 91 | public AuthorizationServerConfiguration( 92 | OAuth2ClientContext oauth2ClientContext, 93 | ClientDetailsService clientDetailsService) { 94 | super(); 95 | this.oauth2ClientContext = oauth2ClientContext; 96 | this.clientDetailsService = clientDetailsService; 97 | } 98 | 99 | @Override 100 | public void configure(AuthorizationServerEndpointsConfigurer endpoints) 101 | throws Exception { 102 | } 103 | 104 | @Override 105 | protected void configure(HttpSecurity http) throws Exception { 106 | // @formatter:off 107 | http.antMatcher("/**").authorizeRequests() 108 | .antMatchers("/", "/login**", "/webjars/**").permitAll().anyRequest() 109 | .authenticated().and().exceptionHandling() 110 | .authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/")).and().logout() 111 | .logoutSuccessUrl("/").permitAll().and().csrf() 112 | .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).and() 113 | .addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class); 114 | // @formatter:on 115 | } 116 | 117 | @Bean 118 | FilterRegistrationBean oauth2ClientFilterRegistration( 119 | OAuth2ClientContextFilter filter) { 120 | FilterRegistrationBean registration = new FilterRegistrationBean(); 121 | registration.setFilter(filter); 122 | registration.setOrder(-100); 123 | return registration; 124 | } 125 | 126 | @Bean 127 | @ConfigurationProperties("github") 128 | ClientResources github() { 129 | return new ClientResources(); 130 | } 131 | 132 | @Bean 133 | @ConfigurationProperties("facebook") 134 | ClientResources facebook() { 135 | return new ClientResources(); 136 | } 137 | 138 | private Filter ssoFilter() { 139 | CompositeFilter filter = new CompositeFilter(); 140 | List filters = Arrays.asList( 141 | ssoFilter(facebook(), "/login/facebook"), 142 | ssoFilter(github(), "/login/github")); 143 | filter.setFilters(filters); 144 | return filter; 145 | } 146 | 147 | private Filter ssoFilter(ClientResources client, String path) { 148 | 149 | OAuth2ClientAuthenticationProcessingFilter filter = new OAuth2ClientAuthenticationProcessingFilter( 150 | path); 151 | 152 | OAuth2RestTemplate template = new OAuth2RestTemplate(client.getClient(), 153 | oauth2ClientContext); 154 | filter.setRestTemplate(template); 155 | filter.setTokenServices(new UserInfoTokenServices(client.getResource() 156 | .getUserInfoUri(), client.getClient().getClientId())); 157 | return filter; 158 | } 159 | 160 | @Override 161 | public void configure(AuthorizationServerSecurityConfigurer security) 162 | throws Exception { 163 | } 164 | 165 | @Override 166 | public void configure(ClientDetailsServiceConfigurer clients) throws Exception { 167 | clients.withClientDetails(this.clientDetailsService); 168 | } 169 | } 170 | 171 | @Component 172 | class DataCommandLineRunner implements CommandLineRunner { 173 | 174 | private final AccountRepository accountRepository; 175 | 176 | private final ClientRepository clientRepository; 177 | 178 | @Autowired 179 | public DataCommandLineRunner(AccountRepository accountRepository, 180 | ClientRepository clientRepository) { 181 | this.accountRepository = accountRepository; 182 | this.clientRepository = clientRepository; 183 | } 184 | 185 | @Override 186 | public void run(String... args) throws Exception { 187 | 188 | Stream 189 | .of("dsyer,cloud", "pwebb,boot", "mminella,batch", "rwinch,security", 190 | "jlong,spring") 191 | .map(s -> s.split(",")) 192 | .forEach( 193 | tuple -> accountRepository.save(new Account(tuple[0], tuple[1], true))); 194 | 195 | Stream.of("html5,secret", "android,secret").map(x -> x.split(",")) 196 | .forEach(x -> clientRepository.save(new Client(x[0], x[1]))); 197 | } 198 | } 199 | 200 | class ClientResources { 201 | 202 | @NestedConfigurationProperty 203 | private AuthorizationCodeResourceDetails client = new AuthorizationCodeResourceDetails(); 204 | 205 | @NestedConfigurationProperty 206 | private ResourceServerProperties resource = new ResourceServerProperties(); 207 | 208 | public AuthorizationCodeResourceDetails getClient() { 209 | return client; 210 | } 211 | 212 | public ResourceServerProperties getResource() { 213 | return resource; 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /edge-it/src/integration-test/java/edge/EdgeIT.java: -------------------------------------------------------------------------------- 1 | package edge; 2 | 3 | import cnj.CloudFoundryService; 4 | import org.apache.commons.logging.Log; 5 | import org.apache.commons.logging.LogFactory; 6 | import org.cloudfoundry.operations.CloudFoundryOperations; 7 | import org.cloudfoundry.operations.applications.ApplicationManifest; 8 | import org.cloudfoundry.operations.applications.RestartApplicationRequest; 9 | import org.cloudfoundry.operations.applications.SetEnvironmentVariableApplicationRequest; 10 | import org.cloudfoundry.operations.applications.UnsetEnvironmentVariableApplicationRequest; 11 | import org.junit.Assert; 12 | import org.junit.Before; 13 | import org.junit.Test; 14 | import org.junit.runner.RunWith; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | import org.springframework.boot.test.context.SpringBootTest; 17 | import org.springframework.core.ParameterizedTypeReference; 18 | import org.springframework.http.*; 19 | import org.springframework.retry.RetryCallback; 20 | import org.springframework.retry.support.RetryTemplate; 21 | import org.springframework.test.context.junit4.SpringRunner; 22 | import org.springframework.util.Base64Utils; 23 | import org.springframework.util.LinkedMultiValueMap; 24 | import org.springframework.util.StringUtils; 25 | import org.springframework.web.client.RestTemplate; 26 | 27 | import java.io.File; 28 | import java.net.URI; 29 | import java.nio.charset.Charset; 30 | import java.util.*; 31 | import java.util.stream.Stream; 32 | 33 | import static org.springframework.http.HttpHeaders.*; 34 | import static org.springframework.http.MediaType.parseMediaType; 35 | 36 | @RunWith(SpringRunner.class) 37 | @SpringBootTest(classes = Config.class) 38 | public class EdgeIT { 39 | 40 | @Autowired 41 | private RestTemplate restTemplate; 42 | 43 | @Autowired 44 | private RetryTemplate retryTemplate; 45 | 46 | @Autowired 47 | private CloudFoundryOperations cloudFoundryOperations; 48 | 49 | @Autowired 50 | private CloudFoundryService service; 51 | 52 | private File root, authServiceManifest, eurekaManifest, edgeServiceManifest, 53 | greetingsServiceManifest, html5ClientManifest; 54 | 55 | // never deletes the apps if theyre 56 | // already there 57 | private static volatile boolean RESET = false; 58 | 59 | private Log log = LogFactory.getLog(getClass()); 60 | 61 | @Before 62 | public void before() throws Throwable { 63 | log.info("RESET=" + RESET); 64 | baseline(RESET); 65 | RESET = false; 66 | } 67 | 68 | @Test 69 | // this should work.. not 100% after the version revs. 70 | public void restClients() throws Throwable { 71 | 72 | log.info("running restClients()"); 73 | // resttemplate 74 | baselineDeploy(new String[] { "insecure" }, 75 | Collections.singletonMap("security.basic.enabled", "false"), null, 76 | new String[] { "insecure" }, 77 | Collections.singletonMap("security.basic.enabled", "false"), null); 78 | testEdgeRestClient("Shafer", "/api/resttemplate/"); 79 | 80 | // feign 81 | baselineDeploy(new String[] { "insecure" }, 82 | Collections.singletonMap("security.basic.enabled", "false"), null, 83 | "insecure,feign".split(","), 84 | Collections.singletonMap("security.basic.enabled", "false"), null); 85 | testEdgeRestClient("Watters", "/api/feign/"); 86 | } 87 | 88 | @Test 89 | public void testAuth() throws Throwable { 90 | 91 | log.info("running testAuth()"); 92 | 93 | ApplicationInstanceConfiguration callback = (appId) -> { 94 | String prop = "security.basic.enabled"; 95 | this.cloudFoundryOperations 96 | .applications() 97 | .unsetEnvironmentVariable( 98 | UnsetEnvironmentVariableApplicationRequest.builder().name(appId) 99 | .variableName(prop).build()).block(); 100 | }; 101 | 102 | baselineDeploy(new String[] { "secure" }, new HashMap<>(), callback, 103 | new String[] { "secure", "sso" }, new HashMap<>(), callback); 104 | 105 | String accessToken = this.obtainToken(); 106 | 107 | String userEndpointOnEdgeService = this.service.urlForApplication(this 108 | .appNameFromManifest(this.greetingsServiceManifest)) + "/greet/OAuth"; 109 | 110 | RequestEntity requestEntity = RequestEntity 111 | .get(URI.create(userEndpointOnEdgeService)) 112 | .header(HttpHeaders.AUTHORIZATION, "bearer " + accessToken).build(); 113 | ResponseEntity responseEntity = restTemplate.exchange(requestEntity, 114 | String.class); 115 | String body = responseEntity.getBody(); 116 | this.log.info("body from authorized request: " + body); 117 | } 118 | 119 | @Test 120 | public void testCors() throws Throwable { 121 | log.info("running testCors()"); 122 | Map e = Collections.singletonMap("security.basic.enabled", 123 | "false"); 124 | this.baselineDeploy(new String[] { "insecure" }, e, null, 125 | "cors,insecure".split(","), e, null); 126 | String edgeServiceUri = this.service 127 | .urlForApplication(appNameFromManifest(this.edgeServiceManifest)) 128 | + "/lets/greet/Phil"; 129 | String html5ClientUri = this.service.urlForApplication(this 130 | .appNameFromManifest(this.html5ClientManifest)); 131 | this.log.info("edge-service URI " + edgeServiceUri); 132 | this.log.info("html5-client URI " + html5ClientUri); 133 | List headerList = Arrays.asList(ACCEPT, "X-Requested-With", ORIGIN); 134 | String headersString = StringUtils.arrayToDelimitedString( 135 | headerList.toArray(), ", ").trim(); 136 | RequestEntity requestEntity = RequestEntity 137 | .options(URI.create(edgeServiceUri)) 138 | .header(ACCEPT, parseMediaType("*/*").toString()) 139 | .header(ACCESS_CONTROL_REQUEST_METHOD, HttpMethod.GET.toString()) 140 | .header(ACCESS_CONTROL_REQUEST_HEADERS, headersString) 141 | .header(REFERER, html5ClientUri).header(ORIGIN, html5ClientUri).build(); 142 | Set httpMethods = restTemplate.optionsForAllow(edgeServiceUri); 143 | httpMethods.forEach(m -> log.info(m)); 144 | ResponseEntity responseEntity = this.retryTemplate.execute(ctx -> { 145 | ResponseEntity exchange = restTemplate.exchange(requestEntity, 146 | Void.class); 147 | if (!exchange.getHeaders().containsKey(ACCESS_CONTROL_ALLOW_ORIGIN)) { 148 | log.info(ACCESS_CONTROL_ALLOW_ORIGIN + " not present in response."); 149 | throw new RuntimeException("there's no " + ACCESS_CONTROL_ALLOW_ORIGIN 150 | + " header present."); 151 | } 152 | return exchange; 153 | }); 154 | HttpHeaders headers = responseEntity.getHeaders(); 155 | headers.forEach((k, v) -> log.info(k + '=' + v.toString())); 156 | log.info("response received: " + responseEntity.toString()); 157 | Assert.assertTrue("preflight response should contain " 158 | + ACCESS_CONTROL_ALLOW_ORIGIN, 159 | headers.containsKey(ACCESS_CONTROL_ALLOW_ORIGIN)); 160 | } 161 | 162 | private String obtainToken() throws Exception { 163 | 164 | String authServiceAppId = this.appNameFromManifest(this.authServiceManifest); 165 | 166 | URI uri = URI.create(this.service.urlForApplication(authServiceAppId) 167 | + "/uaa/oauth/token"); 168 | String username = "jlong"; 169 | String password = "spring"; 170 | String clientSecret = "password"; 171 | String client = "html5"; 172 | 173 | LinkedMultiValueMap map = new LinkedMultiValueMap() { 174 | 175 | { 176 | this.add("client_secret", clientSecret); 177 | this.add("client_id", client); 178 | this.add("scope", "openid"); 179 | this.add("grant_type", "password"); 180 | this.add("username", username); 181 | this.add("password", password); 182 | } 183 | }; 184 | 185 | String token = Base64Utils.encodeToString((client + ":" + clientSecret) 186 | .getBytes(Charset.forName("UTF-8"))); 187 | 188 | RequestEntity> requestEntity = RequestEntity 189 | .post(uri).accept(MediaType.APPLICATION_JSON) 190 | .header("Authorization", "Basic " + token).body(map); 191 | ParameterizedTypeReference> type = new ParameterizedTypeReference>() { 192 | }; 193 | String accessToken = this.retryTemplate.execute((ctx) -> { 194 | ResponseEntity> responseEntity = this.restTemplate 195 | .exchange(requestEntity, type); 196 | Map body = responseEntity.getBody(); 197 | return body.get("access_token"); 198 | }); 199 | log.info("access_token: " + accessToken); 200 | return accessToken; 201 | } 202 | 203 | private void testEdgeRestClient(String testName, String urlSuffix) 204 | throws Throwable { 205 | String root = this.service 206 | .urlForApplication(appNameFromManifest(this.edgeServiceManifest)); 207 | String edgeServiceUrl = root + urlSuffix + testName; 208 | String healthUrl = root + "/health"; 209 | ResponseEntity responseEntity = this.restTemplate.getForEntity( 210 | healthUrl, String.class); 211 | log.info("health endpoint: " + responseEntity.getBody()); 212 | String body = retryTemplate 213 | .execute((RetryCallback) context -> { 214 | ResponseEntity response = restTemplate.getForEntity(edgeServiceUrl, 215 | String.class); 216 | if (!response.getStatusCode().is2xxSuccessful()) { 217 | String msg = "couldn't get a valid response calling the edge service "; 218 | this.log.info(msg); 219 | throw new RuntimeException(msg + edgeServiceUrl); 220 | } 221 | return response.getBody(); 222 | }); 223 | Assert.assertTrue(body.contains("Hello, " + testName)); 224 | } 225 | 226 | private void destroy() throws Throwable { 227 | log.info("destroy()"); 228 | String authServiceAppId = this.appNameFromManifest(this.authServiceManifest); 229 | String eurekaAppId = this.appNameFromManifest(this.eurekaManifest); 230 | String html5AppId = this.appNameFromManifest(this.html5ClientManifest); 231 | String edgeServiceAppId = this.appNameFromManifest(this.edgeServiceManifest); 232 | String greetingsServiceAppId = this 233 | .appNameFromManifest(this.greetingsServiceManifest); 234 | Stream.of(html5AppId, edgeServiceAppId, greetingsServiceAppId, eurekaAppId, 235 | authServiceAppId).forEach(appId -> { 236 | try { 237 | this.service.destroyApplicationIfExists(appId); 238 | this.log.info("attempted to delete application " + appId); 239 | } 240 | catch (Throwable t) { 241 | // don't care 242 | } 243 | }); 244 | 245 | Stream.of(eurekaAppId, authServiceAppId).forEach(svcId -> { 246 | try { 247 | this.service.destroyServiceIfExists(svcId); 248 | log.info("attempted to delete service " + svcId); 249 | } 250 | catch (Throwable t) { 251 | // don't care 252 | } 253 | }); 254 | } 255 | 256 | private void setEnvironmentVariable(String appId, String k, String v) { 257 | log.info("set-env " + appId + " " + k + " " + v); 258 | this.cloudFoundryOperations 259 | .applications() 260 | .setEnvironmentVariable( 261 | SetEnvironmentVariableApplicationRequest.builder().name(appId) 262 | .variableName(k).variableValue(v).build()).block(); 263 | } 264 | 265 | private void reconfigureApplicationProfile(String appId, String profiles[]) { 266 | String profileVarName = "spring_profiles_active".toUpperCase(); 267 | String profilesString = StringUtils 268 | .arrayToCommaDelimitedString(profiles(profiles)); 269 | this.setEnvironmentVariable(appId, profileVarName, profilesString); 270 | } 271 | 272 | private void restart(String appId) { 273 | this.cloudFoundryOperations.applications() 274 | .restart(RestartApplicationRequest.builder().name(appId).build()).block(); 275 | log.info("restarted " + appId); 276 | } 277 | 278 | private static String[] profiles(String... profiles) { 279 | Collection p = new ArrayList<>(); 280 | if (null != profiles && 0 != profiles.length) { 281 | p.addAll(Arrays.asList(profiles)); 282 | } 283 | p.add("cloud"); 284 | return p.toArray(new String[p.size()]); 285 | } 286 | 287 | private void deployAppAndServiceIfDoesNotExist(File manifest) { 288 | 289 | String appName = this.appNameFromManifest(manifest); 290 | this.log.info("deploying " + appName); 291 | this.service 292 | .applicationManifestFrom(manifest) 293 | .entrySet() 294 | .stream() 295 | .map(e -> { 296 | if (!service.applicationExists(appName)) { 297 | // service.destroyServiceIfExists(appId); 298 | service.pushApplicationAndCreateUserDefinedServiceUsingManifest(e.getKey(), 299 | e.getValue()); 300 | this.log.info("deployed " + appName + "."); 301 | } 302 | return appName; 303 | }).findAny().orElse(null); 304 | } 305 | 306 | private String deployAppIfDoesNotExist(File manifest) { 307 | String appName = this.appNameFromManifest(manifest); 308 | this.log.info("deploying " + appName); 309 | 310 | this.service.applicationManifestFrom(manifest).entrySet().stream().map(e -> { 311 | File f = e.getKey(); 312 | ApplicationManifest am = e.getValue(); 313 | String appId = am.getName(); 314 | if (!this.service.applicationExists(appId)) { 315 | this.service.pushApplicationUsingManifest(f, am, true); 316 | this.log.info("deployed " + appName + "."); 317 | } 318 | return appId; 319 | }).findAny().orElse(null); 320 | 321 | return appName; 322 | } 323 | 324 | private void baseline(boolean delete) throws Throwable { 325 | this.root = new File("."); 326 | this.authServiceManifest = new File(root, "../auth-service/manifest.yml"); 327 | this.eurekaManifest = new File(root, "../service-registry/manifest.yml"); 328 | this.edgeServiceManifest = new File(root, "../edge-service/manifest.yml"); 329 | this.greetingsServiceManifest = new File(root, 330 | "../greetings-service/manifest.yml"); 331 | this.html5ClientManifest = new File(root, "../html5-client/manifest.yml"); 332 | 333 | Assert.assertTrue(this.authServiceManifest.exists()); 334 | Assert.assertTrue(this.html5ClientManifest.exists()); 335 | Assert.assertTrue(this.greetingsServiceManifest.exists()); 336 | Assert.assertTrue(this.eurekaManifest.exists()); 337 | Assert.assertTrue(this.edgeServiceManifest.exists()); 338 | 339 | if (delete) { 340 | this.destroy(); 341 | } 342 | } 343 | 344 | private String appNameFromManifest(File a) { 345 | return this.service.applicationManifestFrom(a).entrySet().stream() 346 | .map(e -> e.getValue().getName()).findAny().orElse(null); 347 | } 348 | 349 | public interface ApplicationInstanceConfiguration { 350 | 351 | void configure(String appId); 352 | } 353 | 354 | private void baselineDeploy( 355 | 356 | // greetings-service 357 | String[] gsProfiles, Map gsEnv, 358 | ApplicationInstanceConfiguration gsCallback, 359 | 360 | // edge-service 361 | String[] esProfiles, Map esEnv, 362 | ApplicationInstanceConfiguration esCallback 363 | 364 | ) throws Throwable { 365 | 366 | // backing services 367 | this.deployBackingServices(); 368 | 369 | this.deployAppAndServiceIfDoesNotExist(this.eurekaManifest); 370 | this.deployAppAndServiceIfDoesNotExist(this.authServiceManifest); 371 | this.deployAppWithSettings(this.greetingsServiceManifest, gsProfiles, gsEnv, 372 | gsCallback); 373 | this.deployAppWithSettings(this.edgeServiceManifest, esProfiles, esEnv, 374 | esCallback); 375 | this.deployAppIfDoesNotExist(this.html5ClientManifest); 376 | 377 | } 378 | 379 | private void deployBackingServices() { 380 | service.createServiceIfMissing("elephantsql", "turtle", "auth-service-pgsql"); 381 | } 382 | 383 | private void deployAppWithSettings(File ma, String[] profiles, 384 | Map env, ApplicationInstanceConfiguration callback) { 385 | String appId = this.deployAppIfDoesNotExist(ma); 386 | if (null != callback) { 387 | callback.configure(appId); 388 | } 389 | this.reconfigureApplicationProfile(appId, profiles); 390 | env.forEach((k, v) -> this.setEnvironmentVariable(appId, k, v)); 391 | this.restart(appId); 392 | } 393 | 394 | } 395 | --------------------------------------------------------------------------------