CAS5.3 单点登录/登出/springboot/springmvc

时间:2023-03-09 08:38:05
CAS5.3  单点登录/登出/springboot/springmvc

环境:

jdk:1.8

cas server:5.3.14 + tomcat 8.5

cas client:3.5.1

客户端1:springmvc 传统web项目(使用web.xml)

客户端2:springboot

参考博客:https://blog.****.net/anumbrella/category_7765386.html

为了方便,没配置https.如果需要参考上面博客

一.CAS 服务端搭建

1.下载源码包:cas overlay github地址:https://github.com/apereo/cas-overlay-template/tree/5.3

2.部署:

在根目录下执行mvn clean package,下载依赖需要一段时间,如果有的包下载失败就要修改pom.xml比如xmlsectool的jar包要先从maven*仓库手动下载,然后加进去,我的放到了图中的maven目录下

CAS5.3  单点登录/登出/springboot/springmvc

以下是我修改的pom.xml,对xmlselectool依赖路径进行了更改,增加了json注册服务,jdbc验证等依赖包

 <?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 http://maven.apache.org/xsd/maven-4.0.0.xsd "> <modelVersion>4.0.0</modelVersion>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-overlay</artifactId>
<packaging>war</packaging>
<version>1.0</version> <build>
<plugins>
<plugin>
<groupId>com.rimerosolutions.maven.plugins</groupId>
<artifactId>wrapper-maven-plugin</artifactId>
<version>0.0.5</version>
<configuration>
<verifyDownload>true</verifyDownload>
<checksumAlgorithm>MD5</checksumAlgorithm>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${springboot.version}</version>
<configuration>
<mainClass>${mainClassName}</mainClass>
<addResources>true</addResources>
<executable>${isExecutable}</executable>
<layout>WAR</layout>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<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>
</overlay>
</overlays>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
</plugin>
</plugins>
<finalName>cas</finalName>
</build> <properties>
<cas.version>5.3.14</cas.version>
<springboot.version>1.5.18.RELEASE</springboot.version>
<mybatis.spring.version>1.3.2</mybatis.spring.version>
<mybatis.version>3.4.6</mybatis.version>
<!-- app.server could be -jetty, -undertow, -tomcat, or blank if you plan to provide appserver -->
<app.server>-tomcat</app.server> <mainClassName>org.springframework.boot.loader.WarLauncher</mainClassName>
<isExecutable>false</isExecutable>
<manifestFileToUse>${project.build.directory}/war/work/org.apereo.cas/cas-server-webapp${app.server}/META-INF/MANIFEST.MF</manifestFileToUse> <maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> <dependencies>
<dependency>
<groupId>net.shibboleth.tool</groupId>
<artifactId>xmlsectool</artifactId>
<version>2.0.0</version>
<scope>system</scope>
<systemPath>${pom.basedir}/maven/xmlsectool-2.0.0.jar</systemPath>
</dependency>
<!--新增支持jdbc验证-->
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-support-jdbc</artifactId>
<version>${cas.version}</version>
<exclusions>
<exclusion>
<artifactId>jul-to-slf4j</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency> <dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-core-cookie</artifactId>
<version>${cas.version}</version>
</dependency> <dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-support-jdbc-drivers</artifactId>
<version>${cas.version}</version>
<scope>runtime</scope>
</dependency> <dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-core-configuration</artifactId>
<version>${cas.version}</version>
</dependency> <!-- Custom Authentication -->
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-core-authentication-api</artifactId>
<version>${cas.version}</version>
</dependency> <!-- Custom Configuration -->
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-core-configuration-api</artifactId>
<version>${cas.version}</version>
</dependency> <dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-support-json-service-registry</artifactId>
<version>${cas.version}</version>
</dependency> <!-- Authentication Attributes -->
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-core-authentication-attributes</artifactId>
<version>${cas.version}</version>
</dependency> <dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.56</version>
</dependency> <!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
<scope>provided</scope>
</dependency> <dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-core-webflow</artifactId>
<version>${cas.version}</version>
</dependency> </dependencies> <profiles>
<profile>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<id>default</id>
<dependencies>
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-webapp${app.server}</artifactId>
<version>${cas.version}</version>
<type>war</type>
<scope>runtime</scope>
</dependency>
<!--
...Additional dependencies may be placed here...
-->
</dependencies>
</profile> <profile>
<activation>
<activeByDefault>false</activeByDefault>
</activation>
<id>exec</id>
<properties>
<mainClassName>org.apereo.cas.web.CasWebApplication</mainClassName>
<isExecutable>true</isExecutable>
<manifestFileToUse></manifestFileToUse>
</properties>
<build>
<plugins>
<plugin>
<groupId>com.soebes.maven.plugins</groupId>
<artifactId>echo-maven-plugin</artifactId>
<version>0.3.0</version>
<executions>
<execution>
<phase>prepare-package</phase>
<goals>
<goal>echo</goal>
</goals>
</execution>
</executions>
<configuration>
<echos>
<echo>Executable profile to make the generated CAS web application executable.</echo>
</echos>
</configuration>
</plugin>
</plugins>
</build>
</profile> <profile>
<activation>
<activeByDefault>false</activeByDefault>
</activation>
<id>bootiful</id>
<properties>
<app.server>-tomcat</app.server>
<isExecutable>false</isExecutable>
</properties>
<dependencies>
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-webapp${app.server}</artifactId>
<version>${cas.version}</version>
<type>war</type>
<scope>runtime</scope>
</dependency>
</dependencies>
</profile> <profile>
<activation>
<activeByDefault>false</activeByDefault>
</activation>
<id>pgp</id>
<build>
<plugins>
<plugin>
<groupId>com.github.s4u.plugins</groupId>
<artifactId>pgpverify-maven-plugin</artifactId>
<version>1.1.0</version>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
<configuration>
<pgpKeyServer>hkp://pool.sks-keyservers.net</pgpKeyServer>
<pgpKeysCachePath>${settings.localRepository}/pgpkeys-cache</pgpKeysCachePath>
<scope>test</scope>
<verifyPomFiles>true</verifyPomFiles>
<failNoSignature>false</failNoSignature>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

2.接下来我们要做一些定制化,如果想先看下效果参考https://blog.****.net/Anumbrella/article/details/81045885

导入idea,导入的时候可以看到实际导入的是

CAS5.3  单点登录/登出/springboot/springmvc

新建src/main/java和resources,鼠标放到项目上,然后F4,把java目录标记为sources,resources标记为resources

CAS5.3  单点登录/登出/springboot/springmvc

从overlays目录下的WEB-INF拷贝services,META-INF文件夹已经application.properties到我们新建的resources目录下

先说下services文件夹,该文件夹下的json文件定义了哪些应用要接入cas,命名格式为 app-id.json如

CAS5.3  单点登录/登出/springboot/springmvc

重点讲下两个属性:1.serviceId定义哪些服务可以接入,可以写统配符,也可以用项目名2.attributeReleaseStrategy定义返回哪些属性,比如登录后返回的用户信息,其他的参考

https://apereo.github.io/cas/5.3.x/installation/Service-Management.html,提醒下logouturl谨慎使用,一定概率造成无法单点退出

CAS5.3  单点登录/登出/springboot/springmvc

application.properties,端口号,项目名,开启json服务识别

 ##
# CAS Server Context Configuration
#
server.context-path=/cas
#server.port=8443
server.port=8090
server.ssl.enabled=false
#server.ssl.key-store=file:/etc/cas/thekeystore
#server.ssl.key-store-password=changeit
#server.ssl.key-password=changeit cas.tgc.secure=false spring.devtools.restart.enabled=true server.max-http-header-size=2097152
server.use-forward-headers=true
server.connection-timeout=20000
server.error.include-stacktrace=ALWAYS server.compression.enabled=true
server.compression.mime-types=application/javascript,application/json,application/xml,text/html,text/xml,text/plain server.tomcat.max-http-post-size=2097152
server.tomcat.basedir=build/tomcat
server.tomcat.accesslog.enabled=true
server.tomcat.accesslog.pattern=%t %a "%r" %s (%D ms)
server.tomcat.accesslog.suffix=.log
server.tomcat.min-spare-threads=10
server.tomcat.max-threads=200
server.tomcat.port-header=X-Forwarded-Port
server.tomcat.protocol-header=X-Forwarded-Proto
server.tomcat.protocol-header-https-value=https
server.tomcat.remote-ip-header=X-FORWARDED-FOR
server.tomcat.uri-encoding=UTF-8 spring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true
spring.http.encoding.force=true ##
# CAS Cloud Bus Configuration
#
spring.cloud.bus.enabled=false # Indicates that systemPropertiesOverride can be used.
# Set to false to prevent users from changing the default accidentally. Default true.
spring.cloud.config.allow-override=true # External properties should override system properties.
spring.cloud.config.override-system-properties=false # When allowOverride is true, external properties should take lowest priority, and not override any
# existing property sources (including local config files).
spring.cloud.config.override-none=false # spring.cloud.bus.refresh.enabled=true
# spring.cloud.bus.env.enabled=true
# spring.cloud.bus.destination=CasCloudBus
# spring.cloud.bus.ack.enabled=true endpoints.enabled=false
endpoints.sensitive=true endpoints.restart.enabled=false
endpoints.shutdown.enabled=false # Control the security of the management/actuator endpoints
# The 'enabled' flag below here controls the rendering of details for the health endpoint amongst other things.
management.security.enabled=true
management.security.roles=ACTUATOR,ADMIN
management.security.sessions=if_required
management.context-path=/status
management.add-application-context-header=false # Define a CAS-specific "WARN" status code and its order
management.health.status.order=WARN, DOWN, OUT_OF_SERVICE, UNKNOWN, UP # Control the security of the management/actuator endpoints
# With basic authentication, assuming Spring Security and/or relevant modules are on the classpath.
security.basic.authorize-mode=role
security.basic.path=/cas/status/**
security.basic.enabled=true
security.user.name=casuser
security.user.password=123 ##
# CAS Web Application Session Configuration
#
server.session.timeout=3000
server.session.cookie.http-only=false
server.session.tracking-modes=COOKIE ##
# CAS Thymeleaf View Configuration
#
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.cache=true
spring.thymeleaf.mode=HTML
spring.thymeleaf.template-resolver-order=100
##
# CAS Log4j Configuration
#
# logging.config=file:/etc/cas/log4j2.xml
server.context-parameters.isLog4jAutoInitializationDisabled=true ##
# CAS AspectJ Configuration
#
spring.aop.auto=true
spring.aop.proxy-target-class=true ##
# CAS Authentication Credentials
#
#cas.authn.accept.users=wangyuancheng::Baizhu7958 ##
# Service Registry(服务注册)
#
# 开启识别Json文件,默认false
cas.serviceRegistry.initFromJson=true #自动扫描服务配置,默认开启
#cas.serviceRegistry.watcherEnabled=true #120秒扫描一遍
cas.serviceRegistry.schedule.repeatInterval=120000 #延迟15秒开启
# cas.serviceRegistry.schedule.startDelay=15000 ##
# Json配置
cas.serviceRegistry.json.location=classpath:/services cas.ticket.tgt.maxTimeToLiveInSeconds=28800
cas.ticket.tgt.timeToKillInSeconds=7200
cas.ticket.tgt.rememberMe.enabled=true
# 使用次数
cas.ticket.st.numberOfUses=1
# 过期时间100秒
cas.ticket.st.timeToKillInSeconds=100 cas.httpClient.allowLocalLogoutUrls=true cas.slo.disabled=false
cas.logout.followServiceRedirects=true
cas.logout.removeDescendantTickets=true
cas.slo.asynchronous=true

默认情况下cas会提供一个jdbc的验证方式,把sql写在application.properties,但这种方式无法提供返回的用户信息,因此需要定义验证处理器及配置信息

 package cn.bz.bzsso.authentication;

 import cn.bz.bzsso.entity.User;
import cn.bz.bzsso.handler.MyAuthenticationHandler;
import cn.bz.bzsso.mapper.UserMapper;
import org.apereo.cas.authentication.AuthenticationEventExecutionPlan;
import org.apereo.cas.authentication.AuthenticationEventExecutionPlanConfigurer;
import org.apereo.cas.authentication.AuthenticationHandler;
import org.apereo.cas.authentication.principal.DefaultPrincipalFactory;
import org.apereo.cas.configuration.CasConfigurationProperties;
import org.apereo.cas.services.ServicesManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; /**
* @author tele
* @Description
* @create 2019-12-04
*/
@Configuration("myAuthenticationConfiguration")
@EnableConfigurationProperties(CasConfigurationProperties.class)
public class MyAuthenticationConfiguration implements AuthenticationEventExecutionPlanConfigurer { @Autowired
private CasConfigurationProperties casProperties; @Autowired
@Qualifier("servicesManager")
private ServicesManager servicesManager; /**
* 将自定义验证器注册为Bean
* @return
*/
@Bean
public AuthenticationHandler myAuthenticationHandler() {
MyAuthenticationHandler handler = new MyAuthenticationHandler(MyAuthenticationHandler.class.getSimpleName(), servicesManager, new DefaultPrincipalFactory(), 1);
return handler;
} /**
* 注册验证器
* @param plan
*/
@Override
public void configureAuthenticationExecutionPlan(AuthenticationEventExecutionPlan plan) {
plan.registerAuthenticationHandler(myAuthenticationHandler());
}
}
 package cn.bz.bzsso.handler;

 import cn.bz.bzsso.entity.LoginCode;
import cn.bz.bzsso.entity.LoginStatus;
import cn.bz.bzsso.entity.User;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import org.apereo.cas.authentication.AuthenticationHandlerExecutionResult;
import org.apereo.cas.authentication.PreventedException;
import org.apereo.cas.authentication.UsernamePasswordCredential;
import org.apereo.cas.authentication.handler.support.AbstractUsernamePasswordAuthenticationHandler;
import org.apereo.cas.authentication.principal.PrincipalFactory;
import org.apereo.cas.services.ServicesManager;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map; /**
* @author tele
* @Description
* @create 2019-12-04
*/
public class MyAuthenticationHandler extends AbstractUsernamePasswordAuthenticationHandler { public MyAuthenticationHandler(String name, ServicesManager servicesManager, PrincipalFactory principalFactory, Integer order) {
super(name, servicesManager, principalFactory, order);
} @Override
protected AuthenticationHandlerExecutionResult authenticateUsernamePasswordInternal(UsernamePasswordCredential credential, String originalPassword) throws GeneralSecurityException, PreventedException {
String username = credential.getUsername();
String password = credential.getPassword(); DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("xx");
dataSource.setUsername("xx");
dataSource.setPassword("xx"); // 创建JDBC模板
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource); String sql = "select id,username,nickname,password from tb_user where username = ?"; User user = (User) jdbcTemplate.queryForObject(sql, new Object[]{username}, new BeanPropertyRowMapper(User.class)); LoginStatus loginStatus = new LoginStatus();
loginStatus.setId(user.getId());
loginStatus.setUsername(user.getUserName());
loginStatus.setNickname(user.getNickName()); if(user.getPassword().equals(password)) {
loginStatus.setCode(LoginCode.LOGIN_SUCCESS);
loginStatus.setMessage(LoginCode.MSG_LOGIN_SUCCESS);
}else {
loginStatus.setCode(LoginCode.ERROR_OF_USER_PWD);
loginStatus.setMessage(LoginCode.MSG_ERROR_OF_USER_PWD);
} Map<String,Object> resultMap = new HashMap<>(4); resultMap.put("loginStatus", JSON.toJSONString(loginStatus,SerializerFeature.WriteNullStringAsEmpty)); return createHandlerResult(credential, this.principalFactory.createPrincipal(credential.getUsername(),resultMap), new ArrayList<>(0)); }
}

修改MATA-INF下的spring.factoriesorg.springframework.boot.autoconfigure.EnableAutoConfiguration=xx.MyAuthenticationConfiguration

ok,服务端配置到此结束,你可以自定义返回的信息,状态码等

二.客户端接入

既然接入了cas,那么所有的客户端都应该关闭登录与退出接口,扒了下官网,看到一句话大概意思是cas不是一个session管理器,每个应用应当对自己的session负责,cas只会在退出时给接入的应用发通知,然后移除内部维护的tgt对象

,换句话说,每个应用内部应当调用session.invalidate()来销毁各自的session

1.传统web项目接入.这种指的是带有web.xml的web

加入cas-client 版本3.5.1依赖

 <!--CAS Client-->
<dependency>
<groupId>org.jasig.cas.client</groupId>
<artifactId>cas-client-core</artifactId>
<version>${cas-client.version}</version>
</dependency> <dependency>
<groupId>org.jasig.cas.client</groupId>
<artifactId>cas-client-integration-tomcat-common</artifactId>
<version>${cas-client.version}</version>
</dependency>

在web.xml中加入如下配置,本机环境不建议使用localhost,使用127.0.0.1(5.1版本使用localhost会有退出失效无法通信的问题,浏览器存储cookie时127.0.0.1和localhost是两个不同的文件夹)

servername指的是客户端地址

 <listener>
<listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>
</listener> <!-- 单点登出过滤器-->
<filter>
<filter-name>CAS Single Sign Out Filter</filter-name>
<filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class>
<init-param>
<param-name>casServerUrlPrefix</param-name>
<param-value>http://127.0.0.1:8090/cas/</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CAS Single Sign Out Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping> <!--用来跳转登录-->
<filter>
<filter-name>CAS Authentication Filter</filter-name>
<filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
<init-param>
<param-name>casServerLoginUrl</param-name>
<param-value>http://127.0.0.1:8090/cas/login</param-value>
</init-param>
<init-param>
<param-name>serverName</param-name>
<!--这是客户端的部署地址,认证时会带着这个地址,认证成功后会跳转到这个地址-->
<param-value>http://127.0.0.1:8080</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CAS Authentication Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping> <!--Ticket校验过滤器-->
<filter>
<filter-name>CAS Validation Filter</filter-name>
<filter-class>org.jasig.cas.client.validation.Cas30ProxyReceivingTicketValidationFilter</filter-class>
<init-param>
<param-name>casServerUrlPrefix</param-name>
<param-value>http://127.0.0.1:8090/cas/</param-value>
</init-param>
<init-param>
<param-name>serverName</param-name>
<param-value>http://127.0.0.1:8080</param-value>
</init-param>
<init-param>
<param-name>redirectAfterValidation</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>useSession</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>authn_method</param-name>
<param-value>mfa-duo</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CAS Validation Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping> <!-- 该过滤器使得开发者可以通过org.jasig.cas.client.util.AssertionHolder来获取用户的登录名。-->
<filter>
<filter-name>CASAssertion Thread LocalFilter</filter-name>
<filter-class>org.jasig.cas.client.util.AssertionThreadLocalFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CASAssertion Thread LocalFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping> <!-- 该过滤器负责实现HttpServletRequest请求包装-->
<filter>
<filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
<filter-class>org.jasig.cas.client.util.HttpServletRequestWrapperFilter</filter-class>
</filter> <filter-mapping>
<filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

客户端获得cas返回的登录信息的api,或者使用AssertionHolder.getAssertion().getPrincipal().getAttributes("xx");,严谨一点可以先判空,避免NPE

 @RequestMapping("/login")
@ResponseBody
public String login() {
AttributePrincipal userPrincipal = (AttributePrincipal)request.getUserPrincipal();
return userPrincipal.getAttributes().get("loginStatus").toString();
}

下面讲下退出

@RequestMapping(value = "logout")
public String logout(HttpSession session) throws InterruptedException {
session.invalidate();
return "redirect:" + CASConstant.LOGOUT_URL;
}

我在做的时候发现退出请求总是无法触发,后来发现是项目中有拦截器,于是加了一个excluedUrl,然后在对应的拦截器init的时候filterConfig.getInitParameter("excludedUrl");接下来dofilter时判断如果含有该路径重定向即可,但这样实际相当于重定向两次

CAS5.3  单点登录/登出/springboot/springmvc

CAS5.3  单点登录/登出/springboot/springmvc

还有一个问题是上面redirect的地址如果需要spring静态注入,(当然写死可以),在applicationContext.xml中引入对应的url.properties,然后需要注入的常量类提供set方法即可,需要注意的是spring的注入是基于对象的.所以该set方法不能有static

CAS5.3  单点登录/登出/springboot/springmvc

2.springboot 项目接入cas.springboot内置tomcat,不再需要加入tomcat

pom.xml

 <properties>
<java.version>1.8</java.version>
<cas.client.version>3.5.1</cas.client.version>
</properties> <dependencies> <!--cas的客户端 -->
<dependency>
<groupId>org.jasig.cas.client</groupId>
<artifactId>cas-client-core</artifactId>
<version>${cas.client.version}</version>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.2.0.RELEASE</version>
</dependency> <dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency> <dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency> <dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency> <dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>

在你的application.properties加入如下配置,依然建议使用127.0.0.1

 # 监听退出的接口,即所有接口都会进行监听
spring.cas.sign-out-filters=/*
# 需要拦截的认证的接口
spring.cas.auth-filters=/*
spring.cas.validate-filters=/*
spring.cas.request-wrapper-filters=/*
spring.cas.assertion-filters=/*
# 表示忽略拦截的接口,也就是不用进行拦截
spring.cas.ignore-filters=/test
spring.cas.cas-server-login-url=http://127.0.0.1:8090/cas/login
spring.cas.cas-server-url-prefix=http://127.0.0.1:8090/cas/
spring.cas.redirect-after-validation=true
spring.cas.use-session=true
spring.cas.redirectAfterValidation=true
# 客户端地址
spring.cas.server-name=http://127.0.0.1:8081

下面要注入之前在web.xml中配置的各种拦截器

代码与参考的博客类似,做了些修改,@ConfigurationProperties依赖spring-boot-configuration-processor

 @Configuration
public class CasCustomConfig {
@Autowired
SpringCasAutoconfig autoconfig; private static boolean casEnabled = true; public CasCustomConfig() {
} @Bean
public SpringCasAutoconfig getSpringCasAutoconfig() {
return new SpringCasAutoconfig();
} @Bean
public ServletListenerRegistrationBean<SingleSignOutHttpSessionListener> singleSignOutHttpSessionListener() {
ServletListenerRegistrationBean<SingleSignOutHttpSessionListener> listener = new ServletListenerRegistrationBean<SingleSignOutHttpSessionListener>();
listener.setEnabled(casEnabled);
listener.setListener(new SingleSignOutHttpSessionListener());
listener.setOrder(1);
return listener;
} /**
* 该过滤器用于实现单点登出功能,单点退出配置,一定要放在其他filter之前
* @return
*/
@Bean
public FilterRegistrationBean singleSignOutFilter() {
FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
filterRegistration.setFilter(new SingleSignOutFilter());
filterRegistration.setEnabled(casEnabled);
if (autoconfig.getSignOutFilters().size() > 0) {
filterRegistration.setUrlPatterns(autoconfig.getSignOutFilters());
} else {
filterRegistration.addUrlPatterns("/*");
}
filterRegistration.addInitParameter("casServerUrlPrefix", autoconfig.getCasServerUrlPrefix());
filterRegistration.addInitParameter("serverName",autoconfig.getServerName());
filterRegistration.setOrder(1);
return filterRegistration;
} /**
* 该过滤器负责用户的认证工作
*
* @return
*/
@Bean
public FilterRegistrationBean authenticationFilter() {
FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
filterRegistration.setFilter(new AuthenticationFilter());
filterRegistration.setEnabled(casEnabled);
if (autoconfig.getAuthFilters().size() > 0) {
filterRegistration.setUrlPatterns(autoconfig.getAuthFilters());
} else {
filterRegistration.addUrlPatterns("/*");
}
if (autoconfig.getIgnoreFilters() != null) {
filterRegistration.addInitParameter("ignorePattern", autoconfig.getIgnoreFilters());
}
filterRegistration.addInitParameter("casServerLoginUrl", autoconfig.getCasServerLoginUrl());
filterRegistration.addInitParameter("serverName", autoconfig.getServerName());
// filterRegistration.addInitParameter("useSession", autoconfig.isUseSession() ? "true" : "false");
// filterRegistration.addInitParameter("redirectAfterValidation", autoconfig.isRedirectAfterValidation() ? "true" : "false");
filterRegistration.setOrder(2);
return filterRegistration;
} /**
* 该过滤器负责对Ticket的校验工作,使用CAS 3.0协议
*
* @return
*/
@Bean
public FilterRegistrationBean cas30ProxyReceivingTicketValidationFilter() {
FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
filterRegistration.setFilter(new Cas30ProxyReceivingTicketValidationFilter());
filterRegistration.setEnabled(casEnabled);
if (autoconfig.getValidateFilters().size() > 0) {
filterRegistration.setUrlPatterns(autoconfig.getValidateFilters());
} else {
filterRegistration.addUrlPatterns("/*");
}
filterRegistration.addInitParameter("casServerUrlPrefix", autoconfig.getCasServerUrlPrefix());
filterRegistration.addInitParameter("serverName", autoconfig.getServerName());
filterRegistration.addInitParameter("useSession", autoconfig.isUseSession() ? "true" : "false");
filterRegistration.addInitParameter("redirectAfterValidation", autoconfig.isRedirectAfterValidation() ? "true" : "false");
filterRegistration.addInitParameter("authn_method", "mfa-duo");
filterRegistration.setOrder(3);
return filterRegistration;
} /**
* 该过滤器使得可以通过org.jasig.cas.client.util.AssertionHolder来获取用户的登录名。
* 比如AssertionHolder.getAssertion().getPrincipal().getName()。
* 这个类把Assertion信息放在ThreadLocal变量中,这样应用程序不在web层也能够获取到当前登录信息
*
* @return
*/
@Bean
public FilterRegistrationBean assertionThreadLocalFilter() {
FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
filterRegistration.setFilter(new AssertionThreadLocalFilter());
filterRegistration.setEnabled(true);
if (autoconfig.getAssertionFilters().size() > 0) {
filterRegistration.setUrlPatterns(autoconfig.getAssertionFilters());
} else {
filterRegistration.addUrlPatterns("/*");
}
filterRegistration.setOrder(4);
return filterRegistration;
} @Bean
public FilterRegistrationBean httpServletRequestWrapperFilter() {
FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
filterRegistration.setFilter(new HttpServletRequestWrapperFilter());
filterRegistration.setEnabled(true);
if (autoconfig.getRequestWrapperFilters().size() > 0) {
filterRegistration.setUrlPatterns(autoconfig.getRequestWrapperFilters());
} else {
filterRegistration.addUrlPatterns("/*");
}
filterRegistration.setOrder(5);
return filterRegistration;
}
}
 @ConfigurationProperties(prefix = "spring.cas")
public class SpringCasAutoconfig { static final String separator = ","; private String validateFilters;
private String signOutFilters;
private String authFilters;
private String assertionFilters;
private String requestWrapperFilters;
private String ignoreFilters; //需要放行的url,多个可以使用|分隔,遵循正则 private String casServerUrlPrefix;
private String casServerLoginUrl;
private String serverName;
private boolean useSession = true;
private boolean redirectAfterValidation = true; public String getIgnoreFilters() {
return ignoreFilters;
} public void setIgnoreFilters(String ignoreFilters) {
this.ignoreFilters = ignoreFilters;
} public List<String> getValidateFilters() {
return Arrays.asList(validateFilters.split(separator));
} public void setValidateFilters(String validateFilters) {
this.validateFilters = validateFilters;
} public List<String> getSignOutFilters() {
return Arrays.asList(signOutFilters.split(separator));
} public void setSignOutFilters(String signOutFilters) {
this.signOutFilters = signOutFilters;
} public List<String> getAuthFilters() {
return Arrays.asList(authFilters.split(separator));
} public void setAuthFilters(String authFilters) {
this.authFilters = authFilters;
} public List<String> getAssertionFilters() {
return Arrays.asList(assertionFilters.split(separator));
} public void setAssertionFilters(String assertionFilters) {
this.assertionFilters = assertionFilters;
} public List<String> getRequestWrapperFilters() {
return Arrays.asList(requestWrapperFilters.split(separator));
} public void setRequestWrapperFilters(String requestWrapperFilters) {
this.requestWrapperFilters = requestWrapperFilters;
} public String getCasServerUrlPrefix() {
return casServerUrlPrefix;
} public void setCasServerUrlPrefix(String casServerUrlPrefix) {
this.casServerUrlPrefix = casServerUrlPrefix;
} public String getCasServerLoginUrl() {
return casServerLoginUrl;
} public void setCasServerLoginUrl(String casServerLoginUrl) {
this.casServerLoginUrl = casServerLoginUrl;
} public String getServerName() {
return serverName;
} public void setServerName(String serverName) {
this.serverName = serverName;
} public boolean isRedirectAfterValidation() {
return redirectAfterValidation;
} public void setRedirectAfterValidation(boolean redirectAfterValidation) {
this.redirectAfterValidation = redirectAfterValidation;
} public boolean isUseSession() {
return useSession;
} public void setUseSession(boolean useSession) {
this.useSession = useSession;
}
}

接下来时登录和登出,不要直接丢个@RestController就完事了,这个主解会让redirect失效,当成字符串转成json返回了,最好的方式是使用@Controller,需要转json的加上@ResponseBody

 @Controller
public class LoginController { @Autowired
private UserMapper userMapper; @Autowired
private HttpServletRequest request; @RequestMapping("/login")
@ResponseBody
public String login() {
AttributePrincipal userPrincipal = (AttributePrincipal)request.getUserPrincipal();
return userPrincipal.getAttributes().get("loginStatus").toString();
} @RequestMapping("/logout")
public String logout(HttpSession session) {
session.removeAttribute(AbstractCasFilter.CONST_CAS_ASSERTION);
session.invalidate();
return "redirect:http://127.0.0.1:8090/cas/logout?service=http://127.0.0.1:8081/spring_boot/login/";
} private void println(Object object) {
System.out.println(object);
}
}

三.验证

CAS5.3  单点登录/登出/springboot/springmvc

CAS5.3  单点登录/登出/springboot/springmvc

三.关于源码

源码看了下客户端的拦截器与处理器,大概流程如下,在浏览器输入http://127.0.0.1:8081/spring_boot/login/会被重定向到http://127.0.0.1:8090/cas/login?service=http://127.0.0.1:8081/spring_boot/login/,也就是cas的登录页,点击登录之后,cas server进行验证,之后会进入客户端的拦截器SingleSignOutFilter,拦截器判断请求类型,如果是tokenrequest(带token的请求),保存session和st,如果是登出请求,将st,session从map中移除,在destroySession中session.invalidate(),但是其他接入的客户端并没有销毁session,退出时,cas只会给接入的客户端发通知,当然从哪个客户端发起的退出该客户端的session会被destroy,但其他客户端要在登出接口中session.invalidate(),

CAS5.3  单点登录/登出/springboot/springmvc