父域 Cookie实现sso单点登录

单点登录(Single Sign On, SSO)是指在同一帐号平台下的多个应用系统中,用户只需登录一次,即可访问所有相互信任的应用系统。Cookie 的作用域由 domain 属性和 path 属性共同决定。在 Tomcat 中,domain 属性默认为当前域的域名/IP地址。path 属性的有效值是以"/"开头的路径,在 Tomcat 中,path 属性默认为当前 Web 应用的上下文路径。如果将 Cookie 的 domain 属性设置为当前域的父域,那么就认为它是父域 Cookie。Cookie 有一个特点,即父域中的 Cookie 被子域所共享,换言之,子域会自动继承父域中的Cookie。利用 Cookie 的这个特点,不难想到,将 Session ID(或 Token)保存到父域中不就行了。没错,我们只需要将 Cookie 的 domain 属性设置为父域的域名(主域名),同时将 Cookie 的 path 属性设置为根路径,这样所有的子域应用就都可以访问到这个 Cookie 了。不过这要求应用系统的域名需建立在一个共同的主域名之下,如 http://tieba.baidu.comhttp://map.baidu.com,它们都建立在 http://baidu.com 这个主域名之下,那么它们就可以通过这种方式来实现单点登录。

配置host文件

项目结构

引入依赖

父项目引入依赖,子项目无依赖引入

复制代码
         <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.24</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
            <version>2.6.7</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
        </dependency>

login系统

前端部分

login.html页面,负责显示登录页面并提交登录

html 复制代码
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>Login Module</title>
</head>
<body>
    <h1>用户登录 </h1>
    <p style="color: red;" th:text="${session.msg}"></p>
    <form action="/login" method="POST">
        姓名:  <input name="username" value="">
        <br></br>
        密码:  <input name="password" value="">
        <br></br>
        <button type="submit" style="width:60px;height:30px"> login </button>
    </form>

</body>
</html>

后端部分

跳转登录页面

java 复制代码
import com.sso.login.pojo.User;
import com.sso.login.utils.LogCacheUtil;
import org.springframework.stereotype.Controller;
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.bind.annotation.RequestParam;
import org.thymeleaf.util.StringUtils;

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

@Controller
@RequestMapping("/view")
public class ViewController {
    /**
     * 跳转到登录页面
     * @return
     */
    @GetMapping("/login")
    public String toLogin(@RequestParam(required = false, defaultValue = "") String target,
                          HttpSession session,
                          @CookieValue(required = false, value = "TOKEN") Cookie cookie){
        if(StringUtils.isEmpty(target)){
            target = "login.codeshop.com:9010";
        }
        if(cookie != null){
            // 从父域拿到token
            String value = cookie.getValue();
            User user = LogCacheUtil.loginUser.get(value);
            if(user != null){
                return "redirect:" + target;
            }
        }
        session.setAttribute("target",target);
        return "login";
    }

}

负责登录校验以及解析token

java 复制代码
import com.sso.login.pojo.User;
import com.sso.login.utils.LogCacheUtil;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.thymeleaf.util.StringUtils;

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;

@Controller
@RequestMapping("/login")
public class LoginController {

    private static Set<User> dbUser;
    // 模拟数据库
    static {
        dbUser = new HashSet<>();
        dbUser.add(new User(1, "zxb", "1234567"));
        dbUser.add(new User(2, "admin", "123456"));
    }

    @PostMapping
    public String doLogin(User user, HttpSession session, HttpServletResponse response) {
        String target = (String) session.getAttribute("target");
        // 验证登录
        Optional<User> first = dbUser.stream().filter(dbUser -> dbUser.getUsername().equals(user.getUsername()) &&
                dbUser.getPassword().equals(user.getPassword()))
                .findFirst();
        if (first.isPresent()) {
            String token = UUID.randomUUID().toString();
            // 保存token到父域 Cookie
            Cookie cookie = new Cookie("TOKEN", token);
            cookie.setPath("/");
            cookie.setDomain("codeshop.com");
            response.addCookie(cookie);
            // 登录成功保存到LogCacheUtil工具类中,这个部分可以用redis去实现
            LogCacheUtil.loginUser.put(token, first.get());
        } else {
            session.setAttribute("msg", "用户名或密码错误!");
            // 错误跳转到登录页面
            return "login";
        }
        // 重定向到目标地址
        return "redirect:" + target;
    }

    @GetMapping("info")
    @ResponseBody
    public ResponseEntity<User> getUserInfo(String token) {
        if (!StringUtils.isEmpty(token)) {
            // 验证token并从token获取用户信息并返回
            User user = LogCacheUtil.loginUser.get(token);
            return ResponseEntity.ok(user);
        } else {
            return new ResponseEntity<>(null, HttpStatus.BAD_REQUEST);
        }
    }

    @GetMapping("/logout")
    public String loginOut(@CookieValue(value = "TOKEN") Cookie cookie, HttpServletResponse response, String target) {
        cookie.setMaxAge(0);
        LogCacheUtil.loginUser.remove(cookie.getValue());
        response.addCookie(cookie);
        return "redirect:" + target;
    }
}

model

java 复制代码
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

@Data //添加getter/setter
@NoArgsConstructor  //添加无参构造器
@AllArgsConstructor //添加全参构造器
@Accessors(chain = true) //添加链式调用
public class User {
    private Integer id;
    private String username;
    private String password;
}

保存User工具类

java 复制代码
import com.sso.login.pojo.User;

import java.util.HashMap;
import java.util.Map;

public class LogCacheUtil {
    public static Map<String, User> loginUser = new HashMap<>();

}

Main系统

前端部分

index.html页面

java 复制代码
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>Index Module</title>
</head>
<body>
    <h1>欢迎登录!</h1>
    <span>
        <a th:if="${session.loginUser == null}" href="http://login.codeshop.com:9000/view/login?target=http://login.codeshop.com:9010/view/index">login</a>
        <a th:unless="${session.loginUser == null}" href="http://login.codeshop.com:9000/login/logout?target=http://www.codeshop.com:9010/view/index">logout</a>
    </span>
    <p th:unless="${session.loginUser == null}">
        <span style="color:deepskyblue" th:text="${session.loginUser.username}"></span> 已登录!<br></br>
        <br></br>
        id: <span style="color:deepskyblue" th:text="${session.loginUser.id}"></span><br></br>
        username: <span style="color:deepskyblue" th:text="${session.loginUser.username}"></span><br></br>
    </p>
</body>
</html>

后端部分

Viewcontroller

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
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 org.thymeleaf.util.StringUtils;

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

@Controller
@RequestMapping("/view")
public class ViewController {

    @Autowired
    private 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";
    }
}

配置restTemplate的HTTP客户端工具

java 复制代码
@SpringBootApplication
public class MainApp {
    public static void main(String[] args) {
        SpringApplication.run(MainApp.class,args);
    }

    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

cart系统

前端部分

index.html页面

java 复制代码
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>Blog Module</title>
</head>
<body>
    <h1>登录页面 !</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:unless="${session.loginUser == null}" href="http://login.codeshop.com:9000/login/logout?target=http://cart.codeshop.com:9012/view/index">logout</a>
    </span>
    <p th:unless="${session.loginUser == null}">
        <span style="color:deeppink" th:text="${session.loginUser.username}"></span> 已登录!<br></br>
        <br></br>
        id: <span style="color:deeppink" th:text="${session.loginUser.id}"></span><br></br>
        username: <span style="color:deeppink" th:text="${session.loginUser.username}"></span><br></br>
    </p>
</body>
</html>

后端部分

view的controller

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
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 org.thymeleaf.util.StringUtils;

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

@Controller
@RequestMapping("/view")
public class ViewController {
    @Autowired
    private 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){
            // 从cookie拿到的token去请求获取用户信息需要去实现验证
            String token = cookie.getValue();
            if(!StringUtils.isEmpty(token)){
                Map result = restTemplate.getForObject(USER_INFO_ADDRESS+token,Map.class);
                session.setAttribute("loginUser",result);
            }
        }
        return "index";
    }
}

配置restTemplate的HTTP客户端工具

java 复制代码
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
public class CartApp {
    public static void main(String[] args) {
        SpringApplication.run(CartApp.class,args);
    }
    
    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

测试

启动

访问 mainApp

登录

登录成功

输入cart.codeshop.com:9012/view/index,直接访问cart系统成功

输入www.codeshop.com:9010/view/index,直接访问main系统成功

相关推荐
寻星探路4 小时前
【深度长文】万字攻克网络原理:从 HTTP 报文解构到 HTTPS 终极加密逻辑
java·开发语言·网络·python·http·ai·https
崔庆才丨静觅6 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
曹牧7 小时前
Spring Boot:如何测试Java Controller中的POST请求?
java·开发语言
passerby60617 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了7 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅7 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅7 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
爬山算法7 小时前
Hibernate(90)如何在故障注入测试中使用Hibernate?
java·后端·hibernate
kfyty7258 小时前
集成 spring-ai 2.x 实践中遇到的一些问题及解决方案
java·人工智能·spring-ai