SpringBoot集成SpringSecurity+CAS

导读:本篇文章讲解 SpringBoot集成SpringSecurity+CAS,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

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中的值。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/70261.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!