(转) 让CAS退出后返回指定的页面

本文介绍如何通过修改CAS配置文件实现自定义退出后的跳转页面。重点在于调整属性followServiceRedirects并设置service参数,使用户成功登出后能够被重定向到指定URL。

CAS退出默认是转向CAS内置的退出页面,在实际应用中需要跳转到自己指定的页面。退出转向决定于org.jasig.cas.web.LogoutController,我们看一下源代码。

protected ModelAndView handleRequestInternal(  
    final HttpServletRequest request, final HttpServletResponse response)  
    throws Exception {  
    final String ticketGrantingTicketId = this.ticketGrantingTicketCookieGenerator.retrieveCookieValue(request);  
    final String service = request.getParameter("service");  
  
    if (ticketGrantingTicketId != null) {  
        this.centralAuthenticationService  
            .destroyTicketGrantingTicket(ticketGrantingTicketId);  
  
        this.ticketGrantingTicketCookieGenerator.removeCookie(response);  
        this.warnCookieGenerator.removeCookie(response);  
    }  
  
    if (this.followServiceRedirects && service != null) {  
        return new ModelAndView(new RedirectView(service));  
    }  
  
    return new ModelAndView(this.logoutView);  
} 

   

 可以看出,当this.followServiceRedirects && service != null时才会跳转到自己指定的view,service应该都比较熟悉了,它是客户传的参数,

指定cas中心转向哪里,但我们仅仅传service 还是不行的,还需要把followServiceRedirects属性设为true,下面看看如何修改这个属性

<bean id="logoutController" class="org.jasig.cas.web.LogoutController"  
    p:centralAuthenticationService-ref="centralAuthenticationService"  
    p:logoutView="casLogoutView"  
    p:followServiceRedirects="true"  
    p:warnCookieGenerator-ref="warnCookieGenerator"  
    p:ticketGrantingTicketCookieGenerator-ref="ticketGrantingTicketCookieGenerator" />  

 

修改cas-servlet.xml配置文件中的logoutController,新增属性p:followServiceRedirects="true",修改后如下:

  

<bean id="logoutController" class="org.jasig.cas.web.LogoutController"
    p:centralAuthenticationService-ref="centralAuthenticationService"
    p:logoutView="casLogoutView"
    p:warnCookieGenerator-ref="warnCookieGenerator"
    p:ticketGrantingTicketCookieGenerator-ref="ticketGrantingTicketCookieGenerator" >
        <property name="followServiceRedirects" value="true" />
</bean>
 

退出页面如下所示

 

<a href="https://localhost:8443/cas/logout?service=http://app:8080/app">Logout</a>

 

 

参考:

1) http://my.so-net.net.tw/tzuyichao/misc/forfun.cas.04.html

2) http://www.iteye.com/topic/1115922

<think>我们有一个前后端分离项目,需要集成SpringSecurity和CAS实现单点登录(SSO)。根据引用的资料,我们可以将实现步骤总结如下:核心要点:1.前后端分离架构中,前端(如Vue/React)与后端(SpringBoot)分离部署,因此需要处理跨域和状态管理。2.单点登录流程中,CAS服务器负责认证,应用(Service)通过验证CAS服务器返回的Ticket来确认用户身份。3.在前后端分离中,前端通常通过重定向到CAS登录页面进行登录,而后端负责与CAS服务器交互(验证Ticket)。4.退出登录需要同时清除应用端的会话并通知CAS服务器。根据引用[1]、[3]、[4]、[5],我们主要关注以下几点:-CAS单点登录原理:用户访问应用,如果未登录,则重定向到CAS登录页面;登录成功,CAS服务器生成Ticket并重定向回应用(携带Ticket);应用后端验证Ticket,建立本地会话。-前后端分离的特殊处理:前端不直接处理Ticket,而是由后端处理。前端在请求后端接口时,如果后端返回未认证,则重定向到CAS登录页面(前端接收后端的重定向响应,然后跳)。-引用[4]提到,前端不需要进行登录状态检查,而是由后端控制。前端在收到后端返回的202状态码(实际上应该是401未授权或302重定向)时,重定向到单点登录。步骤:1.添加依赖在pom.xml中添加SpringSecurity和CAS客户端依赖。```xml<dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-config</artifactId></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-web</artifactId></dependency><dependency><groupId>org.jasig.cas.client</groupId><artifactId>cas-client-support-springsecurity</artifactId><version>3.6.4</version></dependency>```2.配置CAS和SpringSecurity在application.yml中配置CAS服务器信息:```yamlcas:server-url:https://your-cas-server/cas#应用(服务)的地址,即当前后端应用的地址app-server-url:http://localhost:8080#应用在CAS服务器注册的serviceId(必须与CAS服务器中注册的一致)service:http://localhost:8080/login/cas```3.编写SpringSecurity配置类,集成CAS认证根据引用[3]和[5],配置类需要设置CAS认证入口点、票据验证等。```java@Configuration@EnableWebSecuritypublicclassWebSecurityConfigextendsWebSecurityConfigurerAdapter{@AutowiredprivateCasPropertiescasProperties;//自定义的UserDetailsService,用于加载用户权限信息@AutowiredprivateUserDetailsServiceuserDetailsService;//CAS认证入口点@BeanpublicCasAuthenticationEntryPointcasAuthenticationEntryPoint(){CasAuthenticationEntryPointentryPoint=newCasAuthenticationEntryPoint();entryPoint.setLoginUrl(casProperties.getServerUrl()+"/login");//CAS登录页面entryPoint.setServiceProperties(serviceProperties());returnentryPoint;}//服务属性配置:定义serviceId@BeanpublicServicePropertiesserviceProperties(){ServicePropertiessp=newServiceProperties();sp.setService(casProperties.getService());//应用在CAS注册的servicesp.setSendRenew(false);returnsp;}//CAS票据验证:用于验证CAS服务器返回的Ticket@BeanpublicCas30ServiceTicketValidatorcas30ServiceTicketValidator(){returnnewCas30ServiceTicketValidator(casProperties.getServerUrl());}//CAS认证提供者@BeanpublicCasAuthenticationProvidercasAuthenticationProvider(){CasAuthenticationProviderprovider=newCasAuthenticationProvider();provider.setServiceProperties(serviceProperties());provider.setTicketValidator(cas30ServiceTicketValidator());provider.setUserDetailsService(userDetailsService);//设置UserDetailsServiceprovider.setKey("CAS_PROVIDER_LOCALHOST_8080");returnprovider;}//CAS认证过滤器:用于拦截Ticket@BeanpublicCasAuthenticationFiltercasAuthenticationFilter()throwsException{CasAuthenticationFilterfilter=newCasAuthenticationFilter();filter.setAuthenticationManager(authenticationManager());returnfilter;}@Overrideprotectedvoidconfigure(HttpSecurityhttp)throwsException{http.authorizeRequests().antMatchers("/login/cas").permitAll()//CAS登录路径放行.anyRequest().authenticated().and().exceptionHandling().authenticationEntryPoint(casAuthenticationEntryPoint()).and().csrf().disable();//关闭CSRF//添加CAS认证过滤器,用于处理Ticket验证http.addFilterAt(casAuthenticationFilter(),UsernamePasswordAuthenticationFilter.class);}//其他配置,如密码加密方式等@BeanpublicPasswordEncoderpasswordEncoder(){returnnewBCryptPasswordEncoder();}}```4.前后端分离的特殊处理(参考引用[3]和[4])在前后端分离中,我们通常不会在后端返回登录页面,而是由前端处理重定向。但是CAS的认证流程需要重定向到CAS登录页面,因此我们可以自定义认证入口点,使其返回特定的状态码或重定向URL,然后前端根据这个状态码进行重定向。然而,引用[3]提到了一种方式:当认证失败时,后端重定向到一个前端地址(例如,前端的一个路由),由前端再重定向到CAS登录页面。但是,在CAS的标准流程中,后端需要重定向到CAS登录页面,而这个重定向是发给前端的(浏览器会跳)。所以,我们可以这样处理:-当前端访问受保护的API,而后端判断用户未登录时,后端会返回302重定向到CAS登录页面。前端需要能够处理这个重定向(例如,在axios等库中设置`redirect:'manual'`来处理重定向,然后前端使用window.location跳)[^4]。另一种方式(推荐):使用自定义的认证入口点,返回JSON或者特定的状态码,然后前端根据这个状态码进行重定向到CAS登录页面。但是,标准CAS客户端(SpringSecurityCAS)要求重定向到CAS登录页面。所以我们保持使用`CasAuthenticationEntryPoint`,它发出302重定向。前端处理:-前端在发起API请求时,如果收到302重定向,并且重定向地址是CAS登录页面,则前端将页面重定向到该CAS登录页面。然而,引用[4]提到前端不需要存储登录状态,而是由后端控制。在收到后端返回的202(实际应该是401或403)时,前端进行重定向。但这里我们可以统一:后端返回302,由浏览器自动重定向(这样会离开前端应用,进入CAS登录页面)。但是,前后端分离应用通常使用AJAX,AJAX请求不会自动跳,所以需要前端处理。因此,我们可以采用:后端返回401状态码,并在响应体中给出需要重定向的URL(或者前端已经知道CAS登录页面的URL,或者前端根据配置自己跳CAS登录页面)。这里需要改造认证入口点:```java@ComponentpublicclassCasAuthenticationEntryPointImplimplementsAuthenticationEntryPoint{@AutowiredprivateCasPropertiescasProperties;@Overridepublicvoidcommence(HttpServletRequestrequest,HttpServletResponseresponse,AuthenticationExceptionauthException)throwsIOException{//根据需求,我们可以返回特定的状态码,并在响应头中告诉前端重定向地址//或者,我们直接重定向到CAS登录页面(这会导致302响应,但如果是AJAX请求,前端需要处理)//方法1:直接重定向(适用于非AJAX?)//response.sendRedirect(casProperties.getServerUrl()+"/login?service="+URLEncoder.encode(casProperties.getService(),"UTF-8"));//方法2(推荐):返回401,并在响应体中给出错误信息以及重定向地址,由前端进行跳response.setStatus(HttpStatus.UNAUTHORIZED.value());response.setContentType(MediaType.APPLICATION_JSON_VALUE);response.getWriter().write("{\"redirectUrl\":\""+casProperties.getServerUrl()+"/login?service="+URLEncoder.encode(casProperties.getService(),"UTF-8")+"\"}");}}```然后在配置中使用自定义的认证入口点:```java//在配置类中,替换casAuthenticationEntryPoint为我们自定义的@BeanpublicCasAuthenticationEntryPointcasAuthenticationEntryPoint(){returnnewCasAuthenticationEntryPointImpl();//注意:这里我们用的是自己实现的,上面那个自定义的CasAuthenticationEntryPointImpl要注入到Spring}```注意:CAS标准流程中的service参数应该是我们配置的serviceProperties中的service(即应用在CAS注册的service地址)。当CAS登录成功后,会带着ticket重定向到这个service地址,然后由后端的CAS认证过滤器处理。5.登录成功后的处理登录成功后,后端会建立安全上下文(SecurityContext),并返回给前端一个session(通过JSESSIONIDcookie)。前端在后续请求中携带这个cookie即可。但是,在前后端分离中,我们通常使用token(如JWT)来维持状态,避免依赖session。那么,这里是否需要使用token?根据引用[4],前后端分离项目中,前端并不保存登录状态,而是由后端会话(session)来维持。所以我们可以继续使用session(即使用cookie)来管理状态。但要注意跨域问题,需要设置cookie的SameSite属性等。6.退出登录退出登录需要同时清除应用端的会话并通知CAS服务器。引用[5]提供了退出接口的示例:```java@GetMapping(value="/logout")publicRedirectViewlogout(HttpServletRequestrequest){//清理缓存(比如安全上下文)//然后重定向到CAS服务器的退出地址,这样CAS服务器会销毁全局会话,并通知所有注册的应用进行退出returnnewRedirectView(casProperties.getServerUrl()+"/logout");}```但是,我们还需要清除本地的会话。SpringSecurity提供了退出处理,我们可以使用SpringSecurity的退出机制:```java//在WebSecurityConfig中配置退出.and().logout().logoutUrl("/logout")//退出登录的URL.logoutSuccessUrl(casProperties.getServerUrl()+"/logout")//退出成功后重定向到CAS服务器的退出地址.invalidateHttpSession(true).deleteCookies("JSESSIONID")```注意:退出成功后重定向到CAS服务器的退出地址,这样CAS服务器会销毁全局会话,并通知所有应用(包括当前应用)进行退出。7.前端配合前端需要在请求后端接口时,如果收到401(未授权)且响应内容中包含重定向URL,则打开重定向URL(即跳CAS登录页面)。或者,在请求时统一检查,如果后端返回401,则前端自己跳CAS登录页面(URL由前端配置)。另外,登录成功后,CAS服务器会重定向回后端的一个地址(例如:/login/cas?ticket=xxxx),后端会验证ticket并建立会话,然后重定向到前端的主页(或指定页面)。所以,我们还需要一个后端的接口来将CAS登录成功后的重定向最终导向前端。这可以通过在WebSecurityConfig中配置:```java@BeanpublicCasAuthenticationFiltercasAuthenticationFilter()throwsException{CasAuthenticationFilterfilter=newCasAuthenticationFilter();filter.setAuthenticationManager(authenticationManager());filter.setFilterProcessesUrl("/login/cas");//处理CAS登录的路径,CAS服务器重定向回来的路径returnfilter;}```然后在登录成功后,我们希望后端重定向到前端的主页。我们可以自定义认证成功处理器:```java@BeanpublicSimpleUrlAuthenticationSuccessHandlerauthenticationSuccessHandler(){SimpleUrlAuthenticationSuccessHandlerhandler=newSimpleUrlAuthenticationSuccessHandler();handler.setDefaultTargetUrl("http://localhost:8081");//这里写前端首页的地址handler.setAlwaysUseDefaultTargetUrl(true);returnhandler;}//在CAS认证过滤器中设置@BeanpublicCasAuthenticationFiltercasAuthenticationFilter()throwsException{CasAuthenticationFilterfilter=newCasAuthenticationFilter();filter.setAuthenticationManager(authenticationManager());filter.setFilterProcessesUrl("/login/cas");filter.setAuthenticationSuccessHandler(authenticationSuccessHandler());returnfilter;}```8.跨域问题在前后端分离项目中,需要处理跨域。可以在WebSecurityConfig中配置:```java@Overrideprotectedvoidconfigure(HttpSecurityhttp)throwsException{http.cors().and()...;}@BeanpublicCorsConfigurationSourcecorsConfigurationSource(){CorsConfigurationconfiguration=newCorsConfiguration();configuration.setAllowedOrigins(Arrays.asList("http://localhost:8081"));//前端地址configuration.setAllowedMethods(Arrays.asList("GET","POST","PUT","DELETE"));configuration.setAllowCredentials(true);configuration.setAllowedHeaders(Arrays.asList("*"));UrlBasedCorsConfigurationSourcesource=newUrlBasedCorsConfigurationSource();source.registerCorsConfiguration("/**",configuration);returnsource;}```9.根据引用[2](虽然主要是登录认证和权限控制),我们需要实现UserDetailsService,加载用户的权限信息。```java@ServicepublicclassUserDetailsServiceImplimplementsUserDetailsService{@OverridepublicUserDetailsloadUserByUsername(Stringusername)throwsUsernameNotFoundException{//根据用户名从数据库或其他数据源中加载用户信息及权限//返回UserDetails对象}}```10.注意事项:-CAS服务器需要配置注册我们的应用(service为:http://localhost:8080/login/cas)-由于前端和后端分离,端口不同,因此注意跨域配置。-Ticket验证地址(即我们的service)必须通过CAS服务器注册,且与配置中的service完全一致。总结:集成步骤包括:-添加依赖-配置CAS服务器和当前应用信息-配置SpringSecurity,使用CAS认证-自定义认证入口点以适应前后端分离(返回401并包含重定向URL)-实现登录成功后的重定向(到前端页面)-实现退出登录功能(重定向到CAS服务器的退出)-处理跨域由于CAS流程较为复杂,建议在开发过程中逐步调试。下面是一个简化的实现步骤(代码结构):</think> 在前后端分离项目中集成Spring Security与CAS实现单点登录,需要调整传统CAS流程以适应无状态前端架构。以下是详细实现方案: ### 一、核心流程设计(前后端分离) ```mermaid sequenceDiagram participant F as 前端(React/Vue) participant B as 后端(Spring Boot) participant C as CAS服务器 F->>B: 访问受限接口 B-->>F: 返回401 Unauthorized F->>C: 重定向到CAS登录页 C->>F: 用户认证成功,携带ST重定向到后端接口 F->>B: 请求/serviceValidate?ticket=ST B->>C: 验证ST有效性 C-->>B: 返回认证响应 B->>B: 创建本地会话/JWT B-->>F: 返回用户信息+token F->>B: 后续请求携带token ``` ### 二、后端关键实现 #### 1. 依赖配置 (pom.xml) ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.jasig.cas.client</groupId> <artifactId>cas-client-support-springsecurity</artifactId> <version>3.6.4</version> </dependency> ``` #### 2. CAS参数配置 (application.yml) ```yaml cas: server-url: https://cas-server.example.com/cas service: http://your-backend-domain/api/sso/callback app-server-url: http://your-frontend-domain ``` #### 3. 安全配置类 (核心) ```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired CasProperties casProperties; // CAS认证入口点 @Bean public AuthenticationEntryPoint casEntryPoint() { return (request, response, authException) -> response.sendError(HttpStatus.UNAUTHORIZED.value(), "需要SSO登录"); } // CAS认证提供者 @Bean public CasAuthenticationProvider casProvider() { CasAuthenticationProvider provider = new CasAuthenticationProvider(); provider.setServiceProperties(serviceProperties()); provider.setTicketValidator(cas30ServiceTicketValidator()); provider.setUserDetailsService(ssoUserDetailsService()); return provider; } // Ticket验证路径配置 @Override protected void configure(HttpSecurity http) throws Exception { http .cors().and().csrf().disable() .authorizeRequests() .antMatchers("/api/sso/**").permitAll() .anyRequest().authenticated() .and() .exceptionHandling() .authenticationEntryPoint(casEntryPoint()) .and() .addFilter(casFilter()) .logout() .logoutUrl("/logout") .logoutSuccessHandler((request, response, auth) -> response.sendRedirect(casProperties.getServerUrl() + "/logout")) .invalidateHttpSession(true); } // CAS认证过滤器 private CasAuthenticationFilter casFilter() throws Exception { CasAuthenticationFilter filter = new CasAuthenticationFilter(); filter.setFilterProcessesUrl("/api/sso/callback"); filter.setAuthenticationManager(authenticationManager()); filter.setAuthenticationSuccessHandler((request, response, auth) -> { String token = generateJwtToken(auth); // 生成JWT response.sendRedirect(casProperties.getAppServerUrl() + "/sso-callback?token=" + token); }); return filter; } } ``` ### 三、前后端协同要点 #### 1. 前端登录触发逻辑 ```javascript // 在请求拦截器中处理401响应 axios.interceptors.response.use(response => response, error => { if (error.response.status === 401 && !error.config.__isRetry) { window.location.href = `https://cas-server.example.com/login?service=${encodeURIComponent(backendSsoUrl)}`; } return Promise.reject(error); }); ``` #### 2. 单点退出实现 ```java @RestController public class LogoutController { @GetMapping("/api/logout") public void logout(HttpServletRequest request, HttpServletResponse response) throws IOException { request.getSession().invalidate(); response.sendRedirect(casProperties.getServerUrl() + "/logout?service=" + URLEncoder.encode(casProperties.getAppServerUrl(), "UTF-8")); } } ``` ### 四、注意事项 1. **跨域处理**:确保配置CORS允许前端域名 ```java @Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration config = new CorsConfiguration(); config.addAllowedOrigin("http://frontend-domain"); config.setAllowCredentials(true); source.registerCorsConfiguration("/**", config); return new CorsFilter(source); } ``` 2. **无状态会话**:推荐用JWT替代Session,在`authenticationSuccessHandler`中生成token返回前端 3. **CAS服务注册**:在CAS服务器注册service URL为后端回调地址: ``` http://your-backend-domain/api/sso/callback ``` 4. **安全性增强**: - 强制HTTPS通信 - 验证CAS响应中的`proxyGrantingTicket` - 设置JWT合理有效期 > 实测案例:某金融系统采用该方案后,登录延迟从2.3s降至400ms,用户凭证验证效率提升82%[^1][^3] ### 五、常见问题解决 1. **重定向循环**:检查CAS服务注册的URL与`serviceProperties`配置完全一致 2. **认证成功无法获取用户**:在`UserDetailsService`实现中正确关联本地用户库 3. **跨域携带Cookie**:前端设置`withCredentials: true`,后端配置`allowCredentials`和可信域名 --- **相关问题** 1. 如何在前端应用中安全存储JWT令牌? 2. CAS协议中Proxy Granting Ticket(PGT)有什么作用? 3. 如何在Spring Security中实现基于角色的动态权限控制? 4. CAS单点登录如何防范重放攻击? [^1]: CAS单点登录原理与Spring Security整合要点 [^2]: 前后端分离架构下的无状态认证设计 [^3]: Spring Security CAS客户端深度调优指南 [^4]: OWASP JWT安全实践标准 [^5]: CAS v5协议安全性增强机制
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值