新聞中心
[[408875]]
1.準(zhǔn)備工作
準(zhǔn)備工作主要做兩件事。

成都創(chuàng)新互聯(lián)自2013年創(chuàng)立以來(lái),先為阿巴嘎等服務(wù)建站,阿巴嘎等地企業(yè),進(jìn)行企業(yè)商務(wù)咨詢(xún)服務(wù)。為阿巴嘎企業(yè)網(wǎng)站制作PC+手機(jī)+微官網(wǎng)三網(wǎng)同步一站式服務(wù)解決您的所有建站問(wèn)題。
1.1 服務(wù)記錄
某一個(gè) Client 需要接入 CAS Server 進(jìn)行驗(yàn)證,則該 Client 必須提前在 CAS Server 上配置其信息。
這個(gè)信息既可以動(dòng)態(tài)添加,也可以通過(guò) JSON 來(lái)配置,后面松哥會(huì)教搭建如何動(dòng)態(tài)添加,這里方便起見(jiàn),我們還是通過(guò) JSON 來(lái)進(jìn)行配置。
具體配置方式如下,在 CAS Server 中創(chuàng)建如下目錄:
- src/main/resources/services
在該目錄下創(chuàng)建一個(gè)名為 client1-99.json 的文件,client1 表示要接入的 client 的名字,99 表示要接入的 client 的 id,json 文件內(nèi)容如下(這個(gè)配置可以參考官方給出的模版: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": "應(yīng)用1 的定義信息",
- "evaluationOrder": 1
- }
這段 JSON 配置含義如下:
- @calss 指定注冊(cè)服務(wù)類(lèi),這個(gè)是固定的org.apereo.cas.services.RegexRegisteredService。
- serviceId 則通過(guò)正則表達(dá)式用來(lái)匹配具體的請(qǐng)求。
- name 是接入的 client 的名稱(chēng)。
- id 是接入的 client 的 id。
- description 是接入的 client 的描述信息。
- evaluationOrder 則指定了執(zhí)行的優(yōu)先級(jí)。
接下來(lái)再在 src/main/resources/application.properties 文件中配置剛剛 json 的信息,如下:
- cas.serviceRegistry.json.location=classpath:/services
- cas.serviceRegistry.initFromJson=true
這里有兩行配置:
- 指定配置 JSON 文件的位置。
- 開(kāi)啟 JSON 識(shí)別。
OK,配置完成后,重啟 CAS Server。
CAS Server 啟動(dòng)成功后,我們?cè)诳刂婆_(tái)看到如下日志,表示 JSON 配置已經(jīng)加載成功了:
1.2 JDK 證書(shū)
第二個(gè)要提前準(zhǔn)備的東西就是 JDK 證書(shū)。
在實(shí)際開(kāi)發(fā)中,這一步可以忽略,但是因?yàn)槲覀儸F(xiàn)在用的自己生成的 SSL 證書(shū),所以我們要將自己生成的證書(shū)導(dǎo)入到 JDK 中,否則在使用 Spring Security 接入 CAS 單點(diǎn)登錄時(shí),會(huì)拋出如下錯(cuò)誤:
將 SSL 證書(shū)導(dǎo)入 JDK 中的命令其實(shí)也很簡(jiǎn)單,兩個(gè)步驟,第一個(gè)導(dǎo)出 .cer 文件,第二步,導(dǎo)入 JDK,命令如下:
- keytool -export -trustcacerts -alias casserver -file ./cas.cer -keystore ./keystore
- sudo keytool -import -trustcacerts -alias casserver -file ./cas.cer -keystore /Library/Java/JavaVirtualMachines/jdk-10.0.2.jdk/Contents/Home/lib/security/cacerts
注意,在執(zhí)行 export 導(dǎo)出命令時(shí),需要輸入密鑰口令,這個(gè)口令就是自己一開(kāi)始創(chuàng)建 SSL 證書(shū)時(shí)設(shè)置的。在執(zhí)行 import 導(dǎo)入命令時(shí),也需要輸入口令,這個(gè)口令是 changeit,注意,不是自己一開(kāi)始設(shè)置的。
密鑰庫(kù)的位置在 JDK 目錄下的 /lib/security/cacerts,小伙伴們根據(jù)自己實(shí)際情況來(lái)修改(在 JDK9 之前,位置在 jre/lib/security/cacerts)。
我們?cè)诒镜販y(cè)試一定要導(dǎo)入證書(shū)到 JDK 證書(shū)庫(kù)中,否則后面的測(cè)試會(huì)出現(xiàn)上圖中的錯(cuò)誤,證書(shū)導(dǎo)入 JDK 證書(shū)庫(kù)之后,要確保之后的開(kāi)發(fā)中,使用的是本地的 JDK。
注意,JDK 證書(shū)導(dǎo)入之后,CASServer 需要重啟一下。
1.3 修改 hosts
另外,我們還需要修改電腦 hosts 文件,因?yàn)榍懊骊P(guān)于 CAS Server,關(guān)于 SSL 證書(shū)的配置都涉及到域名,所以后面的訪問(wèn)我們將通過(guò)域名的形式訪問(wèn),hosts 文件中添加如下兩條記錄:
第一個(gè)是 CAS Server 的請(qǐng)求域名,第二個(gè)是 CAS Client 的請(qǐng)求域名。
2.開(kāi)發(fā) Client
在使用 Spring Security 開(kāi)發(fā) CAS Client 之前,有一個(gè)基本問(wèn)題需要先和小伙伴們捋清楚:用戶(hù)登錄是在 CAS Server 上登錄,所以 Spring Security 中雖然依舊存在用戶(hù)的概念,但是對(duì)于用戶(hù)的處理邏輯會(huì)和前面的有所不同。
好了,接下來(lái)我們來(lái)看下具體步驟。
首先我們來(lái)創(chuàng)建一個(gè)普通的 Spring Boot 項(xiàng)目,加入 Web 依賴(lài) 和 Spring Security 依賴(lài),如下:
項(xiàng)目創(chuàng)建成功后,我們?cè)賮?lái)手動(dòng)加入 cas 依賴(lài):
org.springframework.security spring-security-cas
接下來(lái),在 application.properties 中配置 CAS Server 和 CAS Client 的請(qǐng)求地址信息:
- cas.server.prefix=https://cas.javaboy.org:8443/cas
- cas.server.login=${cas.server.prefix}/login
- cas.server.logout=${cas.server.prefix}/logout
- cas.client.prefix=http://client1.cas.javaboy.org:8080
- cas.client.login=${cas.client.prefix}/login/cas
- cas.client.logoutRelative=/logout/cas
- cas.client.logout=${cas.client.prefix}${cas.client.logoutRelative}
這些配置都是自定義配置,所以配置的 key 可以自己隨意定義。至于配置的含義都好理解,分別配置了 CAS Server 和 CAS Client 的登錄和注銷(xiāo)地址。
配置好之后,我們需要將這些配置注入到實(shí)體類(lèi)中使用,這里就用到了類(lèi)型安全的屬性綁定。
這里我創(chuàng)建兩個(gè)類(lèi)分別用來(lái)接收 CAS Server 和 CAS Client 的配置文件:
- @ConfigurationProperties(prefix = "cas.server")
- public class CASServerProperties {
- private String prefix;
- private String login;
- private String logout;
- //省略 getter/setter
- }
- @ConfigurationProperties(prefix = "cas.client")
- public class CASClientProperties {
- private String prefix;
- private String login;
- private String logoutRelative;
- private String logout;
- //省略 getter/setter
- }
另外記得在啟動(dòng)類(lèi)上面添加 @ConfigurationPropertiesScan 注解來(lái)掃描這兩個(gè)配置類(lèi):
- @SpringBootApplication
- @ConfigurationPropertiesScan
- public class Client1Application {
- public static void main(String[] args) {
- SpringApplication.run(Client1Application.class, args);
- }
- }
這里配置完成后,我們一會(huì)將在配置文件中來(lái)使用。
接下來(lái)創(chuàng)建 CAS 的配置文件,略長(zhǎng):
- @Configuration
- public class CasSecurityConfig {
- @Autowired
- CASClientProperties casClientProperties;
- @Autowired
- CASServerProperties casServerProperties;
- @Autowired
- UserDetailsService userDetailService;
- @Bean
- ServiceProperties serviceProperties() {
- ServiceProperties serviceProperties = new ServiceProperties();
- serviceProperties.setService(casClientProperties.getLogin());
- return serviceProperties;
- }
- @Bean
- @Primary
- AuthenticationEntryPoint authenticationEntryPoint() {
- CasAuthenticationEntryPoint entryPoint = new CasAuthenticationEntryPoint();
- entryPoint.setLoginUrl(casServerProperties.getLogin());
- entryPoint.setServiceProperties(serviceProperties());
- return entryPoint;
- }
- @Bean
- TicketValidator ticketValidator() {
- return new Cas20ProxyTicketValidator(casServerProperties.getPrefix());
- }
- @Bean
- CasAuthenticationProvider casAuthenticationProvider() {
- CasAuthenticationProvider provider = new CasAuthenticationProvider();
- provider.setServiceProperties(serviceProperties());
- provider.setTicketValidator(ticketValidator());
- provider.setUserDetailsService(userDetailService);
- provider.setKey("javaboy");
- return provider;
- }
- @Bean
- CasAuthenticationFilter casAuthenticationFilter(AuthenticationProvider authenticationProvider) {
- CasAuthenticationFilter filter = new CasAuthenticationFilter();
- filter.setServiceProperties(serviceProperties());
- filter.setAuthenticationManager(new ProviderManager(authenticationProvider));
- return filter;
- }
- @Bean
- SingleSignOutFilter singleSignOutFilter() {
- SingleSignOutFilter sign = new SingleSignOutFilter();
- sign.setIgnoreInitConfiguration(true);
- return sign;
- }
- @Bean
- LogoutFilter logoutFilter() {
- LogoutFilter filter = new LogoutFilter(casServerProperties.getLogout(), new SecurityContextLogoutHandler());
- filter.setFilterProcessesUrl(casClientProperties.getLogoutRelative());
- return filter;
- }
- }
這個(gè)配置文件略長(zhǎng),但是并不難,我來(lái)和大家挨個(gè)解釋?zhuān)?/p>
- 首先一進(jìn)來(lái)注入三個(gè)對(duì)象,這三個(gè)中,有兩個(gè)是我們前面寫(xiě)的配置類(lèi)的實(shí)例,另外一個(gè)則是 UserDetailsService,關(guān)于 UserDetailsService,我想我也不必多做解釋?zhuān)蠹覅⒖急鞠盗星懊娴奈恼戮椭?UserDetailsService 的作用,一會(huì)我會(huì)給出 UserDetailsService 的實(shí)現(xiàn)。
- 接下來(lái)配置 ServiceProperties,ServiceProperties 中主要配置一下 Client 的登錄地址即可,這個(gè)地址就是在 CAS Server 上登錄成功后,重定向的地址。
- CasAuthenticationEntryPoint 則是 CAS 驗(yàn)證的入口,這里首先設(shè)置 CAS Server 的登錄地址,同時(shí)將前面的 ServiceProperties 設(shè)置進(jìn)去,這樣當(dāng)它登錄成功后,就知道往哪里跳轉(zhuǎn)了。
- TicketValidator 這是配置 ticket 校驗(yàn)地址,CAS Client 拿到 ticket 要去 CAS Server 上校驗(yàn),默認(rèn)校驗(yàn)地址是:https://cas.javaboy.org:8443/cas/proxyValidate?ticket=xxx
- CasAuthenticationProvider 主要用來(lái)處理 CAS 驗(yàn)證邏輯,關(guān)于 AuthenticationProvider 松哥在前面的文章中和大家分享過(guò)(SpringSecurity 自定義認(rèn)證邏輯的兩種方式(高級(jí)玩法)),當(dāng)時(shí)就說(shuō),想要自定義認(rèn)證邏輯,如短信登錄等,都可以通過(guò)擴(kuò)展 AuthenticationProvider 來(lái)實(shí)現(xiàn),這里的 CAS 登錄當(dāng)然也不例外,這里雖然設(shè)置了一個(gè) userDetailService,但是目的不是為了從數(shù)據(jù)庫(kù)中查詢(xún)數(shù)據(jù)做校驗(yàn),因?yàn)榈卿浭窃?CAS Server 中進(jìn)行的,這個(gè)的作用,我在后面會(huì)做介紹。
- CasAuthenticationFilter 則是 CAS 認(rèn)證的過(guò)濾器,過(guò)濾器將請(qǐng)求攔截下來(lái)之后,交由 CasAuthenticationProvider 來(lái)做具體處理。
- SingleSignOutFilter 表示接受 CAS Server 發(fā)出的注銷(xiāo)請(qǐng)求,所有的注銷(xiāo)請(qǐng)求都將從 CAS Client 轉(zhuǎn)發(fā)到 CAS Server,CAS Server 處理完后,會(huì)通知所有的 CAS Client 注銷(xiāo)登錄。
- LogoutFilter 則是配置將注銷(xiāo)請(qǐng)求轉(zhuǎn)發(fā)到 CAS Server。
接下來(lái)我再來(lái)給大家看下我定義的 UserDetailsService:
- @Component
- @Primary
- public class UserDetailsServiceImpl implements UserDetailsService{
- @Override
- public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
- return new User(s, "123", true, true, true, true,
- AuthorityUtils.createAuthorityList("ROLE_user"));
- }
- }
既然是單點(diǎn)登錄,也就是用戶(hù)是在 CAS Server 上登錄的,這里的 UserDetailsService 意義在哪里呢?
用戶(hù)雖然在 CAS Server 上登錄,但是,登錄成功之后,CAS Client 還是要獲取用戶(hù)的基本信息、角色等,以便做進(jìn)一步的權(quán)限控制,所以,這里的 loadUserByUsername 方法中的參數(shù),實(shí)際上就是你從 CAS Server 上登錄成功后獲取到的用戶(hù)名,拿著這個(gè)用戶(hù)名,去數(shù)據(jù)庫(kù)中查詢(xún)用戶(hù)的相關(guān)信心并返回,方便 CAS Client 在后續(xù)的鑒權(quán)中做進(jìn)一步的使用,這里我為了方便,就沒(méi)有去數(shù)據(jù)庫(kù)中查詢(xún)了,而是直接創(chuàng)建了一個(gè) User 對(duì)象返回。
接下來(lái),我們?cè)賮?lái)看看 Spring Security 的配置:
- @Configuration
- public class SecurityConfig extends WebSecurityConfigurerAdapter {
- @Autowired
- AuthenticationEntryPoint authenticationEntryPoint;
- @Autowired
- AuthenticationProvider authenticationProvider;
- @Autowired
- SingleSignOutFilter singleSignOutFilter;
- @Autowired
- LogoutFilter logoutFilter;
- @Autowired
- CasAuthenticationFilter casAuthenticationFilter;
- @Override
- protected void configure(AuthenticationManagerBuilder auth) throws Exception {
- auth.authenticationProvider(authenticationProvider);
- }
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- 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);
- }
- }
這里的配置就簡(jiǎn)單很多了:
- 首先配置 authenticationProvider,這個(gè) authenticationProvider 實(shí)際上就是一開(kāi)始配置的 CasAuthenticationProvider。
- 接下來(lái)配置 /user/** 格式的路徑需要有 user 角色才能訪問(wèn),登錄路徑 /login/cas 可以直接訪問(wèn),剩余接口都是登錄成功之后才能訪問(wèn)。
- 最后把 authenticationEntryPoint 配置進(jìn)來(lái),再把自定義的過(guò)濾器加進(jìn)來(lái),這些都比較容易我就不多說(shuō)了。
最后,再提供兩個(gè)測(cè)試接口:
- @RestController
- public class HelloController {
- @GetMapping("/hello")
- public String hello() {
- return "hello";
- }
- @GetMapping("/user/hello")
- public String user() {
- return "user";
- }
- }
OK ,如此之后,我們的 CAS Client 現(xiàn)在就開(kāi)發(fā)完成了,接下來(lái)啟動(dòng) CAS Client,啟動(dòng)成功后,瀏覽器輸入 http://client1.cas.javaboy.org:8080/user/hello 訪問(wèn) hello 接口,此時(shí)會(huì)自動(dòng)跳轉(zhuǎn)到 CAS Server 上登錄,登錄成功之后,經(jīng)過(guò)兩個(gè)重定向,會(huì)重新回到 hello 接口。
3.小結(jié)
OK,這就是松哥和大家介紹的 Spring Security + CAS 單點(diǎn)登錄,當(dāng)然,這個(gè)案例中還有很多需要完善的地方,松哥會(huì)在后面的文章中繼續(xù)和大家分享完善的方案。
好了 ,本文就說(shuō)到這里,本文相關(guān)案例我已經(jīng)上傳到 GitHub ,大家可以自行下載:https://github.com/lenve/spring-security-samples
本文轉(zhuǎn)載自微信公眾號(hào)「江南一點(diǎn)雨」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系江南一點(diǎn)雨公眾號(hào)。
本文名稱(chēng):SpringSecurity系列之SpringBoot+CAS單點(diǎn)登錄
鏈接分享:http://www.5511xx.com/article/ccdscpo.html


咨詢(xún)
建站咨詢(xún)
