CAS SSO met veerbeveiliging

1. Overzicht

In deze tutorial kijken we naar de Apereo Central Authentication Service (CAS) en zullen we zien hoe een Spring Boot-service deze kan gebruiken voor authenticatie. CAS is een Enterprise Single Sign-On (SSO) -oplossing die ook open source is.

Wat is SSO? Wanneer u zich met dezelfde inloggegevens aanmeldt bij YouTube, Gmail en Maps, is dat Single Sign-On. We gaan dit demonstreren door een CAS-server en een Spring Boot-app op te zetten. De Spring Boot-app gebruikt CAS voor authenticatie.

2. CAS-server instellen

2.1. CAS-installatie en afhankelijkheden

De server gebruikt de Maven (Gradle) War Overlay-stijl om de installatie en implementatie te vergemakkelijken:

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

Met deze opdracht wordt het cas-overlay-sjabloon in de cas-server directory.

Enkele van de aspecten die we zullen behandelen, zijn onder meer JSON-serviceregistratie en JDBC-databaseverbinding. Dus we zullen hun modules toevoegen aan het afhankelijkheden gedeelte van build.gradle het dossier:

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

Laten we ervoor zorgen dat we de nieuwste versie van casServer controleren.

2.2. CAS-serverconfiguratie

Voordat we de CAS-server kunnen starten, moeten we enkele basisconfiguraties toevoegen. Laten we beginnen met het maken van een cas-server / src / main / resources map en in deze map. Dit zal worden gevolgd door de oprichting van application.properties ook in de map:

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

Laten we doorgaan met het maken van het key-store-bestand waarnaar in de bovenstaande configuratie wordt verwezen. Eerst moeten we de mappen maken / etc / cas en / etc / cas / config in cas-server / src / main / resources.

Vervolgens moeten we de directory wijzigen in cas-server / src / main / resources / etc / cas en voer de opdracht uit om te genereren de sleutelwinkel:

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

Om ervoor te zorgen dat we geen SSL-handshake-fout hebben, moeten we localhost als de waarde van voor- en achternaam. We moeten hetzelfde ook gebruiken voor de naam van de organisatie en de eenheid. Verder moeten we het de sleutelwinkel in de JDK / JRE die we zullen gebruiken om onze client-app uit te voeren:

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

Het wachtwoord voor de bron- en doel-keystore is verander het. Op Unix-systemen moeten we deze opdracht mogelijk uitvoeren met admin (sudo) voorrecht. Na het importeren moeten we alle actieve Java-exemplaren opnieuw starten of het systeem opnieuw opstarten.

We gebruiken JDK11 omdat dit vereist is voor CAS-versie 6.1.x. We hebben ook de omgevingsvariabele $ JAVA11_HOME gedefinieerd die naar zijn homedirectory verwijst. We kunnen nu de CAS-server starten:

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

Wanneer de applicatie start, zien we "READY" gedrukt op de terminal en de server is beschikbaar op // localhost: 8443.

2.3. CAS-server gebruikersconfiguratie

We kunnen nog niet inloggen omdat we geen gebruiker hebben geconfigureerd. CAS heeft verschillende methoden om de configuratie te beheren, inclusief de stand-alone modus. Laten we een configuratiemap maken cas-server / src / main / resources / etc / cas / config waarin we een eigenschappenbestand zullen maken cas.eigenschappen. Nu kunnen we een statische gebruiker definiëren in het eigenschappenbestand:

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

We moeten de locatie van de configuratiemap doorgeven aan de CAS-server om de instellingen van kracht te laten worden. Laten we updaten taken.gradle zodat we de locatie kunnen doorgeven als een JVM-argument vanaf de opdrachtregel:

taakuitvoering (groep: "build", beschrijving: "Voer de CAS-webtoepassing uit in ingesloten containermodus") {afhankelijk van 'build' doLast {def casRunArgs = nieuwe 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 "Begonnen met $ {commandLine}"}}}

We slaan vervolgens het bestand op en voeren het volgende uit:

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

Houd er rekening mee dat de waarde van cas.standalone.configurationDirectory is een absoluut pad. We kunnen nu naar // localhost: 8443 en log in met gebruikersnaam casuser en wachtwoord Mellon.

3. CAS Client instellen

We gebruiken Spring Initializr om een ​​Spring Boot-client-app te genereren. Het zal hebben Web, Veiligheid, Freemarker en DevTools afhankelijkheden. Bovendien zullen we ook de afhankelijkheid voor Spring Security CAS-module aan zijn pom.xml:

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

Laten we tot slot de volgende Spring Boot-eigenschappen toevoegen om de app te configureren:

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

4. Registratie van CAS Server Service

Clienttoepassingen moeten zich vóór elke authenticatie bij de CAS-server registreren. CAS-server ondersteunt het gebruik van YAML-, JSON-, MongoDB- en LDAP-clientregisters.

In deze zelfstudie gebruiken we de JSON Service Registry-methode. Laten we nog een map maken cas-server / src / main / resources / etc / cas / services. Het is deze map waarin de JSON-bestanden van het serviceregister worden ondergebracht.

We maken een JSON-bestand met de definitie van onze clienttoepassing. De naam van het bestand, casSecuredApp-8900.json, volgt het patroon 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 "}

De serviceId kenmerk definieert een regex-URL-patroon voor de clienttoepassing. Het patroon moet overeenkomen met de URL van de clienttoepassing.

De ID kaart kenmerk moet uniek zijn. Met andere woorden, er mogen niet twee of meer services zijn met dezelfde ID kaart geregistreerd op dezelfde CAS-server. Duplicaat hebben ID kaart zal leiden tot conflicten en het overschrijven van configuraties.

We configureren ook het uitlogtype te zijn BACK_CHANNEL en de URL die moet zijn // localhost: 8900 / exit / cas zodat we later eenmalig kunnen afmelden. Voordat de CAS-server gebruik kan maken van ons JSON-configuratiebestand, moeten we het JSON-register inschakelen in ons cas.eigenschappen:
cas.serviceRegistry.initFromJson = true cas.serviceRegistry.json.location = classpath: / etc / cas / services

5. Configuratie CAS Client Single Sign-On

De volgende stap voor ons is om Spring Security te configureren om met de CAS-server te werken. We moeten ook de volledige stroom van interacties controleren, een zogenaamde CAS-reeks.

Laten we de volgende bean-configuraties toevoegen aan het CasSecuredApplication klasse van onze Spring Boot-app:

@Bean openbaar CasAuthenticationFilter casAuthenticationFilter (AuthenticationManager authenticationManager, ServiceProperties serviceProperties) genereert uitzondering {CasAuthenticationFilter filter = nieuw CasAuthenticationFilter (); filter.setAuthenticationManager (authenticationManager); filter.setServiceProperties (serviceProperties); retourfilter; } @Bean public ServiceProperties serviceProperties () {logger.info ("service-eigenschappen"); ServiceProperties serviceProperties = nieuwe ServiceProperties (); serviceProperties.setService ("// cas-client: 8900 / login / cas"); serviceProperties.setSendRenew (false); retourserviceProperties; } @Bean openbare TicketValidator ticketValidator () {retourneer nieuwe Cas30ServiceTicketValidator ("// localhost: 8443"); } @Bean openbare CasAuthenticationProvider casAuthenticationProvider (TicketValidator ticketValidator, ServiceProperties serviceProperties) {CasAuthenticationProvider provider = nieuwe CasAuthenticationProvider (); provider.setServiceProperties (serviceProperties); provider.setTicketValidator (ticketValidator); provider.setUserDetailsService (s -> nieuwe gebruiker ("[email protected]", "Mellon", true, true, true, true, AuthorityUtils.createAuthorityList ("ROLE_ADMIN"))); provider.setKey ("CAS_PROVIDER_LOCALHOST_8900"); terugkeerprovider; }

De Service-eigenschappen bean heeft dezelfde URL als de serviceId in casSecuredApp-8900.json. Dit is belangrijk omdat het deze client identificeert bij de CAS-server.

De sendRenew eigendom van Service-eigenschappen ingesteld op false. Dit betekent dat een gebruiker de inloggegevens maar één keer hoeft te presenteren aan de server.

De AuthenticationEntryPoint bean zal authenticatie-uitzonderingen afhandelen. Het zal de gebruiker dus omleiden naar de login-URL van de CAS-server voor authenticatie.

Samengevat gaat de authenticatiestroom als volgt:

  1. Een gebruiker probeert toegang te krijgen tot een beveiligde pagina, waardoor een authenticatie-uitzondering wordt geactiveerd
  2. De uitzondering treedt in werking AuthenticationEntryPoint. Als reactie hierop heeft de AuthenticationEntryPoint brengt de gebruiker naar de aanmeldingspagina van de CAS-server - // localhost: 8443 / login
  3. Bij succesvolle authenticatie, stuurt de server terug naar de client met een ticket
  4. CasAuthenticationFilter neemt de omleiding op en belt CasAuthenticationProvider
  5. CasAuthenticationProvider zal gebruiken TicketValidator om het gepresenteerde ticket op de CAS-server te bevestigen
  6. Als het ticket geldig is, krijgt de gebruiker een omleiding naar de gevraagde beveiligde URL

Laten we tot slot configureren HttpSecurity om een ​​aantal routes in te beveiligen WebSecurityConfig. Tijdens het proces voegen we ook het authenticatie-ingangspunt toe voor het afhandelen van uitzonderingen:

@Override protected void configure (HttpSecurity http) genereert uitzondering {http.authorizeRequests (). AntMatchers ("/ secure", "/ login") .authenticated () .and (). ExceptionHandling () .authenticationEntryPoint (authenticationEntryPoint ()); }

6. Configuratie enkele afmelding CAS-client

Tot nu toe hebben we te maken gehad met eenmalige aanmelding; Laten we nu eens kijken naar CAS single logout (SLO).

Toepassingen die CAS gebruiken voor het beheren van gebruikersauthenticatie, kunnen een gebruiker op twee plaatsen uitloggen:

  • De clienttoepassing kan een gebruiker lokaal uitloggen - dit heeft geen invloed op de inlogstatus van de gebruiker in andere toepassingen die dezelfde CAS-server gebruiken
  • De clienttoepassing kan de gebruiker ook afmelden bij de CAS-server - hierdoor wordt de gebruiker uitgelogd bij alle andere client-apps die met dezelfde CAS-server zijn verbonden.

We zullen eerst uitloggen op de clienttoepassing en deze vervolgens uitbreiden naar eenmalige afmelding op de CAS-server.

Om duidelijk te maken wat er achter de schermen gebeurt, maken we een uitloggen() methode om de lokale afmelding af te handelen. Als dit lukt, worden we doorgestuurd naar een pagina met een link voor eenmalige afmelding:

@GetMapping ("/ logout") openbare String uitloggen (HttpServletRequest-verzoek, HttpServletResponse-antwoord, SecurityContextLogoutHandler logoutHandler) {Authenticatie auth = SecurityContextHolder .getContext (). GetAuthentication (); logoutHandler.logout (verzoek, antwoord, auth); nieuwe CookieClearingLogoutHandler (AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY). uitloggen (verzoek, antwoord, auth); retourneer "auth / logout"; }

Bij het enkele uitlogproces zal de CAS-server eerst het ticket van de gebruiker laten verlopen en vervolgens een asynchroon verzoek naar alle geregistreerde client-apps sturen. Elke client-app die dit signaal ontvangt, zal lokaal uitloggen. Daardoor het doel van één keer uitloggen wordt bereikt, zal het overal een uitloggen veroorzaken.

Dat gezegd hebbende, laten we enkele bean-configuraties toevoegen aan onze client-app. Specifiek in de CasSecuredApplicaiton:

@Bean openbare SecurityContextLogoutHandler securityContextLogoutHandler () {retourneer nieuwe SecurityContextLogoutHandler (); } @Bean openbaar LogoutFilter logoutFilter () {LogoutFilter logoutFilter = nieuw LogoutFilter ("// localhost: 8443 / logout", securityContextLogoutHandler ()); logoutFilter.setFilterProcessesUrl ("/ logout / cas"); retourneer logoutFilter; } @Bean openbaar SingleSignOutFilter singleSignOutFilter () {SingleSignOutFilter singleSignOutFilter = nieuwe SingleSignOutFilter (); singleSignOutFilter.setCasServerUrlPrefix ("// localhost: 8443"); singleSignOutFilter.setLogoutCallbackPath ("/ exit / cas"); singleSignOutFilter.setIgnoreInitConfiguration (true); retourneer singleSignOutFilter; }

De logoutFilter onderschept verzoeken aan / logout / cas en stuur de applicatie door naar de CAS-server. De SingleSignOutFilter onderschept verzoeken die van de CAS-server komen en voert de lokale afmelding uit.

7. De CAS-server verbinden met een database

We kunnen de CAS-server configureren om inloggegevens uit een MySQL-database te lezen. We gebruiken de test database van een MySQL-server die op een lokale computer draait. Laten we updaten cas-server / src / main / resources / etc / cas / config / cas.properties:

cas.authn.accept.users = cas.authn.jdbc.query [0] .sql = SELECTEER * VAN gebruikers WAAR 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] .quuser = root casbc.query [0] .quuser = root casbc. [0] .password = root cas.authn.jdbc.query [0] .ddlAuto = geen cas.authn.jdbc.query [0] .driverClass = com.mysql.cj.jdbc.Driver cas.authn.jdbc.query [0] .fieldPassword = wachtwoord cas.authn.jdbc.query [0] .passwordEncoder.type = GEEN

We stellen de cas.authn.accept.users naar blanco. Hierdoor wordt het gebruik van statische gebruikersrepository's door de CAS-server gedeactiveerd.

Volgens de bovenstaande SQL worden de inloggegevens van gebruikers opgeslagen in het gebruikers tafel. De e-mail kolom is wat de opdrachtgever van de gebruiker vertegenwoordigt (gebruikersnaam).

Zorg ervoor dat u de lijst met ondersteunde databases, beschikbare stuurprogramma's en dialecten controleert. We hebben ook het wachtwoord-encodertype ingesteld op GEEN. Andere versleutelingsmechanismen en hun eigenaardige eigenschappen zijn ook beschikbaar.

Merk op dat de principal in de database van de CAS-server dezelfde moet zijn als die van de clienttoepassing.

Laten we updaten CasAuthenticationProvider dezelfde gebruikersnaam hebben als de CAS-server:

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

CasAuthenticationProvider gebruikt het wachtwoord niet voor authenticatie. Desalniettemin moet zijn gebruikersnaam overeenkomen met die van de CAS-server om authenticatie te laten slagen. CAS-server vereist een MySQL-server om op te draaien localhost in de haven 3306. De gebruikersnaam en het wachtwoord moeten zijn wortel.

Start de CAS-server en de Spring Boot-app opnieuw op. Gebruik vervolgens de nieuwe inloggegevens voor authenticatie.

8. Conclusie

We hebben gekeken naar het gebruik van CAS SSO met Spring Security en veel van de bijbehorende configuratiebestanden. Er zijn veel andere aspecten van CAS SSO die configureerbaar zijn. Variërend van thema's en protocoltypes tot authenticatiebeleid.

Deze en andere staan ​​in de documenten. De broncode voor de CAS-server en de Spring Boot-app is beschikbaar op GitHub.