下载代码 我使用官方提供的 cas-overlay-template 进行CAS Service的搭建,首先从github上下载代码https://github.com/apereo/cas-overlay-template/tree/5.3 我使用的是5.3版本
修改 maven 依赖配置 修改 <cas.version> 标签为 5.3.1 版本 如下,之前尝试其它版本下载不下来,这里我使用的是5.3.1
1 <cas.version>5.3.1</cas.version>
找到 pom 文件中的 maven-war-plugin插件,修改成如下配置,将 overlays中的文件排除出去,因为我们这些文件要进行重写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.6</version> <configuration> <warName>cas</warName> <failOnMissingWebXml>false</failOnMissingWebXml> <recompressZippedFiles>false</recompressZippedFiles> <archive> <compress>false</compress> <manifestFile>${manifestFileToUse}</manifestFile> </archive> <overlays> <overlay> <groupId>org.apereo.cas</groupId> <artifactId>cas-server-webapp${app.server}</artifactId> <!--原有的服务不再初始化进去--> <excludes> <exclude>WEB-INF/classes/services/*</exclude> <exclude>WEB-INF/classes/application.*</exclude> <exclude>WEB-INF/classes/log4j2.*</exclude> </excludes> </overlay> </overlays> </configuration> </plugin>
工程目录调整 删除多余文件 打开 overlays 目录如果下面有 org.apereo.cas.cas-server-webapp-tomcat-5.3.14 这个目录将其删掉,因为我们使用的是5.3.1这个版本,只保留 org.apereo.cas.cas-server-webapp-tomcat-5.3.1 这个目录
创建自定义代码目录 在根目录下( overlays 同级目录 ) 新建maven风格的目录,并且在编译器中将java文件夹设置成 Sources Root,将 resources 设置成 Resources Root
1 2 3 4 src |-main |-java |-resources
复制配置文件 将 org.apereo.cas.cas-server-webapp-tomcat-5.3.1/WEB-INF/classes 下的 services 文件夹,application.properties ,log4j2.xml复制到我们上一步定义的resources 目录下 注意如果我们之前在 POM 文件中 的maven-war-plugin 排除了这三个文件,编译器中可能会无法显示这三个文件,我们可以先将其注释,等复制完后再排除这三个文件
修改配置文件
修改services下的 客户端接入文件 这个目录下存放的是哪些客户端可以接入CAS 的配置文件, 在CAS中我们可以选择动态的添加客户端,和静态的添加客户端,其中静态添加客户端就是以在services下以json的形式添加,其中json的文件命名是有规定的,json文件名字规则为${name}-${id}.json, id必须为json文件内容id一致 ,我这里删除了原有services目录下的所有文件,新建一个localhost-10000001.json 的文件,内容如下,这里设置了所有以http,https,imaps开头的都能接入,proxyPolicy 是为了设置代理模式支持http协议而设置的,关于代理模式以后再介绍
1 2 3 4 5 6 7 8 9 10 11 12 { "@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, "proxyPolicy": { "@class": "org.apereo.cas.services.RegexMatchingRegisteredServiceProxyPolicy", "pattern": "^(https|http)?://.*" } }
关于json配置文件的简要介绍
1 2 3 4 5 6 7 json文件解释: @class:必须为org.apereo.cas.services.RegisteredService的实现类,对其他属性进行一个json反射对象,常用的有RegexRegisteredService,匹配策略为id的正则表达式 serviceId:唯一的服务id name: 服务名称,会显示在默认登录页 id:全局唯一标志 description:服务描述,会显示在默认登录页 evaluationOrder:确定已注册服务的相对评估顺序。当两个服务URL表达式覆盖相同的服务时,此标志尤其重要;评估顺序决定首先评估哪个注册,并作为内部排序因素。 (越小越优先)
修改 application.properties 在配置文件末尾加入,因为我在内网中,所以关闭https,CAS推荐使用https,如果不使用https协议的话会导致跨域登录或者代理等功能无法使用,所以我们都需要手动关闭这些配置让应用支持http
1 2 3 4 5 6 7 8 9 10 # 开启json服务注册 cas.serviceRegistry.initFromJson=true # 登出后允许跳转到指定页面 cas.logout.followServiceRedirects=true # 设置接入端json文件路径 cas.serviceRegistry.json.location=classpath:/services #关闭https server.ssl.enabled=false cas.tgc.secure=false cas.warningCookie.secure=false
修改log4j2.xml 修改日志配置文件,这里没什么好说的,修改输出路径即可
1 2 3 <Properties> <Property name="baseDir">/home/syd/sso/</Property> </Properties>
接下来直接将应用打包放入tomcat或者直接在idea中接入tomcat运行即可 访问CAS 地址(根据自己的端口和项目名自行修改url)http://localhost:8080/cas/login
客户端的接入 创建Spring Web工程 导入maven 依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--cas的客户端 --> <dependency> <groupId>org.jasig.cas.client</groupId> <artifactId>cas-client-core</artifactId> <version>3.5.0</version> </dependency> </dependencies>
创建客户端拦截器 CAS 客户端的API本质就是一些Servlet 的拦截器,我们将其继承到我们的WEB工程即可,定义拦截器,注意一点filterValidationRegistration过滤器的执行顺序一定要在 filterAuthenticationRegistration之前,具体可以看之前介绍CAS 认证的文章
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 package com.liu; 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.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.ServletListenerRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.EventListener; import java.util.HashMap; import java.util.Map; /** * 功能: TODO(用一句话描述类的功能) * * ────────────────────────────────────────── * version 变更日期 修改人 修改说明 * ------------------------------------------ * V1.0.0 2020/1/21 Liush 初版 * ────────────────────────────────────────── */ @Configuration public class CASConfig { /************************************* 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", URLConfig.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", URLConfig.CAS_SERVER_PATH); initParameters.put("serverName", URLConfig.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", URLConfig.CAS_SERVER_LOGIN_PATH); initParameters.put("serverName", URLConfig.SERVER_NAME); // 不拦截的请求 .* 有后缀的文件 initParameters.put("ignorePattern", ".*"); // 表示过滤所有 initParameters.put("ignoreUrlPatternType", "com.liu.SimpleUrlPatternMatcherStrategy"); registration.setInitParameters(initParameters); // 设定加载的顺序 registration.setOrder(2); 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; } }
接着创建拦截器要用到的白名单URL过滤器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 package com.liu; 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) { } }
一些拦截器用到的URL常量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package com.liu; /** * Cas 的一些配置项 */ public class URLConfig { /** * 当前应用程序的baseUrl(注意最后面的斜线) */ public static String SERVER_NAME = "http://localhost:8181/"; /** * App1 登出成功url */ public static String APP_LOGOUT_PATH = SERVER_NAME + "logoutSuccess"; /** * CAS服务器地址 */ public static String CAS_SERVER_PATH = "http://localhost:8080/cas"; /** * CAS登陆服务器地址 */ public static String CAS_SERVER_LOGIN_PATH = "http://localhost:8080/cas/login"; /** * CAS登出服务器地址 */ public static String CAS_SERVER_LOGOUT_PATH = "http://localhost:8080/cas/logout"; }
编写Http 接口 这里需要注意一下,单点登出,CAS 客户端在第一次完成认证后会把信息缓存在Sesion中,所以这边编写了一个退出的url叫logout,这边会清除当前服务器的session,并且调用CAS的退出服务,CAS 再将所有的服务器的登录信息清除,完成单点退出 ,接下来我们访问这个接口就会跳转到CAS 认证服务器,输入完账号密码后又会跳转回来完成单点登录,同样也可以调用接口进行单点登出功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 package com.liu; 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; /** * 功能: TODO(用一句话描述类的功能) * * ────────────────────────────────────────── * version 变更日期 修改人 修改说明 * ------------------------------------------ * V1.0.0 2020/1/21 Liush 初版 * ────────────────────────────────────────── */ @Controller public class TestController { @RequestMapping("test") @ResponseBody public String test(HttpServletRequest request){ //可以在request中获取登录账号信息 String id=request.getRemoteUser(); System.out.println(id); return "test"; } //退出登录,先失效session再调用CAS Service @RequestMapping("/logout") public String logout(HttpServletRequest request) { // session失效 request.getSession().invalidate(); return "redirect:" + URLConfig.CAS_SERVER_LOGOUT_PATH + "?service="+URLConfig.APP_LOGOUT_PATH; } //重定向到退出登录接口,给成功提示语 @RequestMapping("/logoutSuccess") @ResponseBody public String logoutSuccess(){ return "loginSuccess"; } }
源码地址 https://github.com/liushprofessor/cas_demo