1. Overview

Application development always comes with new ideas. Here is an example of manage users multiple accounts in single browser same like google. Google provides to the facility to login multiple google account in the same browser where a user can switch from one account to another account after adding account.

For user authentication and authorization in java web application development spring security provides so many functionalities using that our application development will be easy and fast. So here is an example of spring security multiple users sessions or account in a single browser.

How multiple user session in single browser work?

While working with multiple user session in the same browser at time cookie will be created with name SESSION.

  • cookies values while create first session, here 0 is session identifier.

0 a7426ded-96e0-48c1-8e64-8b705f49076a

  • While creating the second session in same browser at the value of that cookie as bellow: here 0 is first session cookies identifier, 1 is second session cookies identifier.

0 a7426ded-96e0-48c1-8e64-8b705f49076a 1 9b00d6fb-9efb-4b7a-8705-650caa2f966b

  • Session identifier must be passed in each request like _s=o or _s=1 based on that spring security will identify which user request come and based on that we can manage our business logic.

2. Example

Technology Stack

  • Spring boot
  • Spring Session Management
  • Spring Security
  • Redis Server
  • Tomcat 8
  • Maven
  • Java 8

NOTE: In this example we have used Redis to persist session information and our Redis server is running in 6379 port.

spring security multiple users sessions in single browser - Project Structure

spring security multiple users sessions in single browser – Project Structure

2.1 pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>spring-boot-example</groupId>
    <artifactId>spring-security-multiple-users-sessions-in-single-browser</artifactId>
    <version>1.0-SNAPSHOT</version>
    <description>spring security multiple users sessions in single browser</description>
    <!-- Inherit defaults from Spring Boot -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.4.RELEASE</version>
    </parent>
    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
    <dependencies>
        <!-- https://mvnrepository.com/artifact/com.lambdaworks/lettuce -->
        <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.boot</groupId>                <!-- for tomcat web container-->
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>  <!--starter require for spring boot spring security-->
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>biz.paluch.redis</groupId>
            <artifactId>lettuce</artifactId>
            <version>3.5.0.Final</version>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>              <!--for jsp compilation need provide scope runtime or provided because it available in tomcat -->
            <artifactId>tomcat-embed-jasper</artifactId>
            <scope>runtime</scope>     <!-- in my case provided not working so write runtime-->
        </dependency>
        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-data-redis</artifactId>
            <version>1.3.1.RELEASE</version>
            <type>pom</type>
        </dependency>
        <dependency>
            <groupId>biz.paluch.redis</groupId>
            <artifactId>lettuce</artifactId>
            <version>3.5.0.Final</version>
        </dependency>

    </dependencies>

    <!-- Package as an executable jar -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

2.2 application.properties

spring.mvc.view.prefix: /WEB-INF/jsp/
spring.mvc.view.suffix: .jsp

2.3 SpringBootConfig

package com.javadeveloperzone;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

/**
 * Created by JavaDeveloperZone on 19-07-2017.
 */

@SpringBootApplication
@ComponentScan // Using a root package also allows the @ComponentScan annotation to be used without needing to specify a basePackage attribute
public class SpringBootConfig {
    public static void main(String[] args) throws Exception {
        SpringApplication.run(SpringBootConfig.class, args);            // it wil start application
    }
}

2.4 SecurityConfiguration

  • Here successHandler has been overridden because to add _s parameter in landing page url.
  • httpServletRequest .getAttribute(HttpSessionManager.class.getName()) will return HttpSessionManager. HttpSessionManager contains information session alias.
package com.javadeveloperzone;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.session.web.http.HttpSessionManager;


/**
 * Created by Java Developer Zone on 15-11-2017.
 */
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Autowired      // here is configuration related to spring boot basic authentication
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()                                               // static users
            .withUser("zone1").password("mypassword").roles("USER")
            .and()
            .withUser("zone2").password("mypassword").roles("USER")
            .and()
            .withUser("zone3").password("mypassword").roles("USER")
            .and()
            .withUser("zone4").password("mypassword").roles("USER")
            .and()
            .withUser("zone5").password("mypassword").roles("USER");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
            .authorizeRequests()
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .successHandler((httpServletRequest, httpServletResponse, authentication) -> {              // login success handler
                HttpSessionManager httpSessionManager = (HttpSessionManager) httpServletRequest
                        .getAttribute(HttpSessionManager.class.getName());              
                String url = httpSessionManager
                        .encodeURL("loginSuccess", httpSessionManager.getCurrentSessionAlias(httpServletRequest));  // on login success add session alias in url
                httpServletResponse.sendRedirect(url);
            })
            .failureUrl("/loginFailed")                     // on login failed redirect to this user
            .loginPage("/login")                        // login page
            .permitAll();
    }
}

2.5 HttpSessionConfig

  • JedisConnectionFactory bean has been created for Redis configuration.  Redis used by spring session to persist session information using Redis
  • Here EmbeddedServletContainerCustomizer for cookies, because Tomcat 8.5 does not allow space in cookies name. Click here for more details 
package com.javadeveloperzone;

import org.apache.tomcat.util.http.LegacyCookieProcessor;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
import org.springframework.session.web.context.AbstractHttpSessionApplicationInitializer;


/**
 * Created by Java Developer Zone on 13-11-2017.
 */
@Configuration
@EnableRedisHttpSession
public class HttpSessionConfig extends AbstractHttpSessionApplicationInitializer {

    @Bean
    public JedisConnectionFactory connectionFactory() {
        return new JedisConnectionFactory();                // redis configuration
    }

    @Bean
    public EmbeddedServletContainerCustomizer customizer() { // allowed space in cookies name - https://javadeveloperzone.com/common-error/java-lang-illegalargumentexception-invalid-character-32-present-cookie-value/
        return container -> {
            if (container instanceof TomcatEmbeddedServletContainerFactory) {
                TomcatEmbeddedServletContainerFactory tomcat = (TomcatEmbeddedServletContainerFactory) container;
                tomcat.addContextCustomizers(context -> context.setCookieProcessor(new LegacyCookieProcessor()));
            }
        };
    }
}

2.6 SpringBootExampleController

login controller : before landing to login page we have generated new sessionalias so that session alias will be used when user login success identification.

package com.javadeveloperzone.controller;


import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.security.core.userdetails.User;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
import org.springframework.session.web.http.HttpSessionManager;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
import java.security.Principal;


/**
 * Created by Java Developer Zone on 19-07-2017.
 */
@Controller
public class SpringBootExampleController {

    @RequestMapping(value = "login")
    public String login(HttpServletRequest httpRequest,ModelMap map) {
        HttpSessionManager sessionManager = (HttpSessionManager) httpRequest.getAttribute(HttpSessionManager.class.getName());
        String addAlias = sessionManager.getNewSessionAlias(httpRequest);           // it will create new alis used when new user login
        map.put("s",addAlias);
        map.put("currentLoginUsers",getAssociatedUsers(httpRequest));

        return "login";
    }

    @RequestMapping(value = "loginSuccess")
    public String loginSuccess(HttpServletRequest request,Principal pricipal,ModelMap modelMap){
       Integer integer =(Integer) request.getSession().getAttribute("hitCounter");
       if(integer==null){
           integer=new Integer(0);
           integer++;
           request.getSession().setAttribute("hitCounter",integer);
       }else{
           integer++;
           request.getSession().setAttribute("hitCounter",integer);
       }
        HttpSessionManager httpSessionManager = (HttpSessionManager)request.getAttribute(HttpSessionManager.class.getName());
        modelMap.put("currentLoginUsers",getAssociatedUsers(request));
        modelMap.put("currentLoginUser",pricipal.getName());
        modelMap.put("currentLoginUserKey",httpSessionManager.getCurrentSessionAlias(request) );
        return "welcome";
    }
    public java.util.Map<String,String> getAssociatedUsers(HttpServletRequest request){  // it will return all Associated accounts from request come
        java.util.Map<String,String>  currentLoginUSer=new java.util.HashMap<>();
        SessionRepository<Session> repo =
                (SessionRepository<Session>) request.getAttribute(SessionRepository.class.getName());
        HttpSessionManager httpSessionManager = (HttpSessionManager)request.getAttribute(HttpSessionManager.class.getName());
        java.util.Map<String,String> stringStringMap=  httpSessionManager.getSessionIds(request); // it  will return all session id of same browser from request comes
        java.util.Set<String> keys = stringStringMap.keySet();
        for(String key:keys) {                                                      // logic to get user information from session id
            Session session = repo.getSession(stringStringMap.get(key));
            if(session!=null) {
                SecurityContextImpl securityContext = ((SecurityContextImpl) session
                        .getAttribute("SPRING_SECURITY_CONTEXT"));                      // get spring security context
                if(securityContext!=null) {
                    User user = (User) securityContext.getAuthentication().getPrincipal();          // get user name from spring security context
                    currentLoginUSer.put(key, user.getUsername());
                }
            }
        }
        return currentLoginUSer;
    }

    @RequestMapping(value = "loginFailed")
    public String loginFailed(){
        return "loginFailed";
    }
}

2.7 login.jsp

  • action=’/login?_s=${s}’ each action must contains _s (sessionalias )to identify session, This sessionalias has been generated from login controller as mention in SpringBootExampleController
<%--
  Created by IntelliJ IDEA.
  User: Java Developer Zone
  Date: 18-03-2017
  Time: 07:34
  To change this template use File | Settings | File Templates.
--%>

<%@ taglib prefix="c"
           uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>spring security multiple users sessions in single browser</title>
</head>
<body>
<form name='f' action='/login?_s=${s}' method='POST'>
    User: <input type='text' name='username' value=''>
    Password: <input type='password' name='password'/>
    <input type="hidden"
           name="${_csrf.parameterName}"
           value="${_csrf.token}"/>
    <input name="submit" type="submit" value="Login"/>
</form>


<c:forEach items="${currentLoginUsers}" var="currentLoginUsers">
    <ul>
        <li> Switch To : <a href="loginSuccess?_s=${currentLoginUsers.key}"><c:out value="${currentLoginUsers.value}"/></a></li>
    </ul>
</c:forEach>

</body>
</html>

2.8 welcome.jsp

<%--
  Created by IntelliJ IDEA.
  User: Java Developer Zone
  Date: 18-03-2017
  Time: 07:34
  To change this template use File | Settings | File Templates.
--%>

<%@ taglib prefix="c"
           uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>spring security multiple users sessions in single browser</title>
</head>
<body>

Hi, ${currentLoginUser} (<a href="/logout?_s=${currentLoginUserKey}">Logout</a> )
<br/>
<br/>
<b> ${currentLoginUser}'s Hit Counter : </b>${sessionScope.hitCounter}
<hr>
<c:forEach items="${currentLoginUsers}" var="currentLoginUsers">
<ul>
    <li> Switch To : <a href="loginSuccess?_s=${currentLoginUsers.key}"><c:out value="${currentLoginUsers.value}"/></a>
    </li>
</ul>
</c:forEach>
<hr>
<br>
<a href="login?_s=1">Add Another Account</a>

2.9 loginFailed.jsp

<%--
  Created by IntelliJ IDEA.
  User: Java Developer Zone
  Date: 18-03-2017
  Time: 07:34
  To change this template use File | Settings | File Templates.
--%>

<%@ taglib prefix="c"
           uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>spring security multiple users sessions in single browser</title>
</head>
<body>
Login Failed.
</body>
</html>

2.10 Demo:

Step 1 – Login 1st User (Add one account)

 

Step 1-Login 1st User - Add one account

Step 1-Login 1st User – Add one account

Step 1.1 : After login

Here you can see, I have passed _s parameter in url, _s parameter is compulsory in each URL so that spring security can identify that from which session or account request comes based on that we can write business logic.

Step 1.1 - After Login

Step 1.1 – After Login

Step 2 – Add Another Account

Here screen for add another account, While first user will be login as it is. So two user can login in application at same time in same browser.

Step 2 - Add Another Account

Step 2 – Add Another Account

 

Step 3 – List of Active Session in Current Browser

Here is more logic will be clear:

  • Both users can login in the same browser.
  • Based on _s parameter in URL we can identify user information.
  • The user can switch account from one account to another account.
Step 3 - List of Active Session in Current Browser

Step 3 – List of Active Session in Current Browser

Step 4 : Check Cookies in the browser

  • How cookies will be store in browser while creating multiple session in same browser.
Cookies Information

Cookies Information

 

3. References

4. Source Code

spring-security-multiple-users-sessions-single-browser

 

Was this post helpful?
Let us know, if you liked the post. Only in this way, we can improve us.
Yes
No
Tags:

Leave a Reply

Your email address will not be published. Required fields are marked *