Extra inlogvelden met Spring Security
1. Inleiding
In dit artikel implementeren we een aangepast authenticatiescenario met Spring Security door het toevoegen van een extra veld aan het standaard inlogformulier.
We gaan ons concentreren op 2 verschillende benaderingen, om de veelzijdigheid van het framework en de flexibele manieren waarop we het kunnen gebruiken te laten zien.
Onze eerste benadering wordt een eenvoudige oplossing die zich richt op hergebruik van bestaande kernimplementaties van Spring Security.
Onze tweede benadering zal een meer op maat gemaakte oplossing zijn die mogelijk geschikter is voor geavanceerde gebruiksscenario's.
We bouwen voort op de concepten die zijn besproken in onze vorige artikelen over inloggen met Spring Security.
2. Maven-instellingen
We zullen Spring Boot-starters gebruiken om ons project op te starten en alle noodzakelijke afhankelijkheden in te voeren.
De setup die we zullen gebruiken vereist een ouderverklaring, webstarter en beveiligingsstarter; we zullen ook thymeleaf toevoegen:
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
De meest recente versie van Spring Boot-beveiligingsstarter is te vinden op Maven Central.
3. Eenvoudige projectconfiguratie
In onze eerste benadering zullen we ons concentreren op het hergebruiken van implementaties die worden geleverd door Spring Security. In het bijzonder zullen we hergebruiken DaoAuthenticationProvider en GebruikersnaamPasswordToken aangezien ze "out-of-the-box" bestaan.
De belangrijkste componenten zijn:
- SimpleAuthenticationFilter – een extensie van GebruikersnaamPasswordAuthenticationFilter
- SimpleUserDetailsService – een implementatie van UserDetailsService
- Onseh – een uitbreiding van de Gebruiker klasse aangeboden door Spring Security die onze extra declareert domein veld-
- SecurityConfig – onze Spring Security-configuratie die onze SimpleAuthenticationFilter in de filterketen, verklaart beveiligingsregels en koppelt afhankelijkheden aan
- login.html– een inlogpagina die de gebruikersnaam, wachtwoord, en domein
3.1. Eenvoudig authenticatiefilter
In onze SimpleAuthenticationFilter, de velden voor domein en gebruikersnaam worden uit het verzoek gehaald. We voegen deze waarden samen en gebruiken ze om een instantie van te maken GebruikersnaamPasswordAuthenticationToken.
Het token wordt vervolgens doorgegeven aan de AuthenticationProvider voor authenticatie:
openbare klasse SimpleAuthenticationFilter breidt UsernamePasswordAuthenticationFilter {@Override openbare AuthenticatiepogingAuthenticatie (HttpServletRequest-verzoek, HttpServletResponse-antwoord) uit met AuthenticationException {// ... UsernamePasswordAuthenticationToken authRequest = getAuthRequest (verzoek); setDetails (verzoek, authRequest); retourneer this.getAuthenticationManager () .authenticate (authRequest); } private UsernamePasswordAuthenticationToken getAuthRequest (verzoek HttpServletRequest) {String gebruikersnaam = getUsername (verzoek); String wachtwoord = getPassword (verzoek); String domein = getDomain (verzoek); // ... String gebruikersnaamDomain = String.format ("% s% s% s", gebruikersnaam.trim (), String.valueOf (Character.LINE_SEPARATOR), domein); retourneer nieuwe UsernamePasswordAuthenticationToken (gebruikersnaamDomein, wachtwoord); } // andere methodes }
3.2. Gemakkelijk Gebruikersdetails Onderhoud
De UserDetailsService contract definieert een enkele methode genaamd loadUserByUsername. Onze implementatie haalt het gebruikersnaam en domein. De waarden worden vervolgens doorgegeven aan onze UserRepository om het Gebruiker:
openbare klasse SimpleUserDetailsService implementeert UserDetailsService {// ... @Override openbare UserDetails loadUserByUsername (String gebruikersnaam) genereert UsernameNotFoundException {String [] usernameAndDomain = StringUtils.split (gebruikersnaam, String.valueOf (Character.LINE_SEPARATOR)); if (usernameAndDomain == null || usernameAndDomain.length! = 2) {throw new UsernameNotFoundException ("Gebruikersnaam en domein moeten worden opgegeven"); } Gebruiker user = userRepository.findUser (gebruikersnaamAndDomain [0], gebruikersnaamAndDomain [1]); if (user == null) {throw new UsernameNotFoundException (String.format ("Gebruikersnaam niet gevonden voor domein, gebruikersnaam =% s, domein =% s", gebruikersnaamAndDomain [0], gebruikersnaamAndDomain [1])); } terugkerende gebruiker; }}
3.3. Spring-beveiligingsconfiguratie
Onze setup verschilt van een standaard Spring Security-configuratie omdat we voegen onze SimpleAuthenticationFilter in de filterketen vóór de standaard met een oproep naar addFilterBefore:
@Override protected void configure (HttpSecurity http) genereert uitzondering {http .addFilterBefore (authenticationFilter (), UsernamePasswordAuthenticationFilter.class) .authorizeRequests () .antMatchers ("/ css / **", "/ index"). AllowAll () .antMatchers ("/ gebruiker / **"). geverifieerd (). en () .formLogin (). loginPage ("/ login"). en () .logout () .logoutUrl ("/ logout"); }
We kunnen de verstrekte gebruiken DaoAuthenticationProvider omdat we het configureren met onze SimpleUserDetailsService. Herhaal dat onze SimpleUserDetailsService weet hoe hij onze gebruikersnaam en domein velden en retourneer het juiste Gebruiker te gebruiken bij authenticatie:
openbare AuthenticationProvider authProvider () {DaoAuthenticationProvider-provider = nieuwe DaoAuthenticationProvider (); provider.setUserDetailsService (userDetailsService); provider.setPasswordEncoder (wachtwoordEncoder ()); terugkeerprovider; }
Omdat we een SimpleAuthenticationFilter, we configureren onze eigen AuthenticationFailureHandler om ervoor te zorgen dat mislukte inlogpogingen correct worden afgehandeld:
openbaar SimpleAuthenticationFilter authenticationFilter () genereert uitzondering {SimpleAuthenticationFilter filter = nieuw SimpleAuthenticationFilter (); filter.setAuthenticationManager (authenticationManagerBean ()); filter.setAuthenticationFailureHandler (failureHandler ()); retourfilter; }
3.4. Login pagina
De inlogpagina die we gebruiken, verzamelt onze aanvullende domein veld dat wordt geëxtraheerd door onze SimpleAuthenticationFilter:
Log in alstublieft
Voorbeeld: gebruiker / domein / wachtwoord
Ongeldige gebruiker, wachtwoord of domein
Gebruikersnaam
Domein
Wachtwoord
Log inTerug naar de startpagina
Wanneer we de applicatie starten en toegang krijgen tot de context op // localhost: 8081, zien we een link om toegang te krijgen tot een beveiligde pagina. Als u op de link klikt, wordt de inlogpagina weergegeven. Zoals verwacht, we zien het extra domeinveld:
3.5. Samenvatting
In ons eerste voorbeeld konden we hergebruiken DaoAuthenticationProvider en GebruikersnaamPasswordAuthenticationToken door het veld gebruikersnaam te "faken".
Het resultaat was dat we dat konden ondersteuning toevoegen voor een extra inlogveld met een minimale hoeveelheid configuratie en extra code.
4. Aangepaste projectinstellingen
Onze tweede benadering zal sterk lijken op de eerste, maar kan geschikter zijn voor gevallen van niet-triviaal gebruik.
De belangrijkste componenten van onze tweede benadering zijn:
- CustomAuthenticationFilter – een extensie van GebruikersnaamPasswordAuthenticationFilter
- CustomUserDetailsService – een aangepaste interface die een loadUserbyUsernameAndDomain methode
- CustomUserDetailsServiceImpl – een implementatie van onze CustomUserDetailsService
- CustomUserDetailsAuthenticationProvider – een extensie van AbstractUserDetailsAuthenticationProvider
- CustomAuthenticationToken – een extensie van GebruikersnaamPasswordAuthenticationToken
- Onseh – een uitbreiding van de Gebruiker klasse aangeboden door Spring Security die onze extra declareert domein veld-
- SecurityConfig – onze Spring Security-configuratie die onze CustomAuthenticationFilter in de filterketen, verklaart beveiligingsregels en koppelt afhankelijkheden aan
- login.html– de inlogpagina die het gebruikersnaam, wachtwoord, en domein
4.1. Aangepast verificatiefilter
In onze CustomAuthenticationFilter, wij extraheer de gebruikersnaam, het wachtwoord en de domeinvelden uit het verzoek. Deze waarden worden gebruikt om een instantie van onze CustomAuthenticationToken die wordt doorgegeven aan de AuthenticationProvider voor authenticatie:
openbare klasse CustomAuthenticationFilter breidt uit UsernamePasswordAuthenticationFilter {openbare statische laatste String SPRING_SECURITY_FORM_DOMAIN_KEY = "domein"; @Override openbare authenticatie pogingAuthenticatie (HttpServletRequest verzoek, HttpServletResponse antwoord) gooit AuthenticationException {// ... CustomAuthenticationToken authRequest = getAuthRequest (verzoek); setDetails (verzoek, authRequest); retourneer this.getAuthenticationManager (). authenticate (authRequest); } private CustomAuthenticationToken getAuthRequest (verzoek HttpServletRequest) {String gebruikersnaam = getAuthRequest (verzoek); String wachtwoord = getPassword (verzoek); String domein = getDomain (verzoek); // ... retourneer nieuwe CustomAuthenticationToken (gebruikersnaam, wachtwoord, domein); }
4.2. Op maat Gebruikersdetails Onderhoud
Onze CustomUserDetailsService contract definieert een enkele methode genaamd loadUserByUsernameAndDomain.
De CustomUserDetailsServiceImpl klasse die we creëren, voert eenvoudigweg het contract uit en delegeert naar onze CustomUserRepository om het Gebruiker:
openbare UserDetails loadUserByUsernameAndDomain (String gebruikersnaam, String domein) gooit UsernameNotFoundException {if (StringUtils.isAnyBlank (gebruikersnaam, domein)) {throw nieuwe UsernameNotFoundException ("Gebruikersnaam en domein moeten worden opgegeven"); } Gebruiker user = userRepository.findUser (gebruikersnaam, domein); if (user == null) {throw new UsernameNotFoundException (String.format ("Gebruikersnaam niet gevonden voor domein, gebruikersnaam =% s, domein =% s", gebruikersnaam, domein)); } terugkerende gebruiker; }
4.3. Op maat UserDetailsAuthenticationProvider
Onze CustomUserDetailsAuthenticationProvider strekt zich uit AbstractUserDetailsAuthenticationProvider en afgevaardigden naar onze CustomUserDetailService om het Gebruiker. Het belangrijkste kenmerk van deze klasse is de implementatie van de retrieveUser methode.
Merk op dat we het authenticatietoken naar onze CustomAuthenticationToken voor toegang tot ons aangepaste veld:
@Override beschermde UserDetails retrieveUser (String gebruikersnaam, UsernamePasswordAuthenticationToken authentication) genereert AuthenticationException {CustomAuthenticationToken auth = (CustomAuthenticationToken) authenticatie; UserDetails loadedUser; probeer {loadedUser = this.userDetailsService .loadUserByUsernameAndDomain (auth.getPrincipal () .toString (), auth.getDomain ()); } catch (UsernameNotFoundException notFound) {if (authentication.getCredentials ()! = null) {String gepresenteerdePassword = authentication.getCredentials () .toString (); passwordEncoder.matches (gepresenteerdPassword, userNotFoundEncodedPassword); } gooien notFound; } catch (uitzondering repositoryProblem) {gooi nieuwe InternalAuthenticationServiceException (repositoryProblem.getMessage (), repositoryProblem); } // ... return loadedUser; }
4.4. Samenvatting
Onze tweede benadering is bijna identiek aan de eenvoudige benadering die we als eerste hebben gepresenteerd. Door onze eigen te implementeren AuthenticationProvider en CustomAuthenticationToken, hebben we voorkomen dat we ons gebruikersnaamveld moesten aanpassen met aangepaste parseerlogica.
5. Conclusie
In dit artikel hebben we een formulier login geïmplementeerd in Spring Security die gebruik maakte van een extra login veld. We hebben dit op 2 verschillende manieren gedaan:
- Door onze eenvoudige aanpak hebben we de hoeveelheid code die we moesten schrijven tot een minimum beperkt. We waren in staat om hergebruik DaoAuthenticationProvider en UsernamePasswordAuthentication door de gebruikersnaam aan te passen met aangepaste ontledingslogica
- In onze meer op maat gemaakte aanpak hebben we veldondersteuning op maat geleverd door het uitbreiden van AbstractUserDetailsAuthenticationProvider en het verstrekken van onze eigen CustomUserDetailsService met een CustomAuthenticationToken
Zoals altijd is alle broncode te vinden op GitHub.