Basic single sign on in distributed environment

Knowledge premise

  • Have a certain understanding of distributed scenarios
  • To understand the network domain name, you need to know that different URLs of primary or top-level domain names cannot share cookie s

Environment construction

Here, we create three spring boot projects, all with dynamic template thymeleaf, which is convenient for us to test

1. First, we modify the local host file to simulate three machines

2. Create three springboot projects

They are SSO server (authentication server), SSO client (client 1), and SSO client2 (client 2)
All three projects need spring boot starter web and spring boot starter thymeleaf dependencies
The authentication server also needs to rely on redis

2.1. Create three pages

		Authentication server
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Login page</title>
</head>
<body>
<form action="/doLogin" method="post">
    <label>
        user name:
        <input name="username"/>
    </label><br/>
    <label>
        password:
        <input name="password" type="password"/>
    </label><br/>
    <input type="hidden" name="url" th:value="${url}"/>
    <input type="submit" value="Sign in">
</form>
</body>
</html>
		Client 1
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Employee list</title>
</head>
<body>
<h1>welcome: [[${session.loginUser}]]</h1>
<ul>
    <li th:each="emp: ${emps}">full name: [[${emp}]]</li>
</ul>

</body>
</html>
		Client 2
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Employee list</title>
</head>
<body>
<h1>welcome: [[${session.loginUser}]]</h1>
<ul>
    <li th:each="emp: ${emps}">full name: [[${emp}]]</li>
</ul>

</body>
</html>

2.2. Configuration of authentication server

2.2.1 configuration file

# Configure redis
spring:
	redis:
		host: 127.0.0.1

2.2.2 login processing of authentication server

package com.athuigu.gulimall.ssoserver.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.thymeleaf.util.StringUtils;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;

/**
 * @author wangyang
 */
@Controller
public class LoginController {

    @Autowired
    StringRedisTemplate redisTemplate;

    @ResponseBody
    @GetMapping("/userInfo")
    public String userInfo(@RequestParam("token") String token) {
        return redisTemplate.opsForValue().get(token);
    }

    @GetMapping("/login.html")
    public String loginPage(@RequestParam("redirect_url") String url, Model model,
                            @CookieValue(value = "sso_token", required = false) String sso_token) {
        if (!StringUtils.isEmpty(sso_token)) {
            // It indicates that you have logged in to other systems before, and the browser has left traces
            return "redirect:" + url + "?token=" + sso_token;
        }
        model.addAttribute("url", url);
        return "login";
    }

    @PostMapping("/doLogin")
    public String doLogin(@RequestParam("username") String username,
                          @RequestParam("password") String password,
                          @RequestParam("url") String url,
                          HttpServletResponse response) {
        if (!StringUtils.isEmpty(username) && !StringUtils.isEmpty(password)) {
            //Log in successfully, jump back to the previous page;
            // Save the users who log in successfully
            String uuid = UUID.randomUUID().toString().replace("-", "");
            redisTemplate.opsForValue().set(uuid, username);
            Cookie cookie = new Cookie("sso_token", uuid);
            response.addCookie(cookie);
            return "redirect:" + url + "?token=" + uuid;
        }
        return "login";
    }
}

2.3. Client configuration

2.3.1 configuration file

# Configure the authentication server address of the jump here 
sso:
	server:
		url: http://ssoserver.com:8080/login.html	

2.3.2. Client login configuration class

package com.atguigu.gulimall.ssoclient.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.client.RestTemplate;
import org.thymeleaf.util.StringUtils;

import javax.servlet.http.HttpSession;
import java.util.ArrayList;
import java.util.List;

/**
 * @author wangyang
 */
@Controller
public class HelloController {

    @Value("${sso.server.url}")
    String ssoServerUrl;

    /**
     * No login required
     *
     * @return String
     */
    @ResponseBody
    @GetMapping("/hello")
    public String hello() {
        return "hello";
    }

    @GetMapping("/employees")
    public String employees(Model model, HttpSession session, @RequestParam(value = "token", required = false) String token) {
        if (!StringUtils.isEmpty(token)) {
            // Go to the server carefully and log in successfully. If you jump back, you will bring a token
            // Go to the server to get the user information that the current token really corresponds to
            RestTemplate restTemplate = new RestTemplate();
            ResponseEntity<String> forEntity = restTemplate.getForEntity("http://ssoserver.com:8080/userInfo?token=" + token, String.class);
            String body = forEntity.getBody();
            session.setAttribute("loginUser", body);
        }
        Object loginUser = session.getAttribute("loginUser");
        if (loginUser == null) {
            // Jump to the login page without login and carry the url of the jump. You can use this parameter to jump back when returning
            return "redirect:" + ssoServerUrl + "?redirect_url=http://client1.com:8081/employees";
        } else {
            List<String> emps = new ArrayList<String>();
            emps.add("Zhang San");
            emps.add("Li Si");
            model.addAttribute("emps", emps);
            return "list";
        }
    }
}

Process description

  • When we log in to client 1, we first judge whether we have logged in on other clients, that is, whether we have carried a token. If not, we believe that this is the first login. We need to jump to the login page of the authentication server for the first login, When logging in this time, we need to pass the page returned after logging in to the authentication server as a parameter
  • The SSO first determines whether the client has received other authentication requests, that is, whether it has logged in to the server_ Whether the token is empty. If not, we will take out the address passed by the client and splice it into the SSO_ The token parameter is used to jump. The client repeats the previous operation. If there is a token this time, we will log in directly. If it is empty, we will jump to the login interface of the authentication server. When logging in, we need to store the user data in redis. The format of redis is (uuid, username), and then new a Cookie("sso_token", uuid). Finally, splice the uuid as a token to the address carried by the client, and redirect it back.
  • The redirection comes to the client again. This time, the token is not empty. Then, get the user information by accessing the userInfo interface of the authentication service and successfully log in to the page.

Keywords: Redis Distribution

Added by reecec on Fri, 25 Feb 2022 11:17:16 +0200