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

news2025/1/21 12:06:11

目录

  • 一、认证机制
    • 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发起请求

请添加图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

从零开始学习Slam-旋转矩阵旋转向量四元组(二)

本文参考&#xff1a;计算机视觉life 仅作笔记用 书接上回&#xff0c;上回不清不楚的介绍了旋转矩阵&旋转向量和四元组 现在回顾一下重点&#xff1a; 本着绕谁谁不变的变则 假设绕z轴旋转θ&#xff0c;旋转矩阵为&#xff1a; 再回顾一下旋转向量的表示以及这个基本记不…

【课程总结】Day4:信息论和决策树算法

前言 本章内容主要是学习机器学习中的一个重要模型&#xff1a;决策树&#xff0c;围绕决策树的应用&#xff0c;我们展开了解到&#xff1a;熵的定义、熵的计算、决策树的构建过程(基于快速降熵)、基尼系数等&#xff0c;从而使得我们对决策树有了直观认识。 熵的介绍 因为…

discuz论坛怎么修改备案信息

大家好&#xff0c;今天给大家分享下discuz如何填写备案信息并且展示在网站首页。大家都知道国内网站都需要备案&#xff0c;不通过备案的网站上是没办法通过域名打开的。大家也可以通过搜索网创有方&#xff0c;或者直接点击网创有方 查看悬挂备案号后的效果。 首先大家可以看…

AntV F2 极坐标堆叠柱状图:可视化数据分布

本文由ScriptEcho平台提供技术支持 项目地址&#xff1a;传送门 AntV F2 极坐标堆叠柱状图&#xff1a;可视化数据分布 应用场景 极坐标堆叠柱状图适用于展示不同分类数据在某个维度上的分布情况&#xff0c;例如不同电影的票房占比、不同商品的销售额占比等。通过这种方式…

闽盾杯 2021 DNS协议分析

今年CISCN的Tough DNS 的前戏就是DNS协议分析 直接可以查找到flag的base64形式Zmxh 发现就是请求的dnslog 携带的数据 过滤器就是 dns tshark -r dns.pcapng -T json -Y "dns" >1.json 字段选择 dns.qry.name tshark -r dns.pcapng -T json -Y "dns"…

C# try catch异常捕获

异常捕获 执行过程&#xff1a;try中的代码没有出现异常&#xff0c;则catch里面不会自行&#xff0c;如果try中代码出现异常&#xff0c;则后面的代码都不执行&#xff0c;直接跳到catch中的代码执行。 // try catch 可以捕获多个错误&#xff0c; try...catch...catch.... …

【ETAS CP AUTOSAR基础软件】EcuM模块详解

文章包含了AUTOSAR基础软件&#xff08;BSW&#xff09;中EcuM模块相关的内容详解。本文从AUTOSAR规范解析&#xff0c;ISOLAR-AB配置以及模块相关代码分析三个维度来帮读者清晰的认识和了解EcuM。文中涉及的SOLAR-AB配置以及模块相关代码都是依托于ETAS提供的工具链来配置与生…

桃金娘T2T基因组-文献精读17

Gap-free genome assembly and comparative analysis reveal the evolution and anthocyanin accumulation mechanism of Rhodomyrtus tomentosa 无缺口基因组组装及比较分析揭示了桃金娘的进化和花青素积累机制 摘要 桃金娘&#xff08;Rhodomyrtus tomentosa&#xff09;是…

鸿蒙工程目录介绍

鸿蒙构建完毕生成hhvp文件。 项目结构&#xff1a; .hvigor : 是存储构建配置文件的 .idea : 是开发工具拥有的目录 AppScope : 是全局的公共资源存放位置 hvigor &#xff1a;存放前端构建配置信息 oh_modules : 存放项目用到的第三方包 build-profile.json5 : 应用级别的构…

Git基本配置,使用Gitee(一)

1、设置Giter的user name和email 设置提交用户的信息 git config --global user.name "username" git config --global user.email "Your e-mail"查看配置 git config --list2、生成 SSH 公钥 通过命令 ssh-keygen 生成 SSH Key -t key 类型 -C 注释 ssh-…

客户文章|难能可贵,非模式生物的功能研究与创新

菜豆&#xff08;Phaseolus vulgaris&#xff09;&#xff0c;又名四季豆、芸豆、油豆角&#xff0c;是全球第一大豆类蔬菜&#xff0c;我国是世界上最主要的菜豆生产国和销售国。在田间生产过程中&#xff0c;菜豆常面临着各种生物和非生物逆境的胁迫&#xff0c;对其产量品质…

匹配字符串

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 Python提供了re模块&#xff0c;用于实现正则表达式的操作。在实现时&#xff0c;可以使用re模块提供的方法&#xff08;如search()、match()、finda…

elementui中的el-checkbox-group添加全选按钮

//多选子组件 <template><div class"multiple-choice"><el-checkbox class"no1" v-if"isShowAllBtn" :indeterminate"isIndeterminate1" v-model"checkAll1" border :style"{borderColor:isIndetermina…

鸿蒙ArkTS声明式开发:跨平台支持列表【显隐控制】 通用属性

显隐控制 控制组件是否可见。 说明&#xff1a; 开发前请熟悉鸿蒙开发指导文档&#xff1a; gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md点击或者复制转到。 从API Version 7开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本…

微型导轨在自动化制造中有哪些优势?

微型导轨在自动化制造中发挥重要作用&#xff0c;能够满足自动化设备制造中对精度要求较高的工艺环节。适用于自动装配线、自动检测设备和机器人操作等环节&#xff0c;推动了行业的进步与发展。那么&#xff0c;微型导轨在使用中有哪些优势呢&#xff1f; 1、精度高和稳定性强…

基于鲲鹏服务器搭建简单的开源论坛系统(LAMP)实践分享

LAMPLinux apache mysql( mariadb) PHP 结合利用华为云弹性负载均衡ELB弹性伸缩AS服务 优点&#xff1a; 将访问流量自动分发到多台云服务器&#xff0c;扩展应用系统对外的服务能力&#xff0c;实现更高水平的应用容错&#xff1b; 根据不同的业务、访问需求和预设策略&…

函数的创建和调用

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 提到函数&#xff0c;大家会想到数学函数吧&#xff0c;函数是数学最重要的一个模块&#xff0c;贯穿整个数学学习过程。在Python中&#xff0c;函数…

编程学习 (C规划) 6 {24_4_18} 七 ( 简单扫雷游戏)

首先我们要清楚扫雷大概是如何实现的&#xff1a; 1.布置雷 2.扫雷&#xff08;排查雷&#xff09; &#xff08;1&#xff09;如果这个位置是雷就炸了&#xff0c;游戏结束 &#xff08;2&#xff09;如果不是雷&#xff0c;就告诉周围有几个雷 3.把所有不是雷的位置都找…

Leetcode:Z 字形变换

题目链接&#xff1a;6. Z 字形变换 - 力扣&#xff08;LeetCode&#xff09; 普通版本&#xff08;二维矩阵的直接读写&#xff09; 解决办法&#xff1a;直接依据题目要求新建并填写一个二维数组&#xff0c;最后再将该二维数组中的有效字符按从左到右、从上到下的顺序读取并…

开源硬件初识——Orange Pi AIpro(8T)

开源硬件初识——Orange Pi AIpro&#xff08;8T&#xff09; 大抵是因为缘&#xff0c;妙不可言地就有了这么一块儿新一代AI开发板&#xff0c;乐于接触新鲜玩意儿的小火苗噌一下就燃了起来。 还没等拿到硬件&#xff0c;就已经开始在Orange Pi AIpro 官网上查阅起资料&…