cas5.3.2单点登录-单点登出
既然有单点登录,肯定就要有登出,之前的整合都是只针对了登录,对登出并没有关注,今天我们就来讲讲登出。
关于单点登出原理,参考博客:
https://blog.csdn.net/u010588262/article/details/80201983
https://blog.csdn.net/gdsgdh308227363/article/details/80446168
参数说明
参考官网地址
https://apereo.github.io/cas/5.3.x/installation/Configuration-Properties.html#logout
官网共给出了以下几个属性值:
#配置单点登出
#配置允许登出后跳转到指定页面
cas.logout.followServiceRedirects=false
#跳转到指定页面需要的参数名为 service
cas.logout.redirectParameter=service
#登出后需要跳转到的地址,如果配置该参数,service将无效。
cas.logout.redirectUrl=https://www.taobao.com
#在退出时是否需要 确认退出提示 true弹出确认提示框 false直接退出
cas.logout.confirmLogout=true
#是否移除子系统的票据
cas.logout.removeDescendantTickets=true
#禁用单点登出,默认是false不禁止
#cas.slo.disabled=true
#默认异步通知客户端,清除session
#cas.slo.asynchronous=true
cas 默认登出后默认会跳转到CASServer的登出页,若想跳转到其它资源,可在/logout的URL后面加上service=jumpurl,例如:https://server.cas.com:8443/cas/logout?service=https://www.github.com
但默认servcie跳转不会生效,需要在 cas服务端的application.properties添加cas.logout.followServiceRedirects=true
这个参数也不一定非要叫 service, 可以通过cas.logout.redirectParameter 来修改它。
另外,默认退出的时候没有任何提示,直接就退出了,若想要有弹出提示,需要添加as.logout.confirmLogout=true
再另外,有一个cas.logout.redirectUrl的属性,可以配置默认登出之后跳转到的连接,若 配置该属性,service参数将无效。就算传了service参数,也是走的该页面,所以我们不需要配置此参数。
如果配置了cas.slo.disabled=true 将禁用单点登出。调用登出将无效。
配置过程
服务端配置
application.properties添加以下属性
#配置允许登出后跳转到指定页面
cas.logout.followServiceRedirects=true
#跳转到指定页面需要的参数名为 service
cas.logout.redirectParameter=service
#在退出时是否需要 确认一下 true确认 false直接退出
cas.logout.confirmLogout=true
#是否移除子系统的票据
cas.logout.removeDescendantTickets=true
客户端配置
可以直接在客户端的登出连接写成服务端的登出地址,不过我没有这样做。
我打算客户端写一个UserController,在各自的系统点击登出,先进入本服务的后台方法,在该方法中重定向到服务端的登出地址。
环境:
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.csdn.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目录下
以下是我修改的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.csdn.net/Anumbrella/article/details/81045885
导入idea,导入的时候可以看到实际导入的是
新建src/main/java和resources,鼠标放到项目上,然后F4,把java目录标记为sources,resources标记为resources
从overlays目录下的WEB-INF拷贝services,META-INF文件夹已经application.properties到我们新建的resources目录下
先说下services文件夹,该文件夹下的json文件定义了哪些应用要接入cas,命名格式为 app-id.json如
重点讲下两个属性:1.serviceId定义哪些服务可以接入,可以写统配符,也可以用项目名2.attributeReleaseStrategy定义返回哪些属性,比如登录后返回的用户信息,其他的参考
https://apereo.github.io/cas/5.3.x/installation/Service-Management.html,提醒下logouturl谨慎使用,一定概率造成无法单点退出
application.properties,端口号,项目名,开启json服务识别
1 ## 2 # CAS Server Context Configuration 3 # 4 server.context-path=/cas 5 #server.port=8443 6 server.port=8090 7 server.ssl.enabled=false 8 #server.ssl.key-store=file:/etc/cas/thekeystore 9 #server.ssl.key-store-password=changeit 10 #server.ssl.key-password=changeit 11 12 cas.tgc.secure=false 13 14 spring.devtools.restart.enabled=true 15 16 17 server.max-http-header-size=2097152 18 server.use-forward-headers=true 19 server.connection-timeout=20000 20 server.error.include-stacktrace=ALWAYS 21 22 server.compression.enabled=true 23 server.compression.mime-types=application/javascript,application/json,application/xml,text/html,text/xml,text/plain 24 25 server.tomcat.max-http-post-size=2097152 26 server.tomcat.basedir=build/tomcat 27 server.tomcat.accesslog.enabled=true 28 server.tomcat.accesslog.pattern=%t %a "%r" %s (%D ms) 29 server.tomcat.accesslog.suffix=.log 30 server.tomcat.min-spare-threads=10 31 server.tomcat.max-threads=200 32 server.tomcat.port-header=X-Forwarded-Port 33 server.tomcat.protocol-header=X-Forwarded-Proto 34 server.tomcat.protocol-header-https-value=https 35 server.tomcat.remote-ip-header=X-FORWARDED-FOR 36 server.tomcat.uri-encoding=UTF-8 37 38 spring.http.encoding.charset=UTF-8 39 spring.http.encoding.enabled=true 40 spring.http.encoding.force=true 41 42 ## 43 # CAS Cloud Bus Configuration 44 # 45 spring.cloud.bus.enabled=false 46 47 # Indicates that systemPropertiesOverride can be used. 48 # Set to false to prevent users from changing the default accidentally. Default true. 49 spring.cloud.config.allow-override=true 50 51 # External properties should override system properties. 52 spring.cloud.config.override-system-properties=false 53 54 # When allowOverride is true, external properties should take lowest priority, and not override any 55 # existing property sources (including local config files). 56 spring.cloud.config.override-none=false 57 58 # spring.cloud.bus.refresh.enabled=true 59 # spring.cloud.bus.env.enabled=true 60 # spring.cloud.bus.destination=CasCloudBus 61 # spring.cloud.bus.ack.enabled=true 62 63 endpoints.enabled=false 64 endpoints.sensitive=true 65 66 endpoints.restart.enabled=false 67 endpoints.shutdown.enabled=false 68 69 # Control the security of the management/actuator endpoints 70 # The 'enabled' flag below here controls the rendering of details for the health endpoint amongst other things. 71 management.security.enabled=true 72 management.security.roles=ACTUATOR,ADMIN 73 management.security.sessions=if_required 74 management.context-path=/status 75 management.add-application-context-header=false 76 77 # Define a CAS-specific "WARN" status code and its order 78 management.health.status.order=WARN, DOWN, OUT_OF_SERVICE, UNKNOWN, UP 79 80 # Control the security of the management/actuator endpoints 81 # With basic authentication, assuming Spring Security and/or relevant modules are on the classpath. 82 security.basic.authorize-mode=role 83 security.basic.path=/cas/status/** 84 security.basic.enabled=true 85 security.user.name=casuser 86 security.user.password=123 87 88 ## 89 # CAS Web Application Session Configuration 90 # 91 server.session.timeout=3000 92 server.session.cookie.http-only=false 93 server.session.tracking-modes=COOKIE 94 95 ## 96 # CAS Thymeleaf View Configuration 97 # 98 spring.thymeleaf.encoding=UTF-8 99 spring.thymeleaf.cache=true 100 spring.thymeleaf.mode=HTML 101 spring.thymeleaf.template-resolver-order=100 102 ## 103 # CAS Log4j Configuration 104 # 105 # logging.config=file:/etc/cas/log4j2.xml 106 server.context-parameters.isLog4jAutoInitializationDisabled=true 107 108 ## 109 # CAS AspectJ Configuration 110 # 111 spring.aop.auto=true 112 spring.aop.proxy-target-class=true 113 114 ## 115 # CAS Authentication Credentials 116 # 117 #cas.authn.accept.users=wangyuancheng::Baizhu7958 118 119 120 121 ## 122 # Service Registry(服务注册) 123 # 124 # 开启识别Json文件,默认false 125 cas.serviceRegistry.initFromJson=true 126 127 #自动扫描服务配置,默认开启 128 #cas.serviceRegistry.watcherEnabled=true 129 130 #120秒扫描一遍 131 cas.serviceRegistry.schedule.repeatInterval=120000 132 133 #延迟15秒开启 134 # cas.serviceRegistry.schedule.startDelay=15000 135 136 ## 137 # Json配置 138 cas.serviceRegistry.json.location=classpath:/services 139 140 141 cas.ticket.tgt.maxTimeToLiveInSeconds=28800 142 cas.ticket.tgt.timeToKillInSeconds=7200 143 cas.ticket.tgt.rememberMe.enabled=true 144 # 使用次数 145 cas.ticket.st.numberOfUses=1 146 # 过期时间100秒 147 cas.ticket.st.timeToKillInSeconds=100 148 149 cas.httpClient.allowLocalLogoutUrls=true 150 151 152 cas.slo.disabled=false 153 cas.logout.followServiceRedirects=true 154 cas.logout.removeDescendantTickets=true 155 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依赖
1 <!--CAS Client--> 2 <dependency> 3 <groupId>org.jasig.cas.client</groupId> 4 <artifactId>cas-client-core</artifactId> 5 <version>${cas-client.version}</version> 6 </dependency> 7 8 <dependency> 9 <groupId>org.jasig.cas.client</groupId> 10 <artifactId>cas-client-integration-tomcat-common</artifactId> 11 <version>${cas-client.version}</version> 12 </dependency>
在web.xml中加入如下配置,本机环境不建议使用localhost,使用127.0.0.1(5.1版本使用localhost会有退出失效无法通信的问题,浏览器存储cookie时127.0.0.1和localhost是两个不同的文件夹)
servername指的是客户端地址
1 <listener> 2 <listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class> 3 </listener> 4 5 6 <!-- 单点登出过滤器--> 7 <filter> 8 <filter-name>CAS Single Sign Out Filter</filter-name> 9 <filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class> 10 <init-param> 11 <param-name>casServerUrlPrefix</param-name> 12 <param-value>http://127.0.0.1:8090/cas/</param-value> 13 </init-param> 14 </filter> 15 <filter-mapping> 16 <filter-name>CAS Single Sign Out Filter</filter-name> 17 <url-pattern>/*</url-pattern> 18 </filter-mapping> 19 20 21 22 <!--用来跳转登录--> 23 <filter> 24 <filter-name>CAS Authentication Filter</filter-name> 25 <filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class> 26 <init-param> 27 <param-name>casServerLoginUrl</param-name> 28 <param-value>http://127.0.0.1:8090/cas/login</param-value> 29 </init-param> 30 <init-param> 31 <param-name>serverName</param-name> 32 <!--这是客户端的部署地址,认证时会带着这个地址,认证成功后会跳转到这个地址--> 33 <param-value>http://127.0.0.1:8080</param-value> 34 </init-param> 35 </filter> 36 <filter-mapping> 37 <filter-name>CAS Authentication Filter</filter-name> 38 <url-pattern>/*</url-pattern> 39 </filter-mapping> 40 41 42 <!--Ticket校验过滤器--> 43 <filter> 44 <filter-name>CAS Validation Filter</filter-name> 45 <filter-class>org.jasig.cas.client.validation.Cas30ProxyReceivingTicketValidationFilter</filter-class> 46 <init-param> 47 <param-name>casServerUrlPrefix</param-name> 48 <param-value>http://127.0.0.1:8090/cas/</param-value> 49 </init-param> 50 <init-param> 51 <param-name>serverName</param-name> 52 <param-value>http://127.0.0.1:8080</param-value> 53 </init-param> 54 <init-param> 55 <param-name>redirectAfterValidation</param-name> 56 <param-value>true</param-value> 57 </init-param> 58 <init-param> 59 <param-name>useSession</param-name> 60 <param-value>true</param-value> 61 </init-param> 62 <init-param> 63 <param-name>authn_method</param-name> 64 <param-value>mfa-duo</param-value> 65 </init-param> 66 </filter> 67 <filter-mapping> 68 <filter-name>CAS Validation Filter</filter-name> 69 <url-pattern>/*</url-pattern> 70 </filter-mapping> 71 72 <!-- 该过滤器使得开发者可以通过org.jasig.cas.client.util.AssertionHolder来获取用户的登录名。--> 73 <filter> 74 <filter-name>CASAssertion Thread LocalFilter</filter-name> 75 <filter-class>org.jasig.cas.client.util.AssertionThreadLocalFilter</filter-class> 76 </filter> 77 <filter-mapping> 78 <filter-name>CASAssertion Thread LocalFilter</filter-name> 79 <url-pattern>/*</url-pattern> 80 </filter-mapping> 81 82 83 <!-- 该过滤器负责实现HttpServletRequest请求包装--> 84 <filter> 85 <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name> 86 <filter-class>org.jasig.cas.client.util.HttpServletRequestWrapperFilter</filter-class> 87 </filter> 88 89 <filter-mapping> 90 <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name> 91 <url-pattern>/*</url-pattern> 92 </filter-mapping>
客户端获得cas返回的登录信息的api,或者使用AssertionHolder.getAssertion().getPrincipal().getAttributes("xx");,严谨一点可以先判空,避免NPE
1 @RequestMapping("/login") 2 @ResponseBody 3 public String login() { 4 AttributePrincipal userPrincipal = (AttributePrincipal)request.getUserPrincipal(); 5 return userPrincipal.getAttributes().get("loginStatus").toString(); 6 }
下面讲下退出
@RequestMapping(value = "logout") public String logout(HttpSession session) throws InterruptedException { session.invalidate(); return "redirect:" + CASConstant.LOGOUT_URL; }
我在做的时候发现退出请求总是无法触发,后来发现是项目中有拦截器,于是加了一个excluedUrl,然后在对应的拦截器init的时候filterConfig.getInitParameter("excludedUrl");接下来dofilter时判断如果含有该路径重定向即可,但这样实际相当于重定向两次
还有一个问题是上面redirect的地址如果需要spring静态注入,(当然写死可以),在applicationContext.xml中引入对应的url.properties,然后需要注入的常量类提供set方法即可,需要注意的是spring的注入是基于对象的.所以该set方法不能有static
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
1 # 监听退出的接口,即所有接口都会进行监听 2 spring.cas.sign-out-filters=/* 3 # 需要拦截的认证的接口 4 spring.cas.auth-filters=/* 5 spring.cas.validate-filters=/* 6 spring.cas.request-wrapper-filters=/* 7 spring.cas.assertion-filters=/* 8 # 表示忽略拦截的接口,也就是不用进行拦截 9 spring.cas.ignore-filters=/test 10 spring.cas.cas-server-login-url=http://127.0.0.1:8090/cas/login 11 spring.cas.cas-server-url-prefix=http://127.0.0.1:8090/cas/ 12 spring.cas.redirect-after-validation=true 13 spring.cas.use-session=true 14 spring.cas.redirectAfterValidation=true 15 # 客户端地址 16 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
1 @Controller 2 public class LoginController { 3 4 @Autowired 5 private UserMapper userMapper; 6 7 @Autowired 8 private HttpServletRequest request; 9 10 @RequestMapping("/login") 11 @ResponseBody 12 public String login() { 13 AttributePrincipal userPrincipal = (AttributePrincipal)request.getUserPrincipal(); 14 return userPrincipal.getAttributes().get("loginStatus").toString(); 15 } 16 17 @RequestMapping("/logout") 18 public String logout(HttpSession session) { 19 session.removeAttribute(AbstractCasFilter.CONST_CAS_ASSERTION); 20 session.invalidate(); 21 return "redirect:http://127.0.0.1:8090/cas/logout?service=http://127.0.0.1:8081/spring_boot/login/"; 22 } 23 24 private void println(Object object) { 25 System.out.println(object); 26 } 27 }
三.验证
三.关于源码
源码看了下客户端的拦截器与处理器,大概流程如下,在浏览器输入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(),