├── .gitignore ├── README.md ├── app ├── Dockerfile ├── pom.xml └── src │ └── main │ ├── java │ └── demo │ │ ├── DemoApplication.java │ │ ├── HttpSessionConfig.java │ │ ├── Initializer.java │ │ ├── MvcConfig.java │ │ ├── PagesController.java │ │ ├── SecurityInitializer.java │ │ └── WebSecurityConfig.java │ └── resources │ ├── application.properties │ ├── static │ ├── css │ │ └── style.css │ └── images │ │ └── avatar_2x.png │ └── templates │ ├── hello.html │ ├── home.html │ └── login.html └── docker-compose.yml /.gitignore: -------------------------------------------------------------------------------- 1 | # https://git-scm.com/docs/gitignore 2 | # https://help.github.com/articles/ignoring-files 3 | # Example .gitignore files: https://github.com/github/gitignore 4 | 5 | app/target/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spring Session Demo with Docker and Docker Compose 2 | 3 | This is a demo project, which uses Spring Boot, Spring Session, Redis, HAProxy and Docker to demonstrate the use of Spring Session within a scalable environment. 4 | 5 | ## Prerequisets 6 | 7 | ``` 8 | $ docker-machine create --driver virtualbox dev 9 | ``` 10 | 11 | To be able to let docker talk to the machine we've just created we need to export some environment variables. You can do this by using: 12 | 13 | ``` 14 | $ eval "$(docker-machine env dev)" 15 | ``` 16 | 17 | ## How to run 18 | 19 | Go into the 'app' directory and let's first build the application. 20 | 21 | ``` 22 | $ mvn package 23 | ``` 24 | 25 | Go back into the root of the project. 26 | 27 | Build the container images 28 | 29 | ``` 30 | $ docker-compose build 31 | ``` 32 | 33 | Start docker-compose and watch the containers start 34 | 35 | ``` 36 | $ docker-compose run 37 | ``` 38 | 39 | ## Playing around with the App 40 | 41 | To figure out on which host you can see the application you can run: 42 | 43 | ``` 44 | $ docker-machine ip dev 45 | ``` 46 | 47 | Now we know the ip address on which the container host is running. This is important because HAProxy is exposed on port 80 of the host. 48 | 49 | Now you can connect to http://[DOCKER_HOST]/ and you should see the app. 50 | -------------------------------------------------------------------------------- /app/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM java:8 2 | VOLUME /tmp 3 | ADD target/demo-0.0.1-SNAPSHOT.jar app.jar 4 | RUN bash -c 'touch /app.jar' 5 | EXPOSE 8080 6 | ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"] 7 | -------------------------------------------------------------------------------- /app/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.test 7 | demo 8 | 0.0.1-SNAPSHOT 9 | jar 10 | 11 | demo 12 | Demo project for Spring Boot 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 1.2.5.RELEASE 18 | 19 | 20 | 21 | 22 | UTF-8 23 | 1.8 24 | 25 | 26 | 27 | 28 | org.springframework.boot 29 | spring-boot-starter-security 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-starter-thymeleaf 34 | 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-starter-test 39 | test 40 | 41 | 42 | 43 | org.springframework.session 44 | spring-session 45 | 1.0.1.RELEASE 46 | 47 | 48 | 49 | org.springframework.boot 50 | spring-boot-starter-redis 51 | 52 | 53 | 54 | org.springframework.session 55 | spring-session-data-redis 56 | 1.0.1.RELEASE 57 | pom 58 | 59 | 60 | 61 | org.webjars 62 | bootstrap 63 | 3.2.0 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | org.springframework.boot 72 | spring-boot-maven-plugin 73 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /app/src/main/java/demo/DemoApplication.java: -------------------------------------------------------------------------------- 1 | package demo; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.context.annotation.ComponentScan; 6 | 7 | @SpringBootApplication 8 | @ComponentScan 9 | public class DemoApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(DemoApplication.class, args); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/demo/HttpSessionConfig.java: -------------------------------------------------------------------------------- 1 | package demo; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; 6 | import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession; 7 | 8 | @Configuration 9 | @EnableRedisHttpSession 10 | public class HttpSessionConfig { 11 | 12 | @Bean 13 | public JedisConnectionFactory connectionFactory() { 14 | JedisConnectionFactory connection = new JedisConnectionFactory(); 15 | connection.setHostName("redis"); 16 | return connection; 17 | } 18 | } -------------------------------------------------------------------------------- /app/src/main/java/demo/Initializer.java: -------------------------------------------------------------------------------- 1 | package demo; 2 | 3 | import org.springframework.session.web.context.AbstractHttpSessionApplicationInitializer; 4 | 5 | public class Initializer extends AbstractHttpSessionApplicationInitializer { 6 | 7 | } -------------------------------------------------------------------------------- /app/src/main/java/demo/MvcConfig.java: -------------------------------------------------------------------------------- 1 | package demo; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.web.servlet.config.annotation.EnableWebMvc; 5 | import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; 6 | import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; 7 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; 8 | 9 | @EnableWebMvc 10 | @Configuration 11 | public class MvcConfig extends WebMvcConfigurerAdapter { 12 | 13 | 14 | @Override 15 | public void addResourceHandlers(final ResourceHandlerRegistry registry) { 16 | super.addResourceHandlers(registry); 17 | registry.addResourceHandler("/css/**").addResourceLocations("classpath:/static/css/"); 18 | registry.addResourceHandler("/resources/**").addResourceLocations("/resources/"); 19 | if (!registry.hasMappingForPattern("/webjars/**")) { 20 | registry.addResourceHandler("/webjars/**").addResourceLocations( 21 | "classpath:/META-INF/resources/webjars/"); 22 | } 23 | } 24 | 25 | @Override 26 | public void addViewControllers(ViewControllerRegistry registry) { 27 | super.addViewControllers(registry); 28 | registry.addViewController("/").setViewName("home"); 29 | registry.addViewController("/login").setViewName("login"); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/demo/PagesController.java: -------------------------------------------------------------------------------- 1 | package demo; 2 | 3 | import java.net.InetAddress; 4 | import java.net.UnknownHostException; 5 | 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.stereotype.Controller; 9 | import org.springframework.ui.Model; 10 | import org.springframework.web.bind.annotation.RequestMapping; 11 | 12 | @Controller 13 | public class PagesController { 14 | 15 | private static Logger log = LoggerFactory.getLogger(PagesController.class); 16 | 17 | @RequestMapping("/") 18 | public String getIndex(Model model){ 19 | final InetAddress localHost; 20 | try { 21 | localHost = InetAddress.getLocalHost(); 22 | model.addAttribute("host", localHost.getHostName()); 23 | model.addAttribute("ip", localHost.getHostAddress()); 24 | } catch (UnknownHostException e) { 25 | log.warn("An exception occurred while trying to determine the host and IP address: {}", e); 26 | } 27 | return "home"; 28 | } 29 | 30 | @RequestMapping("/hello") 31 | public String getHello(Model model){ 32 | final InetAddress localHost; 33 | try { 34 | localHost = InetAddress.getLocalHost(); 35 | model.addAttribute("host", localHost.getHostName()); 36 | model.addAttribute("ip", localHost.getHostAddress()); 37 | } catch (UnknownHostException e) { 38 | log.warn("An exception occurred while trying to determine the host and IP address: {}", e); 39 | } 40 | return "hello"; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/demo/SecurityInitializer.java: -------------------------------------------------------------------------------- 1 | package demo; 2 | 3 | import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer; 4 | 5 | public class SecurityInitializer extends 6 | AbstractSecurityWebApplicationInitializer { 7 | 8 | public SecurityInitializer() { 9 | super(WebSecurityConfig.class, HttpSessionConfig.class); 10 | } 11 | } -------------------------------------------------------------------------------- /app/src/main/java/demo/WebSecurityConfig.java: -------------------------------------------------------------------------------- 1 | package demo; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.boot.autoconfigure.security.SecurityProperties; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.core.annotation.Order; 7 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 8 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 9 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 10 | import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity; 11 | 12 | @Configuration 13 | @EnableWebMvcSecurity 14 | @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER) 15 | public class WebSecurityConfig extends WebSecurityConfigurerAdapter { 16 | 17 | @Override 18 | protected void configure(HttpSecurity http) throws Exception { 19 | http 20 | .authorizeRequests() 21 | .antMatchers("/css/**", "/webjars/**", "/", "/home", "/favicon.ico").permitAll() 22 | .anyRequest().authenticated() 23 | .and() 24 | .formLogin() 25 | .loginPage("/login") 26 | .permitAll() 27 | .and() 28 | .logout() 29 | .permitAll() 30 | .logoutSuccessUrl("/") 31 | .and().rememberMe(); 32 | } 33 | 34 | @Autowired 35 | public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { 36 | auth 37 | .inMemoryAuthentication() 38 | .withUser("user").password("password").roles("USER"); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.redis.host=redis 2 | spring.redis.port=6379 -------------------------------------------------------------------------------- /app/src/main/resources/static/css/style.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Specific styles of signin component 3 | */ 4 | /* 5 | * General styles 6 | */ 7 | body, html { 8 | height: 100%; 9 | background-color: #eee; 10 | } 11 | 12 | .card-container.card { 13 | max-width: 350px; 14 | padding: 40px 40px; 15 | } 16 | 17 | .btn { 18 | font-weight: 700; 19 | -moz-user-select: none; 20 | -webkit-user-select: none; 21 | user-select: none; 22 | cursor: default; 23 | } 24 | 25 | /* 26 | * Card component 27 | */ 28 | .card { 29 | background-color: #F7F7F7; 30 | /* just in case there no content*/ 31 | padding: 20px 25px 30px; 32 | margin: 0 auto 25px; 33 | margin-top: 50px; 34 | /* shadows and rounded borders */ 35 | -moz-border-radius: 2px; 36 | -webkit-border-radius: 2px; 37 | border-radius: 2px; 38 | -moz-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3); 39 | -webkit-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3); 40 | box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3); 41 | } 42 | 43 | .profile-img-card { 44 | width: 96px; 45 | height: 96px; 46 | margin: 0 auto 10px; 47 | display: block; 48 | -moz-border-radius: 50%; 49 | -webkit-border-radius: 50%; 50 | border-radius: 50%; 51 | } 52 | 53 | /* 54 | * Form styles 55 | */ 56 | .profile-name-card { 57 | font-size: 16px; 58 | font-weight: bold; 59 | text-align: center; 60 | margin: 10px 0 0; 61 | min-height: 1em; 62 | } 63 | 64 | .reauth-email { 65 | display: block; 66 | color: #404040; 67 | line-height: 2; 68 | margin-bottom: 10px; 69 | font-size: 14px; 70 | text-align: center; 71 | overflow: hidden; 72 | text-overflow: ellipsis; 73 | white-space: nowrap; 74 | -moz-box-sizing: border-box; 75 | -webkit-box-sizing: border-box; 76 | box-sizing: border-box; 77 | } 78 | 79 | .form-signin #inputEmail, 80 | .form-signin #inputPassword { 81 | direction: ltr; 82 | height: 44px; 83 | font-size: 16px; 84 | } 85 | 86 | .form-signin input[type=email], 87 | .form-signin input[type=password], 88 | .form-signin input[type=text], 89 | .form-signin button { 90 | width: 100%; 91 | display: block; 92 | margin-bottom: 10px; 93 | z-index: 1; 94 | position: relative; 95 | -moz-box-sizing: border-box; 96 | -webkit-box-sizing: border-box; 97 | box-sizing: border-box; 98 | } 99 | 100 | .form-signin .form-control:focus { 101 | border-color: rgb(104, 145, 162); 102 | outline: 0; 103 | -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgb(104, 145, 162); 104 | box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgb(104, 145, 162); 105 | } 106 | 107 | .btn.btn-signin { 108 | /*background-color: #4d90fe; */ 109 | background-color: rgb(104, 145, 162); 110 | /* background-color: linear-gradient(rgb(104, 145, 162), rgb(12, 97, 33));*/ 111 | padding: 0px; 112 | font-weight: 700; 113 | font-size: 14px; 114 | height: 36px; 115 | -moz-border-radius: 3px; 116 | -webkit-border-radius: 3px; 117 | border-radius: 3px; 118 | border: none; 119 | -o-transition: all 0.218s; 120 | -moz-transition: all 0.218s; 121 | -webkit-transition: all 0.218s; 122 | transition: all 0.218s; 123 | } 124 | 125 | .btn.btn-signin:hover, 126 | .btn.btn-signin:active, 127 | .btn.btn-signin:focus { 128 | background-color: rgb(12, 97, 33); 129 | } 130 | 131 | .forgot-password { 132 | color: rgb(104, 145, 162); 133 | } 134 | 135 | .forgot-password:hover, 136 | .forgot-password:active, 137 | .forgot-password:focus{ 138 | color: rgb(12, 97, 33); 139 | } 140 | -------------------------------------------------------------------------------- /app/src/main/resources/static/images/avatar_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jreijn/spring-session-docker-demo/a0a45a2bd6d32d119ca649c7f0e47a769cd7b44d/app/src/main/resources/static/images/avatar_2x.png -------------------------------------------------------------------------------- /app/src/main/resources/templates/hello.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | Hello World! 6 | 8 | 9 | 11 | 12 | 13 |
14 |
15 |

Hello [[${#httpServletRequest.remoteUser}]]!

16 |

You are currently on server with name: [[${host}]]

17 |
18 | 19 |
20 |
21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/resources/templates/home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Single sign-on demo 5 | 7 | 9 | 10 | 11 |
12 |
13 |

Welcome!

14 |

You are currently on server with name: [[${host}]]

15 | 16 |

Click here to see a greeting (might require you to login first).

17 |
18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/resources/templates/login.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | Spring Security Example 6 | 8 | 10 | 11 | 12 |
13 |
14 | 15 |

16 | 17 | 22 | 23 |
24 | Invalid username and password. 25 |
26 | 27 |
28 | You have been logged out. 29 |
30 | 31 |
32 |
33 | 34 | 35 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | db: 2 | image: redis:3.0.3 3 | hostname: redis 4 | ports: 5 | - "6379:6379" 6 | app1: 7 | build: app 8 | links: 9 | - "db:redis" 10 | app2: 11 | build: app 12 | links: 13 | - "db:redis" 14 | app3: 15 | build: app 16 | links: 17 | - "db:redis" 18 | haproxy: 19 | image: tutum/haproxy:0.2 20 | ports: 21 | - "80:80" 22 | - "1936:1936" 23 | links: 24 | - app1 25 | - app2 26 | - app3 27 | --------------------------------------------------------------------------------