news 2026/8/12 11:47:27

基于Cookie的SSO单点登录:原理、实现与安全实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于Cookie的SSO单点登录:原理、实现与安全实践

1. 项目概述:为什么我们还在谈基于Cookie的SSO?

在分布式系统和微服务架构大行其道的今天,单点登录(SSO)早已不是什么新鲜概念。JWT、OAuth 2.0、OpenID Connect这些协议听起来更“现代”,讨论热度也更高。但如果你深入企业内部,尤其是那些历史包袱较重、系统迭代周期长的场景,你会发现,基于Cookie的SSO方案依然有着极其顽强的生命力。它可能不够“炫酷”,但足够简单、直接,并且在特定边界内非常可靠。这个项目要探讨的,正是这个看似“传统”却经久不衰的方案:基于Cookie实现的单点登录系统。核心目标很明确:用户只需在一个核心系统(通常称为认证中心)登录一次,其登录状态就能通过Cookie安全地传递到其他信任的子系统中,实现无缝访问。这背后涉及的核心关键词——SSO、Cookie、Session——构成了我们今天要拆解的全部内容。无论你是需要改造一个老系统,还是为一个轻量级的新产品快速搭建认证骨架,理解这套方案的里里外外,都能让你在技术选型时多一份笃定。

2. 核心原理与架构设计拆解

2.1 传统Session-Cookie认证模式的瓶颈

要理解基于Cookie的SSO,必须先看清它要解决什么问题。在单体应用时代,我们熟悉的认证流程是:用户提交用户名密码,服务端验证通过后,在服务器内存或Redis中创建一个Session对象,其中存储用户ID、角色等信息,并生成一个唯一的Session ID。随后,服务器通过Set-Cookie响应头,将这个Session ID种到用户浏览器的Cookie里。浏览器后续的每一次请求,都会自动通过Cookie请求头携带这个ID,服务器借此找到对应的Session,从而识别用户身份。

这套模式简单有效,但它有一个致命的前提:Session存储和验证必须发生在同一个“域”下。因为浏览器有严格的同源策略,A域名下的Cookie不会自动发送到B域名的请求中。当系统拆分为多个独立部署、不同子域甚至完全不同域的应用时,每个应用都有自己的Session,用户就不得不反复登录。这正是SSO要解决的核心痛点:一次登录,处处通行

2.2 基于Cookie的SSO核心思想:信任与票据传递

基于Cookie的SSO方案,其核心思想是引入一个独立的、所有子系统都信任的认证中心。这个中心负责统一的用户认证和会话管理。整个流程可以类比为在一个大型园区(业务系统群)里,设立一个总门卫室(认证中心)。

  1. 用户首次访问:当用户访问业务系统A时,系统A检查发现请求中没有有效的登录凭证(Cookie),于是将用户重定向到认证中心的登录页面。
  2. 统一认证:用户在认证中心完成登录。认证中心验证身份后,在其自身的域下创建主会话(Global Session),并生成一个加密的、有时效的“门票”——我们通常称之为票据
  3. 票据传递与验证:认证中心将用户重定向回系统A,并在URL中附上这张票据。系统A收到票据后,需要向认证中心发起一个后台的、服务端到服务端的请求,验证这张票据的真实性和有效性。
  4. 建立本地会话:票据验证通过后,认证中心会返回用户的基本身份信息。系统A据此在本地(自己的服务器上)创建一个局部会话(Local Session),并为用户浏览器设置一个属于系统A自己域下的Cookie。至此,用户在系统A的登录完成。
  5. 访问其他系统:当用户再去访问系统B时,流程重复上述1-4步。关键在于第2步:用户浏览器在访问认证中心时,会携带认证中心域下的Cookie。认证中心通过这个Cookie发现用户已经存在主会话,于是直接生成新票据并跳转,用户无需再次输入密码。

整个方案的精髓在于:认证中心通过Cookie维持用户的全局登录状态,而各个业务系统通过验证认证中心颁发的票据,在本地建立信任关系并维护自己的局部会话。浏览器同源策略的限制,通过认证中心作为可信中介和票据传递机制被巧妙地绕过。

2.3 关键组件与数据流设计

一个典型的基于Cookie的SSO架构包含以下关键组件:

  • 认证中心:独立的Web应用,负责用户登录/注销、主会话管理、票据颁发与验证。它是整个SSO体系的信任根。
  • 业务系统:需要接入SSO的各个独立应用。它们需要嵌入一个SSO客户端组件,该组件负责拦截未认证请求、重定向到认证中心、接收并验证票据、建立本地会话。
  • 共享存储:通常是一个Redis集群,用于存储认证中心的主会话信息以及颁发的票据。票据必须是一次性的,验证后立即失效,防止重放攻击。
  • 信任关系:业务系统与认证中心之间需要预先共享一个密钥或配置证书,用于票据的签名验证,确保票据不会被伪造。

数据流可以概括为以下几个关键步骤:

  1. 拦截与重定向:客户端访问受保护资源 → 业务系统拦截请求,检查本地Session/Cookie → 若无,构造认证中心登录URL并重定向。
  2. 认证与发券:浏览器访问认证中心 → 认证中心检查自身Cookie(全局Session)→ 若无,展示登录页;若有,直接生成票据 → 将用户重定向回业务系统,URL附带票据。
  3. 后台验证与登录:业务系统从URL参数获取票据 → 业务系统后台向认证中心接口发起请求验证票据 → 认证中心验证票据有效性,返回用户信息并销毁票据 → 业务系统创建本地Session,设置本地Cookie。
  4. 局部会话建立:后续请求,浏览器携带业务系统本地Cookie,业务系统通过本地Session识别用户。

3. 核心细节解析与安全要点

3.1 Cookie的作用域与安全属性

在这个方案中,Cookie扮演了两个角色,它们的配置至关重要:

  1. 认证中心Cookie:用于维持全局会话。其Domain属性应设置为认证中心的顶级域(例如.sso.com),这样所有形如app1.sso.com,auth.sso.com的子域都能共享此Cookie。关键安全属性包括:

    • HttpOnly:必须设置为true。防止JavaScript通过document.cookie访问,有效抵御XSS攻击窃取会话。
    • Secure:在生产环境必须设置为true。仅通过HTTPS传输,防止网络嗅探。
    • SameSite: 这是一个现代浏览器的重要安全策略。对于认证中心,通常建议设置为LaxStrictLax允许在顶级导航(如链接点击)时携带Cookie,而Strict则完全禁止跨站请求携带Cookie。设置为Strict安全性最高,但可能影响从其他域名跳转到认证中心的登录流程,需要仔细设计。如果认证中心和业务系统在同一个顶级域下,这个问题的影响较小。
  2. 业务系统本地Cookie:用于维持局部会话。其Domain设置为业务系统自身的域。安全属性同样需要设置HttpOnlySecureSameSite属性可以根据业务系统的跨站需求灵活设置,对于纯后端API交互的业务,设置为Strict是安全的。

注意:Chrome等现代浏览器对SameSite的默认策略已从None变为Lax。这意味着如果你的业务系统需要通过iframe嵌入或由第三方网站发起POST请求来触发SSO流程,且需要携带认证中心Cookie,你必须显式地将认证中心Cookie的SameSite设置为None,并且同时必须设置Secure=true(即仅限HTTPS)。这是实践中一个非常常见的坑。

3.2 票据的设计与验证机制

票据是整个流程中跨系统传递信任的载体,其设计必须兼顾安全与效率。

  • 票据内容:通常应包含:用户唯一标识、票据颁发时间、过期时间、随机数。绝对不要包含敏感信息如密码。
  • 票据格式:为了便于在URL中传递,通常进行Base64编码。更安全的做法是使用JWT格式,将上述信息作为Payload,并附上签名。
  • 签名与验证:认证中心使用私钥或共享密钥对票据内容进行签名(如HMAC SHA256)。业务系统使用对应的公钥或共享密钥验证签名,确保票据未被篡改。
  • 一次性与时效性:票据必须有很短的过期时间(如10秒),并且必须在认证中心验证后立即标记为失效或删除。这可以防止票据被截获后重复使用(重放攻击)。验证票据的接口必须设计为幂等的。

一个简单的票据生成与验证示例(概念性代码)

# 认证中心 - 生成票据 import time, hmac, hashlib, base64, json def generate_ticket(user_id, secret_key): ticket_data = { 'uid': user_id, 'iat': int(time.time()), # 颁发时间 'exp': int(time.time()) + 10, # 10秒后过期 'nonce': os.urandom(16).hex() # 随机数防重放 } # 1. 生成签名字符串 payload_json = json.dumps(ticket_data, separators=(',', ':')) signature = hmac.new(secret_key.encode(), payload_json.encode(), hashlib.sha256).hexdigest() # 2. 组合成票据字符串 ticket_string = base64.urlsafe_b64encode(payload_json.encode()).decode() + '.' + signature return ticket_string # 业务系统 - 验证票据 def verify_ticket(ticket_string, secret_key): try: payload_b64, signature = ticket_string.split('.', 1) payload_json = base64.urlsafe_b64decode(payload_b64).decode() ticket_data = json.loads(payload_json) # 验证过期时间 if ticket_data['exp'] < time.time(): return None, 'Ticket expired' # 验证签名 expected_sig = hmac.new(secret_key.encode(), payload_json.encode(), hashlib.sha256).hexdigest() if not hmac.compare_digest(expected_sig, signature): return None, 'Invalid signature' # 验证票据是否已使用(需查询共享存储) if is_ticket_used(ticket_data['nonce']): return None, 'Ticket already used' mark_ticket_used(ticket_data['nonce']) # 标记已使用 return ticket_data['uid'], None except Exception as e: return None, f'Verification error: {e}'

3.3 全局会话与局部会话的管理

  • 全局会话:存储在认证中心,通常以键值对形式存在于Redis中,键是认证中心Cookie的值(Session ID),值包含用户核心身份信息和会话创建时间。需要设置合理的过期时间,并考虑会话续期逻辑。
  • 局部会话:各个业务系统独立管理。验证票据成功后,业务系统可以创建自己的Session,也可以直接生成一个自包含的、签名的本地Token(如JWT)存入Cookie。局部会话的过期时间应小于或等于全局会话的过期时间,以确保当用户从认证中心注销时,所有子系统的会话都能自然失效或通过其他机制被清理。

4. 完整实操流程与核心代码实现

4.1 环境准备与依赖配置

假设我们使用Java Spring Boot生态来实现。我们需要准备:

  1. 认证中心服务:一个独立的Spring Boot应用。
  2. 业务系统A:另一个Spring Boot应用。
  3. Redis:用于共享会话存储。
  4. 共享配置:业务系统与认证中心共享一个密钥,用于票据签名。

Maven依赖(认证中心与业务系统类似)

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> </dependency> <!-- 用于JWT或HMAC签名 --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.11.5</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency>

4.2 认证中心核心实现

1. 登录接口与全局会话创建

@RestController @RequestMapping("/auth") public class AuthController { @Autowired private StringRedisTemplate redisTemplate; private final String SECRET_KEY = "your-shared-secret-key"; private final long GLOBAL_SESSION_TIMEOUT = 1800; // 30分钟 @PostMapping("/login") public ResponseEntity<?> login(@RequestBody LoginRequest request, HttpServletResponse response) { // 1. 验证用户名密码(略) User user = userService.authenticate(request.getUsername(), request.getPassword()); if (user == null) { return ResponseEntity.status(401).body("Invalid credentials"); } // 2. 创建全局会话ID String globalSessionId = UUID.randomUUID().toString(); String sessionKey = "global:session:" + globalSessionId; // 存储用户核心信息到Redis Map<String, String> sessionMap = new HashMap<>(); sessionMap.put("userId", user.getId()); sessionMap.put("username", user.getUsername()); redisTemplate.opsForHash().putAll(sessionKey, sessionMap); redisTemplate.expire(sessionKey, GLOBAL_SESSION_TIMEOUT, TimeUnit.SECONDS); // 3. 设置认证中心Cookie Cookie sessionCookie = new Cookie("GSESSIONID", globalSessionId); sessionCookie.setDomain(".sso.com"); // 注意顶级域设置 sessionCookie.setPath("/"); sessionCookie.setHttpOnly(true); sessionCookie.setSecure(true); // 生产环境启用 sessionCookie.setMaxAge((int) GLOBAL_SESSION_TIMEOUT); // 根据跨站需求设置SameSite,这里设为Lax response.addHeader("Set-Cookie", String.format("GSESSIONID=%s; Domain=.sso.com; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=%d", globalSessionId, GLOBAL_SESSION_TIMEOUT)); return ResponseEntity.ok().body(Map.of("message", "Login successful")); } }

2. 票据颁发接口这个接口通常在用户已登录(携带GSESSIONID Cookie)后,由业务系统重定向触发访问。

@GetMapping("/ticket") public ResponseEntity<?> issueTicket(@CookieValue(value = "GSESSIONID", required = false) String gsessionid, @RequestParam("service") String serviceUrl, HttpServletRequest request) { if (gsessionid == null) { // 没有全局会话,重定向到登录页,并带上回调地址 return ResponseEntity.status(302) .header("Location", "/login?redirect=" + URLEncoder.encode(serviceUrl, "UTF-8")) .build(); } // 验证全局会话是否存在 String sessionKey = "global:session:" + gsessionid; if (!redisTemplate.hasKey(sessionKey)) { // 会话过期,同样重定向到登录页 return ResponseEntity.status(302) .header("Location", "/login?redirect=" + URLEncoder.encode(serviceUrl, "UTF-8")) .build(); } // 获取用户信息 String userId = (String) redisTemplate.opsForHash().get(sessionKey, "userId"); // 生成一次性票据 String ticket = generateTicket(userId); // 存储票据,用于后续验证,设置短时间过期(如10秒) String ticketKey = "ticket:" + ticket; redisTemplate.opsForValue().set(ticketKey, userId, Duration.ofSeconds(10)); // 重定向回业务系统,携带票据 String redirectUrl = serviceUrl + (serviceUrl.contains("?") ? "&" : "?") + "ticket=" + URLEncoder.encode(ticket, "UTF-8"); return ResponseEntity.status(302).header("Location", redirectUrl).build(); } private String generateTicket(String userId) { // 使用JWT或自定义格式,这里用简单示例 String ticketId = UUID.randomUUID().toString(); // 实际应包含签名 return ticketId; }

3. 票据验证接口这是一个供业务系统后台调用的内部API。

@PostMapping("/validate") public ResponseEntity<?> validateTicket(@RequestBody ValidateRequest request) { String ticket = request.getTicket(); String ticketKey = "ticket:" + ticket; // 1. 检查票据是否存在且未过期 String userId = redisTemplate.opsForValue().get(ticketKey); if (userId == null) { return ResponseEntity.status(401).body(Map.of("valid", false, "message", "Invalid or expired ticket")); } // 2. 获取用户信息(从全局会话或用户库) String sessionKey = "global:session:*" + userId; // 简化查询,实际需维护ticket到session的映射或从用户库查 // 假设我们能通过userId找到会话 // ... Map<String, String> userInfo = new HashMap<>(); userInfo.put("userId", userId); userInfo.put("username", "testUser"); // 3. **关键步骤:删除票据,确保一次性使用** redisTemplate.delete(ticketKey); return ResponseEntity.ok().body(Map.of("valid", true, "user", userInfo)); }

4.3 业务系统客户端集成

业务系统需要实现一个过滤器或拦截器,用于拦截未认证的请求。

SSO客户端过滤器

@Component public class SsoClientFilter extends OncePerRequestFilter { @Value("${sso.auth-center-url}") private String authCenterUrl; // 认证中心地址,如 https://auth.sso.com @Value("${sso.app-service-url}") private String appServiceUrl; // 当前业务系统地址,如 https://app1.sso.com @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { // 1. 检查当前请求是否已登录(存在本地会话) HttpSession session = request.getSession(false); if (session != null && session.getAttribute("user") != null) { chain.doFilter(request, response); return; } // 2. 检查请求中是否携带认证中心发回的票据 String ticket = request.getParameter("ticket"); if (ticket != null && !ticket.isEmpty()) { // 3. 后台向认证中心验证票据 UserInfo userInfo = validateTicketWithAuthCenter(ticket); if (userInfo != null) { // 4. 票据有效,创建本地会话 HttpSession newSession = request.getSession(true); newSession.setAttribute("user", userInfo); // 可选:设置本地Cookie // 重定向到原始请求(去除ticket参数),避免URL中残留票据 String originalUrl = removeTicketParam(request.getRequestURL().toString(), request.getQueryString()); response.sendRedirect(originalUrl); return; } else { // 票据无效,重定向到认证中心登录 redirectToAuthCenter(request, response); return; } } // 5. 既无本地会话,也无票据,重定向到认证中心 redirectToAuthCenter(request, response); } private UserInfo validateTicketWithAuthCenter(String ticket) { // 使用RestTemplate或HttpClient调用认证中心的 /auth/validate 接口 // 传递共享密钥或使用其他安全机制 // 返回用户信息或null // 示例伪代码 try { RestTemplate restTemplate = new RestTemplate(); ValidateRequest req = new ValidateRequest(ticket); ResponseEntity<ValidateResponse> resp = restTemplate.postForEntity( authCenterUrl + "/auth/validate", req, ValidateResponse.class ); if (resp.getStatusCode().is2xxSuccessful() && resp.getBody().isValid()) { return resp.getBody().getUser(); } } catch (Exception e) { logger.error("Ticket validation failed", e); } return null; } private void redirectToAuthCenter(HttpServletRequest request, HttpServletResponse response) throws IOException { String currentUrl = appServiceUrl + request.getRequestURI(); String queryString = request.getQueryString(); if (queryString != null) { currentUrl += "?" + queryString; } String encodedUrl = URLEncoder.encode(currentUrl, "UTF-8"); String authUrl = authCenterUrl + "/auth/ticket?service=" + encodedUrl; response.sendRedirect(authUrl); } private String removeTicketParam(String url, String queryString) { // 实现去除ticket参数的逻辑 if (queryString == null || !queryString.contains("ticket=")) return url; // ... 解析并重组URL return url.split("\\?")[0]; // 简化处理 } }

业务系统配置application.yml中:

sso: auth-center-url: https://auth.sso.com app-service-url: https://app1.sso.com shared-secret: your-shared-secret-key # 用于签名验证

4.4 注销的联动处理

单点登录必须配套单点注销。用户在任何一个系统点击退出,应该通知认证中心,并由认证中心通知所有已登录的业务系统。

  1. 认证中心注销接口:接收业务系统的注销请求,根据全局Session ID,找到该用户登录的所有业务系统记录(需要在用户登录时记录),然后向每个业务系统的注销回调接口发送异步通知。
  2. 业务系统注销回调接口:接收认证中心通知,根据传递的用户标识,销毁本地Session。
  3. 前端跳转:业务系统前端收到退出请求后,先调用本地退出接口清除本地Cookie/Session,然后重定向到认证中心的全局注销接口,认证中心执行上述通知逻辑后,再重定向回业务系统或登录页。

这是一个相对复杂的分布式事务,为了简化,很多实现采用“被动过期”策略:即业务系统的本地Session设置一个较短的过期时间,依赖认证中心的全局Session过期。当用户关闭浏览器或全局Session超时后,所有局部会话也会陆续失效。但这并非严格意义上的实时单点注销。

5. 常见问题、排查技巧与进阶考量

5.1 典型问题与解决方案速查表

问题现象可能原因排查步骤与解决方案
登录后无限重定向到认证中心1. 票据验证失败。
2. 业务系统本地Cookie设置失败(SameSite、Secure属性问题)。
3. 认证中心Cookie未成功种上或域设置错误。
1. 检查浏览器开发者工具的NetworkApplication标签页,查看重定向链条和Cookie设置情况。
2. 确认认证中心和业务系统的域名关系,检查Set-Cookie头中的DomainSecureSameSite属性是否符合预期。
3. 在业务系统后端日志中,查看票据验证接口的调用是否成功,解析返回结果。
在Chrome等高版本浏览器中跨域登录失败Chrome默认的SameSite=Lax策略阻止了跨站POST请求携带Cookie。1. 确保认证中心和业务系统使用HTTPS。
2. 将认证中心Cookie的SameSite显式设置为None,并必须同时设置Secure=true
3. 考虑将登录表单的提交改为由认证中心域下的页面处理,避免跨站POST。
票据验证报“无效签名”或“票据已使用”1. 认证中心与业务系统使用的共享密钥不一致。
2. 票据验证后未及时删除,导致重复验证失败。
3. 系统间时间不同步,导致票据过期时间判断有误。
1. 核对双方的密钥配置。
2. 检查认证中心/validate接口中,验证成功后删除票据的代码逻辑是否执行。
3. 确保所有服务器使用NTP进行时间同步。
用户在一个系统退出后,其他系统仍能访问单点注销未正确实现。1. 检查认证中心的全局注销逻辑是否触发了对所有业务系统的回调通知。
2. 检查业务系统的注销回调接口是否正常工作,能否正确销毁本地Session。
3. 考虑引入更复杂的会话管理机制,如将Session ID存入Redis并设置过期,每次请求校验。
性能瓶颈出现在票据验证接口每个业务系统的每次登录,都需要调用一次认证中心的验证接口,高并发下压力大。1. 在业务系统端对验证结果进行短期缓存(如缓存5分钟),用票据ID作为Key。注意平衡缓存时间与安全性。
2. 优化认证中心验证接口的性能,如使用更快的签名算法、优化Redis查询。

5.2 安全加固要点

  1. 防CSRF:虽然SSO流程本身涉及重定向,但业务系统自身的敏感操作仍需配备CSRF Token防护。
  2. 防重放攻击:确保票据一次性使用且超时时间极短(如10秒),并在验证后立即失效。
  3. 敏感操作复核:对于修改密码、支付等极高风险操作,即使处于SSO登录态,也应要求用户再次输入密码或进行二次验证。
  4. HTTPS强制:整个SSO流程,包括认证中心和所有业务系统,必须全程使用HTTPS,防止Cookie和票据在传输中被窃听。
  5. 监控与审计:记录所有登录、票据颁发与验证、注销事件,便于安全审计和异常行为分析。

5.3 方案局限性及适用场景

基于Cookie的SSO方案有其清晰的边界:

  • 优点:原理简单,实现直接,对浏览器兼容性好(除现代SameSite策略需额外处理),非常适合同一顶级域下的子系统(如app1.company.com,app2.company.com,auth.company.com)。
  • 缺点
    • 跨域限制:虽然通过重定向和票据传递解决了登录态共享,但Cookie本身受同源策略限制。对于完全不同的域名(如www.a.comwww.b.com),方案会变得复杂,通常需要借助隐藏iframe或前端JavaScript进行跨域通信,实现难度和风险增加。
    • 对非Web客户端不友好:移动端App、桌面客户端、API调用等无法直接利用浏览器Cookie机制,需要适配其他方案(如OAuth 2.0的授权码模式)。
    • 注销联动复杂:实现完美的实时单点注销需要复杂的通知机制。

因此,这个方案最适合的场景是:企业内部系统、平台型产品的后台管理系统、所有子系统共享同一个父域名的Web应用集群。如果你的系统需要面向公众、跨完全不同的域名、或需要支持多种类型的客户端,那么OAuth 2.0/OpenID Connect是更通用和标准的选择。

5.4 从简单到复杂的演进路径

在实际项目中,技术选型往往不是非此即彼。你可以从简单的基于Cookie的SSO开始,随着业务发展逐步演进:

  1. 初期:所有系统在同一个域下,使用上述方案快速落地。
  2. 发展期:部分系统拆分到新域名。此时可以保留认证中心,让新域名系统通过前端JavaScript与认证中心跨域通信(使用CORS或PostMessage)来获取登录态,或者采用更标准的OAuth 2.0授权码模式。
  3. 平台期:需要对外开放API或对接第三方登录。此时可以引入完整的OAuth 2.0和OpenID Connect服务,将原有的认证中心升级为统一的身份提供商,同时兼容老的Cookie SSO协议和新的OIDC协议。

理解基于Cookie的SSO,不仅是掌握一种具体的实现,更是深入理解了Web单点登录最本质的“信任传递”思想。无论后续技术栈如何变化,这份对核心流程和安全要点的把握,都能让你在构建任何身份认证系统时游刃有余。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/12 11:44:49

INT4量化大语言模型本地部署指南:从Hugging Face下载到交互式对话

在实际的 AI 模型应用和部署场景中&#xff0c;我们经常遇到一个核心矛盾&#xff1a;如何在资源受限的环境下&#xff0c;依然能够运行一个性能尚可的大语言模型。本地部署、边缘计算、移动端集成等需求&#xff0c;使得对模型进行量化压缩成为一项关键技术。inclusionAI/Ling…

作者头像 李华
网站建设 2026/8/12 11:41:40

从插件到站点:AI驱动开发范式转向与Codex Sites实战部署

1. 从“插件”到“站点”&#xff1a;一次开发范式的悄然转向最近在开发者圈子里&#xff0c;一个词的热度正在悄然攀升&#xff1a;Codex Sites。如果你和我一样&#xff0c;常年混迹于各种技术社区&#xff0c;会发现围绕“Codex”的讨论&#xff0c;正从“如何安装插件”、“…

作者头像 李华
网站建设 2026/8/12 11:40:55

5个理由告诉你为什么AutoDock Vina是分子对接的首选工具

5个理由告诉你为什么AutoDock Vina是分子对接的首选工具 【免费下载链接】AutoDock-Vina AutoDock Vina 项目地址: https://gitcode.com/gh_mirrors/au/AutoDock-Vina 如果你正在寻找一款能够显著提升药物研发效率的计算工具&#xff0c;AutoDock Vina绝对是你的最佳选择…

作者头像 李华
网站建设 2026/8/12 11:38:51

计算机毕业设计之基于Spring Boot+Vue的电影院订票系统

随着科技发展和人们生活水平提高&#xff0c;电影观影成为重要娱乐方式&#xff0c;传统电影院订票方式效率低、问题多&#xff0c;无法满足现代消费者便捷、高效、个性化的服务需求。同时&#xff0c;互联网、移动智能终端及云计算、大数据等技术的广泛应用&#xff0c;为在线…

作者头像 李华
网站建设 2026/8/12 11:37:47

具身智能核心技术栈解析与开发者学习路线指南

最近在技术圈和投资圈&#xff0c;一个词的热度持续攀升&#xff1a;具身智能。从学术论文到创业公司&#xff0c;从大厂战略到资本动向&#xff0c;这个领域正以前所未有的速度吸引着全球的目光。而近期&#xff0c;国内机器人领域的明星公司“宇树科技”传出上市消息&#xf…

作者头像 李华
网站建设 2026/8/12 11:36:48

基于MQTT与SpringBoot构建实验室设备数据采集与实时监控系统

1. 项目概述与核心价值 最近在实验室里折腾设备数据采集&#xff0c;发现很多老设备还在用串口、Modbus这类传统协议&#xff0c;数据孤岛现象严重&#xff0c;想实时看个温湿度曲线都得手动记录&#xff0c;效率太低。正好手头有个项目&#xff0c;需要把十几台不同品牌、不同…

作者头像 李华