Dodatna polja za prijavu s proljetnom sigurnošću

1. Uvod

U ovom ćemo članku implementirati prilagođeni scenarij provjere autentičnosti s Spring Securityom do dodavanje dodatnog polja u standardni obrazac za prijavu.

Usredotočit ćemo se na 2 različita pristupa, kako bismo prikazali svestranost okvira i fleksibilne načine na koje ga možemo koristiti.

Naš prvi pristup bit će jednostavno rješenje koje se usredotočuje na ponovnu upotrebu postojećih temeljnih implementacija Spring Securitya.

Naš drugi pristup bit će prilagođenije rješenje koje bi moglo biti prikladnije za slučajeve napredne upotrebe.

Nadgradit ćemo se na koncepte koji su raspravljeni u našim prethodnim člancima o prijavi na Spring Security.

2. Postavljanje Mavena

Koristit ćemo pokretače Spring Boot za pokretanje našeg projekta i unošenje svih potrebnih ovisnosti.

Postava koju ćemo koristiti zahtijeva nadređenu deklaraciju, web pokretač i sigurnosni pokretač; uključit ćemo i timijan:

 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-majčina dušica org.thymeleaf.extras majčina dušica-dodaci-springsecurity5 

Najnoviju verziju sigurnosnog startera Spring Boot možete pronaći u Maven Central.

3. Jednostavno postavljanje projekta

U našem prvom pristupu usredotočit ćemo se na ponovnu upotrebu implementacija koje pruža Spring Security. Konkretno, ponovno ćemo upotrijebiti DaoAuthenticationProvider i UsernamePasswordToken budući da postoje "izvan kutije".

Ključne komponente sastojat će se od:

  • SimpleAuthenticationFilterprodužetak UsernamePasswordAuthenticationFilter
  • SimpleUserDetailsServiceprovedba UserDetailsService
  • Nasovajprodužetak Korisnik klase koju pruža Spring Security koja nas proglašava dodatnom domena polje
  • SecurityConfignaša konfiguracija Spring Security koja ubacuje našu SimpleAuthenticationFilter u lanac filtara, deklarira sigurnosna pravila i povezuje ovisnosti
  • login.htmlstranicu za prijavu koja prikuplja Korisničko ime, zaporka, i domena

3.1. Jednostavan filtar za provjeru autentičnosti

U našem SimpleAuthenticationFilter, iz zahtjeva se izdvajaju polja domene i korisničkog imena. Te vrijednosti spajamo i koristimo ih za stvaranje instance UsernamePasswordAuthenticationToken.

Zatim se token prosljeđuje na AutentificationProvider za autentifikaciju:

javna klasa SimpleAuthenticationFilter proširuje UsernamePasswordAuthenticationFilter {@Override javnu provjeru autentičnosti pokušajAuthentication (HttpServletRequest zahtjev, HttpServletResponse odgovor) baca AuthenticationException {// ... UsernamePasswordAuthenticationToken authRequest = getAuthRequest; setDetails (zahtjev, authRequest); vrati this.getAuthenticationManager () .authenticate (authRequest); } private UsernamePasswordAuthenticationToken getAuthRequest (HttpServletRequest zahtjev) {String username = yieldUsername (zahtjev); Lozinka niza = yieldPassword (zahtjev); Niz domene = dobitiDomena (zahtjev); // ... String usernameDomain = String.format ("% s% s% s", username.trim (), String.valueOf (Character.LINE_SEPARATOR), domena); vratiti novo UsernamePasswordAuthenticationToken (usernameDomain, lozinka); } // ostale metode}

3.2. Jednostavan Pojedinosti o korisniku Servis

The UserDetailsService ugovor definira jednu metodu koja se naziva loadUserByUsername. Naša implementacija izdvaja Korisničko ime i domena. Vrijednosti se zatim prosljeđuju našem UserRepository da biste dobili Korisnik:

javna klasa SimpleUserDetailsService implementira UserDetailsService {// ... @Override public UserDetails loadUserByUsername (String username) baca UsernameNotFoundException {String [] usernameAndDomain = StringUtils.split (username, String.valueOf (Character.LINE_S); if (usernameAndDomain == null || usernameAndDomain.length! = 2) {baciti novo UsernameNotFoundException ("Korisničko ime i domena moraju biti navedeni"); } Korisnik korisnik = userRepository.findUser (usernameAndDomain [0], usernameAndDomain [1]); if (user == null) {throw new UsernameNotFoundException (String.format ("Korisničko ime nije pronađeno za domenu, korisničko ime =% s, domena =% s", usernameAndDomain [0], usernameAndDomain [1])); } vratiti korisnika; }} 

3.3. Proljetna sigurnosna konfiguracija

Naša se postavka razlikuje od standardne konfiguracije Spring Security jer ubacujemo svoje SimpleAuthenticationFilter u lanac filtra prije zadanog s pozivom na addFilterBefore:

@Override zaštićena void konfiguracija (HttpSecurity http) baca izuzetak {http .addFilterBefore (authenticationFilter (), UsernamePasswordAuthenticationFilter.class) .authorizeRequests () .antMatchers ("/ css / **", "/ index") .MattAll (). ("/ user / **"). authenticated () .and () .formLogin (). loginPage ("/ login") .and () .logout () .logoutUrl ("/ logout"); }

U mogućnosti smo koristiti ponuđeno DaoAuthenticationProvider jer ga konfiguriramo s našim SimpleUserDetailsService. Sjetite se toga naše SimpleUserDetailsService zna raščlaniti naš Korisničko ime i domena polja i vratiti odgovarajuće Korisnik koristiti prilikom autentifikacije:

javni AuthenticationProvider authProvider () {DaoAuthenticationProvider pružatelj = novi DaoAuthenticationProvider (); provider.setUserDetailsService (userDetailsService); provider.setPasswordEncoder (passwordEncoder ()); povratnik; } 

Budući da koristimo SimpleAuthenticationFilter, mi sami konfiguriramo AuthenticationFailureHandler kako bismo osigurali da se neuspjeli pokušaji prijave pravilno obrađuju:

javni SimpleAuthenticationFilter authenticationFilter () baca iznimku {SimpleAuthenticationFilter filter = novi SimpleAuthenticationFilter (); filter.setAuthenticationManager (authenticationManagerBean ()); filter.setAuthenticationFailureHandler (neuspjehHandler ()); povratni filter; }

3.4. Stranica za prijavu

Stranica za prijavu koju koristimo prikuplja naše dodatne domena polje koje naše izvadi SimpleAuthenticationFilter:

Molim, potpišite

Primjer: korisnik / domena / lozinka

Nevažeći korisnik, lozinka ili domena

Korisničko ime

Domena

Zaporka

Prijaviti se

Povratak na početnu stranicu

Kada pokrenemo aplikaciju i pristupimo kontekstu na // localhost: 8081, vidimo vezu za pristup osiguranoj stranici. Klikom na vezu prikazat će se stranica za prijavu. Očekivano, vidimo dodatno polje domene:

3.5. Sažetak

U našem prvom primjeru uspjeli smo ponovno upotrijebiti DaoAuthenticationProvider i UsernamePasswordAuthenticationToken "lažiranjem" polja korisničkog imena.

Kao rezultat, mogli smo dodajte podršku za dodatno polje za prijavu s minimalnom količinom konfiguracije i dodatnim kodom.

4. Prilagođeno postavljanje projekta

Naš drugi pristup bit će vrlo sličan prvom, ali možda će biti prikladniji za slučajeve netrivijalne upotrebe.

Ključne komponente našeg drugog pristupa uključuju:

  • CustomAuthenticationFilterprodužetak UsernamePasswordAuthenticationFilter
  • CustomUserDetailsServiceprilagođeno sučelje koje proglašava a loadUserbyUsernameAndDomain metoda
  • CustomUserDetailsServiceImplprovedba našeg CustomUserDetailsService
  • CustomUserDetailsAuthenticationProviderprodužetak AbstractUserDetailsAuthenticationProvider
  • CustomAuthenticationTokenprodužetak UsernamePasswordAuthenticationToken
  • Nasovajprodužetak Korisnik klase koju pruža Spring Security koja nas proglašava dodatnom domena polje
  • SecurityConfignaša konfiguracija Spring Security koja ubacuje našu CustomAuthenticationFilter u lanac filtara, deklarira sigurnosna pravila i povezuje ovisnosti
  • login.htmlstranicu za prijavu koja prikuplja Korisničko ime, zaporka, i domena

4.1. Prilagođeni filtar za provjeru autentičnosti

U našem CustomAuthenticationFilter, mi iz zahtjeva izvadite korisničko ime, lozinku i polja domene. Te se vrijednosti koriste za stvaranje instance našeg PrilagođenogAuthenticationToken koji se prenosi na AutentificationProvider za autentifikaciju:

javna klasa CustomAuthenticationFilter proširuje UsernamePasswordAuthenticationFilter {javni statički završni niz SPRING_SECURITY_FORM_DOMAIN_KEY = "domena"; @Override public Authentication ProbaAuthentication (HttpServletRequest zahtjev, HttpServletResponse response) baca AuthenticationException {// ... CustomAuthenticationToken authRequest = getAuthRequest (zahtjev); setDetails (zahtjev, authRequest); vrati this.getAuthenticationManager (). authenticate (authRequest); } private CustomAuthenticationToken getAuthRequest (HttpServletRequest zahtjev) {String username = yieldUsername (zahtjev); Lozinka niza = yieldPassword (zahtjev); Niz domene = dobitiDomena (zahtjev); // ... povratak novog CustomAuthenticationToken (korisničko ime, lozinka, domena); }

4.2. Prilagođen Pojedinosti o korisniku Servis

Naše CustomUserDetailsService ugovor definira jednu metodu koja se naziva loadUserByUsernameAndDomain.

The CustomUserDetailsServiceImpl klase koju kreiramo jednostavno provodi ugovor i delegira na našu CustomUserRepository da biste dobili Korisnik:

 javni UserDetails loadUserByUsernameAndDomain (korisničko ime niza, domena niza) baca UsernameNotFoundException {if (StringUtils.isAnyBlank (korisničko ime, domena)) {baciti novo UsernameNotFoundException ("Korisničko ime i domena moraju biti navedeni"); } Korisnik korisnik = userRepository.findUser (korisničko ime, domena); if (user == null) {throw new UsernameNotFoundException (String.format ("Korisničko ime nije pronađeno za domenu, korisničko ime =% s, domena =% s", korisničko ime, domena)); } vratiti korisnika; }

4.3. Prilagođen UserDetailsAuthenticationProvider

Naše CustomUserDetailsAuthenticationProvider proteže se AbstractUserDetailsAuthenticationProvider i delegati na našem CustomUserDetailService za preuzimanje Korisnik. Najvažnija značajka ove klase je implementacija retrieveUser metoda.

Imajte na umu da token za provjeru moramo prebaciti na naš CustomAuthenticationToken za pristup našem prilagođenom polju:

@Override protected UserDetails retrieveUser (String username, UsernamePasswordAuthenticationToken authentication) baca AuthenticationException {CustomAuthenticationToken auth = (CustomAuthenticationToken) authentication; UserDetails loadedUser; probajte {loadedUser = this.userDetailsService .loadUserByUsernameAndDomain (auth.getPrincipal () .toString (), auth.getDomain ()); } catch (UsernameNotFoundException notFound) {if (authentication.getCredentials ()! = null) {String predstavljenPassword = authentication.getCredentials () .toString (); passwordEncoder.matches (presentPassword, userNotFoundEncodedPassword); } baciti notFound; } catch (Exception repositoryProblem) {throw new InternalAuthenticationServiceException (repositoryProblem.getMessage (), repositoryProblem); } // ... return loadedUser; }

4.4. Sažetak

Naš drugi pristup gotovo je identičan jednostavnom pristupu koji smo prvi predstavili. Implementacijom vlastitog AutentificationProvider i CustomAuthenticationToken, izbjegli smo potrebu prilagođavanja našeg korisničkog imena prilagođenom logikom raščlanjivanja.

5. Zaključak

U ovom smo članku u Spring Security implementirali prijavu putem obrasca koja je koristila dodatno polje za prijavu. To smo učinili na 2 različita načina:

  • U našem jednostavnom pristupu smanjili smo količinu koda potrebnu za pisanje. Mogli smo ponovna upotreba DaoAuthenticationProvider i UsernamePasswordAuthentication prilagođavanjem korisničkog imena s prilagođenom logikom raščlanjivanja
  • U našem prilagođenijem pristupu pružili smo prilagođenu terensku podršku od proširivanje AbstractUserDetailsAuthenticationProvider i pružanje vlastitog CustomUserDetailsService s CustomAuthenticationToken

Kao i uvijek, sav izvorni kod možete pronaći na GitHubu.


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