在开展项目之前,需要一些基本的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
未经允许请勿转载:程序喵 » Cas 5.2.x版本使用 —— 实现SSO单点登录(九)
程序喵