三、SpringBoot集成CAS 单点登录,CAS Client。CAS 单点登录动态开关。

CAS 简介

1、CAS 单点登录分为两个部分,第一个是认证中心 Cas Server,第二个是 Cas Server。
我们使用 SpringBoot 集成 Cas 只需要集成Client。
2、CAS API官方文档
3、cas 简介博客

序言:

简单的说下我做完 cas 集成后的经验与技巧。
首先 cas 与 oauth2 认证方式不同,cas 需要集成:pom 依赖、配置类、配置文件。而 oauth2 则是接口方式,侵入性小。
其次认证方式不同,cas 是每次请求资源路径时,判断是否拥有票据,有票就验票,验票过了就跳转,没票带着你现在请求的资源路径为参数去跳单点登录的登录页,登录成功后,cas server 带着票跳你传的地址。比如:https://ehall.edu.cn/cas?sevice=请求资源的原始路径。oauth2 的登录流程这里就不赘述了。
我的配置还是比较灵活的,CAS 可根据配置文件动态开关,拦截路径可根据配置文件指定。
大体就这些,想起来我再补充。

与传统 SpringBoot 集成 CAS Client 略有不同,我这个集成不需要在 SpringBoot 启动类上加入启用 CAS Client 的 @EnableCasClient 注解。如果加了这个注解, CAS Client是不能实现根据配置文件开关的,每次想要关掉,必须修改代码,注释调注解才行。

在这里插入图片描述

SpringBoot 集成 CAS 步骤

1、引入 CAS Client pom 依赖。
2、配置类 CasConfig 配置 CAS Filter 拦截器(配置拦截规则)。
3、配置文件中配置 CAS 相关属性。
4、获取 CAS 用户Utils 类。

一、引入 POM 依赖

版本有很多,我用的这个版本比较稳定,兼容多个版本的 CAS Server。

		<dependency>
            <groupId>org.jasig.cas.client</groupId>
            <artifactId>cas-client-core</artifactId>
            <version>3.5.0</version>
        </dependency>

二、CasConfig 配置类

import lombok.extern.slf4j.Slf4j;
import org.jasig.cas.client.authentication.AuthenticationFilter;
import org.jasig.cas.client.session.SingleSignOutFilter;
import org.jasig.cas.client.session.SingleSignOutHttpSessionListener;
import org.jasig.cas.client.util.HttpServletRequestWrapperFilter;
import org.jasig.cas.client.validation.Cas30ProxyReceivingTicketValidationFilter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
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.core.Ordered;

/**
 * @Author: 
 * @Date: 
 * @Description: CAS集成核心配置类
 */
@Configuration
@Slf4j
@ConditionalOnProperty(value = "cas.loginType", havingValue = "cas")
public class CasFilterConfig {

    /**
     * 需要走cas拦截的地址(/* 所有地址都拦截)
     */
    @Value("${cas.urlPattern:/casLogin}")
    private String filterUrl;

    /**
     * 默认的cas地址,防止通过 配置信息获取不到
     */
    @Value("${cas.server-url-prefix:http://cas.server.com:8443/cas}")
    private String casServerUrl;

    /**
     * 应用访问地址(这个地址需要在cas服务端进行配置)
     */
    @Value("${cas.authentication-url:http://localhost:8090}")
    private String authenticationUrl;

    /**
     * 应用访问地址(这个地址需要在cas服务端进行配置)
     */
    @Value("${cas.client-host-url:http://localhost:8090}")
    private String appServerUrl;

    @Bean
    public ServletListenerRegistrationBean servletListenerRegistrationBean() {
        log.info(" \n cas 单点登录配置 \n appServerUrl = " + appServerUrl + "\n casServerUrl = " + casServerUrl);
        log.info(" servletListenerRegistrationBean ");
        ServletListenerRegistrationBean listenerRegistrationBean = new ServletListenerRegistrationBean();
        listenerRegistrationBean.setListener(new SingleSignOutHttpSessionListener());
        listenerRegistrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
        return listenerRegistrationBean;
    }

    /**
     * 单点登录退出
     *
     * @return
     */
    @Bean
    public FilterRegistrationBean singleSignOutFilter() {
        log.info(" servletListenerRegistrationBean ");
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        registrationBean.setFilter(new SingleSignOutFilter());
        registrationBean.addUrlPatterns(filterUrl);
        registrationBean.addInitParameter("casServerUrlPrefix", casServerUrl);
        registrationBean.setName("CAS Single Sign Out Filter");
        registrationBean.setOrder(1);
        return registrationBean;
    }

    /**
     * 单点登录认证
     *
     * @return
     */
    @Bean
    public FilterRegistrationBean AuthenticationFilter() {
        log.info(" AuthenticationFilter ");
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        registrationBean.setFilter(new AuthenticationFilter());
        registrationBean.addUrlPatterns(filterUrl);
        registrationBean.setName("CAS Filter");
        registrationBean.addInitParameter("casServerLoginUrl", casServerUrl);
        registrationBean.addInitParameter("serverName", appServerUrl);
        registrationBean.setOrder(1);
        return registrationBean;
    }

    /**
     * 单点登录校验
     *
     * @return
     */
    @Bean
    public FilterRegistrationBean Cas30ProxyReceivingTicketValidationFilter() {
        log.info(" Cas30ProxyReceivingTicketValidationFilter ");
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        registrationBean.setFilter(new Cas30ProxyReceivingTicketValidationFilter());
        registrationBean.addUrlPatterns(filterUrl);
        registrationBean.setName("CAS Validation Filter");
        registrationBean.addInitParameter("casServerUrlPrefix", authenticationUrl);
        registrationBean.addInitParameter("serverName", appServerUrl);
        registrationBean.setOrder(1);
        return registrationBean;
    }

    /**
     * 单点登录请求包装
     *
     * @return
     */
    @Bean
    public FilterRegistrationBean httpServletRequestWrapperFilter() {
        log.info(" httpServletRequestWrapperFilter ");
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        registrationBean.setFilter(new HttpServletRequestWrapperFilter());
        registrationBean.addUrlPatterns(filterUrl);
        registrationBean.setName("CAS HttpServletRequest Wrapper Filter");
        registrationBean.setOrder(1);
        return registrationBean;
    }

}

三、yml 配置文件

注:authentication-url 是认证服务器的地址,这个地址千万不要在末尾处追加 /login,否则验票的时候会出现认证服务器无响应,验票不通过的情况。
server-url-prefix 地址配置要加/login,认证服务器地址不要加/login。

cas:
  server-url-prefix: # 认证中心登录页面地址
  client-host-url: # 应用地址,也就是自己的系统地址。
  authentication-url: # 认证中心地址
  loginType: cas # 动态开启 cas 单点登录
  urlPattern: /* # cas  验票拦截路径
# 配置示例:
cas:
  server-url-prefix: https://ehall.ba.cn/cas/login
  client-host-url: http://59.88.12.56:8090/
  authentication-url:  https://ehall.ba.cn/cas/
  loginType: cas
  urlPattern: /api/loginByNameAndCardNo  

生产环境的配置文件:

在这里插入图片描述

四、获取 CAS 用户

CAS 认证通过后,回调我们接口的时候,一般数据结构基本一致。所以我写了一个获取用户的工具类,可参考,但不一定兼容所有 CAS Server。

1.CasUtils 工具类

package com.pty.charge.common.util;

import com.pty.charge.vo.CasUserInfo;
import org.jasig.cas.client.validation.Assertion;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;

/**
 * @Author: 
 * @Date: 
 * @Description: 使用cas对接封装的cas返回的用户信息的对象
 */
public class CasUtil {

    private static final Logger LOGGER = LoggerFactory.getLogger(CasUtil.class);
    /**
     * cas client 默认的session key
     */
    public final static String CAS = "_const_cas_assertion_";

    /**
     * 封装CasUserInfo
     *
     * @param request
     * @return
     */
    public static CasUserInfo getCasUserInfoFromCas(HttpServletRequest request) {
        Object object = request.getSession().getAttribute(CAS);
        if (null == object) {
            return null;
        }
        Assertion assertion = (Assertion) object;
        return buildCasUserInfoByCas(assertion);
    }

    /**
     * 构建CasUserInfo
     *
     * @param assertion
     * @return
     */
    private static CasUserInfo buildCasUserInfoByCas(Assertion assertion) {
        if (null == assertion) {
            LOGGER.error(" Cas没有获取到用户 ");
            return null;
        }
        CasUserInfo casUserInfo = new CasUserInfo();
        String userName = assertion.getPrincipal().getName();
        LOGGER.info(" cas对接登录用户= " + userName);
        casUserInfo.setUserAccount(userName);
        //获取属性值
        Map<String, Object> attributes = assertion.getPrincipal().getAttributes();
        Object name = attributes.get("cn");
        casUserInfo.setUserName(name == null ? userName : name.toString());
        casUserInfo.setAttributes(attributes);
        return casUserInfo;
    }

}

2.CAS 用户 VO 类

package com.pty.charge.vo;

import lombok.Getter;
import lombok.Setter;

import java.util.Map;

/**
 * @Author: 
 * @Date: 
 * @Description: 返回的用户信息
 */
@Setter
@Getter
public class CasUserInfo {

    /** 用户名 */
    private String userName;
    /** 用户 */
    private String userAccount;
    /** 用户信息 */
    private Map<String, Object> attributes;

}

五、单点登录示例

/**
   * cas 单点登录
   *
   * @param request 请求头(姓名+身份证号)
   * @param ticket cas 票据
   * @return
   */
  @GetMapping(value = "/api/loginByNameAndCardNo")
  @ApiOperation("cas单点登录")
  public String loginByNameAndCardNo(HttpServletRequest request) {
    CasUserInfo userInfo = CasUtil.getCasUserInfoFromCas(request);
    log.info("userInfo = " + JSONObject.toJSON(userInfo));
    String url = "main";
    MadStudent student = new MadStudent();
    student.setName(userInfo.getAttributes().get("Name").toString());
    student.setCardNo(userInfo.getAttributes().get("IdCard").toString());
  	// 登录用户校验 
  	// xxxxx
  	// 用户数据为 true
  	// 跳转页面
    return "url";
    } else {
      return "redirect:" + casUrl;
    }

  }

附上 CAS Server 本地搭建的博客地址:
CAS Server 本地搭建

六、补充

引言:
后期项目与多个第三方 CAS Server 认证中心做了单点登录,出现了一些问题,特此补充。
问题1:从认证中心认证通过,带票跳转失败,前端页面直接报错,后台报取不到票据带的用户信息,因为时间太久了,忘记报错信息是什么了,所以就不贴报错上来了。

解决办法:
经排查,发现是票据验证TicketValidationFilter版本的问题,部分认证中心使用的 CAS Server 的版本影响到了 Cas Client 的 TicketValidationFilter,导致不兼容,引起了冲突。
之前的文章里提到的TicketValidationFilter的版本是Cas30ProxyReceivingTicketValidationFilter,这里我灵活处理了一下,对于正在使用30版本的客户,还保留原来的配置。对于新对接的,且报错的,使用版本较低的配置,通过配置文件来动态决定使用哪个版本的验票过滤器。

代码如下(可对比第二步的 CasConfig 配置类中的代码参考):

	/**
     * 决定票据验证过滤器的版本,默认30,old是20版
     */
    @Value("${cas.filterVersion:new}")
    private String filterVersion;

	/**
     * 单点登录校验
     *
     * @return
     */
    @Bean
    public FilterRegistrationBean Cas30ProxyReceivingTicketValidationFilter() {

        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        if (StringUtils.isNotBlank(filterVersion) && filterVersion.equals("old")) {
            log.info(" Cas20ProxyReceivingTicketValidationFilter ");
            registrationBean.setFilter(new Cas20ProxyReceivingTicketValidationFilter());
        } else {
            log.info(" Cas30ProxyReceivingTicketValidationFilter ");
            registrationBean.setFilter(new Cas30ProxyReceivingTicketValidationFilter());
        }
        registrationBean.addUrlPatterns(filterUrl);
        registrationBean.setName("CAS Validation Filter");
        registrationBean.addInitParameter("casServerUrlPrefix", authenticationUrl);
        registrationBean.addInitParameter("serverName", appServerUrl);
        registrationBean.setOrder(1);
        return registrationBean;
    }

欢迎补充!

腾讯副总裁吴军博士说“成功的道路并不像想象得那么拥挤,因为在人生的马拉松长路上,绝大部分人跑不到一半就主动退下来了。到后来,剩下的少数人不是嫌竞争对手太多,而是发愁怎样找一个同伴陪自己一同跑下去。因此,教育是一辈子的事情,笑到最后的人是一辈子接受教育的人。回过头来看,一些过去比我们读书更优秀,在起跑线上抢到了更好位置的人,早已放弃了人生的马拉松,我们能够跑得更远,仅仅是因为我们还在跑,如此而已。”急功近利不好,悲观绝望也不好。


  • 10
    点赞
  • 64
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
Spring Boot集成CAS单点登录的步骤如下: 1. 配置CAS服务器 首先需要配置CAS服务器,包括安装和配置,这里不再赘述。 2. 引入CAS客户端依赖 在Spring Boot项目中引入CAS客户端依赖,例如: ``` <dependency> <groupId>org.jasig.cas.client</groupId> <artifactId>cas-client-core</artifactId> <version>3.6.</version> </dependency> ``` 3. 配置CAS客户端 在Spring Boot项目中配置CAS客户端,包括CAS服务器地址、CAS客户端地址、CAS登录地址等,例如: ``` cas.server.url=https://cas.example.com/cas cas.client.host.url=https://example.com cas.login.url=https://cas.example.com/cas/login ``` 4. 配置Spring Security 在Spring Boot项目中配置Spring Security,包括登录页面、登录成功后的跳转页面等,例如: ``` @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private CasAuthenticationProvider casAuthenticationProvider; @Autowired private CasAuthenticationEntryPoint casAuthenticationEntryPoint; @Autowired private CasAuthenticationFilter casAuthenticationFilter; @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/login").permitAll() .anyRequest().authenticated() .and() .exceptionHandling() .authenticationEntryPoint(casAuthenticationEntryPoint) .and() .addFilter(casAuthenticationFilter) .logout() .logoutUrl("/logout") .logoutSuccessUrl("/") .invalidateHttpSession(true) .deleteCookies("JSESSIONID") .and() .csrf().disable(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(casAuthenticationProvider); } } ``` 5. 配置CAS认证提供者 在Spring Boot项目中配置CAS认证提供者,例如: ``` @Configuration public class CasConfig { @Value("${cas.server.url}") private String casServerUrl; @Value("${cas.client.host.url}") private String casClientHostUrl; @Value("${cas.login.url}") private String casLoginUrl; @Bean public CasAuthenticationEntryPoint casAuthenticationEntryPoint() { CasAuthenticationEntryPoint entryPoint = new CasAuthenticationEntryPoint(); entryPoint.setLoginUrl(casLoginUrl); entryPoint.setServiceProperties(serviceProperties()); return entryPoint; } @Bean public ServiceProperties serviceProperties() { ServiceProperties serviceProperties = new ServiceProperties(); serviceProperties.setService(casClientHostUrl + "/login/cas"); serviceProperties.setSendRenew(false); return serviceProperties; } @Bean public CasAuthenticationFilter casAuthenticationFilter() throws Exception { CasAuthenticationFilter filter = new CasAuthenticationFilter(); filter.setAuthenticationManager(authenticationManager()); return filter; } @Bean public CasAuthenticationProvider casAuthenticationProvider() { CasAuthenticationProvider provider = new CasAuthenticationProvider(); provider.setServiceProperties(serviceProperties()); provider.setTicketValidator(new Cas30ServiceTicketValidator(casServerUrl)); provider.setUserDetailsService(userDetailsService()); provider.setKey("casAuthProviderKey"); return provider; } @Bean public UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(Collections.emptyList()); } @Bean public AuthenticationManager authenticationManager() throws Exception { return new ProviderManager(Collections.singletonList(casAuthenticationProvider())); } } ``` 6. 编写登录页面 在Spring Boot项目中编写登录页面,例如: ``` <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Login</title> </head> <body> <form action="/login/cas" method="post"> <input type="submit" value="Login"> </form> </body> </html> ``` 7. 运行项目 最后运行Spring Boot项目,访问登录页面,输入CAS服务器的用户名和密码,即可实现CAS单点登录

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值