单点登录(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.com 和 http://map.baidu.com,它们都建立在 http://baidu.com 这个主域名之下,那么它们就可以通过这种方式来实现单点登录。


父项目引入依赖,子项目无依赖引入
- <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.html页面,负责显示登录页面并提交登录
- html>
- <html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
- <head>
- <meta charset="UTF-8">
- <title>Login Moduletitle>
- 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>
跳转登录页面
- 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
- 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
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
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
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
- 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工具类
- import com.sso.login.pojo.User;
-
- import java.util.HashMap;
- import java.util.Map;
-
- public class LogCacheUtil {
- public static Map
loginUser = new HashMap<>(); -
- }
index.html页面
- "en" xmlns:th="http://www.w3.org/1999/xhtml">
- "UTF-8">
-
Index Module -
欢迎登录!
-
-
-
"${session.loginUser == null}">
- "color:deepskyblue" th:text="${session.loginUser.username}"> 已登录!
-
- id: "color:deepskyblue" th:text="${session.loginUser.id}">
- username: "color:deepskyblue" th:text="${session.loginUser.username}">
-
Viewcontroller
- 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客户端工具
- @SpringBootApplication
- public class MainApp {
- public static void main(String[] args) {
- SpringApplication.run(MainApp.class,args);
- }
-
- @Bean
- public RestTemplate restTemplate(){
- return new RestTemplate();
- }
- }
index.html页面
- "en" xmlns:th="http://www.w3.org/1999/xhtml">
- "UTF-8">
-
Blog Module -
登录页面 !
-
-
-
"${session.loginUser == null}">
- "color:deeppink" th:text="${session.loginUser.username}"> 已登录!
-
- id: "color:deeppink" th:text="${session.loginUser.id}">
- username: "color:deeppink" th:text="${session.loginUser.username}">
-
view的controller
- 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客户端工具
- 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系统成功