Ekstra påloggingsfelt med vårsikkerhet

1. Introduksjon

I denne artikkelen implementerer vi et tilpasset autentiseringsscenario med Spring Security av legge til et ekstra felt i standard innloggingsskjema.

Vi skal fokusere på 2 forskjellige tilnærminger, for å vise allsidigheten i rammeverket og de fleksible måtene vi kan bruke den på.

Vår første tilnærming vil være en enkel løsning som fokuserer på gjenbruk av eksisterende kjerne-vårsikkerhetsimplementeringer.

Den andre tilnærmingen vår vil være en mer tilpasset løsning som kan være mer egnet for avanserte bruksområder.

Vi vil bygge videre på konsepter som er diskutert i våre tidligere artikler om Spring Security-pålogging.

2. Maven-oppsett

Vi bruker Spring Boot-startere for å starte opp prosjektet vårt og bringe inn alle nødvendige avhengigheter.

Oppsettet vi bruker krever en foreldredeklarasjon, nettstarter og sikkerhetsstarter; 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 versjonen av Spring Boot-sikkerhetsstarter finner du hos Maven Central.

3. Enkelt prosjektoppsett

I vår første tilnærming vil vi fokusere på å gjenbruke implementeringer som leveres av Spring Security. Spesielt vil vi gjenbruke DaoAuthenticationProvider og BrukernavnPassordToken ettersom de eksisterer “out-of-the-box”.

Nøkkelkomponentene vil omfatte:

  • SimpleAuthenticationFilteren utvidelse av BrukernavnPassordAuthenticationFilter
  • SimpleUserDetailsServiceen implementering av UserDetailsService
  • Osseren utvidelse av Bruker klasse levert av Spring Security som erklærer vår ekstra domene felt
  • SecurityConfigvårsikkerhetskonfigurasjon som setter inn vår SimpleAuthenticationFilter inn i filterkjeden, erklærer sikkerhetsregler og kobler opp avhengigheter
  • login.htmlen påloggingsside som samler inn brukernavn, passord, og domene

3.1. Enkelt autentiseringsfilter

I vår SimpleAuthenticationFilter, domenet og brukernavnet feltene er hentet fra forespørselen. Vi sammenføyer disse verdiene og bruker dem til å lage en forekomst av BrukernavnPassordAuthenticationToken.

Token blir deretter ført videre til AuthenticationProvider for autentisering:

offentlig klasse SimpleAuthenticationFilter utvider brukernavnPassordAuthenticationFilter {@Override offentlig autentiseringsforsøkAuthentication (HttpServletRequest-forespørsel, HttpServletResponse-svar) kaster AuthenticationException {// ... UsernamePasswordAuthenticationToken authRequest = getAuthRequest; setDetails (forespørsel, authRequest); returner this.getAuthenticationManager () .authenticate (authRequest); } privat brukernavnPasswordAuthenticationToken getAuthRequest (HttpServletRequest forespørsel) {String brukernavn = få brukernavn (forespørsel); Strengpassord = få Passord (forespørsel); Strengdomene = obtainDomain (forespørsel); // ... String usernameDomain = String.format ("% s% s% s", username.trim (), String.valueOf (Character.LINE_SEPARATOR), domain); returner nytt brukernavnPasswordAuthenticationToken (brukernavnDomene, passord); } // andre metoder}

3.2. Enkel UserDetails Service

De UserDetailsService kontrakt definerer en enkelt metode som kalles loadUserByUsername. Implementeringen vår trekker ut brukernavn og domene. Verdiene blir deretter overført til U-en vårserRepository å få Bruker:

offentlig klasse SimpleUserDetailsService implementerer UserDetailsService {// ... @Override public UserDetails loadUserByUsername (String username) kaster UsernameNotFoundException {String [] usernameAndDomain = StringUtils.split (username, String.valueOf (Character.LINE_SEPAR;) if (usernameAndDomain == null || usernameAndDomain.length! = 2) {kast nytt brukernavnNotFoundException ("Brukernavn og domene må oppgis"); } Brukerbruker = userRepository.findUser (brukernavnAndDomene [0], brukernavnAndDomene [1]); if (user == null) {throw new UsernameNotFoundException (String.format ("Brukernavn ikke funnet for domene, brukernavn =% s, domene =% s", brukernavnAndDomain [0], brukernavnAndDomain [1])); } returner bruker; }} 

3.3. Vårs sikkerhetskonfigurasjon

Vårt oppsett er forskjellig fra en standard Spring Security-konfigurasjon fordi vi setter inn vår SimpleAuthenticationFilter inn i filterkjeden før standard med en samtale til addFilterBefore:

@ Override-beskyttet tom konfigurering (HttpSecurity http) kaster unntak {http .addFilterBefore (authenticationFilter (), UsernamePasswordAuthenticationFilter.class) .authorizeRequests () .antMatchers ("/ css / **", "/ index"). PermitAll () .antMatchers ("/ user / **"). autentisert () .and () .formLogin (). loginPage ("/ login"). og () .logout () .logoutUrl ("/ logout"); }

Vi kan bruke den medfølgende DaoAuthenticationProvider fordi vi konfigurerer den med vår SimpleUserDetailsService. Husk det våre SimpleUserDetailsService vet å analysere brukernavn og domene Enger og returner det aktuelle Bruker å bruke når du autentiserer:

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

Siden vi bruker en SimpleAuthenticationFilter, konfigurerer vi vår egen AuthenticationFailureHandler for å sikre at mislykkede påloggingsforsøk håndteres riktig:

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

3.4. Påloggingsside

Innloggingssiden vi bruker samler inn tillegg domene felt som blir hentet ut av vårt SimpleAuthenticationFilter:

Vennligst logg inn

Eksempel: bruker / domene / passord

Ugyldig bruker, passord eller domene

Brukernavn

Domene

Passord

Logg inn

Tilbake til hjemmesiden

Når vi kjører applikasjonen og får tilgang til konteksten på // localhost: 8081, ser vi en lenke for å få tilgang til en sikret side. Ved å klikke på lenken vil påloggingssiden vises. Som forventet, vi ser det ekstra domenefeltet:

3.5. Sammendrag

I vårt første eksempel klarte vi å gjenbruke DaoAuthenticationProvider og BrukernavnPassordAuthenticationToken ved å "fake ut" brukernavnfeltet.

Som et resultat klarte vi å gjøre det legge til støtte for et ekstra påloggingsfelt med minimal konfigurasjon og tilleggskode.

4. Egendefinert prosjektoppsett

Den andre tilnærmingen vår vil være veldig lik den første, men kan være mer passende for ikke-trivielle bruksområder.

Hovedkomponentene i den andre tilnærmingen vår vil omfatte:

  • CustomAuthenticationFilteren utvidelse av BrukernavnPassordAuthenticationFilter
  • CustomUserDetailsServiceet tilpasset grensesnitt som erklærer et loadUserbyUsernameAndDomain metode
  • CustomUserDetailsServiceImplen implementering av vår CustomUserDetailsService
  • CustomUserDetailsAuthenticationProvideren utvidelse av AbstractUserDetailsAuthenticationProvider
  • CustomAuthenticationTokenen utvidelse av BrukernavnPassordAuthenticationToken
  • Osseren utvidelse av Bruker klasse levert av Spring Security som erklærer vår ekstra domene felt
  • SecurityConfigvårsikkerhetskonfigurasjon som setter inn vår CustomAuthenticationFilter inn i filterkjeden, erklærer sikkerhetsregler og kobler opp avhengigheter
  • login.htmlpåloggingssiden som samler inn brukernavn, passord, og domene

4.1. Egendefinert godkjenningsfilter

I vår CustomAuthenticationFilter, vi trekk ut brukernavn, passord og domenefelt fra forespørselen. Disse verdiene brukes til å lage en forekomst av vår tilpassedeAuthenticationToken som sendes til AuthenticationProvider for autentisering:

offentlig klasse CustomAuthenticationFilter utvider UsernamePasswordAuthenticationFilter {public static final Streng SPRING_SECURITY_FORM_DOMAIN_KEY = "domene"; @Override public Authentication attemptAuthentication (HttpServletRequest request, HttpServletResponse response) kaster AuthenticationException {// ... CustomAuthenticationToken authRequest = getAuthRequest (forespørsel); setDetails (forespørsel, authRequest); returner this.getAuthenticationManager (). autentisere (authRequest); } privat CustomAuthenticationToken getAuthRequest (HttpServletRequest forespørsel) {String brukernavn = få brukernavn (forespørsel); Strengpassord = få Passord (forespørsel); Strengdomene = obtainDomain (forespørsel); // ... returner nytt CustomAuthenticationToken (brukernavn, passord, domene); }

4.2. Tilpasset UserDetails Service

Våre CustomUserDetailsService kontrakt definerer en enkelt metode som kalles loadUserByUsernameAndDomain.

De CustomUserDetailsServiceImpl klasse vi lager implementerer bare kontrakten og delegerer til vår CustomUserRepository å få Bruker:

 offentlige UserDetails loadUserByUsernameAndDomain (String brukernavn, String domene) kaster UsernameNotFoundException {if (StringUtils.isAnyBlank (brukernavn, domene)) {throw new UsernameNotFoundException ("Brukernavn og domene må oppgis"); } Brukerbruker = userRepository.findUser (brukernavn, domene); if (user == null) {throw new UsernameNotFoundException (String.format ("Brukernavn ikke funnet for domene, brukernavn =% s, domene =% s", brukernavn, domene)); } returner bruker; }

4.3. Tilpasset UserDetailsAuthenticationProvider

Våre CustomUserDetailsAuthenticationProvider strekker AbstractUserDetailsAuthenticationProvider og delegater til vår CustomUserDetailService for å hente Bruker. Det viktigste i denne klassen er implementeringen av hente brukeren metode.

Merk at vi må kaste godkjenningstokenet til vårt CustomAuthenticationToken for tilgang til vårt tilpassede felt:

@ Override-beskyttet UserDetails retrieveUser (streng brukernavn, brukernavnPasswordAuthenticationToken-godkjenning) kaster AuthenticationException {CustomAuthenticationToken auth = (CustomAuthenticationToken) authentication; UserDetails loadedUser; prøv {loadedUser = this.userDetailsService .loadUserByUsernameAndDomain (auth.getPrincipal () .toString (), auth.getDomain ()); } catch (UsernameNotFoundException notFound) {if (authentication.getCredentials ()! = null) {String presentPassword = authentication.getCredentials () .toString (); passwordEncoder.matches (presentPassword, userNotFoundEncodedPassword); } kast ikkeFunnet; } catch (Exception repositoryProblem) {throw new InternalAuthenticationServiceException (repositoryProblem.getMessage (), repositoryProblem); } // ... return loadedUser; }

4.4. Sammendrag

Den andre tilnærmingen vår er nesten identisk med den enkle tilnærmingen vi presenterte først. Ved å implementere vår egen AuthenticationProvider og CustomAuthenticationToken, vi unngikk å måtte tilpasse brukernavnet vårt med tilpasset parselogikk.

5. Konklusjon

I denne artikkelen har vi implementert en skjemainnlogging i Spring Security som benyttet seg av et ekstra påloggingsfelt. Vi gjorde dette på to forskjellige måter:

  • I vår enkle tilnærming minimerte vi antall koder vi trengte å skrive. Vi klarte å gjenbruk DaoAuthenticationProvider og UsernamePasswordAuthentication ved å tilpasse brukernavnet med tilpasset parselogikk
  • I vår mer tilpassede tilnærming ga vi tilpasset feltstøtte av utvide AbstractUserDetailsAuthenticationProvider og tilby vårt eget CustomUserDetailsService med en CustomAuthenticationToken

Som alltid kan all kildekode bli funnet på GitHub.