Ekstra login felter med Spring Security

1. Introduktion

I denne artikel implementerer vi et tilpasset godkendelsesscenarie med Spring Security af tilføje et ekstra felt til standard loginformularen.

Vi skal fokusere på 2 forskellige tilgange, for at vise rammens alsidighed og de fleksible måder, vi kan bruge den på.

Vores første tilgang vil være en enkel løsning, der fokuserer på genbrug af eksisterende kerneforebyggelsessikkerhedsimplementeringer.

Vores anden tilgang vil være en mere brugerdefineret løsning, der kan være mere egnet til avancerede brugssager.

Vi bygger på toppen af ​​koncepter, der diskuteres i vores tidligere artikler om Spring Security login.

2. Maven-opsætning

Vi bruger Spring Boot-startere til at bootstrape vores projekt og bringe alle nødvendige afhængigheder ind.

Den opsætning, vi bruger, kræver en overordnet erklæring, webstarter og sikkerhedsstarter; Vi inkluderer også thymeleaf:

 org.springframework.boot spring-boot-starter-parent 2.2.6.RELEASE org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-security org.springframework.boot spring-boot- starter-thymeleaf org.thymeleaf.extras thymeleaf-extras-springsecurity5 

Den nyeste version af Spring Boot-sikkerhedsstarter kan findes på Maven Central.

3. Enkel projektopsætning

I vores første tilgang vil vi fokusere på at genbruge implementeringer, der leveres af Spring Security. Vi genbruger især DaoAuthenticationProvider og BrugernavnPasswordToken da de eksisterer "out-of-the-box".

De vigtigste komponenter vil omfatte:

  • SimpleAuthenticationFilteren udvidelse af BrugernavnPasswordAuthenticationFilter
  • SimpleUserDetailsServiceen implementering af UserDetailsService
  • Oseren udvidelse af Bruger klasse leveret af Spring Security, der erklærer vores ekstra domæne Mark
  • SecurityConfigvores Spring Security-konfiguration, der indsætter vores SimpleAuthenticationFilter ind i filterkæden, erklærer sikkerhedsregler og trækker afhængigheder op
  • login.htmlen login side, der samler brugernavn, adgangskodeog domæne

3.1. Simpelt godkendelsesfilter

I vores SimpleAuthenticationFilter, felterne domæne og brugernavn udvindes fra anmodningen. Vi sammenkæder disse værdier og bruger dem til at oprette en forekomst af BrugernavnPasswordAuthenticationToken.

Token sendes derefter videre til AuthenticationProvider til godkendelse:

offentlig klasse SimpleAuthenticationFilter udvider UsernamePasswordAuthenticationFilter {@Override public Authentication attemptAuthentication (HttpServletRequest anmodning, HttpServletResponse svar) kaster AuthenticationException {// ... UsernamePasswordAuthenticationToken authRequest = getAuthRequest setDetails (anmodning, authRequest); returner this.getAuthenticationManager () .authenticate (authRequest); } privat brugernavnPasswordAuthenticationToken getAuthRequest (HttpServletRequest anmodning) {String brugernavn = få brugernavn (anmodning); Strengadgangskode = få adgangskode (anmodning); Strengdomæne = obtainDomain (anmodning); // ... String usernameDomain = String.format ("% s% s% s", username.trim (), String.valueOf (Character.LINE_SEPARATOR), domæne); returner nyt brugernavnPasswordAuthenticationToken (brugernavnDomæne, adgangskode); } // andre metoder}

3.2. Enkel UserDetails Service

Det UserDetailsService kontrakt definerer en enkelt metode kaldet loadUserByUsername. Vores implementering udtrækker brugernavn og domæne. Værdierne overføres derefter til vores UserRepository at få Bruger:

offentlig klasse SimpleUserDetailsService implementerer UserDetailsService {// ... @ Override offentlige UserDetails loadUserByUsername (String username) kaster UsernameNotFoundException {String [] usernameAndDomain = StringUtils.split (username, String.valueOf (Character.LINE_SEPAR;) hvis (brugernavnAndDomæne == null || brugernavnAndDomæne.længde! = 2) {kast nyt brugernavnNotFoundException ("Brugernavn og domæne skal angives"); } Brugerbruger = userRepository.findUser (brugernavnAndDomæne [0], brugernavnAndDomæne [1]); if (user == null) {throw new UsernameNotFoundException (String.format ("Brugernavn ikke fundet for domæne, brugernavn =% s, domæne =% s", brugernavnAndDomæne [0], brugernavnAndDomæne [1])); } returner bruger; }} 

3.3. Forårssikkerhedskonfiguration

Vores opsætning er forskellig fra en standard Spring Security-konfiguration, fordi vi indsætter vores SimpleAuthenticationFilter ind i filterkæden før standard med et opkald til addFilterBefore:

@ Override beskyttet ugyldig konfiguration (HttpSecurity http) kaster undtagelse {http .addFilterBefore (authenticationFilter (), UsernamePasswordAuthenticationFilter.class) .authorizeRequests () .antMatchers ("/ css / **", "/ index"). PermitAll () .antMatchers ("/ bruger / **"). godkendt () .og () .formLogin (). loginPage ("/ login"). og () .logout () .logoutUrl ("/ logout"); }

Vi er i stand til at bruge den medfølgende DaoAuthenticationProvider fordi vi konfigurerer det med vores SimpleUserDetailsService. Husk det vores SimpleUserDetailsService ved, hvordan man kan analysere vores brugernavn og domæne felter og returner det relevante Bruger til brug ved godkendelse:

public AuthenticationProvider authProvider () {DaoAuthenticationProvider provider = new DaoAuthenticationProvider (); provider.setUserDetailsService (userDetailsService); provider.setPasswordEncoder (passwordEncoder ()); returudbyder } 

Da vi bruger en SimpleAuthenticationFilter, konfigurerer vi vores egne AuthenticationFailureHandler for at sikre, at mislykkede loginforsøg håndteres korrekt:

offentlig SimpleAuthenticationFilter authenticationFilter () kaster undtagelse {SimpleAuthenticationFilter filter = ny SimpleAuthenticationFilter (); filter.setAuthenticationManager (authenticationManagerBean ()); filter.setAuthenticationFailureHandler (failureHandler ()); returfilter; }

3.4. Login side

Login-siden, vi bruger, indsamler vores ekstra domæne felt, der udvindes af vores SimpleAuthenticationFilter:

Log ind

Eksempel: bruger / domæne / adgangskode

Ugyldig bruger, adgangskode eller domæne

Brugernavn

Domæne

Adgangskode

Log ind

Tilbage til startsiden

Når vi kører applikationen og får adgang til konteksten på // localhost: 8081, ser vi et link til adgang til en sikret side. Hvis du klikker på linket, vises login-siden. Som forventet, vi ser det ekstra domæne felt:

3.5. Resumé

I vores første eksempel kunne vi genbruge DaoAuthenticationProvider og BrugernavnPasswordAuthenticationToken ved at "udslette" brugernavnfeltet.

Som et resultat kunne vi tilføj support til et ekstra loginfelt med en minimal mængde konfiguration og yderligere kode.

4. Opsætning af brugerdefineret projekt

Vores anden tilgang vil være meget lig den første, men kan være mere passende til ikke-trivielle anvendelsessager.

De vigtigste komponenter i vores anden tilgang vil omfatte:

  • CustomAuthenticationFilteren udvidelse af BrugernavnPasswordAuthenticationFilter
  • CustomUserDetailsServiceen brugerdefineret grænseflade, der erklærer en loadUserbyUsernameAndDomain metode
  • CustomUserDetailsServiceImplen implementering af vores CustomUserDetailsService
  • CustomUserDetailsAuthenticationProvideren udvidelse af AbstractUserDetailsAuthenticationProvider
  • CustomAuthenticationTokenen udvidelse af BrugernavnPasswordAuthenticationToken
  • Oseren udvidelse af Bruger klasse leveret af Spring Security, der erklærer vores ekstra domæne Mark
  • SecurityConfigvores Spring Security-konfiguration, der indsætter vores CustomAuthenticationFilter ind i filterkæden, erklærer sikkerhedsregler og trækker afhængigheder op
  • login.htmlloginsiden, der samler brugernavn, adgangskodeog domæne

4.1. Tilpasset godkendelsesfilter

I vores CustomAuthenticationFilter, vi udpakke brugernavnet, adgangskoden og domænefelterne fra anmodningen. Disse værdier bruges til at oprette en forekomst af vores brugerdefineredeAuthenticationToken som sendes til AuthenticationProvider til godkendelse:

offentlig klasse CustomAuthenticationFilter udvider UsernamePasswordAuthenticationFilter {public static final String SPRING_SECURITY_FORM_DOMAIN_KEY = "domæne"; @Override offentlig godkendelsesforsøgAuthentication (HttpServletRequest anmodning, HttpServletResponse svar) kaster AuthenticationException {// ... CustomAuthenticationToken authRequest = getAuthRequest (anmodning); setDetails (anmodning, authRequest); returner this.getAuthenticationManager (). authenticate (authRequest); } privat CustomAuthenticationToken getAuthRequest (HttpServletRequest anmodning) {String brugernavn = få Brugernavn (anmodning); Strengadgangskode = få adgangskode (anmodning); Strengdomæne = obtainDomain (anmodning); // ... returner nyt CustomAuthenticationToken (brugernavn, adgangskode, domæne); }

4.2. Brugerdefinerede UserDetails Service

Vores CustomUserDetailsService kontrakt definerer en enkelt metode kaldet loadUserByUsernameAndDomain.

Det CustomUserDetailsServiceImpl klasse, vi opretter, implementerer blot kontrakten og delegerer til vores CustomUserRepository at få Bruger:

 offentlige UserDetails loadUserByUsernameAndDomain (String-brugernavn, String-domæne) kaster UsernameNotFoundException {hvis (StringUtils.isAnyBlank (brugernavn, domæne)) {kast nyt UsernameNotFoundException ("Brugernavn og domæne skal angives"); } Brugerbruger = userRepository.findUser (brugernavn, domæne); if (user == null) {throw new UsernameNotFoundException (String.format ("Brugernavn ikke fundet for domæne, brugernavn =% s, domæne =% s", brugernavn, domæne)); } returner bruger; }

4.3. Brugerdefinerede UserDetailsAuthenticationProvider

Vores CustomUserDetailsAuthenticationProvider strækker sig AbstractUserDetailsAuthenticationProvider og delegerede til vores CustomUserDetailService for at hente Bruger. Det vigtigste ved denne klasse er implementeringen af retrieveUser metode.

Bemærk, at vi skal kaste godkendelsestokenet til vores CustomAuthenticationToken for adgang til vores brugerdefinerede felt:

@ Override-beskyttet UserDetails retrieveUser (streng brugernavn, brugernavnPasswordAuthenticationToken-godkendelse) kaster AuthenticationException {CustomAuthenticationToken auth = (CustomAuthenticationToken) authentication; UserDetails loadedUser; prøv {loadedUser = this.userDetailsService .loadUserByUsernameAndDomain (auth.getPrincipal () .toString (), auth.getDomain ()); } fange (UsernameNotFoundException notFound) {if (authentication.getCredentials ()! = null) {String presentPassword = authentication.getCredentials () .toString (); passwordEncoder.matches (præsenteretPassword, userNotFoundEncodedPassword); } kast ikkeFundet; } catch (Exception repositoryProblem) {kast nyt InternalAuthenticationServiceException (repositoryProblem.getMessage (), repositoryProblem); } // ... return loadedUser; }

4.4. Resumé

Vores anden tilgang er næsten identisk med den enkle tilgang, vi præsenterede først. Ved at implementere vores egne AuthenticationProvider og CustomAuthenticationToken, undgik vi at skulle tilpasse vores brugernavnfelt med brugerdefineret parselogik.

5. Konklusion

I denne artikel har vi implementeret et formularlogin i Spring Security, der brugte et ekstra loginfelt. Vi gjorde dette på to forskellige måder:

  • I vores enkle tilgang minimerede vi den mængde kode, vi havde brug for, for at skrive. Det var vi i stand til genbruge DaoAuthenticationProvider og UsernamePasswordAuthentication ved at tilpasse brugernavnet med brugerdefineret parselogik
  • I vores mere tilpassede tilgang leverede vi brugerdefineret feltstøtte af udvidelse af AbstractUserDetailsAuthenticationProvider og levering af vores egne CustomUserDetailsService med en CustomAuthenticationToken

Som altid kan al kildekode findes på GitHub.


$config[zx-auto] not found$config[zx-overlay] not found