Sådan bruges Spring Security med SAML Protocol Binding
I dette indlæg vil jeg vise, hvordan vi kan bruge Spring Security med SAML Protocol Binding til at integrere med Keycloak Identity Provider. Og hvis du vil læse om, hvordan du bruger Keycloak, kan du læse her.
Hvad er SAML?
SAML står for Security Assertion Markup Language. Det er en åben standard for
udveksling af godkendelses- og autorisationsdata mellem en tjenesteudbyder (SP) og identitetsudbyder (IdP).
Identitetsudbyder – udfører godkendelse og validerer brugeridentitet til autorisation og videregiver det til tjenesteudbyderen.
Tjenesteudbyder – Stoler på identitetsudbyderen og giver brugeren adgang til service baseret på autorisation.
SAML-godkendelsesflow
Som en del af dette flow vil vi bygge en simpel opgavelisteapplikation. Fremover vil en bruger få adgang til applikationen, og han vil blive omdirigeret til godkendelse.
SAML-godkendelsesbrugerflow:
- Brugeren får adgang til Service Provider (SP) ToDo List Application.
- Applikationen omdirigerer brugeren til Keycloak-loginskærmen. Under denne omdirigering sender applikationen en AuthnRequest til Keycloak IDP.
- Keycloak IDP validerer anmodningen, hvis den kommer fra den rigtige pålidelige part/tjenesteudbyder. Den søger efter udsteder og omdirigerings-URI (ACS URL).
- Keycloak IDP sender et SAML-svar tilbage til tjenesteudbyderen.
- Tjenesteudbyderen validerer det signerede svar med et offentligt IDP-certifikat.
- Hvis svaret er gyldigt, udtrækker vi attributten NameID fra påstanden og logger brugeren ind.
Bemærk – Spring Security SAML-udvidelsen var et bibliotek, der plejede at levere SAML-understøttelse.
Men efter 2018 flyttede Spring Security-teamet det projekt og understøtter nu SAML
2-godkendelse som en del af kernesikkerheden for Spring .
Brug Spring Security med SAML Protocol Binding
Derfor, når du har oprettet et Spring Boot-projekt, bliver vi nødt til at importere følgende afhængigheder.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
/*
* Spring Security
*/
implementation 'org.springframework.boot:spring-boot-starter-security'
runtimeOnly 'mysql:mysql-connector-java'
providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
implementation 'org.springframework.security:spring-security-saml2-service-provider:5.3.5' +
'.RELEASE'
/*
* Keycloak
*/
implementation 'org.keycloak:keycloak-spring-boot-starter:11.0.3'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
}
Derfor er afhængigheden spring-security-saml2-service-provider
giver os mulighed for at tilføje registrering af afhængige parter. Det hjælper også med registrering af identitetsudbyder.
Nu tilføjer vi denne registrering i voresSecurityConfig
som nedenfor:
@Bean
public RelyingPartyRegistrationRepository relyingPartyRegistrationRepository() throws CertificateException
{
final String idpEntityId = "http://localhost:8180/auth/realms/ToDoListSAMLApp";
final String webSSOEndpoint = "http://localhost:8180/auth/realms/ToDoListSAMLApp/protocol/saml";
final String registrationId = "keycloak";
final String localEntityIdTemplate = "{baseUrl}/saml2/service-provider-metadata" +
"/{registrationId}";
final String acsUrlTemplate = "{baseUrl}/login/saml2/sso/{registrationId}";
Saml2X509Credential idpVerificationCertificate;
try (InputStream pub = new ClassPathResource("credentials/idp.cer").getInputStream())
{
X509Certificate c = (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(pub);
idpVerificationCertificate = new Saml2X509Credential(c, VERIFICATION);
}
catch (Exception e)
{
throw new RuntimeException(e);
}
RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration
.withRegistrationId(registrationId)
.providerDetails(config -> config.entityId(idpEntityId))
.providerDetails(config -> config.webSsoUrl(webSSOEndpoint))
.providerDetails(config -> config.signAuthNRequest(false))
.credentials(c -> c.add(idpVerificationCertificate))
.assertionConsumerServiceUrlTemplate(acsUrlTemplate)
.build();
return new InMemoryRelyingPartyRegistrationRepository(relyingPartyRegistration);
}
Vores login ændres også med HttpSecurity
som følger:
httpSecurity.authorizeRequests()
.antMatchers("/js/**","/css/**","/img/**").permitAll()
.antMatchers("/signup","/forgotpassword").permitAll()
.antMatchers("/saml/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login").permitAll()
.and()
.saml2Login(Customizer.withDefaults()).exceptionHandling(exception ->
exception.authenticationEntryPoint(entryPoint()))
.logout()
.logoutUrl("/logout")
.logoutSuccessHandler(logoutSuccessHandler)
.deleteCookies("JSESSIONID")
.permitAll();
Vi bruger nu saml2Login
. Som standard, hvis du får adgang til applikationen, omdirigeres den til en identitetsudbyder. Vi ønsker at konfigurere vores brugerdefinerede login-side, før den kan omdirigeres til identitetsudbyderen – keycloak. Det er derfor, vi har authenticationEntryPoint
som giver os mulighed for at konfigurere vores brugerdefinerede login-side. Så nu hvis vi får adgang til vores applikation på https://localhost:8743/login
, vil vi se nedenstående login-side:
Så når du vælger muligheden for Login with Keycloak SAML
, vil den sende en AuthnRequest
til Keycloak. Denne anmodning er også en usigneret anmodning. Keycloak sender et underskrevet svar. En controller vil modtage dette signerede svar for at afkode NameId
attribut.
@GetMapping(value="/index")
public String getHomePage(Model model, @AuthenticationPrincipal Saml2AuthenticatedPrincipal saml2AuthenticatedPrincipal)
{
String principal = saml2AuthenticatedPrincipal.getName();
model.addAttribute("username", principal);
return "index";
}
Når NameId
er hentet, vil det logge brugeren ind.
Konfiguration på Keycloak
Vi bliver nødt til at konfigurere vores applikation i Keycloak-administrationskonsollen.
- Opret en REALM til din applikation.
- Vælg endepunkter – SAML 2.0 IdP-metadata
- I klienter – Tilføj tjenesteudbyderen.
- For din klient skal du konfigurere rod-URL, SAML-behandlings-URL (https://localhost:8743/saml2/service-provider-metadata/keycloak)
- Du kan også justere de andre indstillinger som – signering af påstande, inklusive AuthnStatement.
- Konfigurer bestemt ACS URL'en i afsnittet "Fine Grain SAML Endpoint Configuration".
Kodelager
Koden til dette projekt er tilgængelig i mit github-lager. Jeg dækkede også dette mere detaljeret i min bog Simplifying Spring Security. Hvis du vil vide mere, kan du købe min bog her.
Konklusion
I dette indlæg viste jeg, hvordan man bruger Spring Security med SAML-protokol. Gennem årene har der været en masse forbedringer i Spring Security, og nu kan det nemt bruges med forskellige protokoller som OAuth, OIDC. Hvis du kunne lide dette indlæg, så abonner på min blog her.