一、企業(yè)單一登錄(CAS)
1.Java(Spring Webflow / MVC servlet)服務器組件
2.可插拔認證支持(LDAP,數(shù)據(jù)庫,X.509,雙因素)
3.支持多種協(xié)議(CAS,SAML,OAuth,OpenID)
4.跨平臺的客戶端支持(Java,.Net,PHP,Perl,Apache等)
5.與uPortal,Liferay,BlueSocket,Moodle和Google Apps集成,僅舉幾例
CAS提供了一個友好的開源社區(qū),積極支持和貢獻項目。雖然該項目植根于更高級的開放源代碼,但已經發(fā)展成為世界500強企業(yè)和小型專用設施的國際用戶。
二、如何部署您的CAS
在項目中安裝CAS服務器,需要去官方github下載CAS標準WAR文件,在WAR文件中有標準的單點登錄登出頁面。當然您還需要對deployerConfigContext.xml中指定AuthenticationHandler進行簡單的修改,已滿足您對數(shù)據(jù)庫的操作需求。CAS 本身包含大量的AuthenticationHandler,可以協(xié)助解決相應的問題。
除CAS服務器本身之外,其他關鍵角色當然是在企業(yè)中部署的安全Web應用程序,這些Web應用程序被稱為“服務“。有三種類型的服務:驗證服務票據(jù),獲得代理票據(jù),驗證代理票據(jù)。驗證代理票據(jù)的不同之處在于代理列表必須經過驗證,并且通常可以重用代理。
CAS本身設計在HTTPS環(huán)境下,在本地測試以及個人學習情況下可以對CAS做些相應修改,使它支持HTTP訪問。在CAS的WAR文件目錄:WEB-INF\classes\services下修改HTTPSandIMAPS-10000001.json配置文件,將serviceId屬性的值修改為:
"serviceId":"^(https|imaps|http)://.*"
在CAS 4.2版本后,CAS的所有配置都放在cas.properties文件中,所以為了可以自定義cas.properties的路徑,您可以修改WEB-INF\spring-configuration\propertyFileConfigurer.xml文件中的:
<util:properties id="casProperties" location="classpath:cas.properties" />
為了讓CAS能夠通過數(shù)據(jù)庫鑒定用戶憑證,需要配置Database Authentication。官方文檔詳見:https://apereo.github.io/cas/4.2.x/installation/Database-Authentication.html。數(shù)據(jù)庫認證有四種:
1.QueryDatabaseAuthenticationHandler,通過用戶名和明文密碼進行驗證。
首先在cas.properties中配置:
# cas.jdbc.authn.query.sql=select password from users where username=?
在deployerConfigContext.xml中配置
<alias name="queryDatabaseAuthenticationHandler" alias="primaryAuthenticationHandler" />
<alias name="dataSource" alias="queryDatabaseDataSource" />
2.SearchModeSearchDatabaseAuthenticationHandler,通過查詢用戶名和密碼來搜索用戶記錄; 如果至少有一個結果被發(fā)現(xiàn),用戶將被認證。
首先在cas.properties中配置
# cas.jdbc.authn.search.password=
# cas.jdbc.authn.search.user=
# cas.jdbc.authn.search.table=
在deployerConfigContext.xml中配置
<alias name="searchModeSearchDatabaseAuthenticationHandler" alias="primaryAuthenticationHandler" />
<alias name="dataSource" alias="searchModeDatabaseDataSource" />
3.BindModeSearchDatabaseAuthenticationHandler,嘗試使用用戶名和(散列)密碼創(chuàng)建數(shù)據(jù)庫連接來對用戶進行身份驗證。
在deployerConfigContext.xml中配置
<alias name="bindModeSearchDatabaseAuthenticationHandler" alias="primaryAuthenticationHandler" />
<alias name="dataSource" alias="bindSearchDatabaseDataSource" />
4.QueryAndEncodeDatabaseAuthenticationHandler,一個JDBC查詢處理程序,它將撤回用戶的密碼和私有salt值,并使用公共salt值驗證編碼的密碼。 假設一切都在同一個數(shù)據(jù)庫表內。 支持迭代次數(shù)和私鹽的設置。
首先在cas.properties中配置
# cas.jdbc.authn.query.encode.sql=
# cas.jdbc.authn.query.encode.alg=
# cas.jdbc.authn.query.encode.salt.static=
# cas.jdbc.authn.query.encode.password=表字段名
# cas.jdbc.authn.query.encode.salt=表字段名
# cas.jdbc.authn.query.encode.iterations.field=表字段名
# cas.jdbc.authn.query.encode.iterations=
在deployerConfigContext.xml中配置
<alias name="queryAndEncodeDatabaseAuthenticationHandler" alias="primaryAuthenticationHandler" />
<alias name="dataSource" alias="queryEncodeDatabaseDataSource" />
一般選擇第四種數(shù)據(jù)認證方式,修改完成后丟到tomcat下運行即可。
cas的訪問地址:ip:port/cas/login
cas的登出地址:ip:port/cas/logout
三、Spring Security和CAS的集成
Web瀏覽器,CAS服務器和Spring安全服務之間的基本交互如下:
CAS或Spring Security不管理公共頁面的處理,當用戶請求一個安全的頁面或者它使用的一個安全的頁面。 Spring Security的ExceptionTranslationFilter將檢測到AccessDeniedException或AuthenticationException。
由于用戶的Authentication對象(或缺少)導致AuthenticationException,因此ExceptionTranslationFilter將調用已配置的AuthenticationEntryPoint。如果使用CAS,這將是CasAuthenticationEntryPoint類。
CasAuthenticationEntryPoint將把用戶的瀏覽器重定向到CAS服務器。它還會顯示一個服務參數(shù),它是Spring Security服務(您的應用程序)的回調URL。例如,瀏覽器重定向到的URL可能是
https://my.company.com/cas/login?service= HTTPS%3A%2F%2Fserver3.company.com%2Fwebapp%2Flogin / CAS。
用戶的瀏覽器重定向到CAS后,系統(tǒng)會提示用戶輸入用戶名和密碼。如果用戶提交了一個表示他們以前登錄過的會話cookie,他們將不會再被提示重新登錄(這個過程是個例外,我們將在后面介紹)。 CAS將使用上述的PasswordHandler(或使用CAS 3.0的AuthenticationHandler)來決定用戶名和密碼是否有效。
CAS成功登錄后,會將用戶瀏覽器重定向到原始服務。它還將包含一個票據(jù)參數(shù),這是一個不透明的字符串,代表“服務票據(jù)”。繼續(xù)前面的例子,瀏覽器被重定向到的URL可能是
https://server3.company.com/webapp/login/cas?ticket=ST-0-ER94xMJmn6pha35CQRoZ。
回到服務Web應用程序,CasAuthenticationFilter總是監(jiān)聽/ login / cas的請求(這是可配置的,但是我們將使用這個介紹中的默認值)。處理過濾器將構建代表服務票據(jù)的UsernamePasswordAuthenticationToken。主體將等于CasAuthenticationFilter.CAS_STATEFUL_IDENTIFIER,而憑證將是服務票證不透明值。這個認證請求將被交給配置的AuthenticationManager。
AuthenticationManager實現(xiàn)將是ProviderManager,它又被配置了CasAuthenticationProvider。 CasAuthenticationProvider只響應包含CAS特定主體(如CasAuthenticationFilter.CAS_STATEFUL_IDENTIFIER)和CasAuthenticationToken(稍后討論)的UsernamePasswordAuthenticationToken。
CasAuthenticationProvider將使用TicketValidator實現(xiàn)來驗證服務票證。這通常是一個Cas20ServiceTicketValidator,它是包含在CAS客戶端庫中的一個類。如果應用程序需要驗證代理票證,則使用Cas20ProxyTicketValidator。 TicketValidator向CAS服務器發(fā)出HTTPS請求,以驗證服務票據(jù)。它也可能包含一個代理回調URL,它包含在這個例子中:
https://my.company.com/cas/proxyValidate?service=https%3A%2F%2Fserver3.company.com%2Fwebapp%2Flogin/cas&ticket= ST-0-ER94xMJmn6pha35CQRoZ&pgtUrl = HTTPS://server3.company.com/webapp/login/cas/proxyreceptor。
回到CAS服務器,驗證請求將被接收。如果所提供的服務票據(jù)與發(fā)行票據(jù)的服務URL相匹配,則CAS將以XML表示用戶名的肯定響應。如果任何代理參與了身份驗證(如下所述),那么代理列表也會包含在XML響應中。
[可選]如果對CAS驗證服務的請求包含代理回調URL(在pgtUrl參數(shù)中),則CAS將在XML響應中包含一個pgtIou字符串。這pgtIou代表代理授予票借條。然后,CAS服務器將創(chuàng)建自己的HTTPS連接回pgtUrl。這是為了相互認證CAS服務器和聲稱的服務URL。 HTTPS連接將用于將授權票據(jù)的代理發(fā)送到原始Web應用程序。例如,
https://server3.company.com/webapp/login/cas/proxyreceptor?pgtIou=PGTIOU-0-R0zlgrl4pdAQwBvJWO3vnNpevwqStbSGcq3vKB2SqSFFRnjPHt&pgtId=PGT-1-si9YkkHLrtACBo64rmsi3v2nf7cpCResXg5MpESZFArbaZiOKH。
Cas20TicketValidator將解析從CAS服務器收到的XML。它將返回CasAuthenticationProvider TicketResponse,其中包括用戶名(強制),代理列表(如果有任何涉及),和代理授予票證IOU(如果代理回調被請求)。
接下來,CasAuthenticationProvider將調用已配置的CasProxyDecider。 CasProxyDecider指示TicketResponse中的代理列表是否可以被服務接受。 Spring Security提供了幾個實現(xiàn):RejectProxyTickets,AcceptAnyCasProxy和NamedCasProxyDecider。這些名稱在很大程度上是不言而喻的,除了NamedCasProxyDecider允許提供可信代理列表。
CasAuthenticationProvider接下來將請求一個AuthenticationUserDetailsS??ervice來加載適用于Assertion中包含的用戶的GrantedAuthority對象。
如果沒有問題,CasAuthenticationProvider構造一個CasAuthenticationToken,包括TicketResponse和GrantedAuthoritys中包含的細節(jié)。
控制然后返回到CasAuthenticationFilter,它將創(chuàng)建的CasAuthenticationToken放置在安全上下文中。
用戶的瀏覽器被重定向到導致AuthenticationException的原始頁面(或根據(jù)配置的自定義目標)。
四、Spring Boot +Spring Security+CAS開發(fā)(代理票據(jù)認證)
CasAuthenticationProvider區(qū)分有狀態(tài)和無狀態(tài)客戶端。 有狀態(tài)的客戶端被認為是提交給CasAuthenticationFilter的filterProcessUrl的。 無狀態(tài)客戶端是指向除FilterProcessUrl以外的URL向CasAuthenticationFilter提交身份驗證請求的任何客戶端。
由于遠程協(xié)議無法在HttpSession的上下文中呈現(xiàn),因此不可能依賴于在請求之間的會話中存儲安全上下文的默認實踐。 此外,由于CAS服務器在TicketValidator驗證之后使其無效,因此在后續(xù)請求中顯示相同的代理票證將不起作用。
CasConfing配置:
//客戶端配置
public static String casServiceHost="http://127.0.0.1:8080";
public static String casServiceLogin=casServiceHost+"/login/cas";
public static String casServiceLogout=casServiceHost+"/logout/cas";
public static String casServiceProxyCallbackUrl="/login/cas/proxyreceptor";
public static String casServiceFailureHandler="/cas/casfailed";
//cas服務端配置
@Value("${cas.server.host:http://127.0.0.1:8081/cas}")
public static String casServerUrlPrefix="http://127.0.0.1:8081/cas";
public static String casServerUrlLogin=casServerUrlPrefix+"/login";
public static String casServerUrlLogout=casServerUrlPrefix+"/logout";
@Autowired
public static ProxyGrantingTicketStorageImpl pgtStorage;
@Bean
public ServiceProperties serviceProperties(){
ServiceProperties serviceProperties=new ServiceProperties();
serviceProperties.setService(casServiceLogin);
serviceProperties.setAuthenticateAllArtifacts(true);
return serviceProperties;
}
@Bean
public CasAuthenticationEntryPoint casAuthenticationEntryPoint(@Qualifier("serviceProperties") ServiceProperties serviceProperties){
CasAuthenticationEntryPoint entryPoint=new CasAuthenticationEntryPoint();
entryPoint.setServiceProperties(serviceProperties);
entryPoint.setLoginUrl(casServerUrlLogin);
return entryPoint;
}
@Bean("pgtStorage")
public ProxyGrantingTicketStorageImpl proxyGrantingTicketStorageImpl(){
return new ProxyGrantingTicketStorageImpl();
}
@Bean("casAuthenticationProvider")
public CasAuthenticationProvider casAuthenticationProvider(@Qualifier("serviceProperties") ServiceProperties serviceProperties,
@Qualifier("customCasUserDetailsService") CustomCasUserDetailsService customCasUserDetailsService){
CasAuthenticationProvider authenticationProvider=new CasAuthenticationProvider();
authenticationProvider.setKey("casProvider") ;
authenticationProvider.setServiceProperties(serviceProperties);
Cas20ProxyTicketValidator ticketValidator=new Cas20ProxyTicketValidator(casServerUrlPrefix);
ticketValidator.setAcceptAnyProxy(true);//允許所有代理回調鏈接
ticketValidator.setProxyGrantingTicketStorage(pgtStorage);
authenticationProvider.setTicketValidator(ticketValidator);
authenticationProvider.setAuthenticationUserDetailsService(customCasUserDetailsService);
//無狀態(tài)緩存
EhCacheBasedTicketCache ticketCache=new EhCacheBasedTicketCache();
ticketCache.setCache(new Cache("casTickets", 50, true, false, 3600, 900));
authenticationProvider.setStatelessTicketCache(ticketCache);
return authenticationProvider;
}
//單點登出,跳轉到客戶端的登出鏈接
@Bean("requestSingleLogoutFilter")
public LogoutFilter logoutFilter() {
LogoutFilter logoutFilter = new LogoutFilter(casServerUrlLogout, new SecurityContextLogoutHandler());
logoutFilter.setFilterProcessesUrl(casServiceLogout);
return logoutFilter;
}
WebSecurityCasConfig配置:
@Autowired
CasAuthenticationProvider casAuthenticationProvider;
@Autowired
CasAuthenticationEntryPoint casAuthenticationEntryPoint;
@Autowired
LogoutFilter requestSingleLogoutFilter;
@Autowired
ServiceProperties serviceProperties;
public CasAuthenticationFilter casAuthenticationFilter() throws Exception{
CasAuthenticationFilter casAuthenticationFilter=new CasAuthenticationFilter();
casAuthenticationFilter.setAuthenticationManager(authenticationManager());
casAuthenticationFilter.setServiceProperties(serviceProperties);
casAuthenticationFilter.setProxyGrantingTicketStorage(CasConfing.pgtStorage);
casAuthenticationFilter.setProxyReceptorUrl(CasConfing.casServiceProxyCallbackUrl);
casAuthenticationFilter.setAuthenticationDetailsSource(new ServiceAuthenticationDetailsSource(serviceProperties));
casAuthenticationFilter.setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler(CasConfing.casServiceFailureHandler));
return casAuthenticationFilter;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// TODO Auto-generated method stub
http
.authorizeRequests()
.antMatchers("/cas/casfailed").permitAll()
.antMatchers("/secure/extreme/").access("hasRole('ROLE_SUPERVISOR')")
.antMatchers("/secure/**").access("hasRole('ROLE_USER')")
.anyRequest().authenticated()
.and()
.logout()
.logoutUrl("/logout/cas")
.logoutSuccessUrl(CasConfing.casServerUrlLogout+"?service="+CasConfing.casServiceHost+"/index")
.permitAll()
.and()
.csrf().disable();
//CAS服務器的單點登錄
SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter();
singleSignOutFilter.setCasServerUrlPrefix(CasConfing.casServerUrlPrefix);
http
.exceptionHandling().authenticationEntryPoint(casAuthenticationEntryPoint)
.and()
.addFilter(casAuthenticationFilter())
.addFilterBefore(requestSingleLogoutFilter, LogoutFilter.class)
.addFilterBefore(singleSignOutFilter, CasAuthenticationFilter.class);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// TODO Auto-generated method stub
auth.authenticationProvider(casAuthenticationProvider);
super.configure(auth);
}
CustomCasUserDetailsService自定義認證用戶信息處理配置:
@Service
public class CustomCasUserDetailsService implements AuthenticationUserDetailsService<CasAssertionAuthenticationToken>{
@Override
public UserDetails loadUserDetails(CasAssertionAuthenticationToken token) throws UsernameNotFoundException {
// TODO Auto-generated method stub
System.err.println("當前認證成功的用戶名:"+token.getName());
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
GrantedAuthority grantedAuthority=new SimpleGrantedAuthority("ROLE_SUPERVISOR");
grantedAuthorities.add(grantedAuthority);
grantedAuthority=new SimpleGrantedAuthority("ROLE_USER");
grantedAuthorities.add(grantedAuthority);
return new User(token.getName(), "a52302c58f4a60f49b1ad2f36add6d0a-000000", grantedAuthorities);
}
}
至此,Spring Security+CAS集成配置以完成。
您可以通過訪問客戶端Security安全頁面:
http://127.0.0.1:8080/index,
security會轉到CAS服務器登錄鏈接
http://127.0.0.1:8081/cas/login?service=http%3A%2F%2F127.0.0.1%3A8080%2Flogin%2Fcas
登錄認證通過后即可訪問安全頁面。
多站點:
分別部署兩個站點:serviceCas01,serviceCas02
http://127.0.0.1:8080/serviceCas01/index,
http://127.0.0.1:8082/serviceCas02/index,
serviceCas01登錄認證成功后,直接通過訪問 http://127.0.0.1:8082/serviceCas02/index,即可無需登錄訪問。通過http://127.0.0.1:8080/serviceCas01/logout/cas成功登出后, 重新刷新頁面http://127.0.0.1:8082/serviceCas02/index,也會登出。
后續(xù)有時間,再配圖啦。不足之處,謝謝指教。
銘言:
吾等前方,再無對手