1.准备工作准备工作主要做两件事。1.1服务记录如果客户端需要访问CAS服务器进行验证,客户端必须事先在CAS服务器上配置自己的信息。此信息可以动态添加或通过JSON配置。后面松哥会教如何动态添加。这里为了方便,我们还是通过JSON来配置。具体配置方法如下,在CASServer中创建如下目录:src/main/resources/services在该目录下创建名为client1-99.json的文件,client1代表要连接的客户端名称,99代表要连接的客户端名称导入客户端的id,json文件内容如下(这个配置可以参考官方模板:overlays/org.apereo.cas.cas-server-webapp-tomcat-5.3.14/WEB-INF/classes/services/Apereo-10000002.json):{"@class":"org.apereo.cas.services.RegexRegisteredService","serviceId":"^(https|http)://.*","name":"client1","id":99,"description":"应用1的定义信息","evaluationOrder":1}这个JSON配置的含义如下:@calss指定注册的服务类,固定为org.apereo.cas.services。正则表达式注册服务。serviceId用于通过正则表达式匹配特定的请求。name是连接的客户端的名称。id是连接的客户端的id。description是连接客户端的描述信息。evaluationOrder指定执行的优先级。接下来在src/main/resources/application.properties文件中配置刚才的json信息,如下:cas.serviceRegistry.json.location=classpath:/servicescas.serviceRegistry.initFromJson=true这里有两行配置:指定配置JSON文件的位置。启用JSON识别。OK,配置完成后,重启CASServer。CASServer启动成功后,我们在控制台看到如下日志,说明JSON配置加载成功:1.2JDK证书第二个要提前准备的就是JDK证书。在实际开发中,这一步可以忽略,但是因为我们现在使用的是自生成的SSL证书,所以需要将自生成的证书导入到JDK中,否则在使用SpringSecurity访问CAS单点登录时,会抛出出现如下错误:将SSL证书导入JDK的命令其实很简单,两步,第一步导出.cer文件,第二步导入JDK,命令如下:keytool-export-trustcacerts-aliascasserver-file./cas.cer-keystore./keystoresudokeytool-import-trustcacerts-aliascasserver-file./cas.cer-keystore/Library/Java/JavaVirtualMachines/jdk-10.0.2.jdk/Contents/home/lib/security/cacerts需要注意的是,在执行export导出命令时,需要输入密钥密码,这个密码是你第一次创建SSL证书时设置的。执行导入命令时,还需要输入密码。这个密码是changeit。注意不是一开始就自己设置的。keystore的位置是JDK目录下的/lib/security/cacerts,可以根据自己的实际情况修改(在JDK9之前,位置是jre/lib/security/cacerts)。我们必须将证书导入到JDK证书库中进行本地测试,否则在后续测试中会出现上图所示的错误。将证书导入到JDK证书库中后,需要保证后续开发使用的是本地的JDK。注意导入JDK证书后需要重启CASServer。1.3修改hosts另外我们还需要修改电脑的hosts文件,因为上面CASServer的配置和SSL证书涉及到域名,所以后面的访问都是通过域名来访问的,并且在hosts文件中添加如下两条记录:第一条是CASServer请求的域名,第二条是CASClient请求的域名。2.开发Client在使用SpringSecurity开发CASClient之前,有一个基本的问题需要跟小伙伴们搞清楚:用户登录是在CASServer上登录的,所以虽然SpringSecurity中仍然存在用户的概念,但是user的处理逻辑会和之前的不一样。好了,下面我们来看具体的步骤。首先我们创建一个普通的SpringBoot项目,添加Web依赖和SpringSecurity依赖,如下:项目创建成功后,我们手动添加cas依赖:org.springframework.securityspring-security-cas接下来在application.properties中配置CASServer和CASClient的请求地址信息:cas.server.prefix=https://cas.javaboy.org:第8443章8080cas.client.login=${cas.client.prefix}/login/cascas.client.logoutRelative=/logout/cascas.client.logout=${cas.client.prefix}${cas.client.logoutRelative}这些配置都是自定义配置,所以配置的key可以自由定义。至于配置的意思,很容易理解。分别配置CASServer和CASClient的登录和注销地址。配置完成后,我们需要将这些配置注入到实体类中使用。这里使用了类型安全的属性绑定。这里我创建了两个类来接收CASServer和CASClient的配置文件:@ConfigurationProperties(prefix="cas.server").client")publicclassCASClientProperties{privateStringprefix;privateStringlogin;privateStringlogoutRelative;privateStringlogout;//省略getter/setter}另外记得在启动类上加上@ConfigurationPropertiesScan注解扫描这两个配置类:@SpringBootApplication@ConfigurationPropertiesScanpublicclassClimainent1Application{public(String[staticvoid)]args){SpringApplication.run(Client1Application.class,args);}}这里配置完成后,我们后面会在配置文件中使用。接下来创建CAS的配置文件,略长:@ConfigurationpublicclassCasSecurityConfig{@AutowiredCASClientPropertiescasClientProperties;@AutowiredCASServerPropertiescasServerProperties;@AutowiredUserDetailsS??erviceuserDetailService;@BeanServicePropertiesserviceProperties(){ServicePropertiesserviceProperties=newServiceProperties();serviceProperties.setService(casClientProperties.getLogin());returnserviceProperties;}@Bean@PrimaryAuthenticationEntryPointauthenticationEntryPoint(){CasAuthenticationEntryPointentryPoint=newCasAuthenticationEntryPoint();entryPoint.setLoginUrl(casServerProperties.getLogin());entryPoint.setServiceProperties(serviceProperties());returnentryPoint;}@BeanTicketValidatorticketValidator(){returnnewCas20ProxyTicketServerValidator.@casgetPrefix()BeanCasAuthenticationProvidercasAuthenticationProvider(){CasAuthenticationProviderprovider=newCasAuthenticationProvider();provider.setServiceProperties(serviceProperties());provider.setTicketValidator(ticketValidator());provider.setUserDetailsS??ervice(userDetailService);provider.setKey("javaboy");returnprovider;}@BeanCasAuthenticationFiltercasAuthenticationFilter(AuthenticationProviderauthenticationProvider){CasAuthenticationFilterfilter=newCasAuthenticationFilter();filter.setServiceProperties(serviceProperties());filter.setAuthenticationManager(newProviderManager(authenticationProvider));returnfilter;}@BeanSingleSignOutFiltersingleSignOutFilter(){SingleSignOutFiltersign=newSingleSignOutFilter();sign.setIgnoreInitConfiguration(true);returnsign;}@BeanLogoutFilterlogoutFilter(){LogoutFilterfilter=newLogoutFilter(casServerProperties.getHlogoutContext()),newS;filter.setFilterProcessesUrl(casClientProperties.getLogoutRelative());returnfilter;}}这个配置文件有点长,但是不难。我一一给大家解释一下:首先,注入三个对象,而这三个中,有两个是我们前面写的配置类的实例,另一个是UserDetailsS??ervice。我认为我不需要对UserDetailsS??ervice做更多的解释。UserDetailsS??ervice的作用可以参考本系列之前的文章。一会儿给UserDet。ailsS??ervice的实现接下来,配置ServiceProperties。在ServiceProperties中,只需要配置Client的登录地址即可。该地址为登录CASServer成功后的重定向地址。CasAuthenticationEntryPoint是CAS身份验证的入口点。这里先设置CASServer的登录地址,同时设置前面的ServiceProperties,这样当它登录成功的时候,就知道跳转到哪里了。TicketValidator这是配置票证验证地址。CASClient拿到票证去CASServer验证。默认验证地址为:https://cas.javaboy.org:8443/cas/proxyValidate?ticket=xxxCasAuthenticationProvider主要用于处理CAS验证逻辑,关于AuthenticationProvider松哥在上一篇文章中给大家分享过(两种方式SpringSecurity自定义认证逻辑(进阶玩法)),当时他说想自定义认证逻辑,比如短信登录等,可以通过扩展AuthenticationProvider来实现。这里的CAS登录当然也不例外。这里虽然设置了一个userDetailService,但目的并不是从数据库中查询数据进行验证,因为登录是在CASServer中进行的。功能,后面会介绍。CasAuthenticationFilter是用于CAS身份验证的过滤器。Filter拦截请求后,交给CasAuthenticationProvider进行具体处理。SingleSignOutFilter表示接受CASServer发送的注销请求,所有的注销请求都会从CASClient转发给CASServer,CASServer处理后通知所有CASClient注销。LogoutFilter配置为将注销请求转发到CAS服务器。接下来,让我向您展示我定义的UserDetailsS??ervice:@Component@PrimarypublicclassUserDetailsS??erviceImplimplementsUserDetailsS??ervice{@OverridepublicUserDetailsloadUserByUsername(Strings)throwsUsernameNotFoundException{returnnewUser(s,"123",true,true,true,true,AuthorityUtils.createAuthority_List("RO"user);}}既然是单点登录,即用户在CASServer上登录,那么这里的UserDetailsS??ervice是什么意思呢?虽然用户是在CASServer上登录的,但是登录成功后,CASClient还需要获取基本信息,角色等,才能做进一步的权限控制,所以这里的loadUserByUsername方法中的参数其实就是你从CASServer登录成功后得到的用户名,取这个用户名和在数据库中查询用户的相关置信度和返回值,以便在后续的认证中进一步使用CASClient,这里为了方便,我没有去数据库查询,而是直接创建了一个User对象返回。接下来,我们再来看看SpringSecurity的配置:@ConfigurationpublicclassSecurityConfigextendsWebSecurityConfigurerAdapter{@AutowiredAuthenticationEntryPointauthenticationEntryPoint;@AutowiredAuthenticationProviderauthenticationProvider;@AutowiredSingleSignOutFiltersingleSignOutFilter;@AutowiredLogoutFilterlogoutFilter;@AutowiredCasAuthenticationFiltercasAuthenticationFilter;@Overrideprotectedvoidconfigure(AuthenticationManagerBuilderauth)throwsException{auth.authenticationProvider(authenticationProvider);}@Overrideprotectedvoidconfigure(HttpSecurityhttp)throwsException{http.authorizeRequests().antMatchers("/user/**").hasRole("user").antMatchers("/login/cas").permitAll().anyRequest().authenticated().and().exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).and().addFilter(casAuthenticationFilter).addFilterBefore(singleSignOutFilter,CasAuthenticationFilter.class).addFilterBefore(logoutFilter,LogoutFilter.class);}}这里配置就简单多了:先配置authenticationProvider,这个authenticationProvider其实就是一开始配置的CasAuthenticationProvider,然后配置/user/**格式的路径只允许用户角色访问,登录路径/login/cas即可可直接访问,其余界面需登录成功后才能访问。最后,配置authenticationEntryPoint,然后添加自定义过滤器。这些比较容易,我就不多说了。最后再提供两个测试接口:@RestControllerpublicclassHelloController{@GetMapping("/hello")publicStringhello(){return"hello";}@GetMapping("/user/hello")publicStringuser(){return"user";}}OK,至此,我们的CASClient就开发完成了。接下来,启动CAS客户端。启动成功后,在浏览器中输入http://client1.cas.javaboy.org:8080/user/hello,进入hello界面。此时会自动跳转到CASServer登录,登录成功后,经过两次重定向后返回hello界面。3、总结OK,这就是松哥和大家介绍的SpringSecurity+CAS单点登录。当然,在这种情况下还有很多需要改进的地方。松哥将在后续文章中继续与大家分享完美解决方案。好吧,这就是本文的全部内容。本文相关案例我已经上传到GitHub,大家可以自行下载:https://github.com/lenve/spring-security-samples,可以通过以下二维码关注。转载本文请联系江南一点鱼公众号。