Spring Boot + Vue is a separate project from the front and back end of Spring Boot. But previously we were singleton based. How can we solve this problem if our project is clustered?

Today we’ll take a look at how Spring Security handles session concurrency in a clustered deployment.

This is the 17th article in the Spring Security series, and reading the previous articles will help you understand it better:

  1. Dig a big hole and Spring Security will do it!
  2. How to decrypt the password
  3. A step-by-step guide to customizing form logins in Spring Security
  4. Spring Security does front and back separation, so don’t do page jumps! All JSON interactions
  5. Authorization in Spring Security used to be so simple
  6. How does Spring Security store user data into the database?
  7. Spring Security+Spring Data Jpa, Security management is only easier!
  8. Spring Boot + Spring Security enables automatic login
  9. Spring Boot automatic login. How to control security risks?
  10. How is Spring Security better than Shiro in microservices projects?
  11. Two ways for SpringSecurity to customize authentication logic (advanced play)
  12. How can I quickly view information such as the IP address of the login user in Spring Security?
  13. Spring Security automatically kicks out the previous login user.
  14. How can I kick out a user who has logged in to Spring Boot + Vue?
  15. Spring Security comes with a firewall! You have no idea how secure your system is!
  16. What is a session fixed attack? How do I defend against session fixation attacks in Spring Boot?

1. Cluster session scheme

In a traditional single-server architecture, generally speaking, there is only one server, so there is no Session sharing problem. However, in distributed/cluster projects, Session sharing is an issue that must be faced. Let’s look at a simple architecture diagram:

In such an architecture, there are some problems that do not exist in A single service. For example, the client initiates A request, which arrives at Nginx, is forwarded to Tomcat A, which stores A copy of the data into the session, and the next request comes, The request is forwarded to Tomcat B, which then goes to Session to retrieve the data and finds that there is no previous data.

1.1 sharing session

To solve this kind of problem, the current mainstream solution is to save the data that needs to be shared between services in a public place (the mainstream solution is Redis) :

Outside the chain picture archiving failure, the source station might be hotlinking prevention mechanism, proposed to directly upload picture preserved (img – aaFMeebv – 1589763397129) (img.itboyhub.com/2020/05/14-…)”

When Tomcat needs to write to Session, it writes to Redis, and when Tomcat needs to read data, it reads from Redis. In this way, different services can use the same Session data.

Such a scheme, can be manually implemented by developers, that is, manually store data in Redis, manually read data from Redis, equivalent to the use of some Redis client tools to achieve such a function, there is no doubt that manual implementation of the workload is quite large.

A simplified solution is to use Spring Session to implement this function. Spring Session uses the Spring proxy filter to intercept all Session operations and automatically synchronize data to Redis. Or automatically read data from Redis.

All Session synchronization operations are transparent to developers, who use Spring Sessions and, once configured, use them as if they were using a normal Session.

1.2 the session a copy

Session copy refers to copying session data directly between Tomcat Tomcat without using Redis. However, this method is A little inefficient. If the session of any one of Tomcat A, B and C changes, it needs to be copied to other Tomcat. If you have a very large number of servers in a cluster, this approach is not only inefficient, but also has significant latency.

Therefore, this kind of scheme is generally regarded as understanding.

1.3 Sticky Conversations

Sticky sessions are requests from the same IP address that are routed to the same Tomcat via Nginx without session sharing and synchronization. This is an option, but in some extreme cases, it may cause load imbalance (because in most cases, many people use the same public IP address).

Therefore, Session sharing has become the mainstream solution to this problem.

2. The Session to be Shared

2.1 Creating a Project

Create a Spring Boot project to introduce Web, Spring Session, Spring Security, and Redis:

After successful creation, the pom.xml file looks like this:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-data-redis</artifactId>
</dependency>
Copy the code

2.2 configuration

Spring. Redis. Password = 123 spring. Redis. Port = 6379 spring. Redis. Host = 127.0.0.1 spring. Security. The user. The name = javaboy spring.security.user.password=123 server.port=8080Copy the code

Configure the basic information of Redis. To simplify things, I’ll set the user name and password directly to application.properties, and finally set the project port number.

2.3 the use of

Spring Session (HttpSession); HttpSession (Redis); HttpSession (HttpSession);

@RestController
public class HelloController {
    @Value("${server.port}")
    Integer port;
    @GetMapping("/set")
    public String set(HttpSession session) {
        session.setAttribute("user"."javaboy");
        return String.valueOf(port);
    }
    @GetMapping("/get")
    public String get(HttpSession session) {
        return session.getAttribute("user") + ":"+ port; }}Copy the code

Considering that Spring Boot will be started in cluster mode later, in order to get which Spring Boot service is provided by each request, we need to return the port number of the current service on each request, so HERE I inject server.port.

Next, project packaging:

Outside the chain picture archiving failure, the source station might be hotlinking prevention mechanism, proposed to directly upload picture preserved (img – hqUwnFQc – 1589763397136) (img.itboyhub.com/2020/05/14-…)”

After packaging, start two instances of the project:

Jar --server.port=8080 java-jar session-4-0.0.1 -- snapshot. jar --server.port=8081Copy the code

Save a variable to the Session of the 8080 service. When you log in for the first time, the login page will be automatically redirected. Enter the user name and password to log in. After successful access, the data is automatically synchronized to Redis:

Outside the chain picture archiving failure, the source station might be hotlinking prevention mechanism, proposed to directly upload picture preserved (img – HKNFuzWj – 1589763397137) (img.itboyhub.com/2020/05/202…)”

Localhost :8081/get (localhost:8081/get);

At this point, the configuration of session sharing is complete, and we have seen the effect of session sharing.

2.4 the Security configuration

Session sharing has been implemented, but we found a new problem, in Spring Boot + Vue front and back end of the project separation, how to kick the logged-in user? The session concurrency management we configured in this article is invalid.

That is, if I add the following configuration:

protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests().anyRequest()
            ...
            .sessionManagement()
            .maximumSessions(1)
            .maxSessionsPreventsLogin(true);
}
Copy the code

Now that doesn’t work, users can still log in from multiple browsers at the same time.

What’s going on here?

First of all, I recommend you to recall the Spring Boot + Vue front and back end separation project. How to kick out the logged-in user? The article.

In this article, we mentioned that the session registry is maintained by default by SessionRegistryImpl, and SessionRegistryImpl is memory-based maintenance. Now we have enabled Spring Session+Redis to share sessions, but SessionRegistryImpl is still maintained based on memory, so we need to change SessionRegistryImpl implementation logic.

Modify the way is simple, in fact, the Spring Session provides us with the corresponding implementation class SpringSessionBackedSessionRegistry, specific configuration is as follows:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    FindByIndexNameSessionRepository sessionRepository;
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest()
                ...
                .sessionManagement()
                .maximumSessions(1)
                .maxSessionsPreventsLogin(true)
                .sessionRegistry(sessionRegistry());
    }
    @Bean
    SpringSessionBackedSessionRegistry sessionRegistry(a) {
        return newSpringSessionBackedSessionRegistry(sessionRepository); }}Copy the code

Here we only need to provide a SpringSessionBackedSessionRegistry instance, and configure it to sessionManagement. After the session concurrent data maintenance will be completed by SpringSessionBackedSessionRegistry, rather than SessionRegistryImpl, so, we are about the session concurrent configuration will take effect, in a cluster environment, Users can also log in from only one device.

To make our case a little more perfect, let’s introduce Nginx, which implements automatic service instance switching.

3. Introduce the Nginx

Simply go to the Nginx installation directory conf directory (default: /usr/local/nginx/conf) and edit the nginx.conf file:

In this configuration:

  1. Upstream: configures the upstream server
  2. Javaboy.org indicates the name of the server cluster, which can be arbitrarily named
  3. Separate services are configured in upstream
  4. Weight represents the weight of the service, which means what percentage of requests will be forwarded to the service from Nginx
  5. Proxy_pass in location represents the address requested for forwarding,/All requests are intercepted and forwarded to the newly configured service cluster
  6. Proxy_redirect indicates that nginx automatically corrects the response header data when a redirect request occurs.

After the configuration is complete, upload the local Spring Boot packaged JAR to Linux, and then start two Spring Boot instances on Linux:

Port = 8080&nohup java-jar session-4-0.0.1 -snapshot.jar --server.port= 8080&nohup java-jar session-4-0.0.1 -snapshot.jar --server.port=8081 &Copy the code

Among them

  • Nohup indicates that Spring Boot should not stop running when the terminal is shut down
  • & enables Spring Boot to start in the background

After the configuration is complete, restart Nginx:

/usr/local/nginx/sbin/nginx -s reload
Copy the code

After Nginx is successfully started, we first manually clean the data on Redis, and then access 192.168.66.128/set to save data to the session. This request is first sent to Nginx. Nginx forwards it to a Spring Boot instance:

Spring Boot with port 8081 processes the /set request and then accesses /get:

As you can see, the/GET request is handled by the service with port 8080.

4. To summarize

This article mainly introduces the use of Spring Session, but also involves some Nginx use, although this article is long, but in fact, Spring Session configuration is not much, the configuration involved are very simple.

For those of you who have not used Spring Sessions in the SSM architecture, it may not be easy to understand how convenient it is to use Spring Sessions in Spring Boot, because in the SSM architecture, To use Spring Session, you need to configure three places, one is web. XML configure proxy filter, then configure Redis in the Spring container, and finally configure Spring Session. The steps are still a little tedious. Spring Boot directly saves us these tedious steps!

Well, that’s all for this article. Related cases in this article have been uploaded to GitHub, and you can download them by yourself :github.com/lenve/sprin…

If you feel that you have a harvest, remember to click under the encouragement of Songko oh ~