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.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验证等依赖包
1 <?xml version="1.0" encoding="UTF-8"?> 2 <project xmlns="http://maven.apache.org/POM/4.0.0" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd "> 5 6 7 <modelVersion>4.0.0</modelVersion> 8 <groupId>org.apereo.cas</groupId> 9 <artifactId>cas-overlay</artifactId> 10 <packaging>war</packaging> 11 <version>1.0</version> 12 13 <build> 14 <plugins> 15 <plugin> 16 <groupId>com.rimerosolutions.maven.plugins</groupId> 17 <artifactId>wrapper-maven-plugin</artifactId> 18 <version>0.0.5</version> 19 <configuration> 20 <verifyDownload>true</verifyDownload> 21 <checksumAlgorithm>MD5</checksumAlgorithm> 22 </configuration> 23 </plugin> 24 <plugin> 25 <groupId>org.springframework.boot</groupId> 26 <artifactId>spring-boot-maven-plugin</artifactId> 27 <version>${springboot.version}</version> 28 <configuration> 29 <mainClass>${mainClassName}</mainClass> 30 <addResources>true</addResources> 31 <executable>${isExecutable}</executable> 32 <layout>WAR</layout> 33 </configuration> 34 <executions> 35 <execution> 36 <goals> 37 <goal>repackage</goal> 38 </goals> 39 </execution> 40 </executions> 41 </plugin> 42 <plugin> 43 <groupId>org.apache.maven.plugins</groupId> 44 <artifactId>maven-war-plugin</artifactId> 45 <version>2.6</version> 46 <configuration> 47 <warName>cas</warName> 48 <failOnMissingWebXml>false</failOnMissingWebXml> 49 <recompressZippedFiles>false</recompressZippedFiles> 50 <archive> 51 <compress>false</compress> 52 <manifestFile>${manifestFileToUse}</manifestFile> 53 </archive> 54 <overlays> 55 <overlay> 56 <groupId>org.apereo.cas</groupId> 57 <artifactId>cas-server-webapp${app.server}</artifactId> 58 </overlay> 59 </overlays> 60 </configuration> 61 </plugin> 62 <plugin> 63 <groupId>org.apache.maven.plugins</groupId> 64 <artifactId>maven-compiler-plugin</artifactId> 65 <version>3.3</version> 66 </plugin> 67 </plugins> 68 <finalName>cas</finalName> 69 </build> 70 71 <properties> 72 <cas.version>5.3.14</cas.version> 73 <springboot.version>1.5.18.RELEASE</springboot.version> 74 <mybatis.spring.version>1.3.2</mybatis.spring.version> 75 <mybatis.version>3.4.6</mybatis.version> 76 <!-- app.server could be -jetty, -undertow, -tomcat, or blank if you plan to provide appserver --> 77 <app.server>-tomcat</app.server> 78 79 <mainClassName>org.springframework.boot.loader.WarLauncher</mainClassName> 80 <isExecutable>false</isExecutable> 81 <manifestFileToUse>${project.build.directory}/war/work/org.apereo.cas/cas-server-webapp${app.server}/META-INF/MANIFEST.MF</manifestFileToUse> 82 83 <maven.compiler.source>1.8</maven.compiler.source> 84 <maven.compiler.target>1.8</maven.compiler.target> 85 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> 86 </properties> 87 88 <dependencies> 89 <dependency> 90 <groupId>net.shibboleth.tool</groupId> 91 <artifactId>xmlsectool</artifactId> 92 <version>2.0.0</version> 93 <scope>system</scope> 94 <systemPath>${pom.basedir}/maven/xmlsectool-2.0.0.jar</systemPath> 95 </dependency> 96 <!--新增支持jdbc验证--> 97 <dependency> 98 <groupId>org.apereo.cas</groupId> 99 <artifactId>cas-server-support-jdbc</artifactId> 100 <version>${cas.version}</version> 101 <exclusions> 102 <exclusion> 103 <artifactId>jul-to-slf4j</artifactId> 104 <groupId>org.slf4j</groupId> 105 </exclusion> 106 </exclusions> 107 </dependency> 108 109 110 <dependency> 111 <groupId>org.apereo.cas</groupId> 112 <artifactId>cas-server-core-cookie</artifactId> 113 <version>${cas.version}</version> 114 </dependency> 115 116 <dependency> 117 <groupId>org.apereo.cas</groupId> 118 <artifactId>cas-server-support-jdbc-drivers</artifactId> 119 <version>${cas.version}</version> 120 <scope>runtime</scope> 121 </dependency> 122 123 <dependency> 124 <groupId>org.apereo.cas</groupId> 125 <artifactId>cas-server-core-configuration</artifactId> 126 <version>${cas.version}</version> 127 </dependency> 128 129 <!-- Custom Authentication --> 130 <dependency> 131 <groupId>org.apereo.cas</groupId> 132 <artifactId>cas-server-core-authentication-api</artifactId> 133 <version>${cas.version}</version> 134 </dependency> 135 136 <!-- Custom Configuration --> 137 <dependency> 138 <groupId>org.apereo.cas</groupId> 139 <artifactId>cas-server-core-configuration-api</artifactId> 140 <version>${cas.version}</version> 141 </dependency> 142 143 144 <dependency> 145 <groupId>org.apereo.cas</groupId> 146 <artifactId>cas-server-support-json-service-registry</artifactId> 147 <version>${cas.version}</version> 148 </dependency> 149 150 <!-- Authentication Attributes --> 151 <dependency> 152 <groupId>org.apereo.cas</groupId> 153 <artifactId>cas-server-core-authentication-attributes</artifactId> 154 <version>${cas.version}</version> 155 </dependency> 156 157 <dependency> 158 <groupId>com.alibaba</groupId> 159 <artifactId>fastjson</artifactId> 160 <version>1.2.56</version> 161 </dependency> 162 163 164 <!-- lombok --> 165 <dependency> 166 <groupId>org.projectlombok</groupId> 167 <artifactId>lombok</artifactId> 168 <version>1.18.10</version> 169 <scope>provided</scope> 170 </dependency> 171 172 <dependency> 173 <groupId>org.apereo.cas</groupId> 174 <artifactId>cas-server-core-webflow</artifactId> 175 <version>${cas.version}</version> 176 </dependency> 177 178 179 180 181 </dependencies> 182 183 <profiles> 184 <profile> 185 <activation> 186 <activeByDefault>true</activeByDefault> 187 </activation> 188 <id>default</id> 189 <dependencies> 190 <dependency> 191 <groupId>org.apereo.cas</groupId> 192 <artifactId>cas-server-webapp${app.server}</artifactId> 193 <version>${cas.version}</version> 194 <type>war</type> 195 <scope>runtime</scope> 196 </dependency> 197 <!-- 198 ...Additional dependencies may be placed here... 199 --> 200 </dependencies> 201 </profile> 202 203 <profile> 204 <activation> 205 <activeByDefault>false</activeByDefault> 206 </activation> 207 <id>exec</id> 208 <properties> 209 <mainClassName>org.apereo.cas.web.CasWebApplication</mainClassName> 210 <isExecutable>true</isExecutable> 211 <manifestFileToUse></manifestFileToUse> 212 </properties> 213 <build> 214 <plugins> 215 <plugin> 216 <groupId>com.soebes.maven.plugins</groupId> 217 <artifactId>echo-maven-plugin</artifactId> 218 <version>0.3.0</version> 219 <executions> 220 <execution> 221 <phase>prepare-package</phase> 222 <goals> 223 <goal>echo</goal> 224 </goals> 225 </execution> 226 </executions> 227 <configuration> 228 <echos> 229 <echo>Executable profile to make the generated CAS web application executable.</echo> 230 </echos> 231 </configuration> 232 </plugin> 233 </plugins> 234 </build> 235 </profile> 236 237 <profile> 238 <activation> 239 <activeByDefault>false</activeByDefault> 240 </activation> 241 <id>bootiful</id> 242 <properties> 243 <app.server>-tomcat</app.server> 244 <isExecutable>false</isExecutable> 245 </properties> 246 <dependencies> 247 <dependency> 248 <groupId>org.apereo.cas</groupId> 249 <artifactId>cas-server-webapp${app.server}</artifactId> 250 <version>${cas.version}</version> 251 <type>war</type> 252 <scope>runtime</scope> 253 </dependency> 254 </dependencies> 255 </profile> 256 257 <profile> 258 <activation> 259 <activeByDefault>false</activeByDefault> 260 </activation> 261 <id>pgp</id> 262 <build> 263 <plugins> 264 <plugin> 265 <groupId>com.github.s4u.plugins</groupId> 266 <artifactId>pgpverify-maven-plugin</artifactId> 267 <version>1.1.0</version> 268 <executions> 269 <execution> 270 <goals> 271 <goal>check</goal> 272 </goals> 273 </execution> 274 </executions> 275 <configuration> 276 <pgpKeyServer>hkp://pool.sks-keyservers.net</pgpKeyServer> 277 <pgpKeysCachePath>${settings.localRepository}/pgpkeys-cache</pgpKeysCachePath> 278 <scope>test</scope> 279 <verifyPomFiles>true</verifyPomFiles> 280 <failNoSignature>false</failNoSignature> 281 </configuration> 282 </plugin> 283 </plugins> 284 </build> 285 </profile> 286 </profiles> 287 </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,但这种方式无法提供返回的用户信息,因此需要定义验证处理器及配置信息
1 package cn.bz.bzsso.authentication; 2 3 import cn.bz.bzsso.entity.User; 4 import cn.bz.bzsso.handler.MyAuthenticationHandler; 5 import cn.bz.bzsso.mapper.UserMapper; 6 import org.apereo.cas.authentication.AuthenticationEventExecutionPlan; 7 import org.apereo.cas.authentication.AuthenticationEventExecutionPlanConfigurer; 8 import org.apereo.cas.authentication.AuthenticationHandler; 9 import org.apereo.cas.authentication.principal.DefaultPrincipalFactory; 10 import org.apereo.cas.configuration.CasConfigurationProperties; 11 import org.apereo.cas.services.ServicesManager; 12 import org.springframework.beans.factory.annotation.Autowired; 13 import org.springframework.beans.factory.annotation.Qualifier; 14 import org.springframework.boot.context.properties.EnableConfigurationProperties; 15 import org.springframework.context.annotation.Bean; 16 import org.springframework.context.annotation.Configuration; 17 18 /** 19 * @author tele 20 * @Description 21 * @create 2019-12-04 22 */ 23 @Configuration("myAuthenticationConfiguration") 24 @EnableConfigurationProperties(CasConfigurationProperties.class) 25 public class MyAuthenticationConfiguration implements AuthenticationEventExecutionPlanConfigurer { 26 27 @Autowired 28 private CasConfigurationProperties casProperties; 29 30 @Autowired 31 @Qualifier("servicesManager") 32 private ServicesManager servicesManager; 33 34 35 /** 36 * 将自定义验证器注册为Bean 37 * @return 38 */ 39 @Bean 40 public AuthenticationHandler myAuthenticationHandler() { 41 MyAuthenticationHandler handler = new MyAuthenticationHandler(MyAuthenticationHandler.class.getSimpleName(), servicesManager, new DefaultPrincipalFactory(), 1); 42 return handler; 43 } 44 45 /** 46 * 注册验证器 47 * @param plan 48 */ 49 @Override 50 public void configureAuthenticationExecutionPlan(AuthenticationEventExecutionPlan plan) { 51 plan.registerAuthenticationHandler(myAuthenticationHandler()); 52 } 53 }
1 package cn.bz.bzsso.handler; 2 3 import cn.bz.bzsso.entity.LoginCode; 4 import cn.bz.bzsso.entity.LoginStatus; 5 import cn.bz.bzsso.entity.User; 6 import com.alibaba.fastjson.JSON; 7 import com.alibaba.fastjson.serializer.SerializerFeature; 8 import org.apereo.cas.authentication.AuthenticationHandlerExecutionResult; 9 import org.apereo.cas.authentication.PreventedException; 10 import org.apereo.cas.authentication.UsernamePasswordCredential; 11 import org.apereo.cas.authentication.handler.support.AbstractUsernamePasswordAuthenticationHandler; 12 import org.apereo.cas.authentication.principal.PrincipalFactory; 13 import org.apereo.cas.services.ServicesManager; 14 import org.springframework.jdbc.core.BeanPropertyRowMapper; 15 import org.springframework.jdbc.core.JdbcTemplate; 16 import org.springframework.jdbc.datasource.DriverManagerDataSource; 17 import java.security.GeneralSecurityException; 18 import java.util.ArrayList; 19 import java.util.HashMap; 20 import java.util.Map; 21 22 /** 23 * @author tele 24 * @Description 25 * @create 2019-12-04 26 */ 27 public class MyAuthenticationHandler extends AbstractUsernamePasswordAuthenticationHandler { 28 29 30 public MyAuthenticationHandler(String name, ServicesManager servicesManager, PrincipalFactory principalFactory, Integer order) { 31 super(name, servicesManager, principalFactory, order); 32 } 33 34 @Override 35 protected AuthenticationHandlerExecutionResult authenticateUsernamePasswordInternal(UsernamePasswordCredential credential, String originalPassword) throws GeneralSecurityException, PreventedException { 36 String username = credential.getUsername(); 37 String password = credential.getPassword(); 38 39 40 DriverManagerDataSource dataSource = new DriverManagerDataSource(); 41 dataSource.setDriverClassName("com.mysql.jdbc.Driver"); 42 dataSource.setUrl("xx"); 43 dataSource.setUsername("xx"); 44 dataSource.setPassword("xx"); 45 46 // 创建JDBC模板 47 JdbcTemplate jdbcTemplate = new JdbcTemplate(); 48 jdbcTemplate.setDataSource(dataSource); 49 50 String sql = "select id,username,nickname,password from tb_user where username = ?"; 51 52 User user = (User) jdbcTemplate.queryForObject(sql, new Object[]{username}, new BeanPropertyRowMapper(User.class)); 53 54 LoginStatus loginStatus = new LoginStatus(); 55 loginStatus.setId(user.getId()); 56 loginStatus.setUsername(user.getUserName()); 57 loginStatus.setNickname(user.getNickName()); 58 59 if(user.getPassword().equals(password)) { 60 loginStatus.setCode(LoginCode.LOGIN_SUCCESS); 61 loginStatus.setMessage(LoginCode.MSG_LOGIN_SUCCESS); 62 }else { 63 loginStatus.setCode(LoginCode.ERROR_OF_USER_PWD); 64 loginStatus.setMessage(LoginCode.MSG_ERROR_OF_USER_PWD); 65 } 66 67 Map<String,Object> resultMap = new HashMap<>(4); 68 69 resultMap.put("loginStatus", JSON.toJSONString(loginStatus,SerializerFeature.WriteNullStringAsEmpty)); 70 71 return createHandlerResult(credential, this.principalFactory.createPrincipal(credential.getUsername(),resultMap), new ArrayList<>(0)); 72 73 } 74 }
修改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
1 <properties> 2 <java.version>1.8</java.version> 3 <cas.client.version>3.5.1</cas.client.version> 4 </properties> 5 6 <dependencies> 7 8 <!--cas的客户端 --> 9 <dependency> 10 <groupId>org.jasig.cas.client</groupId> 11 <artifactId>cas-client-core</artifactId> 12 <version>${cas.client.version}</version> 13 </dependency> 14 15 16 <dependency> 17 <groupId>org.springframework.boot</groupId> 18 <artifactId>spring-boot-devtools</artifactId> 19 <optional>true</optional> 20 </dependency> 21 22 <dependency> 23 <groupId>org.springframework.boot</groupId> 24 <artifactId>spring-boot-configuration-processor</artifactId> 25 </dependency> 26 27 28 <dependency> 29 <groupId>org.springframework.boot</groupId> 30 <artifactId>spring-boot-starter-web</artifactId> 31 </dependency> 32 33 <dependency> 34 <groupId>org.springframework.boot</groupId> 35 <artifactId>spring-boot-autoconfigure</artifactId> 36 <version>2.2.0.RELEASE</version> 37 </dependency> 38 39 <dependency> 40 <groupId>org.mybatis.spring.boot</groupId> 41 <artifactId>mybatis-spring-boot-starter</artifactId> 42 <version>2.1.1</version> 43 </dependency> 44 45 <dependency> 46 <groupId>mysql</groupId> 47 <artifactId>mysql-connector-java</artifactId> 48 <scope>runtime</scope> 49 </dependency> 50 51 <dependency> 52 <groupId>com.alibaba</groupId> 53 <artifactId>druid</artifactId> 54 <version>1.1.10</version> 55 </dependency> 56 57 <dependency> 58 <groupId>org.projectlombok</groupId> 59 <artifactId>lombok</artifactId> 60 <optional>true</optional> 61 </dependency> 62 <dependency> 63 <groupId>org.springframework.boot</groupId> 64 <artifactId>spring-boot-starter-test</artifactId> 65 <scope>test</scope> 66 <exclusions> 67 <exclusion> 68 <groupId>org.junit.vintage</groupId> 69 <artifactId>junit-vintage-engine</artifactId> 70 </exclusion> 71 </exclusions> 72 </dependency> 73 </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
1 @Configuration 2 public class CasCustomConfig { 3 @Autowired 4 SpringCasAutoconfig autoconfig; 5 6 private static boolean casEnabled = true; 7 8 public CasCustomConfig() { 9 } 10 11 @Bean 12 public SpringCasAutoconfig getSpringCasAutoconfig() { 13 return new SpringCasAutoconfig(); 14 } 15 16 @Bean 17 public ServletListenerRegistrationBean<SingleSignOutHttpSessionListener> singleSignOutHttpSessionListener() { 18 ServletListenerRegistrationBean<SingleSignOutHttpSessionListener> listener = new ServletListenerRegistrationBean<SingleSignOutHttpSessionListener>(); 19 listener.setEnabled(casEnabled); 20 listener.setListener(new SingleSignOutHttpSessionListener()); 21 listener.setOrder(1); 22 return listener; 23 } 24 25 26 27 /** 28 * 该过滤器用于实现单点登出功能,单点退出配置,一定要放在其他filter之前 29 * @return 30 */ 31 @Bean 32 public FilterRegistrationBean singleSignOutFilter() { 33 FilterRegistrationBean filterRegistration = new FilterRegistrationBean(); 34 filterRegistration.setFilter(new SingleSignOutFilter()); 35 filterRegistration.setEnabled(casEnabled); 36 if (autoconfig.getSignOutFilters().size() > 0) { 37 filterRegistration.setUrlPatterns(autoconfig.getSignOutFilters()); 38 } else { 39 filterRegistration.addUrlPatterns("/*"); 40 } 41 filterRegistration.addInitParameter("casServerUrlPrefix", autoconfig.getCasServerUrlPrefix()); 42 filterRegistration.addInitParameter("serverName",autoconfig.getServerName()); 43 filterRegistration.setOrder(1); 44 return filterRegistration; 45 } 46 47 48 /** 49 * 该过滤器负责用户的认证工作 50 * 51 * @return 52 */ 53 @Bean 54 public FilterRegistrationBean authenticationFilter() { 55 FilterRegistrationBean filterRegistration = new FilterRegistrationBean(); 56 filterRegistration.setFilter(new AuthenticationFilter()); 57 filterRegistration.setEnabled(casEnabled); 58 if (autoconfig.getAuthFilters().size() > 0) { 59 filterRegistration.setUrlPatterns(autoconfig.getAuthFilters()); 60 } else { 61 filterRegistration.addUrlPatterns("/*"); 62 } 63 if (autoconfig.getIgnoreFilters() != null) { 64 filterRegistration.addInitParameter("ignorePattern", autoconfig.getIgnoreFilters()); 65 } 66 filterRegistration.addInitParameter("casServerLoginUrl", autoconfig.getCasServerLoginUrl()); 67 filterRegistration.addInitParameter("serverName", autoconfig.getServerName()); 68 // filterRegistration.addInitParameter("useSession", autoconfig.isUseSession() ? "true" : "false"); 69 // filterRegistration.addInitParameter("redirectAfterValidation", autoconfig.isRedirectAfterValidation() ? "true" : "false"); 70 filterRegistration.setOrder(2); 71 return filterRegistration; 72 } 73 74 /** 75 * 该过滤器负责对Ticket的校验工作,使用CAS 3.0协议 76 * 77 * @return 78 */ 79 @Bean 80 public FilterRegistrationBean cas30ProxyReceivingTicketValidationFilter() { 81 FilterRegistrationBean filterRegistration = new FilterRegistrationBean(); 82 filterRegistration.setFilter(new Cas30ProxyReceivingTicketValidationFilter()); 83 filterRegistration.setEnabled(casEnabled); 84 if (autoconfig.getValidateFilters().size() > 0) { 85 filterRegistration.setUrlPatterns(autoconfig.getValidateFilters()); 86 } else { 87 filterRegistration.addUrlPatterns("/*"); 88 } 89 filterRegistration.addInitParameter("casServerUrlPrefix", autoconfig.getCasServerUrlPrefix()); 90 filterRegistration.addInitParameter("serverName", autoconfig.getServerName()); 91 filterRegistration.addInitParameter("useSession", autoconfig.isUseSession() ? "true" : "false"); 92 filterRegistration.addInitParameter("redirectAfterValidation", autoconfig.isRedirectAfterValidation() ? "true" : "false"); 93 filterRegistration.addInitParameter("authn_method", "mfa-duo"); 94 filterRegistration.setOrder(3); 95 return filterRegistration; 96 } 97 98 99 /** 100 * 该过滤器使得可以通过org.jasig.cas.client.util.AssertionHolder来获取用户的登录名。 101 * 比如AssertionHolder.getAssertion().getPrincipal().getName()。 102 * 这个类把Assertion信息放在ThreadLocal变量中,这样应用程序不在web层也能够获取到当前登录信息 103 * 104 * @return 105 */ 106 @Bean 107 public FilterRegistrationBean assertionThreadLocalFilter() { 108 FilterRegistrationBean filterRegistration = new FilterRegistrationBean(); 109 filterRegistration.setFilter(new AssertionThreadLocalFilter()); 110 filterRegistration.setEnabled(true); 111 if (autoconfig.getAssertionFilters().size() > 0) { 112 filterRegistration.setUrlPatterns(autoconfig.getAssertionFilters()); 113 } else { 114 filterRegistration.addUrlPatterns("/*"); 115 } 116 filterRegistration.setOrder(4); 117 return filterRegistration; 118 } 119 120 121 @Bean 122 public FilterRegistrationBean httpServletRequestWrapperFilter() { 123 FilterRegistrationBean filterRegistration = new FilterRegistrationBean(); 124 filterRegistration.setFilter(new HttpServletRequestWrapperFilter()); 125 filterRegistration.setEnabled(true); 126 if (autoconfig.getRequestWrapperFilters().size() > 0) { 127 filterRegistration.setUrlPatterns(autoconfig.getRequestWrapperFilters()); 128 } else { 129 filterRegistration.addUrlPatterns("/*"); 130 } 131 filterRegistration.setOrder(5); 132 return filterRegistration; 133 } 134 }
1 @ConfigurationProperties(prefix = "spring.cas") 2 public class SpringCasAutoconfig { 3 4 static final String separator = ","; 5 6 private String validateFilters; 7 private String signOutFilters; 8 private String authFilters; 9 private String assertionFilters; 10 private String requestWrapperFilters; 11 private String ignoreFilters; //需要放行的url,多个可以使用|分隔,遵循正则 12 13 private String casServerUrlPrefix; 14 private String casServerLoginUrl; 15 private String serverName; 16 private boolean useSession = true; 17 private boolean redirectAfterValidation = true; 18 19 public String getIgnoreFilters() { 20 return ignoreFilters; 21 } 22 23 public void setIgnoreFilters(String ignoreFilters) { 24 this.ignoreFilters = ignoreFilters; 25 } 26 27 public List<String> getValidateFilters() { 28 return Arrays.asList(validateFilters.split(separator)); 29 } 30 31 public void setValidateFilters(String validateFilters) { 32 this.validateFilters = validateFilters; 33 } 34 35 public List<String> getSignOutFilters() { 36 return Arrays.asList(signOutFilters.split(separator)); 37 } 38 39 public void setSignOutFilters(String signOutFilters) { 40 this.signOutFilters = signOutFilters; 41 } 42 43 public List<String> getAuthFilters() { 44 return Arrays.asList(authFilters.split(separator)); 45 } 46 47 public void setAuthFilters(String authFilters) { 48 this.authFilters = authFilters; 49 } 50 51 public List<String> getAssertionFilters() { 52 return Arrays.asList(assertionFilters.split(separator)); 53 } 54 55 public void setAssertionFilters(String assertionFilters) { 56 this.assertionFilters = assertionFilters; 57 } 58 59 public List<String> getRequestWrapperFilters() { 60 return Arrays.asList(requestWrapperFilters.split(separator)); 61 } 62 63 public void setRequestWrapperFilters(String requestWrapperFilters) { 64 this.requestWrapperFilters = requestWrapperFilters; 65 } 66 67 public String getCasServerUrlPrefix() { 68 return casServerUrlPrefix; 69 } 70 71 public void setCasServerUrlPrefix(String casServerUrlPrefix) { 72 this.casServerUrlPrefix = casServerUrlPrefix; 73 } 74 75 public String getCasServerLoginUrl() { 76 return casServerLoginUrl; 77 } 78 79 public void setCasServerLoginUrl(String casServerLoginUrl) { 80 this.casServerLoginUrl = casServerLoginUrl; 81 } 82 83 public String getServerName() { 84 return serverName; 85 } 86 87 public void setServerName(String serverName) { 88 this.serverName = serverName; 89 } 90 91 public boolean isRedirectAfterValidation() { 92 return redirectAfterValidation; 93 } 94 95 public void setRedirectAfterValidation(boolean redirectAfterValidation) { 96 this.redirectAfterValidation = redirectAfterValidation; 97 } 98 99 public boolean isUseSession() { 100 return useSession; 101 } 102 103 public void setUseSession(boolean useSession) { 104 this.useSession = useSession; 105 } 106 }
接下来时登录和登出,不要直接丢个@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(),