前言
在企业级应用开发中,随着系统数量的不断增加,用户需要频繁登录不同系统,这不仅降低了工作效率,也增加了管理成本。单点登录(single sign-on,sso)技术应运而生,而中央认证服务(cas,central authentication service)作为一种成熟的开源单点登录解决方案,在 java 生态中得到广泛应用。本文将深入探讨 spring boot 基于 cas 实现单点登录的原理、实现方式、优缺点以及优化策略,并通过具体代码示例帮助读者快速掌握这一技术。
一、cas 实现单点登录原理
1.1 cas 核心组件
cas 系统主要由两部分组成:cas server和cas client。
- cas server:作为认证中心,负责处理用户的登录请求,验证用户身份,并发放票据。它独立于应用系统,可进行集群部署,以保证高可用性。
- cas client:集成在各个应用系统中,负责拦截用户的请求,判断用户是否已经通过认证。如果用户未认证,则将用户重定向到 cas server 进行登录;如果已认证,则允许用户访问资源。
1.2 认证流程
- 用户请求资源:用户访问集成了 cas client 的应用系统(如app1.example.com)的受保护资源。
- 未认证重定向:cas client 检测到用户未认证,生成一个 service ticket request,并将用户重定向到 cas server 的登录页面(如cas.example.com/login)。
- 用户登录:用户在 cas server 登录页面输入用户名和密码,cas server 验证用户凭据。如果验证通过,cas server 会生成一个 ticket granting ticket(tgt),并将其存储在服务器端的会话中,同时返回一个 ticket granting cookie(tgc)到用户的浏览器。
- 获取 service ticket:用户再次访问app1.example.com时,cas client 携带 tgc 向 cas server 请求 service ticket(st)。cas server 验证 tgc 的有效性后,生成 st 并返回给 cas client。
- 验证 service ticket:cas client 将 st 发送给 cas server 进行验证。如果验证成功,cas server 返回验证结果,表明用户已通过认证,cas client 则允许用户访问受保护资源。
1.3 票据机制
- ticket granting ticket(tgt):相当于用户在 cas server 的长期会话凭证,用于获取 st。tgt 包含用户的身份信息和过期时间等。
- service ticket(st):用于访问特定应用系统的临时凭证,具有一次性和时效性,每次访问资源时都需要验证。
二、spring boot 基于 cas 的实现方式
2.1 项目搭建与依赖添加
创建一个 spring boot 项目,在pom.xml
文件中添加以下依赖:
<dependencies> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-web</artifactid> </dependency> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-security</artifactid> </dependency> <dependency> <groupid>org.jasig.cas.client</groupid> <artifactid>cas-client-core</artifactid> <version>3.6.2</version> </dependency> </dependencies>
2.2 配置 cas server 和 client 信息
在application.yml
文件中配置 cas server 的地址和应用系统的相关信息:
cas: server-url-prefix: https://cas.example.com # cas server地址 client-host-url: https://app1.example.com # 应用系统地址 login-path: /login # cas server登录路径 logout-path: /logout # cas server登出路径
2.3 配置安全过滤器
创建securityconfig
类,配置 cas 认证相关的过滤器:
import org.jasig.cas.client.authentication.authenticationfilter; import org.jasig.cas.client.session.singlesignoutfilter; import org.jasig.cas.client.session.singlesignouthttpsessionlistener; import org.springframework.boot.web.servlet.filterregistrationbean; import org.springframework.boot.web.servlet.servletlistenerregistrationbean; import org.springframework.context.annotation.bean; import org.springframework.context.annotation.configuration; import org.springframework.security.config.annotation.web.builders.httpsecurity; import org.springframework.security.config.annotation.web.configuration.enablewebsecurity; import org.springframework.security.config.annotation.web.configuration.websecurityconfigureradapter; import org.springframework.web.filter.delegatingfilterproxy; @configuration @enablewebsecurity public class securityconfig extends websecurityconfigureradapter { @value("${cas.server-url-prefix}") private string casserverurlprefix; @value("${cas.client-host-url}") private string casclienthosturl; @override protected void configure(httpsecurity http) throws exception { http .csrf().disable() .authorizerequests() .antmatchers("/", "/public/**").permitall() .anyrequest().authenticated() .and() .logout() .logouturl(casserverurlprefix + "/logout") .logoutsuccessurl(casclienthosturl); } @bean public filterregistrationbean<delegatingfilterproxy> casauthenticationfilter() { filterregistrationbean<delegatingfilterproxy> registration = new filterregistrationbean<>(); delegatingfilterproxy filter = new delegatingfilterproxy("authenticationfilter"); filter.settargetfilterlifecycle(true); registration.setfilter(filter); registration.addurlpatterns("/*"); return registration; } @bean public authenticationfilter authenticationfilter() { authenticationfilter authenticationfilter = new authenticationfilter(); authenticationfilter.setcasserverloginurl(casserverurlprefix + "/login"); authenticationfilter.setservername(casclienthosturl); return authenticationfilter; } @bean public filterregistrationbean<singlesignoutfilter> singlesignoutfilter() { filterregistrationbean<singlesignoutfilter> registration = new filterregistrationbean<>(); registration.setfilter(new singlesignoutfilter()); registration.addurlpatterns("/*"); return registration; } @bean public servletlistenerregistrationbean<singlesignouthttpsessionlistener> singlesignouthttpsessionlistener() { servletlistenerregistrationbean<singlesignouthttpsessionlistener> registration = new servletlistenerregistrationbean<>(); registration.setlistener(new singlesignouthttpsessionlistener()); return registration; } }
上述配置中,authenticationfilter
用于拦截未认证的请求并将用户重定向到 cas server 进行登录;singlesignoutfilter
和singlesignouthttpsessionlistener
用于实现单点登出功能,当用户在 cas server 登出时,自动注销所有应用系统的会话。
2.4 创建受保护资源示例
创建一个简单的控制器,模拟受保护资源:
import org.springframework.web.bind.annotation.getmapping; import org.springframework.web.bind.annotation.restcontroller; @restcontroller public class protectedresourcecontroller { @getmapping("/protected") public string protectedresource() { return "this is a protected resource accessed via cas sso."; } }
三、优缺点分析
3.1 优点
- 成熟稳定:cas 作为开源项目,经过多年发展,拥有庞大的用户群体和成熟的社区支持,稳定性和可靠性高。
- 跨语言支持:不仅适用于 java 项目,还支持多种编程语言和框架,方便企业整合异构系统。
- 安全性高:采用票据机制,避免了用户密码在多个系统间传递,降低了密码泄露风险。同时支持 ssl/tls 加密传输,进一步保障数据安全。
- 易于扩展:提供丰富的插件和扩展点,可根据需求添加多因素认证、审计日志等功能。
3.2 缺点
- 部署复杂:需要独立部署 cas server,涉及服务器配置、端口映射、证书管理等操作,对运维人员要求较高。
- 性能开销:每次请求都需要与 cas server 进行票据验证,在高并发场景下可能会成为性能瓶颈。
- 用户体验问题:用户在首次登录时需要跳转到 cas server 登录页面,可能会影响用户体验。
四、需要注意的问题和难点
4.1 配置问题
- cas server 与 client 的地址配置:确保cas.server-url-prefix和cas.client-host-url配置正确,否则会导致认证失败。
- 过滤器顺序:过滤器的注册顺序很重要,需要保证authenticationfilter在其他过滤器之前执行,以确保请求先经过认证处理。
4.2 性能问题
- 票据验证效率:在高并发情况下,频繁的票据验证会对 cas server 造成较大压力。可以通过缓存票据、优化数据库查询等方式提高验证效率。
- 网络延迟:cas client 与 cas server 之间的网络通信可能会带来延迟,影响用户体验。可以通过优化网络架构、使用负载均衡等方式减少延迟。
4.3 安全问题
- 票据泄露:如果 service ticket 或 ticket granting ticket 泄露,攻击者可能会冒充用户访问资源。需要采取措施保护票据安全,如设置合理的票据过期时间、使用加密传输等。
- 跨站请求伪造(csrf):虽然 cas 在一定程度上可以防止 csrf 攻击,但仍需在应用系统中采取额外的防护措施,如添加 csrf 令牌。
五、优化策略
5.1 集群部署
将 cas server 进行集群部署,通过负载均衡器(如 nginx、apache)将请求分发到不同的 cas server 节点,提高系统的可用性和性能。同时,集群部署还可以实现会话共享,确保用户在不同节点间切换时无需重新登录。
通过 nginx 负载均衡实现 cas server 集群,提升系统可用性。配置 nginx 反向代理:
upstream cas_servers { server cas1.example.com:8443; server cas2.example.com:8443; } server { listen 443 ssl; server_name cas.example.com; ssl_certificate /path/to/cas.crt; ssl_certificate_key /path/to/cas.key; location / { proxy_pass https://cas_servers; proxy_set_header host $host; proxy_set_header x-real-ip $remote_addr; proxy_set_header x-forwarded-for $proxy_add_x_forwarded_for; proxy_set_header x-forwarded-proto $scheme; } }
5.2 票据缓存
引入缓存机制(如 redis、ehcache)对 service ticket 和 ticket granting ticket 进行缓存,减少对数据库或文件系统的访问,提高票据验证的效率。在 spring boot 中,可以使用@cacheable注解方便地实现缓存功能。
引入 redis 缓存 service ticket,减少数据库或文件系统的 i/o 操作。使用 spring cache 集成 redis:
<dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-data-redis</artifactid> </dependency>
import org.springframework.cache.annotation.enablecaching; import org.springframework.context.annotation.configuration; @configuration @enablecaching public class cacheconfig { // 配置redis缓存管理器等相关bean }
在票据验证逻辑中添加缓存注解:
import org.springframework.cache.annotation.cacheable; @cacheable("servicetickets") public boolean validateserviceticket(string serviceticket) { // 实际验证逻辑 }
5.3 优化用户体验
- 自定义登录页面:可以根据企业风格自定义 cas server 的登录页面,提升用户体验。
- 静默登录:利用 tgc 实现静默登录,当用户已经在 cas server 认证过,再次访问其他集成 cas client 的应用系统时,无需再次跳转到登录页面,直接完成认证。
结语
通过本文的介绍,我们详细了解了 spring boot 基于 cas 实现单点登录的原理、实现方式、优缺点以及优化策略,并通过具体代码示例进行了实践。cas 作为一种成熟的单点登录解决方案,能够有效解决企业多系统间的统一认证问题。在实际应用中,需要根据项目需求和特点,合理配置和优化 cas 系统,以实现安全、高效、用户友好的单点登录功能。
到此这篇关于spring boot 基于 cas 实现单点登录:原理、实践与优化全解析的文章就介绍到这了,更多相关springboot cas 单点登录内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!