Single sign on principle and code implementation

Don't talk nonsense this time. Just throw dry goods. I've just learned single sign on. If you have any questions, please comment and discuss them together.

1, Single system login mechanism

1. http stateless protocol

web application adopts B/S architecture and http as communication protocol. Because http itself is a stateless protocol and does not store any login information, there is no association between the system servers every time they log in, and the server will process independently. Steal a picture from the Internet to explain to you. The following figure will explain the process:This means that any user can access the server resources. If you want to protect the server resources, you need to restrict the requests to the server; If you want to restrict the requests to the server, you need to authenticate the requests of the server, respond to legal requests and ignore illegal requests; To identify whether a request is legitimate or not, you need to know the status of the browser. Since the http protocol is stateless, the browser and server need to maintain a state together, which is the session mechanism.

2. Conversation mechanism

When the browser requests the server for the first time, the server will create a session and take the session id as a part of the responding browser, and the browser will store the session id; When the browser accesses the browser for the second time, the third time and the nth time, the request will be carried with the session id. when the server obtains the session id, it will judge whether it is the same user, and the subsequent requests will be associated with the first time; The following figure will explain:The server will save the session id in memory, so where will the browser save it? It has two types:
1. Save the session id in the request parameters
2. Save in cookie
Taking the session id as the parameter of each request, the server receives the request and can parse it to obtain the session id to judge whether it is the same user. It is obvious that this method is unsafe. Then use the browser itself to maintain the session id, and automatically send the session id when sending a request. The cookie mechanism can just do this. The cookie itself is a mechanism to store a small amount of data. The data is stored in a "key/value" key value pair, and the browser will automatically bring the cookie information when requesting.
Of course, the Tomcat session mechanism also implements cookies. When accessing the Tomcat server, the browser will see a cookie named "JSESSIONID", which is the session id maintained by the browser, as shown in the figure:   

3. Login status

Just after learning the session mechanism, it is easy to understand the login status. When the browser requests for the first time, enter the user name and password, and then compare the user name and password with the database. If it is more correct, it will be recorded as a legal user, otherwise it will be recorded as an illegal user, and the legal user will be marked as "authorized" or "logged in". Since it is a session status, Then, it is naturally saved in the session object. The login status of tomcat session is set in the session object as follows:

HttpSession session = request.getSession();
session.setAttribute("isLogin", true);

When the user logs in again, tomcat will view the session object in the session object:

HttpSession session = request.getSession();
session.getAttribute("isLogin");

2, Complexity of multi system

With the progress of the times, the era of single system has become history, and now it has developed into a complex application group composed of multiple systems. In the face of so many subsystems, when users log in and log out, do they need to log in and log out one by one? This method is certainly not desirable. As shown in the figure below:Web system has developed from a single system to a complex multi system application group. This complexity should be borne by the system itself, not by users. No matter how many subsystems the system has and how complex it is, it is a whole for users, that is to say, users accessing the web system application group should log in and log out once just like accessing a single system.The core of the single system solution is cookies. The session id carried by cookies maintains the session state between the browser and the server, but cookies are limited. This limitation is the problem of the domain (usually corresponding to the domain name of the website). When the browser sends an http request, it will automatically carry cookies matching the domain, not all cookiesAt this time, you may say that you can use a common top-level domain, that is, all domain names can be set to "*. baidu.com". This method is theoretically possible. Even in the early days, some application groups solved it in this way, but it is not good in practice. First, the domain names should be unified, and then the technologies used by the application groups should be the same. Otherwise, the cookie key s are different and the session cannot be maintained, And can not realize the development of wide platform language; Third: cookies are inherently unsafe.
Therefore, single sign on is needed to solve this problem.

3, Single sign on

The full name of Single Sign On is Single Sign On (SSO). Single Sign On is that in the case of multi system cluster, only one system needs to be logged in, and then the other systems can be authorized without logging in again, including Single Sign On and single sign off.
Compared with a single system, SSO needs an independent authentication center. Only the authentication center can accept security information such as user name and password. Other systems will no longer provide registration and login entry, but only accept the indirect authorization of the authentication center. The indirect authorization is realized through the token. The authentication center judges that it is legal, and the user will create the token. In the next jump process, the authentication center will send the token as a parameter to each system. The subsystem gets the token, that is, it is authorized to create a local session. The login method of the local session is the same as that of the single system. This is the principle of single sign on system, as shown in the figure:The above figure is analyzed and explained below:

  1. When the user accesses the protected resources of system 1, system 1 finds that the user is not logged in, jumps to the sso authentication center and takes his address as a parameter
  2. The sso authentication center finds that the user is not logged in and guides the user to the login page
  3. The user enters the user name and password to submit the login application
  4. The sso authentication center verifies the user information, creates a session between the user and the sso authentication center, which is called a global session, and creates an authorization token at the same time
  5. sso authentication center jumps to the original request address with the token (system 1)
  6. System 1 gets the token and goes to sso authentication center to verify whether the token is valid
  7. sso authentication center verifies the token, returns valid, and registers the system 1
  8. System 1 uses this token to create a session with the user, called a local session, and returns protected resources
  9. Users access protected resources of system 2
  10. System 2 finds that the user is not logged in, jumps to the sso authentication center and takes his address as a parameter
  11. The sso authentication center finds that the user has logged in, jumps back to the address of system 2 and attaches a token
  12. System 2 gets the token and goes to the sso authentication center to verify whether the token is valid
  13. sso authentication center verifies the token, returns valid, and registers the system 2
  14. System 2 uses the token to create a local session with the user and return protected resources

When the user logs in successfully, a session will be established with the SSO authentication center and each subsystem. The session established by the user with the SSO authentication center is commonly known as the global session, and the session created by the user with each subsystem is called the local session. After the local session is created, the user can access the protected resources of the subsystem. Through the SSO authentication center, the global session and the local session have the following relationship:

  1. If the local session exists, the global session must exist
  2. The global session exists, but the local session does not necessarily exist
  3. The global session is destroyed, and the local session must be destroyed

2. Write off

Single sign on naturally requires single sign off. If one system logs off, all systems will log off. The following figure will illustrate:The SSO authentication center always monitors the global session status. Once the global session is destroyed, the listener will notify all registered systems to perform the logout operation.
The above figure will be explained below:

  1. The user sends a logout request to system 1
  2. System 1 obtains the token according to the session id established between the user and system 1 and sends a logout request to the sso authentication center
  3. The sso authentication center verifies that the token is valid, destroys the global session, and takes out all the system addresses registered with this token
  4. sso authentication center sends a logout request to all registration systems
  5. Each registration system receives the logout request from the sso authentication center and destroys the local session
  6. The sso authentication center guides the user to the login page

4, Deployment diagram

Single sign on involves the sso authentication center and many subsystems. The subsystem and the sso authentication center need to communicate to exchange tokens, verify tokens and initiate logoff requests. Therefore, the subsystem must integrate the sso client, and the sso authentication center is the sso server. The whole single sign on process is essentially the communication process between the sso client and the server, which is described in the figure below

5, Realize

1. General description

Let me first introduce the content I have implemented. This application group includes four systems: login system, home page system, VIP system and shopping cart system. The general idea is: when a user accesses the protected resources of any system, he will judge whether there is an access token (whether he is logged in). If he is logged in, he will be allowed to access. If he is not logged in, he will directly intercept it, jump to the login page, log in, and then enter the user name and password for verification. If it is correct, he will generate a token and distribute it to each subsystem, and then jump to the system where the user just belongs, Then verify its token. If the token is correct, you can access the protected system resources. The code is roughly as follows. The created project is gradle project and uses template. You can also change it to maven project and front-end and back-end separated projects.
The configuration code of gradle is as follows:

buildscript {
    repositories {
        mavenLocal()
        mavenCentral()
    }
    ext{
        springBootVersion = '2.1.3.RELEASE'
    }

    dependencies {
        classpath "org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}"
    }
    
}

subprojects {
    group 'com.sso'
    version '1.0-SNAPSHOT'
    apply plugin: 'java'
    apply plugin: 'io.spring.dependency-management'
    apply plugin: 'org.springframework.boot'

    repositories {
        mavenLocal()
        mavenCentral()
    }

    dependencies {
        compile 'org.springframework.boot:spring-boot-starter-web'
        annotationProcessor 'org.projectlombok:lombok:1.18.2'
        compileOnly 'org.projectlombok:lombok:1.18.2'
        compile 'org.springframework.boot:spring-boot-starter-thymeleaf'
    }
}


2. SSO login

LoginController. The Java code is shown in the following figure:
Note: this code does not enter the database to simulate data and verify. The port number of this subsystem is 9000. Remember to configure the port number in yml.

package com.sso.login.controller;

import com.sso.login.utils.LoginCacheUtil;
import com.sso.pojo.User;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;

/**
 * @Author: Comrade Yan Gaoling
 * @Date: 2020/9/13  21:45
 * @Version 1.0
 */
@Controller
@RequestMapping("/login")
public class LoginController {
    //Analog user data
    private static Set<User> dbUsers;
    static {
        dbUsers = new HashSet<>();
        dbUsers.add(new User(0,"zhangsan","zhangsan"));
        dbUsers.add(new User(1,"lisi","lisi"));
        dbUsers.add(new User(2,"wangwu","wangwu"));
    }

    @PostMapping
    public String doLogin(User user , HttpSession session , HttpServletResponse response){
        System.out.println("4444444: "+user);
        //Record the URL from which page to jump
        String target = (String) session.getAttribute("target");
        //Simulate finding users in the database through the login user name and password from the database
        Optional<User> first = dbUsers.stream().filter(dbUser -> dbUser.getUsername().equals(user.getUsername()) &&
                dbUser.getPassword().equals(user.getPassword())).findFirst();
        //Judge whether the user logs in
        if (first.isPresent()){
            //Save user login information
            //Randomly generate a token, that is, a token
            String token = UUID.randomUUID().toString();
            Cookie cookie = new Cookie("TOKEN", token);
            //To solve the cross domain problem, pay attention to the 127.0.0.1 address mapping problem
            cookie.setDomain("codeshop.com");
            response.addCookie(cookie);
            //Store information in loginUser
            LoginCacheUtil.loginUser.put(token,first.get());

        }else {
            //Login failed
            session.setAttribute("msg","Wrong user name or password");
            return "login";
        }

        //Redirect to target address
        return "redirect:"+target;
    }

    @GetMapping("info")
    @ResponseBody
    public ResponseEntity<User> getUserInfo(String token){
        if (!StringUtils.isEmpty(token)){
            User user = LoginCacheUtil.loginUser.get(token);
            return ResponseEntity.ok(user);
        }else {
            return new ResponseEntity<>(null, HttpStatus.BAD_REQUEST);
        }
    }
}

ViewController. The Java code is shown in the following figure:

package com.sso.login.controller;

import com.sso.login.utils.LoginCacheUtil;
import com.sso.pojo.User;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpSession;

/**
 * @Author: Comrade Yan Gaoling
 * @Date: 2020/9/13  21:48
 * @Version 1.0
 */
//Page Jump logic
@Controller
@RequestMapping("/view")
public class ViewController {
    /**
     * Jump to login page
     * @return
     */
    @GetMapping("/login")
    //The target may be empty. Use the @ RequestParam annotation to set it. This is also the case with cookie s
    private String toLogin(@RequestParam(required = false,defaultValue = "") String target, HttpSession session, @CookieValue(required = false,value = "TOKEN") Cookie cookie){
        //If the target is empty, it will be set as the homepage URL, and finally jump to the homepage
        if (StringUtils.isEmpty(target)){
            target = "http://www.codeshop.com:9010";
        }
        if (cookie != null){
            //If the logged in user accesses the login system again, it will be redirected
            String value = cookie.getValue();
            User user = LoginCacheUtil.loginUser.get(value);
            if (user != null){
                return "redirect:"+target;
            }
        }
        //Redirect address
        session.setAttribute("target",target);
        return "login";
    }
}

Tools are as shown in the figure:
Login subsystem startup class:
user. The Java entity class is shown in the figure below:
The HTML of the login page is shown in the figure below:

2. Main page system (SSO main)

Main page system architecture:
The port number is 9010. Remember to change it in the configuration file!
ViewController. The Java code is shown in the figure:

package com.sso.main.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.client.RestTemplate;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpSession;
import java.util.Map;

/**
 * @Author: Comrade Yan Gaoling
 * @Date: 2020/10/12  11:13
 * @Version 1.0
 */
@Controller
@RequestMapping("/view")
public class ViewController {

    @Autowired
    private RestTemplate restTemplate;

    private final String LOGIN_INFO_ADDRESS = "http://login.codeshop.com:9000/login/info?token=";

    @GetMapping("/index")
    public String toIndex(@CookieValue(required = false ,value = "TOKEN")Cookie cookie, HttpSession session){
        if (cookie != null){
            String token = cookie.getValue();
            //Determine whether to log in
            if (!StringUtils.isEmpty(token)){
                //Take out the login user and save it into the session
                Map result = restTemplate.getForObject(LOGIN_INFO_ADDRESS + token, Map.class);
                session.setAttribute("loginUser",result);
            }
        }
        return "index";
    }
}

The startup class code is shown in the figure:
Note that the RestTemplate template class in Spring is used here. You can baidu by yourself.

The main page is shown as follows:

3. VIP system

The overall architecture of the system is shown in the figure:
The subsystem port number is 9011. Remember to change it in the configuration file.
viewController. The Java code is shown in the figure:

package com.sso.vip.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.client.RestTemplate;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpSession;
import java.util.Map;

/**
 * @Author: Comrade Yan Gaoling
 * @Date: 2020/10/12  11:22
 * @Version 1.0
 */
@Controller
@RequestMapping("/view")
public class ViewController {

        @Autowired
        protected RestTemplate restTemplate;

        private final String USER_INFO_ADDRESS = "http://login.codeshop.com:9000/login/info?token=";

        @GetMapping("/index")
        public String toIndex(@CookieValue(required = false,value = "TOKEN") Cookie cookie,
                              HttpSession session) {
            if (cookie != null){
                String token = cookie.getValue();
                if (!StringUtils.isEmpty(token)){
                    Map result = restTemplate.getForObject(USER_INFO_ADDRESS + token, Map.class);
                    session.setAttribute("loginUser",result);
                }
            }
            return "index";
        }
}

The startup class (vipapp.java) is shown in the figure below:

package com.sso.vip;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

/**
 * @Author: Comrade Yan Gaoling
 * @Date: 2020/10/12  11:20
 * @Version 1.0
 */
@SpringBootApplication
public class VipApp {
    public static void main(String[] args) {
        SpringApplication.run(VipApp.class,args);
    }
    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

index. The HTML page is shown as follows:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>Vip</title>
</head>
<body>
    <h1>Welcome to VIP system</h1>
    <span>
        <a th:if="${session.loginUser == null}" href="http://login. codeshop. com:9000/view/login? target= http://vip.codeshop.com:9011/view/index "> login</a>
        <a th:if="${session.loginUser != null}" href="#"> Exit</a>
    </span>
    <p th:unless="${session.loginUser == null}">
        <span style="color : red;" th:text="${session.loginUser.username}">Logged in</span>
    </p>
</body>
</html>

4. Shopping cart system (SSO cart)

The general overview is shown in the figure below:
viewController.java is as shown in the figure:

package com.sso.cart.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.client.RestTemplate;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpSession;
import java.util.Map;

/**
 * @Author: Comrade Yan Gaoling
 * @Date: 2020/10/12  11:32
 * @Version 1.0
 */
@Controller
@RequestMapping("view")
public class ViewController {
    @Autowired
    protected RestTemplate restTemplate;

    private final String USER_INFO_ADDRESS = "http://login.codeshop.com:9000/login/info?token=";

    @GetMapping("index")
    public String toIndex(@CookieValue(required = false,value = "TOKEN") Cookie cookie,
                          HttpSession session){
        if (cookie != null){
            String token = cookie.getValue();
            if (!StringUtils.isEmpty(token)){
                Map result = restTemplate.getForObject(USER_INFO_ADDRESS+token, Map.class);
                session.setAttribute("loginUser",result);
            }
        }
        return "index";
    }
}

CartApp. The Java startup class is shown in the following figure:

package com.sso.cart;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

/**
 * @Author: Comrade Yan Gaoling
 * @Date: 2020/10/12  11:24
 * @Version 1.0
 */
@SpringBootApplication
public class CartApp {
    public static void main(String[] args) {
        SpringApplication.run(CartApp.class,args);
    }
    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

The cart page (index.html) is shown as follows:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>Cart</title>
</head>
<body>
    <h1>Welcome to Cart page</h1>
    <span>
        <a th:if="${session.loginUser == null}" href="http://login. codeshop. com:9000/view/login? target= http://cart.codeshop.com:9012/view/index "> login</a>
        <a th:if="${session.loginUser != null}" href="#"> Exit</a>
    </span>
    <p th:unless="${session.loginUser == null}">
        <span style="color : red;" th:text="${session.loginUser.username}"></span>Logged in
    </p>
</body>
</html>

OK, this is the end of single sign on. Single sign off has not been realized yet. If there is time later, single sign off will be realized. The idea is to destroy cookie s. You can try it first.
It's convenient to be with others and yourself
Come on, Ollie

Tags: Java Session Spring Spring Boot cookie

Posted by zahidraf on Wed, 11 May 2022 15:10:44 +0300