一、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.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.0.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-cas</artifactId> </dependency> </dependencies>
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </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("用戶不存在!"); } List<SimpleGrantedAuthority> simpleGrantedAuthorities = 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,個人看法。