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、 项目依赖

主要依赖如下

1<dependency> 2 <groupId>org.springframework.boot</groupId> 3 <artifactId>spring-boot-starter-security</artifactId> 4</dependency> 5<dependency> 6 <groupId>org.springframework.security</groupId> 7 <artifactId>spring-security-cas</artifactId> 8</dependency> 9<dependency> 10 <groupId>org.projectlombok</groupId> 11 <artifactId>lombok</artifactId> 12 <optional>true</optional> 13</dependency>

3、 项目配置

Application配置。

1@SpringBootApplication(scanBasePackages = "com.wawscm") 2@EnableWebSecurity 3public class Application { 4 5 public static void main(String[] args) { 6 new SpringApplicationBuilder(Application.class).web(true).run(args); 7 } 8 9}

增加CAS参数配置

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

1@Data 2@ConfigurationProperties(prefix = "security.cas.server") 3public class CasServerConfig { 4 private String host; 5 private String login; 6 private String logout; 7 8} 9 10@Data 11@ConfigurationProperties(prefix = "security.cas.service") 12public class CasServiceConfig { 13 private String host; 14 private String login; 15 private String logout; 16 private Boolean sendRenew = false; 17}

配置内容如下

1security: 2 cas: 3 server: 4 host: http://192.168.1.202:9082/cas 5 login: ${security.cas.server.host}/login 6 logout: ${security.cas.server.host}/logout 7 service: 8 host: http://localhost:9088 9 login: /login/cas 10 logout: /logout

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

SpringSecurity Bean配置

1@Configuration 2@EnableConfigurationProperties({CasServerConfig.class, CasServiceConfig.class}) 3public class SecurityConfiguration { 4 5 @Autowired 6 private CasServerConfig casServerConfig; 7 8 @Autowired 9 private CasServiceConfig casServiceConfig; 10 11 @Bean 12 public ServiceProperties serviceProperties() { 13 ServiceProperties serviceProperties = new ServiceProperties(); 14 serviceProperties.setService(this.casServiceConfig.getHost() + this.casServiceConfig.getLogin()); 15 serviceProperties.setSendRenew(this.casServiceConfig.getSendRenew()); 16 return serviceProperties; 17 } 18 19 @Bean 20 public CasAuthenticationFilter casAuthenticationFilter(AuthenticationManager authenticationManager, ServiceProperties serviceProperties) { 21 CasAuthenticationFilter casAuthenticationFilter = new CasAuthenticationFilter(); 22 casAuthenticationFilter.setAuthenticationManager(authenticationManager); 23 casAuthenticationFilter.setServiceProperties(serviceProperties); 24 casAuthenticationFilter.setFilterProcessesUrl(this.casServiceConfig.getLogin()); 25 casAuthenticationFilter.setContinueChainBeforeSuccessfulAuthentication(false); 26 casAuthenticationFilter.setAuthenticationSuccessHandler( 27 new SimpleUrlAuthenticationSuccessHandler("/") 28 ); 29 return casAuthenticationFilter; 30 } 31 32 @Bean 33 public CasAuthenticationEntryPoint casAuthenticationEntryPoint(ServiceProperties serviceProperties) { 34 CasAuthenticationEntryPoint entryPoint = new CasAuthenticationEntryPoint(); 35 entryPoint.setLoginUrl(this.casServerConfig.getLogin()); 36 entryPoint.setServiceProperties(serviceProperties); 37 return entryPoint; 38 } 39 40 @Bean 41 public Cas20ServiceTicketValidator cas20ServiceTicketValidator() { 42 return new Cas20ServiceTicketValidator(this.casServerConfig.getHost()); 43 } 44 45 @Bean 46 public CasAuthenticationProvider casAuthenticationProvider( 47 AuthenticationUserDetailsService<CasAssertionAuthenticationToken> userDetailsService, 48 ServiceProperties serviceProperties, Cas20ServiceTicketValidator ticketValidator) { 49 CasAuthenticationProvider provider = new CasAuthenticationProvider(); 50 provider.setKey("casProvider"); 51 provider.setServiceProperties(serviceProperties); 52 provider.setTicketValidator(ticketValidator); 53 provider.setAuthenticationUserDetailsService(userDetailsService); 54 55 return provider; 56 } 57 58 @Bean 59 public LogoutFilter logoutFilter() { 60 String logoutRedirectPath = this.casServerConfig.getLogout() + "?service=" + 61 this.casServiceConfig.getHost(); 62 LogoutFilter logoutFilter = new LogoutFilter(logoutRedirectPath, new SecurityContextLogoutHandler()); 63 logoutFilter.setFilterProcessesUrl(this.casServiceConfig.getLogout()); 64 return logoutFilter; 65 } 66}

ServiceProperties :服务配置,我们自己的服务。 CasAuthenticationFilter:CAS认证过滤器,主要实现票据认证和认证成功后的跳转。 LogoutFilter:注销功能

Spring Security配置

1@Configuration 2@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER) 3public class CasWebSecurityConfiguration extends WebSecurityConfigurerAdapter { 4 5 @Autowired 6 private CasAuthenticationEntryPoint casAuthenticationEntryPoint; 7 8 @Autowired 9 private CasAuthenticationProvider casAuthenticationProvider; 10 11 @Autowired 12 private CasAuthenticationFilter casAuthenticationFilter; 13 14 @Autowired 15 private LogoutFilter logoutFilter; 16 17 @Autowired 18 private CasServerConfig casServerConfig; 19 20 @Override 21 protected void configure(HttpSecurity http) throws Exception { 22 http.headers().frameOptions().disable(); 23 24 http.csrf().disable(); 25 26 http.authorizeRequests() 27 .requestMatchers(CorsUtils::isPreFlightRequest).permitAll() 28 .antMatchers("/static/**").permitAll() // 不拦截静态资源 29 .antMatchers("/api/**").permitAll() // 不拦截对外API 30 .anyRequest().authenticated(); // 所有资源都需要登陆后才可以访问。 31 32 http.logout().permitAll(); // 不拦截注销 33 34 http.exceptionHandling().authenticationEntryPoint(casAuthenticationEntryPoint); 35 // 单点注销的过滤器,必须配置在SpringSecurity的过滤器链中,如果直接配置在Web容器中,貌似是不起作用的。我自己的是不起作用的。 36 SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter(); 37 singleSignOutFilter.setCasServerUrlPrefix(this.casServerConfig.getHost()); 38 39 http.addFilter(casAuthenticationFilter) 40 .addFilterBefore(logoutFilter, LogoutFilter.class) 41 .addFilterBefore(singleSignOutFilter, CasAuthenticationFilter.class); 42 43 http.antMatcher("/**"); 44 } 45 46 @Autowired 47 public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { 48 auth.authenticationProvider(casAuthenticationProvider); 49 } 50 51 @Bean 52 public ServletListenerRegistrationBean<SingleSignOutHttpSessionListener> singleSignOutHttpSessionListener(){ 53 ServletListenerRegistrationBean<SingleSignOutHttpSessionListener> servletListenerRegistrationBean = 54 new ServletListenerRegistrationBean<>(); 55 servletListenerRegistrationBean.setListener(new SingleSignOutHttpSessionListener()); 56 return servletListenerRegistrationBean; 57 } 58 59}

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

4 、处理回跳地址

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

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

1public class HttpParamsFilter implements Filter { 2 public String REQUESTED_URL = "CasRequestedUrl"; 3 4 @Override 5 public void init(FilterConfig filterConfig) throws ServletException { 6 7 } 8 9 @Override 10 public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) 11 throws IOException, ServletException { 12 final HttpServletRequest request = (HttpServletRequest) servletRequest; 13 final HttpServletResponse response = (HttpServletResponse) servletResponse; 14 HttpSession session = request.getSession(); 15 16 String requestPath = WebUtils.getFullPath(request); 17 18 session.setAttribute(REQUESTED_URL, requestPath); 19 20 chain.doFilter(request, response); 21 } 22 23 @Override 24 public void destroy() { 25 26 } 27}

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

1@Bean 2public FilterRegistrationBean httpParamsFilter() { 3 FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(); 4 filterRegistrationBean.setFilter(new HttpParamsFilter()); 5 filterRegistrationBean.setOrder(-999); 6 filterRegistrationBean.addUrlPatterns("/"); 7 return filterRegistrationBean; 8}

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

1public class MyUrlAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { 2 3 public NeteaseUrlAuthenticationSuccessHandler() { 4 super(); 5 } 6 7 public NeteaseUrlAuthenticationSuccessHandler(String defaultTargetUrl) { 8 super(defaultTargetUrl); 9 } 10 11 @Override 12 protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response) { 13 if (isAlwaysUseDefaultTargetUrl()) { 14 return this.getDefaultTargetUrl(); 15 } 16 17 // Check for the parameter and use that if available 18 String targetUrl = null; 19 20 if (this.getTargetUrlParameter() != null) { 21 targetUrl = request.getParameter(this.getTargetUrlParameter()); 22 23 if (StringUtils.hasText(targetUrl)) { 24 logger.debug("Found targetUrlParameter in request: " + targetUrl); 25 26 return targetUrl; 27 } 28 } 29 30 if (!StringUtils.hasText(targetUrl)) { 31 HttpSession session = request.getSession(); 32 targetUrl = (String) session.getAttribute(HttpParamsFilter.REQUESTED_URL); 33 } 34 35 if (!StringUtils.hasText(targetUrl)) { 36 targetUrl = this.getDefaultTargetUrl(); 37 logger.debug("Using default Url: " + targetUrl); 38 } 39 40 return targetUrl; 41 } 42}

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

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

11. 访问http://host/?id=1 -- session: /?id=1 22. CAS登录成功,然后回跳到login/cas?ticket=xxx -- session: login/cas?ticket=xxx 33. 验证票据成功NeteaseUrlAuthenticationSuccessHandler处理跳转,从session中获取跳转地址:login/cas?ticket=xxx 44. 跳转到`login/cas?ticket=xxx`然后重复步骤 2-4

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

由于我的是单页应用,所以我直接拦截主页就可以了。 另一种处理方法是在HttpParamsFilter判断访问地址,如果是login/cas就不更新Session中的值。

文章编写的有点仓促,如内容有误,或有纰漏欢迎各位读者指正。 原文地址http://blog.csdn.net/jaune161/article/details/78060780,转载请注明出处。

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

java将前端的json数组字符串转换为列表

记录下在前端通过ajax提交了一个json数组的字符串,在后端如何转换为列表。前端数据转化与请求varcontracts{id:'1',name:'yanggb合同1'},{id:'2',name:'yanggb合同2'},{id:'3',name:'yang

前端性能优化 - 雅虎军规

无论是在工作中,还是在面试中,web前端性能的优化都是很重要的,那么我们进行优化需要从哪些方面入手呢?可以遵循雅虎的前端优化35条军规,这样对于优化有一个比较清晰的方向.35条军规1.尽量减少HTTP请求个数——须权衡2.使用CDN(内容分发网络)3.为文件头指定Expires或CacheControl,使内容具有缓存性。4.避免空的