十一、全世界最好理解的SSO单点登录


1、非常完美的流程图

2、非常完美的流程说明

下面是举例来详细说明CAS实现单点登录的流程:

一、第一次访问系统A

1、用户访问系统A (www.app1.com),%25EF%25BC%258C) 跳转认证中心 client(www.sso.com),%25EF%25BC%258C) 然后输入用户名,密码登录,然后认证中心 serverSSO 把 cookieSSO 种在认证中心的域名下 (www.sso.com),%25EF%25BC%258C) 重定向到系统A,并且带上生成的 ticket 参数 (www.app1.com?ticket =xxx)

2、系统A (www.app1.com?ticket =xxx)请求系统A的后端 serverA ,serverA 去 serverSSO 验证,通过后,将cookieA种在 www.app1.com下

二、第二次访问系统A 直接携带 cookieA 去访问后端,验证通过后,即登录成功。

三、第三次访问系统B

1、访问系统B (www.app2.com),%25EF%25BC%258C) 跳转到认证中心 client(www.sso.com),%25EF%25BC%258C) 这个时候会把认证中心的cookieSSO也携带上,发现用户已登录过,则直接重定向到系统B(www.app2.com), 并且带上生成的ticket参数(www.app2.com?ticket =xxx)

2、系统B (www.app2.com?ticket =xxx)请求系统B的后端 serverB,serverB 去 serverSSO 验证,通过后,将cookieB种在www.app2.com下

注意cookie生成时机及种的位置。

  • cookieSSO,SSO域名下的cookie
  • cookieA,系统A域名下的cookie
  • cookieB,系统B域名下的cookie、

3、个人总结的流程

1、用户在浏览器访问系统A:http://www.A.com;

2、服务端验证发现没有登录信息,重定向到SSO认证中心,让你登录,但是会携带网址

3、这时候浏览器访问http://www.sso.com/cas/login?service=http://www.A.com

4、我们提交登录信息,这个时候有个很关键的一步认证中心会生成一个cookieSSO种在认证中心的域名下(注

意:用户在 SSO 认证中心登录后,认证中心会 创建一个 Cookie(<font style="color:#DF2A3F;">cookieSSO</font>),并将它发送到浏览器的响应

中。这个 Cookie 会存储在 <font style="color:#DF2A3F;">http://www.sso.com</font> 域名下。这是浏览器自动触发的机制,后端的响应头有cookei,浏览器会识别出来放置到对应的域名下。)

5、浏览器在 http://www.sso.com 这个域名下会有一个cookie,后面专门用于认证中心的登录认证

6、同时认证中心会生成http://www.A.com对应的一个ticketA

7、携带认证中心提供的ticketA重定向到系统A,此时浏览器网址为:http://www.A.com?ticket=ticketA

8、访问呢系统A,系统A的后台serverA会拿着ticketA去认证中心认证,认证通过后会生成一个cookieA

9、登录成功,后续的请求直接携带cookieA即可,不需要再去认证中心认证

10、用户访问系统B:http://www.B.com;

11、直接重定向到http://www.sso.com/cas/login?service=http://www.B.com

12、注意在http://www.sso.com这个域名下我们种了一个cookieSSO,所以能够访问到认真中心

13、认证中心生成一个ticketB,重定向到http://www.B.com?ticket=ticketB访问系统B

14、系统B的后台serverB会拿着ticketB去认证中心认证,认证通过后会生成一个cookieB

15、登录系统B已经成功,并且后续的请求直接携带cookieB即可

4、GPT的总结

  1. 用户访问系统A (http://www.A.com):
  • 系统A检测不到登录信息(比如cookieA)。
  • 系统A将用户重定向到SSO认证中心 (http://www.sso.com/cas/login?service=http://www.A.com),同时携带service参数,表示登录完成后的回调地址。
  1. 用户访问 SSO 登录页面 (http://www.sso.com/cas/login):
  • 用户输入凭据(用户名、密码)。
  • 如果登录成功,SSO认证中心生成一个全局会话,并在http://www.sso.com域下种植一个cookieSSO,以标记用户的认证状态。
  1. 生成并返回ticketA
  • SSO认证中心根据service参数(http://www.A.com)生成一个一次性票据(ticketA)。
  • 用户被重定向回系统A (http://www.A.com?ticket=ticketA)。
  1. 系统A验证ticketA
  • 系统A的后端(serverA)向SSO认证中心验证ticketA的有效性(通过一个后端接口,如/cas/validate)。
  • 验证通过后,系统A生成自己的登录状态,并为用户种植一个cookieA
  1. 完成系统A的登录:
  • 以后用户访问系统A的请求都携带cookieA,无需再次认证。
  1. 用户访问系统B (http://www.B.com):
  • 系统B检测不到登录信息(比如cookieB),重定向到SSO认证中心 (http://www.sso.com/cas/login?service=http://www.B.com)。
  1. SSO认证中心检测cookieSSO
  • 因为SSO认证中心域下已经种植了cookieSSO,可以直接识别用户已登录,无需再次输入凭据。
  1. 生成并返回ticketB
  • SSO认证中心生成一次性票据ticketB,并重定向回系统B (http://www.B.com?ticket=ticketB)。
  1. 系统B验证ticketB
  • 系统B的后端(serverB)向SSO认证中心验证ticketB
  • 验证通过后,系统B生成自己的登录状态,并为用户种植一个cookieB
  1. 完成系统B的登录:
  • 以后用户访问系统B的请求都携带cookieB,无需再次认证。

5、关于4的提问

提问1:

1、 用户访问系统A (http://www.A.com): 系统A检测不到登录信息(比如cookieA)。 系统A将用户重定向到SSO认证中心 (http://www.sso.com/cas/login?service=http://www.A.com), 同时携带service参数,表示登录完成后的回调地址。 这一步是前端做的还是后端做的,系统A检测不到登录信息,是前端检测还是后端检测是怎么检测的,还有系统A将用户重定向到SSO认证中心 (http://www.sso.com/cas/login?service=http://www.A.com), 同时携带service参数,表示登录完成后的回调地址,这个重定向是前端做的还是后端做的,怎么做到的?

回答1:

可以是前端,也可以是后端检测,但建议以后端检测为主。

后端检测:

  • 用户请求系统A时,浏览器会携带系统A的cookieA(如果存在)。
  • 后端会检查该cookieA是否有效(比如通过解密、校验签名、或查询会话数据库)。
  • 如果cookieA无效或不存在,说明用户未登录。
// Express 后端示例
app.use((req, res, next) => {
  const token = req.cookies.cookieA; // 获取用户的 cookie
  if (!token || !isValidToken(token)) {
    // 如果没有有效的 cookie,重定向到 SSO 登录页面
    const serviceUrl = encodeURIComponent("http://www.A.com");
    // 下面这一步直接引发浏览器自动跳转到指定的 URL
    return res.redirect(`http://www.sso.com/cas/login?service=${serviceUrl}`);
  }
  next();
});

function isValidToken(token) {
  // 验证 token 的逻辑(如解密或校验)
  return true; // 假设始终有效
}

前端检测:

  • 前端检查是否存在某种本地存储(如localStoragecookieA)。
  • 如果不存在,就直接重定向到 SSO 登录页面。
// Vue 前端示例
router.beforeEach((to, from, next) => {
  const token = localStorage.getItem("token"); // 或从 cookie 中读取
  if (!token) {
    const serviceUrl = encodeURIComponent(window.location.href);
    window.location.href = `http://www.sso.com/cas/login?service=${serviceUrl}`;
  } else {
    next();
  }
});

提问2:

在用户访问 http://www.A.com?ticket=ticketA 时,系统 A 的后端会自动处理 ticketA 的验证逻辑。这是通过后端监听请求并与 SSO 认证中心进行通信实现的。以下是详细流程和实现示例。

回答2:

  • 用户被重定向到 http://www.A.com?ticket=ticketA
  • 浏览器发送一个 HTTP GET 请求到系统 A
  • 请求中包含 URL 参数 ticket=ticketA
  • 系统 A 的后端接收到请求
  • 提取 ticketA 参数
  • 向 SSO 认证中心发送验证请求(通常是通过一个后端 API,比如 http://www.sso.com/cas/validate
  • SSO 认证中心返回验证结果
  • 如果 ticketA 有效
  • 后端生成一个本地会话或 JWT(cookieA)供用户后续请求使用
  • 向用户返回登录成功的页面或数据
  • 如果 ticketA 无效
  • 返回错误信息或重定向用户重新登录

代码实现:

GET http://www.A.com?ticket=ticketA
  • 后端处理 ticket 并验证
const express = require("express");
const axios = require("axios"); // 用于发送验证请求
const cookieParser = require("cookie-parser");

const app = express();
app.use(cookieParser());

app.get("/", async (req, res) => {
  const ticket = req.query.ticket;

  // 如果 URL 中包含 ticket 参数
  if (ticket) {
    try {
      // 向 SSO 认证中心验证 ticketA
      const ssoResponse = await axios.get(
        `http://www.sso.com/cas/validate?ticket=${ticket}&service=${encodeURIComponent("http://www.A.com")}`
      );

      if (ssoResponse.data && ssoResponse.data.valid) {
        // Ticket 验证成功
        const user = ssoResponse.data.user; // 假设返回用户信息
        res.cookie("cookieA", generateToken(user)); // 设置本地 Cookie 或 Session
        return res.send("Login successful! Welcome to System A.");
      } else {
        // Ticket 验证失败
        return res.status(401).send("Invalid ticket. Please login again.");
      }
    } catch (error) {
      console.error("Error validating ticket:", error);
      return res.status(500).send("Error validating ticket.");
    }
  }

  // 如果没有 ticket 参数,检查是否已登录
  const cookieA = req.cookies.cookieA;
  if (cookieA) {
    return res.send("Welcome back to System A!");
  }

  // 未登录,重定向到 SSO 登录页面
  const serviceUrl = encodeURIComponent("http://www.A.com");
  return res.redirect(`http://www.sso.com/cas/login?service=${serviceUrl}`);
});

// 假设生成 JWT 或 Session 标识
function generateToken(user) {
  return `TOKEN_FOR_${user.id}`; // 伪代码,请用实际 JWT 或其他机制生成
}

app.listen(3000, () => console.log("System A running on http://localhost:3000"));

提问3:

后端认证成功后,执行:return res.send(“Login successful! Welcome to System A.”);前端是怎么实现路由跳转到首页的

回答3:

  • 可以后端重定向到路由首页
// 假设认证成功后重定向到首页
return res.redirect('http://www.A.com/home');
  • 也可以前端可以用生命周期钩子或者路由前置守卫去判断有没有ticket或者cookieA等判断,也可以从路由参数里获取ticket
<template>
  <div>
    <h1>Welcome to System A</h1>
  </div>
</template>

<script>
import axios from 'axios';

export default {
  mounted() {
    // 获取 URL 中的 ticket 参数
    const ticket = new URLSearchParams(window.location.search).get('ticket');
    
    if (ticket) {
      // 发送请求到后端验证 ticket
      axios.get('/api/validate-ticket', { params: { ticket } })
        .then(response => {
          if (response.data.success) {
            // 登录成功,跳转到首页或其他页面
            this.$router.push('/home');
          } else {
            // 验证失败,跳转到登录页或提示错误
            this.$router.push('/login');
          }
        })
        .catch(error => {
          console.error('验证 ticket 失败:', error);
        });
    } else {
      // 如果没有 ticket 参数,跳转到登录页
      this.$router.push('/login');
    }
  }
};
</script>

6、最后的实例总结

步骤 1: 用户访问系统 A

假设用户访问 http://www.A.com,系统 A 会检查是否已经登录。

  • 前端: 在页面加载时,检查用户是否已登录。如果没有登录,系统 A 会重定向到 SSO 认证中心。
// 我们可以直接在主页/home路由对应的组件写
// 在 Vue 组件的 created/mounted 钩子中
mounted() {
  // 检查是否已经登录(检查 cookieA 或 token)
  const isLoggedIn = this.checkLoginStatus();
  if (!isLoggedIn) {
    // 如果没有登录,重定向到 SSO 认证中心
    const serviceUrl = encodeURIComponent(window.location.href); // 当前页面 URL,作为回调地址
    window.location.href = `http://www.sso.com/cas/login?service=${serviceUrl}`;
  }
},

methods: {
  checkLoginStatus() {
    // 检查本地存储或 cookie 中是否有登录信息
    return document.cookie.includes('cookieA');
  }
}

步骤 2: 用户被重定向到 SSO 认证中心

当用户未登录时,浏览器会重定向到 http://www.sso.com/cas/login?service=http://www.A.com。在这个 URL 中,service 参数是指用户登录成功后返回的系统 A 地址。

  • 前端: SSO 认证中心会要求用户输入登录信息,登录后会通过浏览器自动生成一个 cookie(cookieSSO)。

在用户提交登录信息后,SSO 认证中心会生成一个 cookieSSO,并在浏览器中保存该 Cookie。这个 Cookie 主要用于在 SSO 认证中心下识别用户的登录状态。

  • 前端: 这个步骤由认证中心完成,前端无需干预。

步骤 4: 认证中心生成 ticketA,并重定向回系统 A

认证中心认证成功后,会生成一个 ticketA,并重定向用户回到 http://www.A.com?ticket=ticketA。浏览器会携带 ticketA,用于验证用户身份。

  • 前端: 系统 A 在接收到 ticketA 后,需要验证 ticketA 是否有效。

步骤 5: 系统 A 验证 ticketA

在接收到 ticketA 后,前端需要将其发送到后端,后端验证该 ticketA 的有效性。

// 前端发请求到后端验证 ticketA
mounted() {
  const ticket = new URLSearchParams(window.location.search).get('ticket');
  if (ticket) {
    axios.get('/api/validate-ticket', { params: { ticket } })
      .then(response => {
        if (response.data.success) {
          // 登录成功,设置本地的登录状态(比如存储 cookieA)
          document.cookie = 'cookieA=some-value; Path=/';
          this.$router.push('/home');
        } else {
          // 验证失败,跳转到登录页
          this.$router.push('/login');
        }
      })
      .catch(error => {
        console.error('验证 ticketA 失败:', error);
        this.$router.push('/login');
      });
  }
}

步骤 6: 后端验证 ticketA

后端接收到 ticketA 后,需要去 SSO 认证中心进行验证。后端调用 SSO 接口进行验证,如果成功,生成 cookieA 并发送给浏览器。

// 后端:验证 ticketA
app.get('/api/validate-ticket', (req, res) => {
  const ticket = req.query.ticket;
  // 调用 SSO 认证中心验证 ticket
  axios.get(`http://www.sso.com/cas/validate?ticket=${ticket}`)
    .then(response => {
      if (response.data.success) {
        // ticket 验证成功,生成 cookieA 并发送给浏览器
        res.cookie('cookieA', 'some-value', { httpOnly: true });
        res.json({ success: true });
      } else {
        res.json({ success: false });
      }
    })
    .catch(error => {
      res.status(500).json({ success: false, message: '验证失败' });
    });
});

步骤 7: 用户访问系统 B

假设用户访问 http://www.B.com,由于 SSO 认证中心会检查 cookieSSO,如果用户已经登录,认证中心会直接跳转到系统 B,并生成 ticketB

  • 前端: 当系统 B 检测到没有登录时,重定向到 SSO 认证中心。由于浏览器已携带 cookieSSO,认证中心会直接跳过登录过程,生成 ticketB
// 用户访问系统 B,重定向到 SSO 认证中心
mounted() {
  const isLoggedIn = this.checkLoginStatus();
  if (!isLoggedIn) {
    const serviceUrl = encodeURIComponent(window.location.href); // 当前页面 URL,作为回调地址
    window.location.href = `http://www.sso.com/cas/login?service=${serviceUrl}`;
  }
}

步骤 8: 系统 B 验证 ticketB

系统 B 验证 ticketB,如果验证成功,生成 cookieB

// 前端发请求到后端验证 ticketB
mounted() {
  const ticket = new URLSearchParams(window.location.search).get('ticket');
  if (ticket) {
    axios.get('/api/validate-ticket', { params: { ticket } })
      .then(response => {
        if (response.data.success) {
          // 登录成功,设置本地的登录状态(比如存储 cookieB)
          document.cookie = 'cookieB=some-value; Path=/';
          this.$router.push('/home');
        } else {
          // 验证失败,跳转到登录页
          this.$router.push('/login');
        }
      })
      .catch(error => {
        console.error('验证 ticketB 失败:', error);
        this.$router.push('/login');
      });
  }
}

文章作者: 吴俊杰
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 吴俊杰 !
 本篇
十一、全世界最好理解的SSO单点登录 十一、全世界最好理解的SSO单点登录
单点登录是目前大型企业都会用的技术,也不是啥太难的东西,但是你得懂,会梳理流程,不然哪天让你弄还真弄不出来......
2024-12-13
下一篇 
五、统一项目规范 五、统一项目规范
工程规范真的太重要了,统一项目的工程规范,使得团队开发能够遵循标准,依据标准出来的产物不一定是好东西,但一定很舒服......
  目录