WHAT - 用户登录系列(二)- 单点登录 SSO

目录

  • 一、认证机制
    • 1.1 基于会话的认证(Session-based Authentication)
      • 1. 介绍
      • 2. 基本流程
    • 1.2 JSON Web Tokens (JWT)
      • 1. 介绍
      • 2. jwt 组成
      • 3. 基本流程
      • 4. 阻止列表
      • 5. 刷新令牌
  • 二、单点登录:SSO
    • 2.1 单系统登录
    • 2.2 SSO 介绍
    • 2.3 SSO 登录
    • 2.4 SSO 注销
    • 2.5 同域 SSO
    • 2.6 跨域 SSO
      • 1. 介绍
      • 2. 核心原理
      • 3. 基于 OAuth 2.0 的跨域 SSO
      • 4. 示例代码
        • 身份认证服务器(auth.example.com)
        • 应用服务器(app1.com)
        • 应用服务器(app2.com)
        • 后端直接写 Cookie 存在的跨域限制

本文主要详细介绍单点登录。一般来说,一个完整的登录方案,包括:

  1. 认证机制。如基于会话的认证(Session-based Authentication)、JSON Web Tokens (JWT)。
  2. 单点登录。支持跨多个子系统的登录方案。

一、认证机制

1.1 基于会话的认证(Session-based Authentication)

1. 介绍

会话基本上是一种在客户端(通常是浏览器)和服务器之间维护状态的方法。

在会话认证中,用户通过提供凭据(如用户名和密码)来登录,并且在登录成功后,服务器会创建一个会话,并为用户分配一个唯一的会话标识符(通常称为会话ID)。这个会话ID通常存储在客户端的Cookie中,或者通过其他方式(如URL参数)发送到客户端,并在之后的每个请求中被客户端发送回服务器。

2. 基本流程

以下是会话认证的基本流程:

  1. 用户提供凭据: 用户通过提供用户名和密码来登录。这些凭据通常通过一个登录表单或者其他身份验证界面提交给服务器。

  2. 服务器验证凭据: 服务器收到用户提供的凭据后,会验证其有效性。这通常涉及到将用户提供的凭据与服务器中存储的相应凭据进行比对,以确保用户提供的凭据是有效的。

  3. 创建会话: 如果提供的凭据是有效的,服务器会创建一个会话,并为用户分配一个唯一的会话ID。

  4. 将会话ID发送到客户端: 一旦会话被创建,服务器将会话ID发送到客户端。这通常通过在响应中设置一个Cookie,其中包含会话ID,或者通过其他方式(如URL参数)将其发送到客户端。

  5. 客户端发送会话ID: 客户端在之后的每个请求中都会发送会话ID到服务器。这通常通过在请求的Cookie中包含会话ID,或者通过其他方式(如URL参数)将其发送到服务器。

  6. 服务器验证会话ID: 服务器在接收到客户端的请求时,会检查请求中是否包含有效的会话ID。如果会话ID是有效的,服务器将允许请求继续处理;否则,可能需要用户重新进行身份验证。

在该环节,涉及到的具体原理:

  1. 会话ID的存储: 当服务器创建会话时,会为该会话生成一个唯一的会话ID,并将其与用户的身份信息(如用户ID、角色等)关联起来。服务器通常会将这些会话信息存储在内存(适合轻量级应用或短暂会话,因此在服务器重启时即丢失)、数据库(适合长期或持久性的会话)、缓存(适合需要优化访问速度和性能时,常见解决方案包括 Redis、Memcached)或者其他持久化存储中,以便后续验证用户的身份。
  2. 会话ID的发送: 服务器在响应中将会话ID发送到客户端。这通常通过在响应的Cookie中设置会话ID,或者通过其他方式将其发送到客户端。
  3. 请求中的会话ID验证: 当服务器收到客户端的请求时,它会检查请求中是否包含会话ID。这通常通过检查请求中的Cookie,或者其他地方(如URL参数)来获取会话ID。
  4. 会话ID的验证: 服务器将会话ID与存储的会话信息进行比对,以验证会话ID的有效性。服务器会检查会话ID是否存在,并且是否与已存储的会话信息相关联。如果会话ID有效且与相应的会话信息匹配,则服务器将确认用户的身份,并允许请求继续处理。
  5. 无效会话ID的处理: 如果会话ID无效或者与任何会话信息不匹配,服务器可能会要求用户重新进行身份验证,或者以其他方式处理无效会话ID的情况,例如返回错误信息或重定向到登录页面。
  1. 会话管理: 一旦用户完成了会话,服务器可能会终止会话并销毁相关数据,以便释放资源并提高安全性。

会话认证的原理是建立在服务器和客户端之间共享一个标识符(会话ID)的基础上,以便服务器可以识别特定用户的请求。通过这种方式,服务器可以跟踪用户的身份状态,并在必要时进行身份验证。

1.2 JSON Web Tokens (JWT)

1. 介绍

JSON Web Tokens (JWT) 是一种用于在网络应用间安全传递信息的一种简洁的、自包含的方式。JWT可以通过数字签名来验证数据的完整性和来源。JWT通常用于身份验证和信息交换,特别是在分布式环境中。

2. jwt 组成

在JWT中,签名是放在JWT的第三部分,即签名部分。JWT由三部分组成,它们分别是:

  1. Header(头部):包含了关于JWT的元数据信息,通常包括算法(alg)和令牌类型(typ)等。
  2. Payload(负载):包含了JWT的主要信息,例如用户ID、角色、过期时间等。
  3. Signature(签名):用于验证JWT的完整性和真实性的一段字符串,由将Header和Payload使用指定的算法和密钥(始终存储在服务器上)加密后生成的。

在JWT生成过程中,服务器会将Header和Payload进行Base64编码后,使用约定的加密算法(如HMAC、RSA等)和密钥进行签名生成Signature。最终,将这三部分连接起来形成完整的JWT,通常以.分隔,例如:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

其中,第一部分是Header,第二部分是Payload,第三部分是Signature。

而在后续服务器拿到前端传过来的JWT的验证过程中,服务器会首先对Header和Payload进行解码,然后使用相同的密钥和算法对Header和Payload重新进行签名,并与JWT中的Signature部分进行比对,来验证JWT的完整性和真实性。

另外,JWT可能包含其他的声明,如发行人(issuer)、受众(audience)等。服务器可以根据应用程序的需求来验证这些声明是否符合预期,以进一步确保JWT的有效性。

3. 基本流程

以下是基于JWT进行用户登录的流程和原理:

  1. 用户提供凭据: 用户通过提供凭据(例如用户名和密码)来尝试登录到应用程序。

  2. 服务器验证凭据: 服务器接收到用户提供的凭据后,会验证其有效性。通常情况下,这涉及到与存储在服务器上的用户凭据进行比对。如果凭据有效,则服务器将继续处理登录请求。

  3. 生成JWT: 一旦服务器验证了用户的凭据,它会为用户生成一个JWT。JWT通常包含一些关键信息,如用户ID、角色、过期时间等。这些信息会被编码到JWT的负载(Payload)中。

  4. 签名JWT: 服务器使用密钥对JWT进行签名,以确保JWT的完整性和真实性。签名的过程使用一种加密算法,如HMAC(用于对称密钥)或RSA(用于非对称密钥)。

  5. 将JWT发送给客户端: 服务器在响应中将生成的JWT发送给客户端。这通常是通过将JWT包含在响应的JSON对象中发送回客户端的方式来完成的。

  6. 客户端存储JWT: 客户端收到JWT后,通常会将其存储在本地,例如在浏览器的本地存储(localStorage)或会话存储(sessionStorage)中。

  7. 将JWT发送到服务器: 客户端在后续的请求中,通常会将JWT包含在请求的头部(通常是Authorization头)中发送回服务器。

  8. 验证JWT: 服务器在接收到请求时,会检查JWT的有效性。这包括验证JWT的签名以确保其完整性,即对头部和负载部分使用相同的加密算法与服务器上存储的密钥进行签名,并与JWT中的签名部分进行比对;并检查JWT的有效期以确保其尚未过期,JWT通常包含一个exp字段,代表有效期,服务器基于此来判断是否需要重新进行身份验证。

  9. 提取JWT中的信息: 如果JWT有效,服务器会解码JWT的负载并提取其中的用户信息,以确定用户的身份和权限。这样服务器就可以基于用户的身份来处理请求。

  10. 处理请求: 如果JWT有效且包含了足够的信息来识别和授权用户,服务器会处理请求并响应相应的数据或操作。

  11. 处理过期的JWT: 如果JWT过期或者不再有效,服务器可能会要求客户端重新进行身份验证,或者以其他方式处理无效的JWT。

JWT 的原理在于它是一个自包含的令牌,其中包含了所需的用户信息,并且通过数字签名来确保令牌的完整性和真实性。这使得服务器无需在本地存储会话信息,从而简化了会话管理和状态维护。同时,JWT 的使用也提高了跨域分布式环境下的身份验证和信息交换的安全性和效率。

4. 阻止列表

有时,服务器可能会维护一个阻止列表,用于存储已经失效的JWT或者被标记为不可信的JWT。在验证JWT时,服务器可能会先检查JWT是否在阻止列表中,如果是,则拒绝该JWT。

5. 刷新令牌

在某些情况下,服务器可能会使用JWT中的某些信息来生成新的JWT,以实现令牌的刷新。它允许客户端在访问令牌过期或失效时获取新的令牌,而无需重新进行身份验证。

这可以用于延长用户的会话期限或者更新令牌中的其他信息。

这个过程可能会涉及到一些特定的流程,例如检查刷新令牌的有效性等。具体来说,刷新令牌的流程如下:

  1. 获取刷新令牌: 在用户登录或者进行身份验证成功后,服务器会生成一对访问令牌(Access Token)和刷新令牌(Refresh Token),并将它们发送给客户端。刷新令牌通常包含在JWT中的Payload中,可以携带一些额外的信息,如用户ID等。

  2. 使用访问令牌访问资源: 客户端使用访问令牌来访问受保护的资源,例如API端点。服务器会验证访问令牌的有效性,并根据其包含的信息来确定用户的身份和权限。

  3. 访问令牌过期或失效: 当访问令牌过期或失效时,客户端可能会收到一个响应,指示访问被拒绝或者令牌过期。此时客户端可以使用刷新令牌来获取新的访问令牌,而无需重新进行用户身份验证。

  4. 使用刷新令牌获取新的访问令牌: 客户端使用刷新令牌发送请求到服务器,请求服务器颁发新的访问令牌。服务器接收到请求后,会验证刷新令牌的有效性,并根据需要对客户端进行身份验证。

  5. 颁发新的访问令牌: 如果刷新令牌有效且合法,服务器会生成一个新的访问令牌,并将其发送给客户端。客户端可以使用这个新的访问令牌来继续访问受保护的资源。

  6. 重复使用新的访问令牌: 客户端可以继续使用新颁发的访问令牌来访问资源,直到新的访问令牌过期或失效,或者直到需要再次刷新令牌。

刷新令牌的机制允许客户端在访问令牌过期或失效时获取新的访问令牌,而无需用户重新进行身份验证,从而提高了用户体验和安全性。

当使用JWT时,刷新令牌(Refresh Token)通常是在用户登录成功后一并生成并发送给客户端的。下面是一个简单的示例代码,演示了如何生成和使用JWT以及刷新令牌:

const jwt = require('jsonwebtoken');

// 服务器端的密钥,用于签名和验证JWT
const SECRET_KEY = 'my_secret_key';

// 用户登录成功后,生成JWT和刷新令牌
function generateTokens(user_id) {
    // 生成访问令牌(Access Token)
    const access_token_payload = {
        user_id: user_id,
        exp: Math.floor(Date.now() / 1000) + (15 * 60) // 设置过期时间为15分钟
    };
    const access_token = jwt.sign(access_token_payload, SECRET_KEY);

    // 生成刷新令牌(Refresh Token)
    const refresh_token_payload = {
        user_id: user_id
    };
    const refresh_token = jwt.sign(refresh_token_payload, SECRET_KEY);

    return { access_token, refresh_token };
}

// 客户端在使用访问令牌访问资源时,验证令牌的有效性
function verifyAccessToken(token) {
    try {
        const payload = jwt.verify(token, SECRET_KEY);
        return payload;
    } catch (err) {
        if (err.name === 'TokenExpiredError') {
            console.log('Access Token 已过期');
        } else {
            console.log('无效的 Access Token');
        }
        return null;
    }
}

// 当访问令牌过期时,客户端使用刷新令牌获取新的访问令牌
function refreshAccessToken(refresh_token) {
    try {
        const payload = jwt.verify(refresh_token, SECRET_KEY);
        const user_id = payload.user_id;
        // 在这里进行一些额外的验证操作,如验证用户是否存在等
        const new_access_token = jwt.sign({ user_id }, SECRET_KEY);
        return new_access_token;
    } catch (err) {
        if (err.name === 'TokenExpiredError') {
            console.log('Refresh Token 已过期');
        } else {
            console.log('无效的 Refresh Token');
        }
        return null;
    }
}

// 示例演示
// 用户登录成功后生成JWT和刷新令牌
const { access_token, refresh_token } = generateTokens('123456');

// 客户端使用访问令牌访问资源
// 在请求中发送 access_token 给服务器
console.log('访问受保护的资源...');
const access_token_payload = verifyAccessToken(access_token);
if (access_token_payload) {
    console.log('访问令牌有效,用户ID:', access_token_payload.user_id);
} else {
    console.log('无效的访问令牌');
}

// 当访问令牌过期时,使用刷新令牌获取新的访问令牌
const new_access_token = refreshAccessToken(refresh_token);
if (new_access_token) {
    console.log('获取新的访问令牌成功:', new_access_token);
} else {
    console.log('无法获取新的访问令牌');
}

最后需要注意的是,在实际的应用中,刷新令牌通常是由次数限制的,因为刷新令牌的目的是为了避免长时间持有相同的令牌而增加安全风险。通常情况下,刷新令牌会被设计成有一定的有效期,并且只能被使用一次。

在示例代码中,刷新令牌的有效性是通过 jwt.verify() 方法来验证的。一旦刷新令牌被验证过一次后,它就会被标记为已使用,之后再次尝试验证时会触发错误。这样就可以确保刷新令牌只能被使用一次。

在实际应用中,你可以通过记录刷新令牌的使用次数,并设置一个合理的限制,以防止刷新令牌被无限次使用。通常情况下,刷新令牌的使用次数会被限制在一个较小的数量,例如一次或几次。另外,你也可以在刷新令牌的时候进行一些其他的验证操作,例如验证用户的状态、检查访问权限等,以增强安全性和控制访问。

二、单点登录:SSO

2.1 单系统登录

就是在一个系统内进行的登录,不涉及到多个系统之间的登录。在这种情况下,流程相对简单,用户只需要在应用程序的登录页面上进行一次登录操作,就可以在该系统内访问所有的资源和功能。

2.2 SSO 介绍

单点登录(Single Sign-On,简称SSO)是一种身份验证和授权的机制,允许用户在多个应用程序或系统中使用同一组凭据(如用户名和密码)进行登录,而无需在每个应用程序中单独进行登录。

SSO 的基本原理是在一个系统(认证中心)中进行身份验证后,该系统会生成一个令牌(Token),并将用户的身份信息传递给其他相关系统。这样,其他系统就可以使用这个令牌来验证用户的身份,并授权用户访问相应的资源。

  • 认证中心

相比单系统登录,SSO 需要一个独立的认证中心,只有认证中心能接受用户的用户名和密码等安全信息,其他系统则只接受认证中心的间接授权。

  • 令牌

间接授权通过令牌实现,SSO 认证中心验证登录没问题后,会创建授权令牌。在登录成功后的跳转过程中,授权令牌会作为传输传递给子系统,子系统拿到令牌即代表其得到了授权,可借此创建局部会话

  • 全局会话和局部会话

用户登录成功后,与 SSO 认证中心建立的会话称为全局会话,用户与各个子系统建立的会话称为局部会话,局部会话建立后,用户访问子系统上受保护的资源将不再需要通过 SSO 登录中心。

2.3 SSO 登录

请添加图片描述

在上图中,是一个完整的从未登录、到登录、再到令牌生成以及系统1被注册的过程。

而在用户通过系统1登录后,访问系统2时,就可以直接在认证中心验证用户已登录,只需要将令牌返回给系统2,系统2校验后将令牌和系统2地址传递给认证中心,认证中心确认令牌有效,将系统2也进行注册,最后系统2即可和用户创建局部会话,返回受保护资源。

2.4 SSO 注销

请添加图片描述

2.5 同域 SSO

SSO 主流都是基于共享 Cookie 实现的,Cookie 的使用需要考虑跨域问题。

对于同域来说,适用场景:所有子系统都是企业自己的系统,所有系统都使用同一个一级域名,不同在于二级域名。比如 mycompany.com,三个子系统分别是认证中心 sso.mycompany.com、系统1 app1.mycompany.com、系统2 app2.mycompany.com。
请添加图片描述

核心原理:

  1. 认证中心登录成功后会设置 Cookie 的 domain 为一级域名 mycompany.com,如 Set-Cookie: SSO_TOKEN=generated_token; Domain=mycompany.com; Path=/; Secure; HttpOnly ,根据规则,可以共享 Cookie 给所有 xxx.mycompany.com
  2. 使用 Redis、Cookie 等技术让所有系统共享

2.6 跨域 SSO

1. 介绍

假如希望支持第三方系统,由于跨域,不能共享 Cookie 了,因为浏览器安全策略通常禁止跨域请求中的Cookie共享。

对于这种情况,可以通过一个单独的授权服务(UAA)来做统一登录,并基于共享UAA的Cookie来实现单点登录。

举个例子,app1.com 和 app2.com,基于 UAA 授权中心 sso.com 实现单点登录:

请添加图片描述

2. 核心原理

  1. 访问系统1判断未登录,跳转到UAA系统请求授权
  2. 在UAA系统域名sso.com下的登录页面输入用户名和密码登录成功后,UAA 系统把登录信息存储到Redis中,并在浏览器写入 domain 为 sso.com 的 Cookie。并且会重定向回系统1,带上临时授权码code(一般10s内有效,并且只能校验一次)
  3. 系统1使用授权码code向UAA系统请求令牌(一般会返回访问令牌和ID令牌),系统1拿到后可以利用令牌获取用户信息并创建本地会话
  4. 访问系统2判断未登录,则跳转UAA系统请求授权
  5. 由于是跳转到UAA系统的域名下,可以通过浏览器中UAA存储的Cookie读取到之前的登录信息,判断已登录,同样会重定向回系统2,带上临时授权码code,系统2通过code可以交换令牌并创建会话

在这里插入图片描述

3. 基于 OAuth 2.0 的跨域 SSO

在前面我们介绍过,用户可以通过第三方平台(如 Google、Facebook)进行认证,OAuth 2.0 提供授权框架,OpenID Connect 扩展了 OAuth 2.0,用于用户身份认证。

为了实现跨域SSO,需要采用一些特殊的机制,如OAuth 2.0、OpenID Connect、SAML等协议可以安全地在不同域之间传递身份验证信息。这其实也属于跨域 SSO。

4. 示例代码

以下是如何使用Node.js和Express实现一个简单的跨域SSO示例。

身份认证服务器(auth.example.com)

使用expressjsonwebtoken来模拟认证服务器:

const express = require('express');
const jwt = require('jsonwebtoken');
const app = express();
const bodyParser = require('body-parser');

const SECRET_KEY = 'your_secret_key';
const CLIENT_ID = 'your_client_id';
const CLIENT_SECRET = 'your_client_secret';
const REDIRECT_URI = 'http://app1.com/callback';

app.use(bodyParser.json());

app.get('/login', (req, res) => {
    // 模拟登录页面
    res.send('<form action="/authenticate" method="POST"><input name="username"><button type="submit">Login</button></form>');
});

app.post('/authenticate', (req, res) => {
    const { username } = req.body;
    if (username) {
        const authCode = jwt.sign({ username }, SECRET_KEY, { expiresIn: '10m' });
        res.redirect(`${REDIRECT_URI}?code=${authCode}`);
    } else {
        res.status(400).send('Login failed');
    }
});

app.post('/token', (req, res) => {
    const { code } = req.body;
    try {
        const payload = jwt.verify(code, SECRET_KEY);
        const accessToken = jwt.sign(payload, SECRET_KEY, { expiresIn: '1h' });
        const idToken = jwt.sign(payload, SECRET_KEY, { expiresIn: '1h' });
        res.json({ access_token: accessToken, id_token: idToken });
    } catch (error) {
        res.status(400).send('Invalid code');
    }
});

app.listen(3000, () => console.log('Auth server running on port 3000'));
应用服务器(app1.com)

使用expressjsonwebtoken来模拟应用服务器:

const express = require('express');
const jwt = require('jsonwebtoken');
const axios = require('axios');
const cookieParser = require('cookie-parser');
const app = express();

const CLIENT_ID = 'your_client_id';
const CLIENT_SECRET = 'your_client_secret';
const AUTH_SERVER_URL = 'http://auth.example.com';
const SECRET_KEY = 'your_secret_key';

app.use(cookieParser());
app.use(express.json());

app.get('/', (req, res) => {
    const token = req.cookies.token;
    if (token) {
        try {
            const payload = jwt.verify(token, SECRET_KEY);
            res.send(`Welcome, ${payload.username}`);
        } catch (err) {
            res.redirect('/login');
        }
    } else {
        res.redirect('/login');
    }
});

app.get('/login', (req, res) => {
    const authUrl = `${AUTH_SERVER_URL}/login?response_type=code&client_id=${CLIENT_ID}&redirect_uri=http://app1.com/callback`;
    res.redirect(authUrl);
});

app.get('/callback', async (req, res) => {
    const { code } = req.query;
    try {
        const response = await axios.post(`${AUTH_SERVER_URL}/token`, {
            code,
            client_id: CLIENT_ID,
            client_secret: CLIENT_SECRET,
            redirect_uri: 'http://app1.com/callback',
        });
        const { access_token, id_token } = response.data;
        res.cookie('token', access_token, { httpOnly: true });
        res.redirect('/');
    } catch (error) {
        res.status(400).send('Token exchange failed');
    }
});

app.listen(3001, () => console.log('App1 running on port 3001'));
应用服务器(app2.com)

类似于app1.com,但URL和端口不同:

const express = require('express');
const jwt = require('jsonwebtoken');
const axios = require('axios');
const cookieParser = require('cookie-parser');
const app = express();

const CLIENT_ID = 'your_client_id';
const CLIENT_SECRET = 'your_client_secret';
const AUTH_SERVER_URL = 'http://auth.example.com';
const SECRET_KEY = 'your_secret_key';

app.use(cookieParser());
app.use(express.json());

app.get('/', (req, res) => {
    const token = req.cookies.token;
    if (token) {
        try {
            const payload = jwt.verify(token, SECRET_KEY);
            res.send(`Welcome, ${payload.username}`);
        } catch (err) {
            res.redirect('/login');
        }
    } else {
        res.redirect('/login');
    }
});

app.get('/login', (req, res) => {
    const authUrl = `${AUTH_SERVER_URL}/login?response_type=code&client_id=${CLIENT_ID}&redirect_uri=http://app2.com/callback`;
    res.redirect(authUrl);
});

app.get('/callback', async (req, res) => {
    const { code } = req.query;
    try {
        const response = await axios.post(`${AUTH_SERVER_URL}/token`, {
            code,
            client_id: CLIENT_ID,
            client_secret: CLIENT_SECRET,
            redirect_uri: 'http://app2.com/callback',
        });
        const { access_token, id_token } = response.data;
        res.cookie('token', access_token, { httpOnly: true });
        res.redirect('/');
    } catch (error) {
        res.status(400).send('Token exchange failed');
    }
});

app.listen(3002, () => console.log('App2 running on port 3002'));

上述示例展示了如何使用OAuth 2.0和OpenID Connect协议在跨域的情况下实现单点登录。

关键点在于:

  1. 使用授权码和令牌进行身份验证和授权
  2. 通过重定向URL在不同域之间传递身份验证信息
  3. 使用安全的令牌存储和传输机制(如HTTPS和HttpOnly Cookie)

这种方式确保了用户在多个域中的无缝登录体验,同时保证了安全性。

后端直接写 Cookie 存在的跨域限制

注意在上述代码中,后端使用这种方法使得前端刷新页面时,下次请求会自动带上 Cookie。

res.cookie('token', access_token, { httpOnly: true });
res.redirect('/');

请添加图片描述

但默认情况下只能在前端资源和后台服务位于相同的域名或子域时才有效

**假如是前端和后端服务是跨域呢?**因为在当下前后端分离是非常常见的场景。

  • 第一种方案:重定向

通过在服务器端进行重定向或请求代理来设置Cookie。例如,域名A的后台可以重定向用户到域名B的一个特定页面,同时在重定向的过程中通过HTTP响应头设置Cookie。示例:

serviceA.com 后端设置响应头:

HTTP/1.1 302 Found
Location: https://serviceB.com/set-cookie?token=abc123

serviceB.com 接收到请求后,在服务器端设置Cookie:

// 在 `https://serviceB.com/set-cookie` 处理请求
const token = req.query.token;
res.cookie('auth_token', token, { domain: '.serviceB.com', httpOnly: true, secure: true });
res.redirect('/');
  • 第二种方案,由浏览器携带临时授权码code发起请求

请添加图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/664394.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

arcgis api for javascript点击获取要素错乱的问题

今天帮同事看了一个前端地图点击的问题&#xff1a;点击时总会获取到周边的图元&#xff0c;即使我点击线的周围&#xff0c;也是能获取到的&#xff0c;除非离得特别远。 地图组件用的是arcgis api, 图层类是grahicslayer,要素类型是线。这是添加图元的代码&#xff1a; grap…

STM32高级控制定时器之输入捕获模式

目录 概述 1 输入捕获模式 1.1 原理介绍 1.2 实现步骤 1.3 发生输入捕获流程 2 使用STM32Cube配置工程 2.1 软件环境 2.2 配置参数 2.3 生成项目文件 3 功能实现 3.1 PWM调制占空比函数 3.2 应用函数库 4 测试 4.1 功能框图 4.2 运行结果 源代码下载地址&#xf…

基于ELK的日志管理【开发实践】

文章目录 一、ELK简介1.1 ELK的作用与应用1.2 ELK的组成1.3 Elasticsearch1.4 Logstash1.5 Kibana1.6 ELK架构简述1.7 基础知识1.7.1 数据格式1.7.2 正排索引和倒排索引1.7.3 全文搜索 二、ES入门---基于HTTP的使用方式&#xff08;了解&#xff09;2.1 索引操作2.1.1 创建索引…

探索无限可能:API平台引领数据驱动的新时代

在数字化浪潮的推动下&#xff0c;数据已成为推动商业创新和增长的核心动力。然而&#xff0c;数据的获取、整合和应用并非易事&#xff0c;需要跨越技术、安全和效率等多重挑战。幸运的是&#xff0c;API&#xff08;应用程序接口&#xff09;平台的出现&#xff0c;为我们打开…

docker 安装mysql,redis,rabbitmq

文章目录 docker 安装ngnix&#xff0c;mysql,redis,rabbitmq安装docker1.安装下载docker-ce源命令2.安装docker3.查看版本4.查看docker状态5.启动docker6.测试安装ngnix 安装mysql8.0.361.拉取mysql镜像2.安装mysql8 安装redis1.拉取redis7.0.11镜像2.安装redis3.进入容器内部…

159.二叉树:二叉树的层平均值(力扣)

代码解决 /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}* Tre…

mybatisPlus3.5.1有个奇葩bug的解决方法

mybatisPlus3.5.1有个奇葩bug的解决方法 文章目录 1.啥bug&#xff1f;1.1mybatisPlus官网及GitHub项目地址1.2 bug问题 2.解决方法2.1根据报错修改表中字段2.2 升级JSqlParser依赖的版本2.3升级mybatisPlus的版本 3.mysql关键字大全4.总结 1.啥bug&#xff1f; 1.1mybatisPlu…

上传RKP 证书签名请求息上传到 Google 的后端服务器

上传证书签名请求 1.准备环境&#xff1a;OK pip3 install google-auth2.13.0 requests2.28下载 device_info_uploader.py 。 没找到先跳过 选项 1&#xff1a;通过 GCP 帐户使用 device_info_uploader.py 运行脚本。 ./device_info_uploader.py --credentials /secure/s…

软件测试可发现所有bug吗?有什么样的原则和目的?

在今天的高度数字化时代&#xff0c;软件测试的重要性不可忽视。这是一种系统性的过程&#xff0c;通过对软件系统进行评估和验证&#xff0c;以确定其是否满足预期要求。在软件开发的各个阶段&#xff0c;软件测试都扮演着至关重要的角色。它涉及到软件功能、性能、安全性等多…

基于Spring Boot框架的分页查询和文件上传

分页查询 分析 要想从数据库中进行分页查询&#xff0c;我们要使用LIMIT关键字&#xff0c;格式为&#xff1a;limit 开始索引 每页显示的条数 假设一页想展示10条数据 查询第1页数据的SQL语句是&#xff1a; select * from emp limit 0,10; 查询第2页数据的SQL语句是&…

【推荐】用scss循环zoom缩放比例,解决可视化大屏在不同分辨率屏幕下的适配问题

方法1&#xff1a; 指定几种常规屏幕宽度&#xff08;用这种方式就必须要强制用户全屏查看页面&#xff0c;在固定的宽度下才能达到比较不错的显示效果&#xff09; // 适配不同分辨率的页面---------------------------------------- html {overflow: hidden;width: 1920px;…

光电耦合器:航天航空领域的先进连接技术

光电耦合器作为一种关键的电子连接器&#xff0c;在航天航空领域扮演着重要角色。本文将深入探讨光电耦合器在航天航空领域的应用及其技术特点。 光电耦合器在航天航空领域的应用 光电耦合器作为一种高可靠性、高速传输、抗干扰能力强的连接器&#xff0c;在航天航空领域有着广…

Mybatis 查询TypeHandler使用,转译查询数据(逗号分隔转List)

创建自定义的Hanndler /*** Package: com.datalyg.common.core.handler* ClassName: CommaSeparatedStringTypeHandler* Author: dujiayu* Description: 用于mybatis 解析逗号拼接字符串* Date: 2024/5/29 10:03* Version: 1.0*/ public class CommaSeparatedStringTypeHandle…

U盘格式化怎么操作?快来学这4种法

U盘格式化怎么操作&#xff1f;在计算机领域中&#xff0c;格式化通常指对存储设备&#xff08;如硬盘、U盘&#xff09;进行格式化操作&#xff0c;清空其中的数据并重新建立文件系统&#xff0c;以便进行数据存储和管理。 U盘格式化一共有哪些方法&#xff1f;在格式化U盘之…

暑期来临,AI智能视频分析方案筑牢防溺水安全屏障

随着夏季暑期的来临&#xff0c;未成年人溺水事故频发。传统的防溺水方式往往依赖于人工巡逻和警示标识的设置&#xff0c;但这种方式存在人力不足、反应速度慢等局限性。近年来&#xff0c;随着视频监控智能分析技术的不断发展&#xff0c;其在夏季防溺水中的应用也日益凸显出…

Kubernetes 系统监控Metrics Server、HorizontalPodAutoscaler、Prometheus

Metrics Server Linux 系统命令 top 能够实时显示当前系统的 CPU 和内存利用率&#xff0c;它是性能分析和调优的基本工具。 Kubernetes 也提供了类似的命令&#xff0c;就是 kubectl top&#xff0c;不过默认情况下这个命令不会生效&#xff0c;必须要安装一个插件 Metrics …

[图的搜索]5.图解狄克斯特拉算法及其代码演示

狄克斯特拉算法 与前面提到的贝尔曼-福特算法类似&#xff0c;狄克斯特拉&#xff08;Dijkstra&#xff09;算法也是求解最短路径问题的算法&#xff0c;使用它可以求得从起点到终点的路径中权重总和最小的那条路径路径。 图解 01 这里我们设A为起点、G为终点&#xff0c;来讲…

论文笔记:Image Anaimation经典论文-运动关键点模型(Monkey-Net)

Monkey-Net&#xff08;MOviNg KEYpoints&#xff09; paper: https://arxiv.org/pdf/1812.08861, CVPR 2019 code: https://github.com/AliaksandrSiarohin/monkey-net/tree/master 相关工作 视频生成演变过程&#xff1a; spatio-temporal network: 如基于GAN网络的生成模…

探索重庆耶非凡科技:揭秘其背后的技术实力与市场布局

重庆耶非凡科技有限公司&#xff0c;作为重庆当地一家知名的综合性服务型企业&#xff0c;近年来在多个领域取得了显著的成绩。其业务范围广泛&#xff0c;不仅涵盖了传统的行业服务&#xff0c;还积极探索并实践了一系列创新项目&#xff0c;其中最为引人注目的便是选品师项目…

Linux服务器搭建http服务,添加DNS域名解析

效果如下&#xff1a;搭建自己的网站&#xff0c;添加域名解析服务后&#xff0c;外网可访问 1.搭建http服务器&#xff0c;可通过局域网下的ip访问 2.DNS域名解析服务&#xff0c;链接ip到公网&#xff0c;外网可以通过对应的域名访问 1.安装httpd yum install httpd #根据提示…