在开展项目之前,需要一些基本的CAS知识扫盲工作,请仔细理解这几篇文章对后期项目很有帮助,具体重复内容这里不在多说,看文章即可,
项目架构
统一使用 SpringBoot+Meven 构建
项目 | 地址 | 说明 |
cas-overlay-template-master | https://cas.server.com:8443/cas | cas-server 服务端 |
cas-app1 | http://app1.com:8181 | cas-client 客户端1 |
cas-app2 | http://app2.com:8282 | cas-client 客户端2 |
接下来,老夫就要开始飙车了。
一、配置hosts文件
# SSO单点登录Demo 127.0.0.1 cas.server.com 127.0.0.1 app1.com 127.0.0.1 app2.com
二、配置cas服务端
1、配置json服务注册
1、在src/main/resources/
目录下创建services文件夹
2、在war包中,找到HTTPSandIMAPS-10000001.json
文件,并copy到services
文件夹中
3、将HTTPSandIMAPS-10000001.json
文件内容修改如下。
{ "@class" : "org.apereo.cas.services.RegexRegisteredService", "serviceId" : "^(https|http|imaps)://.*", "name" : "HTTPS and HTTP and IMAPS", "id" : 10000001, "description" : "This service definition authorizes all application urls that support HTTPS and HTTP and IMAPS protocols.", "evaluationOrder" : 10000 }
默认cas支持https
,不支持http
客户端站点来登录,所以需要手动进行配置兼容。
如果不做此操作就会出现如下图:未认证授权的服务,所以还是乖乖的听话,跟着我的飙车轨道,别跑丢了。
具体json文件中代表什么含义,查看文章:Cas 5.2.x版本使用 —— Service配置介绍(十一)
2、pom.xml 配置
导入json服务的依赖。经过尝试发现下面不导入也是可以的,但是官网说要加,有点蒙
<!--json服务注册--> <dependency> <groupId>org.apereo.cas</groupId> <artifactId>cas-server-support-json-service-registry</artifactId> <version>${cas.version}</version> </dependency>
3、application.properties 配置
在application.properties文件中,增加配置项cas.serviceRegistry.initFromJson=true
表示开启了json注册服务。
另外为了Demo尽可能的简单方便理解,我这里启用静态账号密码方式认证(非JDBC和Rest)
## # CAS Authentication Credentials # cas.authn.accept.users=tingfeng::tingfeng ## # 开启json服务注册 # cas.serviceRegistry.initFromJson=true ## # 登出后允许跳转到指定页面 # cas.logout.followServiceRedirects=true
三、配置客户端
客户端 app1 和 app2 的项目逻辑相同,不多说。
1、pom.xml 配置
添加 cas-client 包依赖,我用的 3.5.0 版本
<!--cas的客户端 --> <dependency> <groupId>org.jasig.cas.client</groupId> <artifactId>cas-client-core</artifactId> <version>${java.cas.client.version}</version> </dependency>
2、项目结构
➜ src tree . └── main ├── java │ └── com │ └── tingfeng │ ├── AppRun.java (程序入口) │ ├── cas │ │ ├── auth │ │ │ └── SimpleUrlPatternMatcherStrategy.java (设置忽略,不拦截地址) │ │ └── config │ │ └── CasConfig.java (常用cas配置) │ └── controller │ ├── BookController.java (受限接口) │ ├── IndexController.java │ └── UserController.java (用户登出) └── resources └── application.yml
3、 AppRun.java
SpringBoot程序入口,包含cas-client.jar的过滤器配置信息
package com.tingfeng; import com.tingfeng.cas.config.CasConfig; 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.AssertionThreadLocalFilter; import org.jasig.cas.client.util.HttpServletRequestWrapperFilter; import org.jasig.cas.client.validation.Cas30ProxyReceivingTicketValidationFilter; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; 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; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import java.util.EventListener; import java.util.HashMap; import java.util.Map; /** * 过滤器配置介绍Doc: * http://doc.okbase.net/234390216/archive/105696.html * http://blog.csdn.net/u010475041/article/details/78094251 */ @SpringBootApplication public class AppRun { /************************************* SSO配置-开始 ************************************************/ /** * SingleSignOutFilter 登出过滤器 * 该过滤器用于实现单点登出功能,可选配置 * * @return */ @Bean public FilterRegistrationBean filterSingleRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(new SingleSignOutFilter()); // 设定匹配的路径 registration.addUrlPatterns("/*"); Map<String, String> initParameters = new HashMap(); initParameters.put("casServerUrlPrefix", CasConfig.CAS_SERVER_LOGIN_PATH); registration.setInitParameters(initParameters); // 设定加载的顺序 registration.setOrder(1); return registration; } /** * SingleSignOutHttpSessionListener 添加监听器 * 用于单点退出,该过滤器用于实现单点登出功能,可选配置 * * @return */ @Bean public ServletListenerRegistrationBean<EventListener> singleSignOutListenerRegistration() { ServletListenerRegistrationBean<EventListener> registrationBean = new ServletListenerRegistrationBean<EventListener>(); registrationBean.setListener(new SingleSignOutHttpSessionListener()); registrationBean.setOrder(1); return registrationBean; } /** * Cas30ProxyReceivingTicketValidationFilter 验证过滤器 * 该过滤器负责对Ticket的校验工作,必须启用它 * * @return */ @Bean public FilterRegistrationBean filterValidationRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(new Cas30ProxyReceivingTicketValidationFilter()); // 设定匹配的路径 registration.addUrlPatterns("/*"); Map<String, String> initParameters = new HashMap(); initParameters.put("casServerUrlPrefix", CasConfig.CAS_SERVER_PATH); initParameters.put("serverName", CasConfig.SERVER_NAME); // 是否对serviceUrl进行编码,默认true:设置false可以在302对URL跳转时取消显示;jsessionid=xxx的字符串 // 观察CommonUtils.constructServiceUrl方法可以看到 initParameters.put("encodeServiceUrl", "false"); registration.setInitParameters(initParameters); // 设定加载的顺序 registration.setOrder(1); return registration; } /** * AuthenticationFilter 授权过滤器 * * @return */ @Bean public FilterRegistrationBean filterAuthenticationRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean(); Map<String, String> initParameters = new HashMap(); registration.setFilter(new AuthenticationFilter()); registration.addUrlPatterns("/*"); initParameters.put("casServerLoginUrl", CasConfig.CAS_SERVER_LOGIN_PATH); initParameters.put("serverName", CasConfig.SERVER_NAME); // 不拦截的请求 .* 有后缀的文件 initParameters.put("ignorePattern", ".*"); // 表示过滤所有 initParameters.put("ignoreUrlPatternType", "com.tingfeng.cas.auth.SimpleUrlPatternMatcherStrategy"); registration.setInitParameters(initParameters); // 设定加载的顺序 registration.setOrder(1); return registration; } /** * AssertionThreadLocalFilter * * 该过滤器使得开发者可以通过org.jasig.cas.client.util.AssertionHolder来获取用户的登录名。 * 比如AssertionHolder.getAssertion().getPrincipal().getName()。 * * @return */ @Bean public FilterRegistrationBean filterAssertionThreadLocalRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(new AssertionThreadLocalFilter()); // 设定匹配的路径 registration.addUrlPatterns("/*"); // 设定加载的顺序 registration.setOrder(1); return registration; } /* * HttpServletRequestWrapperFilter wraper过滤器 * 该过滤器负责实现HttpServletRequest请求的包裹, * 比如允许开发者通过HttpServletRequest的getRemoteUser()方法获得SSO登录用户的登录名,可选配置。 * * @return */ @Bean public FilterRegistrationBean filterWrapperRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(new HttpServletRequestWrapperFilter()); // 设定匹配的路径 registration.addUrlPatterns("/*"); // 设定加载的顺序 registration.setOrder(1); return registration; } /************************************* SSO配置-结束 ************************************************/ /** * 设定首页 */ @Configuration public class DefaultView extends WebMvcConfigurerAdapter { @Override public void addViewControllers(ViewControllerRegistry registry) { //设定首页为index registry.addViewController("/").setViewName("forward:/index"); //设定匹配的优先级 registry.setOrder(Ordered.HIGHEST_PRECEDENCE); //添加视图控制类 super.addViewControllers(registry); } } public static void main(String[] args) { SpringApplication.run(AppRun.class, args); } }
过滤器的设置,类似于在 web.xml 中配置的过滤器,关于web.xml配置,参考底部文档链接。
有关上面几个过滤器和各个参数的含义,我打算在后面单独做几篇文章进行详细介绍
SimpleUrlPatternMatcherStrategy.java
机能概要:过滤掉一些不需要授权登录的URL
package com.tingfeng.cas.auth; import org.jasig.cas.client.authentication.UrlPatternMatcherStrategy; import java.util.Arrays; import java.util.List; /** * 机能概要:过滤掉一些不需要授权登录的URL */ public class SimpleUrlPatternMatcherStrategy implements UrlPatternMatcherStrategy { /** * 机能概要: 判断是否匹配这个字符串 * * @param url 用户请求的连接 * @return true : 不拦截 * false :必须得登录了 */ @Override public boolean matches(String url) { if(url.contains("/logout")){ return true; } List<String> list = Arrays.asList( "/", "/index", "/favicon.ico" ); String name = url.substring(url.lastIndexOf("/")); if (name.indexOf("?") != -1) { name = name.substring(0, name.indexOf("?")); } System.out.println("name:" + name); boolean result = list.contains(name); if (!result) { System.out.println("拦截URL:" + url); } return result; } /** * 正则表达式的规则,这个地方可以是web传递过来的 */ @Override public void setPattern(String pattern) { } }
UserController.java
用于登出
package com.tingfeng.controller; import com.tingfeng.cas.config.CasConfig; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @Controller @RequestMapping("/user") public class UserController { /** * 用户登出 * * @param request * @return */ @RequestMapping("/logout") public String logout(HttpServletRequest request) { // session失效 request.getSession().invalidate(); return "redirect:" + CasConfig.CAS_SERVER_LOGOUT_PATH; } /** * 用户登出,并重定向回来 * * @param request * @return */ @RequestMapping("/logout2") public String logout2(HttpServletRequest request) { // session失效 request.getSession().invalidate(); return "redirect:" + CasConfig.CAS_SERVER_LOGOUT_PATH + "?service="+CasConfig.APP_LOGOUT_PATH; } /** * 登出成功回调 * @return */ @ResponseBody @RequestMapping("/logout/success") public String logoutPage(){ return "登出成功,跳转登出页面"; } }
四、视频演示地址
https://v.qq.com/x/page/d063304k06a.html
五、我的源码
https://github.com/X-rapido/CAS_SSO_Record/tree/master/simple-sso
六、错误集合
java.lang.RuntimeException: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed:
导致这个问题的原因就是,客户端,没有导入证书,报的错,客户端的jdk,也是需要导入证书的,而且必须和服务端的证书一致。
java.lang.RuntimeException: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target org.jasig.cas.client.util.CommonUtils.getResponseFromServer(CommonUtils.java:403) org.jasig.cas.client.validation.AbstractCasProtocolUrlBasedTicketValidator.retrieveResponseFromServer(AbstractCasProtocolUrlBasedTicketValidator.java:41) org.jasig.cas.client.validation.AbstractUrlBasedTicketValidator.validate(AbstractUrlBasedTicketValidator.java:193) org.jasig.cas.client.validation.AbstractTicketValidationFilter.doFilter(AbstractTicketValidationFilter.java:204) org.jasig.cas.client.session.SingleSignOutFilter.doFilter(SingleSignOutFilter.java:97) org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:88) org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
org.apereo.cas.services.RegisteredServiceAccessStrategyUtils.ensurePrincipalAccessIsAllowedForService(Lorg/apereo/cas/authentication/principal/Service;Lorg/apereo/cas/services/RegisteredService;Lorg/apereo/cas/ticket/TicketGrantingTicket;)V
检查JDK证书是否导入成功
七、总结
本章讲解了cas-client如何使用,但我们实际上远远比这要复杂,当然我们这个demo也作为入门了解阶段学习,但目标很明确,要了解cas的整个转发过程,在测试过程,请多注意使用浏览器的Debug功能进行观察转发和重定向的过程。
八、参考文档
https://github.com/cas-projects/cas-sample-java-webapp
https://github.com/apereo/java-cas-client