在上一篇shiro-cas------整合springboot客户端实现简易的单点登陆后,我们接下来看下单点登出功能。shiro-cas-client-one系统退出,那么shiro-cas-client-two也退出。在这里我们在我们原有的界面加入退出按钮。(这里只在shiro-cas-client-one上加退出按钮,刷新shiro-cas-client-two可以登出即视为单点登出实现)
加入登出按钮后的页面:
点击退出:
发现 shiro-cas-client-one系统退出,刷新shiro-cas-client-two未退出。
经过查看源码,发现在引入的jar中没有配置登出的filter。
<dependency>
<groupId>net.unicon.cas</groupId>
<artifactId>cas-client-autoconfig-support</artifactId>
<version>1.4.0-GA</version>
<exclusions>
<exclusion>
<groupId>org.jasig.cas.client</groupId>
<artifactId>cas-client-core</artifactId>
</exclusion>
</exclusions>
</dependency>
那么我们也实现一个自己的starter,顺便了解下springboot starter的机制。
SpringBoot中的starter是一种非常重要的机制,能够抛弃以前繁杂的配置,将其统一集成进starter,应用者只需要在maven中引入starter依赖,SpringBoot就能自动扫描到要加载的信息并启动相应的默认配置。starter让我们摆脱了各种依赖库的处理,需要配置各种信息的困扰。SpringBoot会自动通过classpath路径下的类发现需要的Bean,并注册进IOC容器。SpringBoot提供了针对日常企业应用研发各种场景的spring-boot-starter依赖模块。所有这些依赖模块都遵循着约定成俗的默认配置,并允许我们调整这些配置,即遵循“约定大于配置”的理念。
在我们的日常开发工作中,经常会有一些独立于业务之外的配置模块,我们经常将其放到一个特定的包下,然后如果另一个工程需要复用这块功能的时候,需要将代码硬拷贝到另一个工程,重新集成一遍,麻烦至极。如果我们将这些可独立于业务代码之外的功配置模块封装成一个个starter,复用的时候只需要将其在pom中引用依赖即可,SpringBoot为我们完成自动装配,简直不要太爽。
工作中遇到的部分场景:1、动态数据源;2、登录模块;2、基于AOP技术实现日志切面等
使用一个公用starter的时候,只需要在Maven的配置文件中添加依赖即可,这个starter可能需要依赖很多jar包,在引用这个starter的同时,依赖的jar包也会自动导入,并且SpringBoot 在启动时会去依赖的starter包中寻找 resources/META-INF/spring.factories 文件,然后根据文件中配置的需要自动加载的类全路径,去加载该配置,并将Bean注入Spring Context 上下文当中。
我们也可以使用@ImportAutoConfiguration({CasAutoConfiguration.class}) 指定自动配置哪些类。
使用@SpringBootApplication(exclude = {CasAutoConfiguration.class}) 来取消自动配置。
其中 spring-boot-configuration-processor 的作用是编译时生成 spring-configuration-metadata.json ,此文件主要给IDE使用。如当配置此jar相关配置属性在 application.properties ,你可以用ctlr+鼠标左键点击属性名,IDE会跳转到你配置此属性的类中。
关于artifactId中的名字cas-client-spring-boot-starter。Spring官方的Starter一般采取spring-boot-starter-{name} 的命名方式,如 spring-boot-starter-web 。
而非官方的Starter,官方建议 artifactId 命名应遵循{name}-spring-boot-starter 的格式。 例如:mybatis-spring-boot-starter
创建一个springboot空项目(注意版本号和引入自定义的boot版本号一致)引入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.lhl.shiro.cas</groupId>
<artifactId>shiro-cas-client-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>shiro-cas-client-starter</name>
<description>Spring Boot 自定义cas starter 登录登出过滤</description>
<properties>
<java.version>1.8</java.version>
<skipTests>true</skipTests>
</properties>
<dependencies>
<!--<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.jasig.cas.client</groupId>
<artifactId>cas-client-core</artifactId>
<version>3.5.0</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.1.0.Final</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<!-- 配置该插件将源码放入仓库 -->
<plugin>
<artifactId>maven-source-plugin</artifactId>
<version>2.1</version>
<configuration>
<attach>true</attach>
</configuration>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
springboot提供了一个注解@ConfigurationProperties,该注解可以完成将application.properties配置文件内的有规则的配置参数映射到实体内的field内,不过需要提供setter方法,@ConfigurationProperties注解内我们使用到了属性preffix,该属性配置了读取参数的前缀,例如serverUrlPrefix属性,对应配置文件中内容就是 cas.server-url-prefix。完整类如下:
package com.lhl.shiro.cas.client.starter.config;
import javax.validation.constraints.NotNull;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author: hualiang.liu-ext
* @description:
* @date: created in 2021/3/2 15:13
*/
@ConfigurationProperties(prefix = "cas", ignoreUnknownFields = false)
public class CasClientConfigurationProperties {
/**
* CAS server URL E.g. https://example.com/cas or https://cas.example. Required.
* CAS 服务端 url 不能为空
*/
@NotNull
private String serverUrlPrefix;
/**
* CAS server login URL E.g. https://example.com/cas/login or https://cas.example/login. Required.
* CAS 服务端登录地址 上面的连接 加上/login 该参数不能为空
*/
@NotNull
private String serverLoginUrl;
/**
* CAS-protected client application host URL E.g. https://myclient.example.com Required.
* 当前客户端的地址
*/
@NotNull
private String clientHostUrl;
/**
* 忽略规则,访问那些地址 不需要登录
*/
private String ignorePattern;
/**
* 自定义UrlPatternMatcherStrategy验证
*/
private String ignoreUrlPatternType;
public String getServerUrlPrefix() {
return serverUrlPrefix;
}
public void setServerUrlPrefix(String serverUrlPrefix) {
this.serverUrlPrefix = serverUrlPrefix;
}
public String getServerLoginUrl() {
return serverLoginUrl;
}
public void setServerLoginUrl(String serverLoginUrl) {
this.serverLoginUrl = serverLoginUrl;
}
public String getClientHostUrl() {
return clientHostUrl;
}
public void setClientHostUrl(String clientHostUrl) {
this.clientHostUrl = clientHostUrl;
}
public String getIgnorePattern() {
return ignorePattern;
}
public void setIgnorePattern(String ignorePattern) {
this.ignorePattern = ignorePattern;
}
public String getIgnoreUrlPatternType() {
return ignoreUrlPatternType;
}
public void setIgnoreUrlPatternType(String ignoreUrlPatternType) {
this.ignoreUrlPatternType = ignoreUrlPatternType;
}
}
其实就是cas的那几个核心filter和监听,只不过是从web.xml中移到了基于javabean配置。
package com.lhl.shiro.cas.client.starter.config;
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.HttpServletRequestWrapperFilter;
import org.jasig.cas.client.validation.Cas30ProxyReceivingTicketValidationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
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;
/**
* @author: hualiang.liu-ext
* @description:
* @date: created in 2021/3/2 15:14
*/
@Configuration
@EnableConfigurationProperties(CasClientConfigurationProperties.class)
public class CasClientConfiguration {
@Autowired
CasClientConfigurationProperties configProps;
/**
* 配置登出过滤器
* @return
*/
@Bean
public FilterRegistrationBean filterSingleRegistration() {
final FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new SingleSignOutFilter());
// 设定匹配的路径
registration.addUrlPatterns("/*");
Map<String,String> initParameters = new HashMap<String, String>();
initParameters.put("casServerUrlPrefix", configProps.getServerUrlPrefix());
registration.setInitParameters(initParameters);
// 设定加载的顺序
registration.setOrder(1);
return registration;
}
/**
* 配置过滤验证器 这里用的是Cas30ProxyReceivingTicketValidationFilter
* @return
*/
@Bean
public FilterRegistrationBean filterValidationRegistration() {
final FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new Cas30ProxyReceivingTicketValidationFilter());
// 设定匹配的路径
registration.addUrlPatterns("/*");
Map<String,String> initParameters = new HashMap<String, String>();
initParameters.put("casServerUrlPrefix", configProps.getServerUrlPrefix());
initParameters.put("serverName", configProps.getClientHostUrl());
initParameters.put("useSession", "true");
registration.setInitParameters(initParameters);
// 设定加载的顺序
registration.setOrder(2);
return registration;
}
/**
* 配置授权过滤器
* @return
*/
@Bean
public FilterRegistrationBean filterAuthenticationRegistration() {
final FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new AuthenticationFilter());
// 设定匹配的路径
registration.addUrlPatterns("/*");
Map<String,String> initParameters = new HashMap<String, String>();
initParameters.put("casServerLoginUrl", configProps.getServerLoginUrl());
initParameters.put("serverName", configProps.getClientHostUrl());
if(configProps.getIgnorePattern() != null && !"".equals(configProps.getIgnorePattern())){
initParameters.put("ignorePattern", configProps.getIgnorePattern());
}
//自定义UrlPatternMatcherStrategy 验证规则
if(configProps.getIgnoreUrlPatternType() != null && !"".equals(configProps.getIgnoreUrlPatternType())){
initParameters.put("ignoreUrlPatternType", configProps.getIgnoreUrlPatternType());
}
registration.setInitParameters(initParameters);
// 设定加载的顺序
registration.setOrder(3);
return registration;
}
/**
* request wraper过滤器
* @return
*/
@Bean
public FilterRegistrationBean filterWrapperRegistration() {
final FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new HttpServletRequestWrapperFilter());
// 设定匹配的路径
registration.addUrlPatterns("/*");
// 设定加载的顺序
registration.setOrder(4);
return registration;
}
/**
* 添加监听器
* @return
*/
@Bean
public ServletListenerRegistrationBean<EventListener> singleSignOutListenerRegistration(){
ServletListenerRegistrationBean<EventListener> registrationBean = new ServletListenerRegistrationBean<EventListener>();
registrationBean.setListener(new SingleSignOutHttpSessionListener());
registrationBean.setOrder(1);
return registrationBean;
}
}
编译基于注解启动方式
第一种方式:使用自定义spring.factories的方式自动配置,步骤如下:
在src/main/resource
目录下创建META-INF
目录,并在目录内添加文件spring.factories
,给@EnableAutoConfiguration去自动扫描,具体内容如下所示:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.lhl.shiro.cas.client.starter.config.CasClientConfiguration
基于spring.factories的方式配置,只需要在pom中添加依赖就可以了,项目启动,自动配置。
第二种:基于注解方式,代码如下:
package com.lhl.shiro.cas.client.starter.config;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
/**
* @author: hualiang.liu-ext
* @description:
* @date: created in 2021/3/2 15:21
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(CasClientConfiguration.class)
public @interface EnableCasClient {
}
使用时,在启动类上添加该注解,就可以了。目前为止我们的自定义starter已经配置完成,下面我们需要新建一个SpringBoot项目来测试我们的自动化配置是否已经生效。
接下来我们将定义好的starter打包编译并安装到maven仓库
然后在我们的两个客户端系统中引入自定义地starter,去除之前地starter.
<dependency>
<groupId>com.lhl.shiro.cas</groupId>
<artifactId>shiro-cas-client-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
application.properties修改以下属性
#cas配置
#cas服务端前缀,不是登录地址
cas.server-url-prefix=https://shiro.sso.com:8443
#cas的登录地址
cas.server-login-url=https://shiro.sso.com:8443/login
#当前客户端的地址
cas.client-host-url=http://127.0.0.1:9010
在启动类上添加注解启动自动装配
然后重新启动两个客户端服务:
点击退出:
发现 shiro-cas-client-one系统退出,刷新shiro-cas-client-two退出。
单点登出集成成功!
参考地址:https://blog.csdn.net/qq_34021712/article/details/81486699