如何用Java案例实现单点登录?

wen java案例 1

本文目录导读:

如何用Java案例实现单点登录?

  1. 目录导读
  2. 单点登录(SSO)核心原理与适用场景
  3. Java SSO 的技术选型与架构设计
  4. 基于Redis + Token 的轻量级SSO案例实现
  5. 基于CAS协议的企业级SSO案例实现
  6. 常见问题与解决方案(Q&A)
  7. 安全性加固与性能优化建议
  8. SSO落地的关键考量

如何用Java案例实现单点登录?从原理到实战

目录导读

  1. 单点登录(SSO)核心原理与适用场景
  2. Java SSO 的技术选型与架构设计
  3. 基于Redis + Token 的轻量级SSO案例实现
  4. 基于CAS协议的企业级SSO案例实现
  5. 常见问题与解决方案(Q&A)
  6. 安全性加固与性能优化建议
  7. SSO落地的关键考量

单点登录(SSO)核心原理与适用场景

1 什么是单点登录?

单点登录(Single Sign-On,简称SSO)是一种身份验证机制,允许用户使用一组凭据(如用户名/密码)登录一次,即可访问多个相互信任的应用程序系统,登录了公司OA系统,无需再次登录即可访问ERP、CRM等子系统。

2 工作原理

SSO的核心是建立一个独立的认证中心(Authentication Center),所有子系统将认证请求统一交给该中心处理,标准流程如下:

  1. 用户访问子系统A,未登录 → 重定向到认证中心。
  2. 用户输入凭据,认证中心验证成功 → 生成全局票据(如Token)。
  3. 认证中心将票据下发,并重定向回子系统A。
  4. 子系统A携带票据向认证中心验证,验证通过后建立本地会话。
  5. 用户访问子系统B时,B重定向到认证中心,认证中心发现用户已登录(凭票据),直接下发新票据给B。

3 适用场景

  • 企业级应用:多系统(CRM、HR、财务)统一身份管理。
  • 微服务架构:不同服务模块共享用户登录状态。
  • B2B/C SaaS平台:集成第三方系统(如GitHub、Google登录)。

Java SSO 的技术选型与架构设计

1 常见SSO实现方案对比

方案 复杂度 安全等级 适用场景
基于Cookie + 共享Session 小规模内网(不推荐)
基于Redis + Token 中小型系统,可控性高
CAS(Central Authentication Service) 大型企业/高校
OAuth2 / OIDC 中高 开放授权 + 单点登录
SAML 2.0 与老系统/退换货系统集成

2 Java技术栈推荐

  • Spring Boot:快速构建微服务。
  • Spring Security:用于认证与授权框架。
  • Redis:存储Token和会话数据(高性能 + 共享)。
  • JWT(JSON Web Token):生成无状态Token。
  • CAS Server:基于Apereo CAS的开源方案。

3 架构图解(文字描述)

[用户浏览器] ⇄ [系统A] ⇄ [认证中心(CAS/JWT验证)]
                       ⇅
                   [Redis共享]
                       ⇅
                     [系统B]

基于Redis + Token 的轻量级SSO案例实现

本案例适合中小团队快速搭建SSO,无需额外安装CAS服务器。

1 项目结构

  • sso-server:认证中心,负责登录验证、Token生成与校验。
  • client-app1client-app2:集成SSO的子系统。

2 核心代码(关键步骤)

步骤1:认证中心登录接口(sso-server)

@RestController
public class AuthController {
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    @PostMapping("/sso/login")
    public String login(@RequestParam String username, @RequestParam String password) {
        // 1. 验证用户名密码(略)
        // 2. 生成全局Token(JWT)
        String globalToken = UUID.randomUUID().toString();
        String jwt = Jwts.builder()
                .setSubject(username)
                .claim("token", globalToken)
                .setExpiration(new Date(System.currentTimeMillis() + 3600_000))
                .signWith(SignatureAlgorithm.HS256, "secret-key")
                .compact();
        // 3. 存入Redis(key=globalToken, value=username)
        redisTemplate.opsForValue().set(globalToken, username, 1, TimeUnit.HOURS);
        return jwt;
    }
    @GetMapping("/sso/verify")
    public String verify(@RequestParam String jwt) {
        try {
            Claims claims = Jwts.parser()
                    .setSigningKey("secret-key")
                    .parseClaimsJws(jwt)
                    .getBody();
            String token = (String) claims.get("token");
            // 检查Redis是否存在该token
            if (redisTemplate.hasKey(token)) {
                return "valid"; // 有效
            }
        } catch (Exception e) {
            return "invalid";
        }
        return "invalid";
    }
}

步骤2:子系统客户端拦截器(client-app1)

@Component
public class SSOInterceptor extends HandlerInterceptorAdapter {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HttpSession session = request.getSession(false);
        if (session != null && session.getAttribute("user") != null) {
            return true; // 已有本地会话
        }
        // 获取全局Token(从Cookie或URL参数)
        String globalToken = CookieUtil.getCookie(request, "global-token");
        if (globalToken == null) {
            response.sendRedirect("http://sso-server.com/sso/login?redirect=" + request.getRequestURL());
            return false;
        }
        // 调用认证中心验证
        ResponseEntity<String> resp = restTemplate.getForEntity(
                "http://sso-server.com/sso/verify?jwt=" + globalToken,
                String.class);
        if ("valid".equals(resp.getBody())) {
            // 创建本地会话
            session.setAttribute("user", getUserFromToken(globalToken));
            return true;
        }
        response.sendRedirect("http://sso-server.com/sso/login");
        return false;
    }
}

3 实现要点

  • 无状态Token:JWT自带过期时间,减少数据库查询。
  • Redis共享:所有子系统都能验证Token的有效性。
  • 回调机制:登录成功的重定向URL,自动携带票据。

基于CAS协议的企业级SSO案例实现

CAS(Central Authentication Service)是最成熟的SSO开源方案之一,适用于大规模企业。

1 环境准备

  • 下载Apereo CAS WAR包(或使用Docker)。
  • 配置SSL证书(生产环境必须HTTPS)。
  • 将CAS部署到Tomcat或Spring Boot中。

2 集成Spring Boot Client

pom.xml依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-cas</artifactId>
</dependency>

application.yml 配置

cas:
  server-url-prefix: https://cas.example.com/cas
  server-login-url: https://cas.example.com/cas/login
  client-host-url: http://client1.example.com
authentication:
  user-details:
    enabled: true

3 安全配置类

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private ServiceProperties serviceProperties;
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/login/**", "/logout/**").permitAll()
            .anyRequest().authenticated()
            .and()
            .exceptionHandling().authenticationEntryPoint(casAuthenticationEntryPoint())
            .and()
            .addFilter(casAuthenticationFilter())
            .logout().logoutSuccessUrl("/logout");
    }
    private CasAuthenticationEntryPoint casAuthenticationEntryPoint() {
        CasAuthenticationEntryPoint entryPoint = new CasAuthenticationEntryPoint();
        entryPoint.setLoginUrl("https://cas.example.com/cas/login");
        entryPoint.setServiceProperties(serviceProperties);
        return entryPoint;
    }
}

4 优势与局限

  • 优势:成熟稳定,支持Ticket验证、代理认证。
  • 局限:配置繁琐,需要独立部署CAS服务器。

常见问题与解决方案(Q&A)

Q1:登录成功但跳转回子系统后仍显示未登录?

原因:通常是票据(Ticket)未正确传递或验证失败。
解决方案

  • 检查子系统与CAS服务器的SSL证书是否一致。
  • 确保Service URL与子系统实际地址匹配(包括端口号)。
  • 查看CAS日志,确认Ticket是否被消费且未过期。

Q2:如何实现单点登出(SLO)?

实现方式

  • 基于Redis方案:子系统登出时,删除Redis中对应的全局Token,并通知其他子系统清除本地会话。
  • CAS方案:CAS支持SLO协议,登出时向所有注册的子系统发送登出请求(建议使用MQ异步通知)。

Q3:跨域问题如何处理?

解决方案

  • 使用CORS(跨域资源共享)配置允许认证中心的域名。
  • 使用Nginx反向代理统一域名(如 *.mycompany.com),避免跨域。
  • Token放在Cookie中时,设置 SameSite=None; Secure 以支持跨站传送。

Q4:Token泄露如何做安全防护?

建议措施

  • 使用HTTPS加密所有通信。
  • Token添加设备指纹(如User-Agent + IP地址)。
  • 设置较短的有效期,定期刷新(如30分钟)。
  • 敏感操作(修改密码)需二次认证(MFA)。

安全性加固与性能优化建议

1 安全加固

  • 日志审计:记录SSO登录/登出、Token校验失败事件。
  • 黑名单机制:Redis中维护被攻击的Token黑名单。
  • IP白名单:限制特定接口只允许子系统IP访问。
  • 防止重放攻击:在Token中加入Nonce(一次性随机数)。

2 性能优化

  • Redis集群:避免单点故障和性能瓶颈。
  • 数据库索引:若Token存储使用数据库,建立哈希索引。
  • 缓存User信息:子系统内缓存用户角色和权限,减少回调。
  • 异步处理:日志写入、登出通知使用异步线程。

SSO落地的关键考量

实现Java单点登录并非简单引入一个依赖,而是需要从业务需求、安全等级、团队能力三个维度做选择:

  1. 小团队快速验证 → 选 Redis + Token 方案(代码可控、成本低)。
  2. 中型多系统集成 → 推荐 OAuth2 + JWT,兼顾扩展性与授权能力。
  3. 大型企业合规需求 → 拥抱 CASSAML 2.0,同时准备好运维团队。

无论哪种方案,永远不要迷信“一次密码永不过期”,定期强制修改密码、启用MFA、监控异常访问日志,才是SSO长期稳定运行的关键。


文末提示
如果你现在准备在Java项目中落地SSO,建议先搭建一个最小原型(只用两个Spring Boot应用加Redis),验证跨应用状态共享是否顺畅,再逐步加入复杂权限控制。

抱歉,评论功能暂时关闭!