SpringBoot集成SpringSecurity+CAS
Posted 大扑棱蛾子
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot集成SpringSecurity+CAS相关的知识,希望对你有一定的参考价值。
1、 简介
本文主要讲述如何通过SpringSecurity+CAS在springboot项目中实现单点登录和单点注销的功能。
参考内容有Spring Security官方文档中的
1. 5. Java Configuration
2. 32. CAS Authentication
本文为Jaune161的原创文章。
原文地址http://blog.csdn.net/jaune161/article/details/78060780,转载请注明出处。
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中的值。
文章编写的有点仓促,如内容有误,或有纰漏欢迎各位读者指正。
原文地址http://blog.csdn.net/jaune161/article/details/78060780,转载请注明出处。
以上是关于SpringBoot集成SpringSecurity+CAS的主要内容,如果未能解决你的问题,请参考以下文章
SpringBoot项目启动后访问任意接口都会跳转到一个莫名其妙的login登录页面
SpringSecurity解决跨域问题,在SpringBoot整合SprinSecurity中如何用前后端分离Ajax登录,Ajax登录返回状态200还是近error