原文地址,转载请注明出处: https://blog.csdn.net/qq_34021712/article/details/81486699 ©王赛超
前言
写这篇文章的原因是在springBoot整合cas的过程中,我使用了一个别人写好的starter ,但是在整合单点登出的时候,发现多个客户端无法实现单点登出。经过查看源码,发现在这个starter中没有配置登出的filter。如果写成基于javaBean的配置也就一个类就能搞定,为了能学习一下如何实现一个 springboot的starter 所以来写一个 cas-client-spring-boot-starter。
使用SpringBoot可以快速开发基于Spring框架的项目,SpringBoot存在很多开箱即用的Starter依赖,使得我们在开发业务代码时能够非常方便的、不需要过多关注框架的配置,而只需要关注业务即可。只要按照springboot自动配置的模式来写,就可以实现自己的starter。
关于starter
使用一个公用starter的时候,只需要在Maven的配置文件中添加依赖即可,这个starter可能需要依赖很多jar包,在引用这个starter的同时,依赖的jar包也会自动导入,并且SpringBoot 在启动时会去依赖的starter包中寻找 resources/META-INF/spring.factories 文件,然后根据文件中配置的需要自动加载的类全路径,去加载该配置,并将Bean注入Spring Context 上下文当中。
我们也可以使用@ImportAutoConfiguration({CasAutoConfiguration.class}) 指定自动配置哪些类。
使用@SpringBootApplication(exclude = {CasAutoConfiguration.class}) 来取消自动配置。
实现过程
第一步:创建一个springboot项目,pom.xml完整依赖如下
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>priv.wangsaichao.cas</groupId>
<artifactId>cas-client-spring-boot-starter</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>cas-client-spring-boot-starter</name>
<description>SpringBoot cas client starter.</description>
<!-- 引用依赖的父包 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
</parent>
<dependencies>
<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>
其中 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提供了一个注解@ConfigurationProperties,该注解可以完成将application.properties配置文件内的有规则的配置参数映射到实体内的field内,不过需要提供setter方法,@ConfigurationProperties注解内我们使用到了属性preffix,该属性配置了读取参数的前缀,例如serverUrlPrefix属性,对应配置文件中内容就是 cas.server-url-prefix。完整类如下:
package priv.wangsaichao.cas.client.configuration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import javax.validation.constraints.NotNull;
/**
* @author: WangSaiChao
* @date: 2018/8/5
* @description: cas springboot 配置类
*/
@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;
}
}
第四步:编写AutoConfigure配置类
其实就是cas的那几个核心filter和监听,只不过是从web.xml中移到了基于javabean配置。
package priv.wangsaichao.cas.client.configuration;
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: WangSaiChao
* @date: 2018/8/6
* @description: 自动化配置类
*/
@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.wangsaichao.cas.client.configuration.CasClientConfiguration
基于spring.factories的方式配置,只需要在pom中添加依赖就可以了,项目启动,自动配置。
第二种:基于注解方式,代码如下:
package priv.wangsaichao.cas.client.configuration;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
/**
* @author: WangSaiChao
* @date: 2018/8/6
* @description: 配置该注解开启cas功能
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(CasClientConfiguration.class)
public @interface EnableCasClient {
}
使用时,在启动类上添加该注解,就可以了。目前为止我们的自定义starter已经配置完成,下面我们需要新建一个SpringBoot项目来测试我们的自动化配置是否已经生效。
测试
在之前依赖第三方starter的项目上进行改动
pom依赖修改为:
<dependency>
<groupId>priv.wangsaichao.cas</groupId>
<artifactId>cas-client-spring-boot-starter</artifactId>
<version>1.0.0</version>
</dependency>
application.properties添加以下属性
cas.server-url-prefix=https://server.cas.com:8443/cas
cas.server-login-url=https://server.cas.com:8443/cas/login
cas.client-host-url=http://app2.cas.com:8082
在启动类上添加注解启动自动装配
package com.wangsaichao.cas;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import priv.wangsaichao.cas.client.configuration.EnableCasClient;
/**
* @author: WangSaiChao
* @date: 2018/8/2
* @description: 启动类
*/
@SpringBootApplication
@EnableCasClient//开启cas
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
启动两个项目测试(另一个是之前传统spring方式项目),结果如下: