题目够长吧?
要求是这样的(其实是我自己提出的要求):
现有一个spring boot web 项目,我想实现在配置文件里设置是否需要登录。比如说,配置文件web.properties里有配置项:
#是否开启单点登录
sso.cas-enable=true
当值为true,访问系统就需要登录;否则无须登录。登录功能使用单点登录系统CAS。
这一要求看起来很简单,但以我现有的水平和对spring boot的了解,却一点都不简单,大费周折。当然,最后我实现了这个功能。同时通过这种自我鞭策,也加深了对spring boot的了解。
1、cas本身是一个完整且独立的单点登录系统,具体应用程序只需集成其客户端即可。
2、读取配置文件的配置项,决定是否启用CAS客户端。
3、如何控制启用CAS客户端?自定义组件选择器selector和自定义条件注解custome condition。
1、引入CAS客户端,修改pom.xml
<dependency>
<groupId>net.unicon.casgroupId>
<artifactId>cas-client-autoconfig-supportartifactId>
<version>1.5.0-GAversion>
dependency>
2、启用CAS客户端,最关键的一句就是在主程序入口加一个注解:
@SpringBootApplication
@EnableCasClient//<------ 启用CAS客户端关键一句。仅此一句足以让访问系统前先跳至登录页
public class ZjfwptApplication extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(ZjfwptApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(ZjfwptApplication.class, args);
}
}
那么,我们能控制这个名为@EnableCasClient
的注解吗?
不能。狗咬乌龟,无处下牙。
那么能否不用注解,用别的什么语句来控制呢?找不到什么资料,也不想多费周折,放弃这个念头。
跳到@EnableCasClient
的实现代码里,其实质是另一个注解:
@Import({CasClientConfiguration.class})
那么,如果我们能控制这个@Import注解,岂不就达到控制是否开启CAS客户端的目的了吗?
3、编写组件选择器SsoSelector,在这个组件选择器里返回待加载的类。当配置文件里开启单点登录,我们返回{CasClientConfiguration.class},否则返回空数组{},即什么都不用加载。
1)修改后的主程序:
@SpringBootApplication
@Import({SsoSelector.class})
public class ZjfwptApplication extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(ZjfwptApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(ZjfwptApplication.class, args);
}
}
2)组件加载器SsoSelector
import net.unicon.cas.client.configuration.CasClientConfiguration;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
/*
* 自定义返回需要导入的组件
* */
public class SsoSelector implements ImportSelector {
/**
*
* @param importingClassMetadata 当前被标记有@Import注解的所有注解信息
* @return
*/
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return CasUtil.isCasEnable()
? new String[]{CasClientConfiguration.class.getName()}
: new String[]{};
}
}
3)静态方法CasUtil.isCasEnable
其实如何读取配置文件里的这个配置项也是实现本功能的一个比较麻烦的地方。因为我们控制的是主程序,采用什么@Autowired这种注解来读取配置文件根本是读不到的,因为程序此时都还没来得及完全启动,很多东西都是没有的。
所以只能采取最原始的办法,自己打开配置文件来读。而且我们在读的过程中也发现,文件会被系统锁定,只能用只读方式打开。
打开文件以后,用正则表达式匹配出配置项。
import org.jasig.cas.client.authentication.AttributePrincipal;
import org.jasig.cas.client.util.AbstractCasFilter;
import org.jasig.cas.client.validation.Assertion;
import org.springframework.util.ResourceUtils;
import javax.servlet.http.HttpServletRequest;
import java.io.RandomAccessFile;
import java.net.URLDecoder;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class CasUtil {
public static boolean isCasEnable(){
boolean enable = false;
try {
String path = ResourceUtils.getURL("classpath:").getPath();
path = URLDecoder.decode(path, "utf-8");
path = String.format("%s\\webconfig.properties",path);//webconfig.properties是自定义的配置文件
RandomAccessFile raf = new RandomAccessFile(path, "r");//只读方式读配置文件
String line = raf.readLine();
String sb = "";
while(line != null && sb.length() == 0){
line = raf.readLine();
sb = getCasEnable(line);
}
raf.close();
if(sb.length() > 0){
enable = Boolean.valueOf(sb);
}
} catch (Exception ex) {
return enable;
}
return enable;
}
/*
配置文件里的配置项
--------------------------------
#是否开启单点登录
sso.cas-enable=true
--------------------------------
*/
static String pattern = "sso\\.cas\\-enable\\=(?true|false)";
private static String getCasEnable(String line){
Pattern r = Pattern.compile(pattern);
Matcher m = r.matcher(line);
return (m.find()) ? m.group("b") : "";
}
}
4、其他过滤器
其他还有一些过滤器,从网上抄过来的。因为它有注解@Configuration,意味着系统会自动解释它。由于这个过滤器与单点登录有关,在关闭单点登录的情况下,应该不要加载它们。这时候就要用到条件注解。
条件注解的作用是对@Configuration的一个补充。符合条件再加载。
CAS过滤器:
import org.jasig.cas.client.session.SingleSignOutFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
@Configuration
@Conditional(CasCondition.class)
public class CasFilter {
@Autowired
protected CasConfig casConfig;
/**
* 单点登录退出
*
* @return
*/
@Bean
public FilterRegistrationBean singleSignOutFilter() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new SingleSignOutFilter());
registrationBean.addUrlPatterns("/*");
registrationBean.addInitParameter("casServerUrlPrefix", casConfig.getServerUrlPrefix());
registrationBean.setName("CAS Single Sign Out Filter");
registrationBean.setOrder(1);
return registrationBean;
}
}
自定义条件注解:
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
/*
加载CAS的条件注解
*/
public class CasCondition implements Condition {
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return CasUtil.isCasEnable();
}
}
有关条件注解@Conditional有好多种,我英文不行,也来不及一一细看,可参考:
https://reflectoring.io/spring-boot-conditionals/