SpringBoot集成SpringSecurity+CAS

摘要:
1简介本文主要描述如何通过spring Security+CAS@在spring boot项目中实现单点登录和单点注销功能SpringBootApplication@EnableWebSecuritypublicclassApplication{publicstaticvoidmain{newSpringApplicationBuilder.web.run;}}添加CAS参数配置,其中包括CASServer配置和CASService配置。服务器是CAS服务的配置,服务是我们自己服务的配置。
1 简介

本文主要讲述如何通过SpringSecurity+CAS在springboot项目中实现单点登录和单点注销的功能。

2 项目依赖

主要依赖如下

<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>
<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
  <optional>true</optional>
</dependency>
3 项目配置

Application配置。

@SpringBootApplication(scanBasePackages = "com.wawscm")
@EnableWebSecurity
public class Application {

  public static void main(String[] args) {

    new SpringApplicationBuilder(Application.class).web(true).run(args);
  }

}


增加CAS参数配置

  这里分为CASServer配置和CASService配置。其中Server是CAS服务的配置,Service是我们自己服务的配置。

@Data
@ConfigurationProperties(prefix = "security.cas.server")
public class CasServerConfig {
  private String host;
  private String login;
  private String logout;
}

@Data
@ConfigurationProperties(prefix = "security.cas.service")
public class CasServiceConfig {
  private String host;
  private String login;
  private String logout;
  private Boolean sendRenew = false;
}

配置内容如下

security:
  cas:
   server:
    host: http://192.168.1.202:9082/cas
    login: ${security.cas.server.host}/login
    logout: ${security.cas.server.host}/logout
   service:
    host: http://localhost:9088
    login: /login/cas
    logout: /logout


后面需要根据实际配置再拼接参数。

SpringSecurity Bean配置

@Configuration
@EnableConfigurationProperties({CasServerConfig.class, CasServiceConfig.class})
public class SecurityConfiguration {

  @Autowired
  private CasServerConfig casServerConfig;

  @Autowired
  private CasServiceConfig casServiceConfig;

  @Bean
  public ServiceProperties serviceProperties() {
    ServiceProperties serviceProperties = new ServiceProperties();
    serviceProperties.setService(this.casServiceConfig.getHost() + this.casServiceConfig.getLogin());
    serviceProperties.setSendRenew(this.casServiceConfig.getSendRenew());
    return serviceProperties;
  }

  @Bean
  public CasAuthenticationFilter casAuthenticationFilter(AuthenticationManager authenticationManager, ServiceProperties serviceProperties) {
    CasAuthenticationFilter casAuthenticationFilter = new CasAuthenticationFilter();
    casAuthenticationFilter.setAuthenticationManager(authenticationManager);
    casAuthenticationFilter.setServiceProperties(serviceProperties);
    casAuthenticationFilter.setFilterProcessesUrl(this.casServiceConfig.getLogin());
    casAuthenticationFilter.setContinueChainBeforeSuccessfulAuthentication(false);
    casAuthenticationFilter.setAuthenticationSuccessHandler(
      new SimpleUrlAuthenticationSuccessHandler("/")
    );
    return casAuthenticationFilter;
  }

  @Bean
  public CasAuthenticationEntryPoint casAuthenticationEntryPoint(ServiceProperties serviceProperties) {
    CasAuthenticationEntryPoint entryPoint = new CasAuthenticationEntryPoint();
    entryPoint.setLoginUrl(this.casServerConfig.getLogin());
    entryPoint.setServiceProperties(serviceProperties);
    return entryPoint;
  }

  @Bean
  public Cas20ServiceTicketValidator cas20ServiceTicketValidator() {
    return new Cas20ServiceTicketValidator(this.casServerConfig.getHost());
  }

  @Bean
  public CasAuthenticationProvider casAuthenticationProvider(
    AuthenticationUserDetailsService<CasAssertionAuthenticationToken> userDetailsService,
    ServiceProperties serviceProperties, Cas20ServiceTicketValidator ticketValidator) {
    CasAuthenticationProvider provider = new CasAuthenticationProvider();
    provider.setKey("casProvider");
    provider.setServiceProperties(serviceProperties);
    provider.setTicketValidator(ticketValidator);
    provider.setAuthenticationUserDetailsService(userDetailsService);
    return provider;
  }

  @Bean
  public LogoutFilter logoutFilter() {
    String logoutRedirectPath = this.casServerConfig.getLogout() + "?service=" + this.casServiceConfig.getHost();
    LogoutFilter logoutFilter = new LogoutFilter(logoutRedirectPath, new SecurityContextLogoutHandler());
    logoutFilter.setFilterProcessesUrl(this.casServiceConfig.getLogout());
    return logoutFilter;
  }
}

ServiceProperties :服务配置,我们自己的服务。

CasAuthenticationFilter:CAS认证过滤器,主要实现票据认证和认证成功后的跳转。

LogoutFilter:注销功能

Spring Security配置

@Configuration
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class CasWebSecurityConfiguration extends WebSecurityConfigurerAdapter {

  @Autowired
  private CasAuthenticationEntryPoint casAuthenticationEntryPoint;

  @Autowired
  private CasAuthenticationProvider casAuthenticationProvider;

  @Autowired
  private CasAuthenticationFilter casAuthenticationFilter;

  @Autowired
  private LogoutFilter logoutFilter;

  @Autowired
  private CasServerConfig casServerConfig;

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.headers().frameOptions().disable();     http.csrf().disable();     http.authorizeRequests()     .requestMatchers(CorsUtils::isPreFlightRequest).permitAll()     .antMatchers("/static/**").permitAll() // 不拦截静态资源     .antMatchers("/api/**").permitAll() // 不拦截对外API     .anyRequest().authenticated(); // 所有资源都需要登陆后才可以访问。     http.logout().permitAll(); // 不拦截注销     http.exceptionHandling().authenticationEntryPoint(casAuthenticationEntryPoint);
    // 单点注销的过滤器,必须配置在SpringSecurity的过滤器链中,如果直接配置在Web容器中,貌似是不起作用的。我自己的是不起作用的。     SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter();     singleSignOutFilter.setCasServerUrlPrefix(this.casServerConfig.getHost());     http.addFilter(casAuthenticationFilter)
    .addFilterBefore(logoutFilter, LogoutFilter.class)     .addFilterBefore(singleSignOutFilter, CasAuthenticationFilter.class);      http.antMatcher("/**");   }   @Autowired   public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {     auth.authenticationProvider(casAuthenticationProvider);   }   @Bean   public ServletListenerRegistrationBean<SingleSignOutHttpSessionListener> singleSignOutHttpSessionListener(){     ServletListenerRegistrationBean<SingleSignOutHttpSessionListener> servletListenerRegistrationBean =     new ServletListenerRegistrationBean<>();     servletListenerRegistrationBean.setListener(new SingleSignOutHttpSessionListener());     return servletListenerRegistrationBean;   } }

  到此SpringBoot、SpringSecurity、CAS集成结束。但是这样配置有一个问题,那就是如果我们登录之前的请求是带参数的,或者跳转的不是首页,那么就会出现登录成功之后直接跳转到主页,而不是我们想要访问的页面,参数也丢失了。下面我们来解决这个问题。

4 、处理回跳地址

  处理的思路是,在登录之前记住访问地址及请求参数,在登录成功之后再取到这个地址然后回跳到对应的地址。

  首先我们需要写一个过滤器来获取我们的请求地址,并放到Session中。

public class HttpParamsFilter implements Filter {

  public String REQUESTED_URL = "CasRequestedUrl";   @Override   public void init(FilterConfig filterConfig) throws ServletException {   }   @Override
  public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)throws IOException, ServletException {
    final HttpServletRequest request = (HttpServletRequest) servletRequest;     
    final HttpServletResponse response = (HttpServletResponse) servletResponse;
    HttpSession session = request.getSession();
    
    String requestPath = WebUtils.getFullPath(request);
    session.setAttribute(REQUESTED_URL, requestPath);     
    chain.doFilter(request, response);   }   @Override   public void destroy() {   } }

然后在CasWebSecurityConfiguration中增加对应的配置。

 @Bean
  public FilterRegistrationBean httpParamsFilter() {
    FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
    filterRegistrationBean.setFilter(new HttpParamsFilter());
    filterRegistrationBean.setOrder(-999);
    filterRegistrationBean.addUrlPatterns("/"); 
    return filterRegistrationBean;
  }

然后扩展SimpleUrlAuthenticationSuccessHandler来实现我们的功能。

public class MyUrlAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {

  public NeteaseUrlAuthenticationSuccessHandler() {
    super();
  }

  public NeteaseUrlAuthenticationSuccessHandler(String defaultTargetUrl) {
    super(defaultTargetUrl);
  }

@Override
protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response) {
  if (isAlwaysUseDefaultTargetUrl()) {
    return this.getDefaultTargetUrl();
  }
  // Check for the parameter and use that if available
  String targetUrl = null;
  if (this.getTargetUrlParameter() != null) {
    targetUrl = request.getParameter(this.getTargetUrlParameter());
    if (StringUtils.hasText(targetUrl)) {
      logger.debug("Found targetUrlParameter in request: " + targetUrl);
    return targetUrl;
 }
}

  if (!StringUtils.hasText(targetUrl)) {
    HttpSession session = request.getSession();
    targetUrl = (String) session.getAttribute(HttpParamsFilter.REQUESTED_URL);
  }

  if (!StringUtils.hasText(targetUrl)) {
    targetUrl = this.getDefaultTargetUrl();
    logger.debug("Using default Url: " + targetUrl);
  }

  return targetUrl;
  }
}

  最后将CasAuthenticationFilter中的SimpleUrlAuthenticationSuccessHandler替换为MyUrlAuthenticationSuccessHandler就可以了。

  这里需要注意一个问题,由于CAS回调是访问的/login/cas(这里是我的配置),所以过滤器一定不能拦截/login/cas否则HttpParamsFilter会将/login/cas放到Session中,就出现了无限循环。

1. 访问http://host/?id=1 -- session: /?id=1

2. CAS登录成功,然后回跳到login/cas?ticket=xxx -- session: login/cas?ticket=xxx

3. 验证票据成功NeteaseUrlAuthenticationSuccessHandler处理跳转,从session中获取跳转地址:login/cas?ticket=xxx

4. 跳转到`login/cas?ticket=xxx`然后重复步骤 2-4

主要是我们保留了请求中的参数,所以一直会有票据信息。所以就出现了无限循环。如果没有保留票据信息,就直接报错了,因为第二次访问的时候票据丢了。

由于我的是单页应用,所以我直接拦截主页就可以了。

另一种处理方法是在HttpParamsFilter判断访问地址,如果是login/cas就不更新Session中的值。

免责声明:文章转载自《SpringBoot集成SpringSecurity+CAS》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Python 全栈开发:dict(字典)常用方法操作、dict嵌套WebAPI文件上传与下载下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

CAS实现单点登录SSO执行原理及部署

一、不落俗套的开始 1、背景介绍 单点登录:Single Sign On,简称SSO,SSO使得在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。 CAS框架:CAS(Central Authentication Service)是实现SSO单点登录的框架。 2、盗一张学习CAS绝大多都看过的图以及执行部分分析 注:已分不清原创,此...

CAS—认证原理

  CAS,Central Authentication Service—中央认证服务,是Yale 大学发起的一个企业级的、开源的项目。旨在为Web应用系统提供一种可靠的SSO解决方式。以下简介SSO,重点介绍CAS认证过程。 一、    SSO简单介绍  1.1   概念   SSO英文全称Single Sign On,是眼下比較流行的服务于企业业务整...

Java:CAS(乐观锁)

本文讲解CAS机制,主要是因为最近准备面试题,发现这个问题在面试中出现的频率非常的高,因此把自己学习过程中的一些理解记录下来,希望能对大家也有帮助。 什么是悲观锁、乐观锁?在java语言里,总有一些名词看语义跟本不明白是啥玩意儿,也就总有部分面试官拿着这样的词来忽悠面试者,以此来找优越感,其实理解清楚了,这些词也就唬不住人了。 synchronize...

CAS实现SSO单点登录原理

一、不落俗套的开始 1、背景介绍 单点登录:Single Sign On,简称SSO,SSO使得在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。 CAS框架:CAS(Central Authentication Service)是实现SSO单点登录的框架。 2、盗一张学习CAS绝大多都看过的图以及执行部分分析 注:已分不清原创,此...

CAS5.3服务器搭建与客户端整合SpringBoot以及踩坑笔记

CAS5.3服务器搭建与客户端整合SpringBoot以及踩坑笔记 cas服务器的搭建 导出证书(1和2步骤是找了课程,随便写了一下存记录,不过对于自己测试不投入使用应该不影响) C:UsersDdlm2>keytool -genkey -alias testcas -keystore D:/testcas -storepass 123456...

cas系列-cas REST协议(三)

cas的rest协议 cas还支持rest协议方式进行访问,格式和参数如下: 1. 获取TGT     请求方式,路径,http协议及请求参数:      POST /cas/v1/tickets HTTP/1.0       username=battags&password=password&additionalParam1=param...