├── .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 |
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 |
--------------------------------------------------------------------------------