一、CAS Client 与受保护的客户端应用部署在一起,以 Filter 方式保护受保护的资源。对于访问受保护资源的每个 Web 请求,CAS Client 会分析该请求的 Http 请求中是否包含 Service Ticket,如果没有,则说明当前用户尚未登录,于是将请求重定向到指定好的 CAS Server 登录地址,并传递 Service (也就是要访问的目的资源地址),以便登录成功过后转回该地址。用户在第 3 步中输入认证信息,如果登录成功,CAS Server 随机产生一个相当长度、唯一、不可伪造的 Service Ticket,并缓存以待将来验证,之后系统自动重定向到 Service 所在地址,并为客户端浏览器设置一个 Ticket Granted Cookie(TGC),CAS Client 在拿到 Service 和新产生的 Ticket 过后,在第 5,6 步中与 CAS Server 进行身份核实,以确保 Service Ticket 的合法性。
二、在该协议中,所有与 CAS 的交互均采用 SSL 协议,确保,ST 和 TGC 的安全性。协议工作过程中会有 2 次重定向的过程,但是 CAS Client 与 CAS Server 之间进行 Ticket 验证的过程对于用户是透明的。
三、cas客户端主要提供的是业务支持,我们在使用的时候更多是通过cas服务端来做认证支持。这里主要讲的是如何搭建cas客户端,配置的东西其实是通过spring的security来进行过滤。然后达到登录的目的,认证中主要是通过Ticket的票据进行认证的,当用户登录成功。会获取到登录的username,然后做进一步处理。
四、服务端的部署参考:https://www.cnblogs.com/ll409546297/p/10410972.html
五、客户端的搭建(这里服务端采用的https的方式)
1)需要的依赖包
<parent> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-parentartifactId> <version>2.0.0.RELEASEversion> parent> <dependencies> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-webartifactId> dependency> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-securityartifactId> dependency> <dependency> <groupId>org.springframework.securitygroupId> <artifactId>spring-security-casartifactId> dependency> dependencies>
<dependencies> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-configuration-processorartifactId> <optional>trueoptional> dependency> dependencies>
说明:下面这个依赖包主要是用于配置
2)目录结构
3)cas的参数配置(cas.properties、CasProperties)
cas.clientUrl=http://localhost:${server.port} cas.clientLogin=/login cas.clientLogout=/logout cas.serverUrl=https://www.casserver.com:8443/cas cas.serverLogin=/login cas.serverLogout=/logout cas.trustStorePath=cas/cas.keystore cas.trustStorePassword=changeit
@PropertySource(value = "classpath:config/cas.properties") @ConfigurationProperties(prefix = "cas") public class CasProperties { //客户端url(本机) private String clientUrl; //登录接口 private String clientLogin; //登出接口 private String clientLogout; //服务端url private String serverUrl; //登录接口 private String serverLogin; //登出接口 private String serverLogout; //证书密匙路径 private String trustStorePath; //密码 private String trustStorePassword; public String getClientUrl() { return clientUrl; } public void setClientUrl(String clientUrl) { this.clientUrl = clientUrl; } public String getClientLogin() { return clientLogin; } public void setClientLogin(String clientLogin) { this.clientLogin = clientLogin; } public String getClientLogout() { return clientLogout; } public void setClientLogout(String clientLogout) { this.clientLogout = clientLogout; } public String getServerUrl() { return serverUrl; } public void setServerUrl(String serverUrl) { this.serverUrl = serverUrl; } public String getServerLogin() { return serverLogin; } public void setServerLogin(String serverLogin) { this.serverLogin = serverLogin; } public String getServerLogout() { return serverLogout; } public void setServerLogout(String serverLogout) { this.serverLogout = serverLogout; } public String getTrustStorePath() { return trustStorePath; } public void setTrustStorePath(String trustStorePath) { this.trustStorePath = trustStorePath; } public String getTrustStorePassword() { return trustStorePassword; } public void setTrustStorePassword(String trustStorePassword) { this.trustStorePassword = trustStorePassword; } }
说明:1、trustStorePath:这个主要使用的服务器上面生成的cas.keystore密钥、在服务器搭建中我们生成了cas.keystore、域名改成www.casserver.com。目的不支持直接使用IP。
本地修改hosts:C:\Windows\System32\drivers\etc\hosts
2、cas.keystore:服务器生成密钥,tomcat进行部署,https访问时需要的私密密钥
3、当然可以不使用cas.keystore,通过服务器上面生成的cas.crt证书然后客户端的jdk也是可以验证通过的。
keytool -import -keystore "E:\Java\jdk1.8.0_192\jre\lib\security\cacerts" -file cas.crt -alias cas -storepass changeit
4)cas相关配置(CasConfiguration、SecurityConfiguration)
@Configuration @Import(CasProperties.class) public class CasConfiguration { //cas相关参数 @Autowired private CasProperties casProperties; //客户端的服务配置,主要用于跳转 @Bean public ServiceProperties serviceProperties() { ServiceProperties serviceProperties = new ServiceProperties(); //该项目的登录地址 serviceProperties.setService(casProperties.getClientUrl() + casProperties.getClientLogin()); serviceProperties.setAuthenticateAllArtifacts(true); return serviceProperties; } //cas认证点 @Bean public CasAuthenticationEntryPoint casAuthenticationEntryPoint() { CasAuthenticationEntryPoint casAuthenticationEntryPoint = new CasAuthenticationEntryPoint(); //cas的登录地址 casAuthenticationEntryPoint.setLoginUrl(casProperties.getServerUrl() + casProperties.getServerLogin()); //入口 casAuthenticationEntryPoint.setServiceProperties(serviceProperties()); return casAuthenticationEntryPoint; } //票据 @Bean public Cas30ServiceTicketValidator cas30ServiceTicketValidator() { return new Cas30ServiceTicketValidator(casProperties.getServerUrl()); } //认证支持 @Bean public CasAuthenticationProvider casAuthenticationProvider(AuthDetailsService authDetailsService) { CasAuthenticationProvider casAuthenticationProvider = new CasAuthenticationProvider(); casAuthenticationProvider.setKey("client1"); casAuthenticationProvider.setServiceProperties(serviceProperties()); casAuthenticationProvider.setTicketValidator(cas30ServiceTicketValidator()); //本地登录后的操作,走security体系 casAuthenticationProvider.setUserDetailsService(authDetailsService); //这里也可以使用setAuthenticationUserDetailsService管理 //casAuthenticationProvider.setAuthenticationUserDetailsService(); return casAuthenticationProvider; } //单点登录过滤 @Bean public SingleSignOutFilter singleSignOutFilter() { SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter(); singleSignOutFilter.setCasServerUrlPrefix(casProperties.getServerUrl()); singleSignOutFilter.setIgnoreInitConfiguration(true); return singleSignOutFilter; } //登出过滤 @Bean public LogoutFilter logoutFilter() { //重定向地址 String logoutRedirectPath = casProperties.getServerUrl() + casProperties.getServerLogout() + "?service=" + casProperties.getClientUrl(); LogoutFilter logoutFilter = new LogoutFilter(logoutRedirectPath, new SecurityContextLogoutHandler()); //登出接口 logoutFilter.setFilterProcessesUrl(casProperties.getServerLogout()); return logoutFilter; } }
@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true) @Import(CasProperties.class) public class SecurityConfiguration extends WebSecurityConfigurerAdapter { //认证 @Autowired private CasAuthenticationProvider authenticationProvider; //认证点 @Autowired private CasAuthenticationEntryPoint authenticationEntryPoint; //登出过滤 @Autowired private LogoutFilter logoutFilter; //单点登出 @Autowired private SingleSignOutFilter singleSignOutFilter; //cas配置 @Autowired private CasProperties casProperties; @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .exceptionHandling() .authenticationEntryPoint(authenticationEntryPoint) .and() .authorizeRequests() .anyRequest().authenticated() .and() //添加认证过滤(这里我遇到一个坑,如果通过注入方式加入,会出现循环依赖问题) .addFilter(casAuthenticationFilter()) //登出过滤 .addFilterBefore(logoutFilter, LogoutFilter.class) //单点登出过滤 .addFilterBefore(singleSignOutFilter, CasAuthenticationFilter.class); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //认证方式 auth.authenticationProvider(authenticationProvider); } @Bean public CasAuthenticationFilter casAuthenticationFilter() throws Exception { //过滤器配置 CasAuthenticationFilter casAuthenticationFilter = new CasAuthenticationFilter(); //使用security的认证管理 casAuthenticationFilter.setAuthenticationManager(authenticationManager()); //拦截登录接口 casAuthenticationFilter.setFilterProcessesUrl(casProperties.getClientLogin()); return casAuthenticationFilter; } }
5)登录后的username处理(AuthDetailsService)
@Service public class AuthDetailsService implements UserDetailsService { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { if (username == null){ throw new UsernameNotFoundException("用户不存在!"); } ListsimpleGrantedAuthorities = new ArrayList<>(); simpleGrantedAuthorities.add(new SimpleGrantedAuthority("ADMIN")); return new org.springframework.security.core.userdetails.User(username, username, simpleGrantedAuthorities); } }
说明:这里只是简单处理,实际可以自己绑定用户处理
6)https处理(CasIinitTask)
@Component @Import(CasProperties.class) public class CasIinitTask { @Autowired private CasProperties casProperties; @PostConstruct public void loadKeystore() throws IOException { //如果使用https,则必须加入密钥 Assert.isTrue(!(casProperties.getServerUrl().startsWith("https") && casProperties.getTrustStorePath() == null), "trustStorePath must not null to configuration https"); //密钥 if (!StringUtils.isEmpty(casProperties.getTrustStorePath())) { Resource resource = new ClassPathResource(casProperties.getTrustStorePath()); System.setProperty("javax.net.ssl.trustStore", resource.getFile().getAbsolutePath()); } //有可能密码的情况 if (StringUtils.isEmpty(casProperties.getTrustStorePassword())) { System.setProperty("javax.net.ssl.trustStorePassword", casProperties.getTrustStorePassword()); } } }
7)启动项目测试:
六、源码:https://github.com/lilin409546297/springboot-cas
七、这里只是简单的搭建过程,实际cas还需要做二次开发。相比于cas和oauth2我个人更加喜欢oauth2,个人看法。