单点登录到底是什么?

单点登录到底是什么?

从 Cookie 到 JWT ,从 Session 到 OAuth2

一篇 图解 + 实战 的 完整入门手册


目录

  1. 导语:为什么要有 SSO
  2. 核心概念
  3. SSO 分类与对比
  4. Cookie-Based SSO 详解
  5. Token-Based SSO 详解
  6. OAuth2 + OpenID Connect 流程
  7. JWT 实战(Spring Boot + Redis)
  8. 常见安全问题 & 防护
  9. 总结:一张图看懂 SSO

导语:为什么要有 SSO?

没有 SSO 的场景:

  • 公司 5 个系统 → 用户记 5 套账号 → IT 部门天天重置密码
  • 员工离职 → 5 个系统分别禁用账号 → 漏一个 = 安全风险

单点登录(SSO) :一次登录,全网通行 ;一次退出,全网失效。


核心概念

概念 说明
CAS Central Authentication Service(中央认证服务)
Token 登录后颁发的 凭证(JWT、Ticket)
TGT Ticket Granting Ticket(CAS 术语)
ST Service Ticket(CAS 术语)
IdP Identity Provider(身份提供者)
SP Service Provider(服务提供者)

SSO 分类与对比

类型 存储 代表协议 适用场景
Cookie-Based 浏览器 Cookie CAS 2.0/3.0 同域名
Token-Based Header/LocalStorage JWT/OIDC 跨域、前后端分离
OAuth2 Access Token OAuth2 + OIDC 第三方登录

1. 架构图(同域名)

2. 关键点

  • Cookie Domain :.example.com → 二级域名共享
  • Token 验证 :各系统向 SSO 中心 校验(Redis/JWT)
  • 退出:SSO 中心清除 Cookie + 各系统清除本地 Session

Token-Based SSO 详解

1. 跨域场景(前后端分离)

sequenceDiagram participant U as 浏览器 participant F as 前端A participant B as 后端A participant S as SSO中心 participant F2 as 前端B participant B2 as 后端B F->>S: POST /api/login {user,pwd} S->>F: 200 {token: jwt} F->>B: GET /api/user Header: Authorization: Bearer jwt B->>B: 验证 JWT → 成功 F2->>S: GET /api/user Header: Authorization: Bearer jwt S->>F2: 200 {userInfo}

2. JWT 结构

css 复制代码
Header.Base64.Payload.Base64.Signature
  • Header :{"alg":"HS256","typ":"JWT"}
  • Payload :{"sub":"alice","exp":1719999999}
  • Signature :HMACSHA256(header + "." + payload, secret)

OAuth2 + OpenID Connect 流程

1. 授权码模式(最常用)

sequenceDiagram participant U as 用户 participant C as 客户端 participant G as Google(IDP) participant R as Resource Server U->>C: 点击"用 Google 登录" C->>G: 302 → authorize?client_id=xxx&response_type=code&scope=openid U->>G: 登录并同意授权 G->>C: 302 → redirect_uri?code=AUTH_CODE C->>G: POST /token {code, client_secret} G->>C: 200 {access_token, id_token(JWT)} C->>R: GET /userinfo Header: Bearer access_token R->>C: 200 {sub: "alice", name: "Alice"}

JWT 实战(Spring Boot + Redis)

1. 项目结构

css 复制代码
src
 ├─ config/JwtConfig.java
 ├─ controller/LoginController.java
 ├─ filter/JwtFilter.java
 └─ SpringBootSsoApplication.java

2. 生成 JWT

java 复制代码
@Component
public class JwtProvider {

    private final String secret = "demoSecretKey";
    private final long validity = 3600_000; // 1h

    public String generateToken(String username) {
        return Jwts.builder()
                .setSubject(username)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + validity))
                .signWith(SignatureAlgorithm.HS256, secret)
                .compact();
    }

    public boolean validateToken(String token) {
        try {
            Jwts.parser().setSigningKey(secret).parseClaimsJws(token);
            return true;
        } catch (JwtException e) {
            return false;
        }
    }
}

3. 过滤器验证

java 复制代码
@Component
public class JwtFilter extends OncePerRequestFilter {

    @Autowired
    private JwtProvider provider;

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        String header = request.getHeader("Authorization");
        if (header != null && header.startsWith("Bearer ")) {
            String token = header.substring(7);
            if (provider.validateToken(token)) {
                String username = Jwts.parser()
                                      .setSigningKey(provider.secret)
                                      .parseClaimsJws(token)
                                      .getBody()
                                      .getSubject();
                SecurityContextHolder.getContext().setAuthentication(
                        new UsernamePasswordAuthenticationToken(username, null, Collections.emptyList()));
            }
        }
        filterChain.doFilter(request, response);
    }
}

4. 登录接口

java 复制代码
@RestController
public class LoginController {

    @Autowired
    private JwtProvider provider;

    @PostMapping("/login")
    public Map<String, Object> login(@RequestBody LoginRequest req) {
        // 省略密码校验
        String token = provider.generateToken(req.getUsername());
        return Map.of("token", token);
    }
}

常见安全问题 & 防护

攻击 防护
Token 泄露 HTTPS + 短有效期
XSS 窃取 HttpOnly Cookie
CSRF SameSite=Strict + CSRF Token
重放攻击 JWT 过期时间 + Redis 黑名单

总结:一张图看懂 SSO

graph TD A[浏览器] -->|登录| B[SSO中心] B -->|颁发 Token| A A -->|携带 Token| C[系统A] A -->|携带 Token| D[系统B] C -->|验证 Token| B D -->|验证 Token| B

口诀 :一次登录,全网通行;一次退出,全网失效;Token 短,HTTPS 长。

相关推荐
苍何5 小时前
开源微信流 Windows,微信聊天记录,可以直接给 Codex 和 Obsidan 了
后端
代码什么用6 小时前
Spring基础使用
java·后端·spring
Su米苏6 小时前
Spring AI 中MCP 与普通 @Tool的区别
java·人工智能·spring
明月_清风6 小时前
Deno 终局来了:从挑战 Node 到被 Cloudflare 收编
前端·后端·node.js
码云数智-园园7 小时前
unique_ptr 还是 shared_ptr?C++ 智能指针选型与内存泄漏实战分析
java·开发语言
老板一杯拿铁7 小时前
Java 异常体系详解:从字节码原理到最佳实践
java
蜗牛互联网7 小时前
Java 17 HttpClient调用文件转写API的超时与失败回退
java·人工智能·后端
禾小西7 小时前
07丨Redis 哨兵机制:主库故障后,如何恢复服务?
java·开发语言·redis
我不是阵雨7 小时前
JDK 21虚拟线程Pinning陷阱:一文拔钉解困
java·开发语言
小卡车5558 小时前
java反射、自定义注解的应用(自定义分页)
java