CAS SSO Dengan Keselamatan Musim Semi

1. Gambaran keseluruhan

Dalam tutorial ini, kita akan melihat Perkhidmatan Pengesahan Pusat Apereo (CAS) dan kita akan melihat bagaimana perkhidmatan Spring Boot dapat menggunakannya untuk pengesahan. CAS adalah penyelesaian Single Sign-On (SSO) perusahaan yang juga merupakan sumber terbuka.

Apa itu SSO? Apabila anda log masuk ke YouTube, Gmail dan Peta dengan kelayakan yang sama, itulah Single Sign-On. Kami akan menunjukkan ini dengan menyediakan pelayan CAS dan aplikasi Spring Boot. Aplikasi Spring Boot akan menggunakan CAS untuk pengesahan.

2. Persediaan Pelayan CAS

2.1. Pemasangan dan Tanggungan CAS

Pelayan menggunakan gaya Overlay Perang Maven (Gradle) untuk memudahkan penyediaan dan penyebaran:

git clone //github.com/apereo/cas-overlay-template.git cas-server

Perintah ini akan mengkloning templat cas-overlay ke dalam direktori pelayan cas .

Beberapa aspek yang akan kita bahas termasuk pendaftaran perkhidmatan JSON dan sambungan pangkalan data JDBC. Jadi, kami akan menambah modul mereka kepada kebergantungan bahagian build.gradle fail:

compile "org.apereo.cas:cas-server-support-json-service-registry:${casServerVersion}" compile "org.apereo.cas:cas-server-support-jdbc:${casServerVersion}"

Mari pastikan untuk memeriksa versi terbaru casServer.

2.2. Konfigurasi Pelayan CAS

Sebelum kita dapat memulakan pelayan CAS, kita perlu menambahkan beberapa konfigurasi asas. Mari mulakan dengan membuat folder cas-server / src / main / resources dan dalam folder ini. Ini akan diikuti dengan penciptaan application.properties dalam folder juga:

server.port=8443 spring.main.allow-bean-definition-overriding=true server.ssl.key-store=classpath:/etc/cas/thekeystore server.ssl.key-store-password=changeit

Mari kita teruskan dengan membuat fail penyimpanan kunci yang dirujuk dalam konfigurasi di atas. Pertama, kita perlu membuat folder / etc / cas dan / etc / cas / config di cas-server / src / main / resources .

Kemudian, kita perlu menukar direktori ke cas-server / src / main / resources / etc / cas dan jalankan arahan untuk menghasilkan kedai kunci :

keytool -genkey -keyalg RSA -alias thekeystore -keystore thekeystore -storepass changeit -validity 360 -keysize 2048

Agar kita tidak mengalami ralat berjabat tangan SSL, kita harus menggunakan localhost sebagai nilai nama depan dan belakang. Kita juga harus menggunakan yang sama untuk nama dan unit organisasi. Tambahan pula, kita perlu mengimport thekeystore ke dalam JDK / JRE kita akan menggunakan untuk menjalankan aplikasi pelanggan kami:

keytool -importkeystore -srckeystore thekeystore -destkeystore $JAVA11_HOME/jre/lib/security/cacerts

Kata laluan untuk sumber dan sumber kunci tujuan adalah perubahan . Pada sistem Unix, kita mungkin harus menjalankan perintah ini dengan hak istimewa admin ( sudo ). Setelah mengimport, kita harus memulakan semula semua contoh Java yang berjalan atau menghidupkan semula sistem.

Kami menggunakan JDK11 kerana diperlukan oleh CAS versi 6.1.x. Juga, kami menentukan pemboleh ubah persekitaran $ JAVA11_HOME yang menunjuk ke direktori asalnya. Kita sekarang boleh memulakan pelayan CAS:

./gradlew run -Dorg.gradle.java.home=$JAVA11_HOME

Apabila aplikasi dimulakan, kita akan melihat "SIAP" dicetak di terminal dan pelayan akan tersedia di // localhost: 8443 .

2.3. Konfigurasi Pengguna Pelayan CAS

Kami belum dapat log masuk kerana kami belum mengkonfigurasi pengguna. CAS mempunyai kaedah pengurusan konfigurasi yang berbeza, termasuk mod yang berdiri sendiri. Mari buat folder konfigurasi cas-server / src / main / resources / etc / cas / config di mana kita akan membuat fail sifat cas.properties . Sekarang, kita dapat menentukan pengguna statik dalam fail sifat:

cas.authn.accept.users=casuser::Mellon

Kita harus menyampaikan lokasi folder konfigurasi ke pelayan CAS agar tetapan tersebut dapat dilaksanakan. Mari kita kemas kini task.gradle supaya kita dapat menyampaikan lokasi sebagai argumen JVM dari baris arahan:

task run(group: "build", description: "Run the CAS web application in embedded container mode") { dependsOn 'build' doLast { def casRunArgs = new ArrayList(Arrays.asList( "-server -noverify -Xmx2048M -XX:+TieredCompilation -XX:TieredStopAtLevel=1".split(" "))) if (project.hasProperty('args')) { casRunArgs.addAll(project.args.split('\\s+')) } javaexec { main = "-jar" jvmArgs = casRunArgs args = ["build/libs/${casWebApplicationBinaryName}"] logger.info "Started ${commandLine}" } } }

Kami kemudian menyimpan fail dan menjalankan:

./gradlew run -Dorg.gradle.java.home=$JAVA11_HOME -Pargs="-Dcas.standalone.configurationDirectory=/cas-server/src/main/resources/etc/cas/config"

Harap maklum bahawa nilai cas.standalone.configurationDirectory adalah jalan mutlak . Kami kini boleh pergi ke // localhost: 8443 dan log masuk dengan nama pengguna casuser dan kata Mellon .

3. Persediaan Pelanggan CAS

Kami akan menggunakan Spring Initializr untuk membuat aplikasi klien Spring Boot. Ia mempunyai pergantungan Web , Keselamatan , Freemarker dan DevTools . Selain itu, kami juga akan menambahkan kebergantungan untuk modul Spring Security CAS ke pom.xmlnya :

 org.springframework.security spring-security-cas 5.3.0.RELEASE 

Akhirnya, mari tambahkan sifat Spring Boot berikut untuk mengkonfigurasi aplikasi:

server.port=8900 spring.freemarker.suffix=.ftl

4. Pendaftaran Perkhidmatan Pelayan CAS

Clients applications must register with the CAS server ahead of any authentication. CAS server supports the use of YAML, JSON, MongoDB and LDAP client registries.

In this tutorial, we'll use the JSON Service Registry method. Let's create yet another folder cas-server/src/main/resources/etc/cas/services. It's this folder that'll house the service registry JSON files.

We'll create a JSON file that contains the definition of our client application. The name of the file, casSecuredApp-8900.json, follows the pattern serviceName-Id.json:

{ "@class" : "org.apereo.cas.services.RegexRegisteredService", "serviceId" : "//localhost:8900/login/cas", "name" : "casSecuredApp", "id" : 8900, "logoutType" : "BACK_CHANNEL", "logoutUrl" : "//localhost:8900/exit/cas" }

The serviceId attribute defines a regex URL pattern for the client application. The pattern should match the URL of the client application.

The id atribut harus unik. Dengan kata lain, tidak boleh ada dua atau lebih perkhidmatan dengan id yang sama yang didaftarkan ke pelayan CAS yang sama. Mempunyai id pendua akan menimbulkan konflik dan mengatasi konfigurasi.

Kami juga mengkonfigurasi jenis logout menjadi BACK_CHANNEL dan URL menjadi // localhost: 8900 / exit / cas supaya kita dapat melakukan logout tunggal kemudian. Sebelum pelayan CAS dapat menggunakan fail konfigurasi JSON kami, kami harus mengaktifkan pendaftaran JSON di cas.properties kami :
cas.serviceRegistry.initFromJson=true cas.serviceRegistry.json.location=classpath:/etc/cas/services

5. Konfigurasi Log Masuk Tunggal Pelanggan CAS

Langkah seterusnya bagi kita adalah mengkonfigurasi Spring Security untuk berfungsi dengan pelayan CAS. Kita juga harus memeriksa aliran interaksi sepenuhnya, yang disebut urutan CAS.

Let's add the following bean configurations to the CasSecuredApplication class of our Spring Boot app:

@Bean public CasAuthenticationFilter casAuthenticationFilter( AuthenticationManager authenticationManager, ServiceProperties serviceProperties) throws Exception { CasAuthenticationFilter filter = new CasAuthenticationFilter(); filter.setAuthenticationManager(authenticationManager); filter.setServiceProperties(serviceProperties); return filter; } @Bean public ServiceProperties serviceProperties() { logger.info("service properties"); ServiceProperties serviceProperties = new ServiceProperties(); serviceProperties.setService("//cas-client:8900/login/cas"); serviceProperties.setSendRenew(false); return serviceProperties; } @Bean public TicketValidator ticketValidator() { return new Cas30ServiceTicketValidator("//localhost:8443"); } @Bean public CasAuthenticationProvider casAuthenticationProvider( TicketValidator ticketValidator, ServiceProperties serviceProperties) { CasAuthenticationProvider provider = new CasAuthenticationProvider(); provider.setServiceProperties(serviceProperties); provider.setTicketValidator(ticketValidator); provider.setUserDetailsService( s -> new User("[email protected]", "Mellon", true, true, true, true, AuthorityUtils.createAuthorityList("ROLE_ADMIN"))); provider.setKey("CAS_PROVIDER_LOCALHOST_8900"); return provider; }

The ServiceProperties bean has the same URL as the serviceId in casSecuredApp-8900.json. This is important because it identifies this client to the CAS server.

The sendRenew property of ServiceProperties is set to false. This means a user only needs to present login credentials to the server once.

The AuthenticationEntryPoint bean will handle authentication exceptions. Thus, it'll redirect the user to the login URL of the CAS server for authentication.

In summary, the authentication flow goes:

  1. A user attempts to access a secure page, which triggers an authentication exception
  2. The exception triggers AuthenticationEntryPoint. In response, the AuthenticationEntryPoint will take the user to the CAS server login page – //localhost:8443/login
  3. On successful authentication, the server redirects back to the client with a ticket
  4. CasAuthenticationFilter will pick up the redirect and call CasAuthenticationProvider
  5. CasAuthenticationProvider will use TicketValidator to confirm the presented ticket on CAS server
  6. If the ticket is valid, the user will get a redirection to the requested secure URL

Finally, let's configure HttpSecurity to secure some routes in WebSecurityConfig. In the process, we'll also add the authentication entry point for exception handling:

@Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests().antMatchers( "/secured", "/login") .authenticated() .and().exceptionHandling() .authenticationEntryPoint(authenticationEntryPoint()); }

6. CAS Client Single Logout Configuration

So far, we've dealt with single sign-on; let's now consider CAS single logout (SLO).

Applications that use CAS for managing user authentication can log out a user from two places:

  • The client application can logout a user from itself locally – this will not affect the user's login status in other applications using the same CAS server
  • The client application can also log out the user from the CAS server – this will cause the user to be logged out from all other client apps connected to the same CAS server.

We'll first put in place logout on the client application and then extend it to single logout on the CAS server.

In order to make obvious what goes on behind the scene, we'll create a logout() method to handle the local logout. On success, it'll redirect us to a page with a link for single logout:

@GetMapping("/logout") public String logout( HttpServletRequest request, HttpServletResponse response, SecurityContextLogoutHandler logoutHandler) { Authentication auth = SecurityContextHolder .getContext().getAuthentication(); logoutHandler.logout(request, response, auth ); new CookieClearingLogoutHandler( AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY) .logout(request, response, auth); return "auth/logout"; }

In the single logout process, the CAS server will first expire the user's ticket and then send an async request to all registered client apps. Each client app that receives this signal will perform a local logout. Thereby accomplishing the goal of logout once, it will cause a log out everywhere.

Having said that, let's add some bean configurations to our client app. Specifically, in the CasSecuredApplicaiton:

@Bean public SecurityContextLogoutHandler securityContextLogoutHandler() { return new SecurityContextLogoutHandler(); } @Bean public LogoutFilter logoutFilter() { LogoutFilter logoutFilter = new LogoutFilter("//localhost:8443/logout", securityContextLogoutHandler()); logoutFilter.setFilterProcessesUrl("/logout/cas"); return logoutFilter; } @Bean public SingleSignOutFilter singleSignOutFilter() { SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter(); singleSignOutFilter.setCasServerUrlPrefix("//localhost:8443"); singleSignOutFilter.setLogoutCallbackPath("/exit/cas"); singleSignOutFilter.setIgnoreInitConfiguration(true); return singleSignOutFilter; }

The logoutFilter will intercept requests to /logout/cas and redirect the application to the CAS server. The SingleSignOutFilter will intercept requests coming from the CAS server and perform the local logout.

7. Connecting the CAS Server to a Database

We can configure the CAS server to read credentials from a MySQL database. We'll use the test database of a MySQL server that's running in a local machine. Let's update cas-server/src/main/resources/etc/cas/config/cas.properties:

cas.authn.accept.users= cas.authn.jdbc.query[0].sql=SELECT * FROM users WHERE email = ? cas.authn.jdbc.query[0].url= jdbc:mysql://127.0.0.1:3306/test? useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC cas.authn.jdbc.query[0].dialect=org.hibernate.dialect.MySQLDialect cas.authn.jdbc.query[0].user=root cas.authn.jdbc.query[0].password=root cas.authn.jdbc.query[0].ddlAuto=none cas.authn.jdbc.query[0].driverClass=com.mysql.cj.jdbc.Driver cas.authn.jdbc.query[0].fieldPassword=password cas.authn.jdbc.query[0].passwordEncoder.type=NONE

We set the cas.authn.accept.users to blank. This will deactivate the use of static user repositories by the CAS server.

According to the SQL above, users' credentials are stored in the users table. The email column is what represents the users' principal (username).

Please make sure to check the list of supported databases, available drivers and dialects. We also set the password encoder type to NONE. Other encryption mechanisms and their peculiar properties are also available.

Note that the principal in the database of the CAS server must be the same as that of the client application.

Let's update CasAuthenticationProvider to have the same username as the CAS server:

@Bean public CasAuthenticationProvider casAuthenticationProvider() { CasAuthenticationProvider provider = new CasAuthenticationProvider(); provider.setServiceProperties(serviceProperties()); provider.setTicketValidator(ticketValidator()); provider.setUserDetailsService( s -> new User("[email protected]", "Mellon", true, true, true, true, AuthorityUtils.createAuthorityList("ROLE_ADMIN"))); provider.setKey("CAS_PROVIDER_LOCALHOST_8900"); return provider; }

CasAuthenticationProvider tidak menggunakan kata laluan untuk pengesahan. Walaupun begitu, nama pengguna harus sepadan dengan pelayan CAS agar pengesahan berjaya. Pelayan CAS memerlukan pelayan MySQL dijalankan di localhost di port 3306 . Nama pengguna dan kata laluan harus berakar .

Mulakan semula pelayan CAS dan aplikasi Spring Boot sekali lagi. Kemudian gunakan kelayakan baru untuk pengesahan.

8. Kesimpulannya

Kami telah melihat bagaimana menggunakan CAS SSO dengan Spring Security dan banyak fail konfigurasi yang terlibat. Terdapat banyak aspek lain dari CAS SSO yang boleh dikonfigurasi. Mulai dari tema dan jenis protokol hingga dasar pengesahan.

Ini dan yang lain ada dalam dokumen. Kod sumber untuk pelayan CAS dan aplikasi Spring Boot tersedia di GitHub.