Extra prihlasovacie polia s jarnou bezpečnosťou

1. Úvod

V tomto článku implementujeme vlastný scenár autentifikácie s Spring Security by pridanie dodatočného poľa do štandardného prihlasovacieho formulára.

Budeme sa sústrediť na 2 rôzne prístupy, aby sme ukázali všestrannosť rámca a flexibilné spôsoby, ako ho môžeme použiť.

Náš prvý prístup bude jednoduché riešenie zamerané na opätovné použitie existujúcich základných implementácií Spring Security.

Náš druhý prístup bude prispôsobenejším riešením, ktoré môže byť vhodnejšie pre pokročilé prípady použitia.

Budeme stavať na vrchole konceptov, o ktorých sa hovorí v našich predchádzajúcich článkoch o prihlásení do Spring Security.

2. Nastavenie Maven

Použijeme štartéry Spring Boot na zavedenie nášho projektu a na zavedenie všetkých potrebných závislostí.

Nastavenie, ktoré použijeme, vyžaduje vyhlásenie rodiča, webový štartér a bezpečnostný štartér; zahrnieme aj brzlík:

 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- štartér-thymeleaf org.thymeleaf.extras thymeleaf-extra-pružinabezpečnosť5 

Najaktuálnejšiu verziu bezpečnostného štartéra Spring Boot nájdete na serveri Maven Central.

3. Jednoduché nastavenie projektu

V našom prvom prístupe sa zameriame na opätovné použitie implementácií, ktoré poskytuje Spring Security. Najmä znova použijeme Poskytovateľ DaoAuthenticationProvider a UsernamePasswordToken pretože existujú „pripravené na použitie“.

Medzi kľúčové komponenty patrí:

  • SimpleAuthenticationFilterrozšírenie o UsernamePasswordAuthenticationFilter
  • SimpleUserDetailsServiceimplementácia UserDetailsService
  • Násehmrozšírenie Používateľ triedy, ktorú poskytuje Spring Security a ktorá deklaruje naše extra doména lúka
  • SecurityConfignaša konfigurácia Spring Security, ktorá vkladá naše SimpleAuthenticationFilter do filtračného reťazca deklaruje bezpečnostné pravidlá a zvyšuje závislosť
  • login.htmlprihlasovacia stránka, ktorá zhromažďuje používateľské meno, hesloa doména

3.1. Jednoduchý autentifikačný filter

V našom SimpleAuthenticationFilter, polia domény a používateľské meno sú extrahované z požiadavky. Tieto hodnoty zreťazíme a použijeme ich na vytvorenie inštancie UsernamePasswordAuthenticationToken.

Token sa potom odovzdá spolu s AuthenticationProvider na autentifikáciu:

verejná trieda SimpleAuthenticationFilter rozširuje UsernamePasswordAuthenticationFilter {@Override public Authentication pokusAuthentication (požiadavka HttpServletRequest, odpoveď HttpServletResponse) hodí AuthenticationException {// ... UsernamePasswordAuthenticationToken authRequest = getAuthRequest (request) setDetails (požiadavka, authRequest); vrátiť this.getAuthenticationManager () .authenticate (authRequest); } private UsernamePasswordAuthenticationToken getAuthRequest (požiadavka HttpServletRequest) {Reťazec username = obtainUsername (požiadavka); Reťazcové heslo = obtainPassword (požiadavka); Reťazcová doména = obtainDomain (požiadavka); // ... String usernameDomain = String.format ("% s% s% s", username.trim (), String.valueOf (Character.LINE_SEPARATOR), doména); vrátiť nové UsernamePasswordAuthenticationToken (usernameDomain, heslo); } // ďalšie metódy}

3.2. Jednoduché UserDetails Služby

The UserDetailsService zmluva definuje jednu metódu nazvanú loadUserByUsername. Naša implementácia extrahuje používateľské meno a doména. Hodnoty sa potom odovzdajú nášmu U.serRepository získať Používateľ:

verejná trieda SimpleUserDetailsService implementuje UserDetailsService {// ... @Override public UserDetails loadUserByUsername (String username) throws UsernameNotFoundException {String [] usernameAndDomain = StringUtils.split (username, String.valueOf (Character.LINE_f) if (usernameAndDomain == null || usernameAndDomain.length! = 2) {hodiť novú UsernameNotFoundException ("Musí byť poskytnuté užívateľské meno a doména"); } Používateľ user = userRepository.findUser (usernameAndDomain [0], usernameAndDomain [1]); if (user == null) {throw new UsernameNotFoundException (String.format ("Username not found for domain, username =% s, domain =% s", usernameAndDomain [0], usernameAndDomain [1]))); } návratový užívateľ; }} 

3.3. Jarná konfigurácia zabezpečenia

Naše nastavenie sa líši od štandardnej konfigurácie Spring Security, pretože vložíme náš SimpleAuthenticationFilter do filtračného reťazca pred predvoleným nastavením s volaním na addFilterBefore:

@Override protected void configure (HttpSecurity http) vyvolá výnimku {http .addFilterBefore (authenticationFilter (), UsernamePasswordAuthenticationFilter.class) .authorizeRequests () .antMatchers ("/ css / **", "/ index"). PermitAll () .antMatchers ("/ user / **"). authenticated () .and () .formLogin (). loginPage ("/ login") .and () .logout () .logoutUrl ("/ logout"); }

Sme schopní použiť poskytnuté Poskytovateľ DaoAuthenticationProvider pretože to konfigurujeme pomocou nášho SimpleUserDetailsService. Pripomeňme si to náš SimpleUserDetailsService vie, ako analyzovať naše používateľské meno a doména polia a vrátiť príslušné Používateľ použiť pri autentifikácii:

public AuthenticationProvider authProvider () {poskytovateľ DaoAuthenticationProvider = nový DaoAuthenticationProvider (); provider.setUserDetailsService (userDetailsService); provider.setPasswordEncoder (hesloEncoder ()); poskytovateľ návratu; } 

Pretože používame a SimpleAuthenticationFilter, nakonfigurujeme si svoje vlastné AuthenticationFailureHandler aby ste zaistili, že sú neúspešné pokusy o prihlásenie správne spracované:

public SimpleAuthenticationFilter authenticationFilter () vyvolá výnimku {SimpleAuthenticationFilter filter = nový SimpleAuthenticationFilter (); filter.setAuthenticationManager (authenticationManagerBean ()); filter.setAuthenticationFailureHandler (failureHandler ()); spätný filter; }

3.4. Prihlasovacia stránka

Prihlasovacia stránka, ktorú používame, zhromažďuje naše ďalšie doména pole, ktoré sa extrahuje našou SimpleAuthenticationFilter:

prosím prihlás sa

Príklad: používateľ / doména / heslo

Neplatný používateľ, heslo alebo doména

Užívateľské meno

Doména

Heslo

Prihlásiť sa

Späť na domovskú stránku

Keď spustíme aplikáciu a vstúpime do kontextu na // localhost: 8081, uvidíme odkaz na prístup na zabezpečenú stránku. Kliknutím na odkaz sa zobrazí prihlasovacia stránka. Podľa očakávania, vidíme ďalšie pole domény:

3.5. Zhrnutie

V našom prvom príklade sme boli schopní znova použiť Poskytovateľ DaoAuthenticationProvider a UsernamePasswordAuthenticationToken „predstieraním“ poľa používateľského mena.

Vďaka tomu sme boli schopní pridať podporu pre ďalšie prihlasovacie pole s minimálnym počtom konfigurácií a dodatočným kódom.

4. Vlastné nastavenie projektu

Náš druhý prístup bude veľmi podobný prvému, ale môže byť vhodnejší pre netriviálne prípady použitia.

Kľúčové komponenty nášho druhého prístupu budú zahŕňať:

  • CustomAuthenticationFilterrozšírenie o UsernamePasswordAuthenticationFilter
  • CustomUserDetailsServicevlastné rozhranie deklarujúce a loadUserbyUsernameAndDomain metóda
  • CustomUserDetailsServiceImplimplementácia nášho CustomUserDetailsService
  • CustomUserDetailsAuthenticationProviderrozšírenie o AbstractUserDetailsAuthenticationProvider
  • CustomAuthenticationTokenrozšírenie o UsernamePasswordAuthenticationToken
  • Násehmrozšírenie Používateľ triedy, ktorú poskytuje Spring Security a ktorá deklaruje naše extra doména lúka
  • SecurityConfignaša konfigurácia Spring Security, ktorá vkladá naše CustomAuthenticationFilter do filtračného reťazca deklaruje bezpečnostné pravidlá a zvyšuje závislosť
  • login.htmlprihlasovacia stránka, ktorá zhromažďuje používateľské meno, hesloa doména

4.1. Vlastný filter overenia

V našom CustomAuthenticationFilter, my z požiadavky extrahujte polia používateľské meno, heslo a doména. Tieto hodnoty sa používajú na vytvorenie inštancie nášho CustomAuthenticationToken ktorý sa odovzdáva AuthenticationProvider pre autentizáciu:

verejná trieda CustomAuthenticationFilter rozširuje UsernamePasswordAuthenticationFilter {public static final String SPRING_SECURITY_FORM_DOMAIN_KEY = "doména"; @ Verzia Verejný pokus AuthenticationAuthentication (požiadavka HttpServletRequest, odpoveď HttpServletResponse) hodí AuthenticationException {// ... CustomAuthenticationToken authRequest = getAuthRequest (požiadavka); setDetails (požiadavka, authRequest); vrátiť this.getAuthenticationManager (). authenticate (authRequest); } private CustomAuthenticationToken getAuthRequest (požiadavka HttpServletRequest) {String username = obtainUsername (request); Reťazcové heslo = obtainPassword (požiadavka); Reťazcová doména = obtainDomain (požiadavka); // ... vrátiť nový CustomAuthenticationToken (používateľské meno, heslo, doména); }

4.2. Vlastné UserDetails Služby

Náš CustomUserDetailsService zmluva definuje jednu metódu nazvanú loadUserByUsernameAndDomain.

The CustomUserDetailsServiceImpl triedy, ktorú vytvoríme, jednoducho implementuje zmluvu a deleguje na našu CustomUserRepository získať Používateľ:

 public UserDetails loadUserByUsernameAndDomain (String username, String domain) throws UsernameNotFoundException {if (StringUtils.isAnyBlank (username, domain)) {throw new UsernameNotFoundException ("User name and domain must be provided"); } User user = userRepository.findUser (používateľské meno, doména); if (user == null) {throw new UsernameNotFoundException (String.format ("Username not found for domain, username =% s, domain =% s", username, domain)); } návratový užívateľ; }

4.3. Vlastné UserDetailsAuthenticationProvider

Náš CustomUserDetailsAuthenticationProvider predlžuje AbstractUserDetailsAuthenticationProvider a delegáti na našu CustomUserDetailService získať Používateľ. Najdôležitejšou vlastnosťou tejto triedy je implementácia retrieveUser metóda.

Upozorňujeme, že musíme odovzdať autentifikačný token nášmu CustomAuthenticationToken pre prístup do nášho vlastného poľa:

@Override chránený UserDetails retrieveUser (reťazec používateľské meno, UsernamePasswordAuthenticationToken autentifikácia) hodí AuthenticationException {CustomAuthenticationToken auth = (CustomAuthenticationToken) autentifikácia; UserDetails loadedUser; skúste {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); } hod notFound; } catch (Exception repositoryProblem) {hodiť novú InternalAuthenticationServiceException (repositoryProblem.getMessage (), repositoryProblem); } // ... return loadedUser; }

4.4. Zhrnutie

Náš druhý prístup je takmer totožný s jednoduchým prístupom, ktorý sme predstavili ako prvý. Implementáciou našich vlastných AuthenticationProvider a CustomAuthenticationToken, nemuseli sme prispôsobovať svoje pole používateľského mena logikou vlastnej analýzy.

5. Záver

V tomto článku sme implementovali prihlásenie do formulára v Spring Security, ktoré využívalo ďalšie prihlasovacie pole. Urobili sme to dvoma rôznymi spôsobmi:

  • V našom jednoduchom prístupe sme minimalizovali množstvo kódu, ktoré sme potrebovali na zápis. Dokázali sme opätovné použitie Poskytovateľ DaoAuthenticationProvider a UsernamePasswordAuthentication úpravou používateľského mena s vlastnou logikou syntaktickej analýzy
  • V našom prispôsobenejšom prístupe sme poskytli podporu vlastného poľa od rozširovanie AbstractUserDetailsAuthenticationProvider a poskytovanie našich vlastných CustomUserDetailsService s CustomAuthenticationToken

Všetky zdrojové kódy nájdete ako vždy na serveri GitHub.