Spring Security versus Apache Shiro

1. Overzicht

Beveiliging is een primaire zorg in de wereld van applicatie-ontwikkeling, vooral op het gebied van zakelijke web- en mobiele applicaties.

In deze korte tutorial, we zullen twee populaire Java-beveiligingsframeworks vergelijken: Apache Shiro en Spring Security.

2. Een beetje achtergrond

Apache Shiro werd in 2004 geboren als JSecurity en werd in 2008 geaccepteerd door de Apache Foundation. Tot op heden heeft het veel releases gezien, de laatste op het moment van schrijven is 1.5.3.

Spring Security begon als Acegi in 2003 en werd opgenomen in het Spring Framework met de eerste publieke release in 2008. Sinds de oprichting heeft het verschillende iteraties doorlopen en de huidige GA-versie is op het moment van schrijven 5.3.2.

Beide technologieën bieden ondersteuning voor authenticatie en autorisatie, samen met oplossingen voor cryptografie en sessiebeheer. Bovendien biedt Spring Security eersteklas bescherming tegen aanvallen zoals CSRF en sessie fixatie.

In de volgende secties zullen we voorbeelden zien van hoe de twee technologieën omgaan met authenticatie en autorisatie. Om het simpel te houden, gebruiken we standaard op Spring Boot gebaseerde MVC-applicaties met FreeMarker-sjablonen.

3. Apache Shiro configureren

Laten we om te beginnen eens kijken hoe configuraties verschillen tussen de twee frameworks.

3.1. Afhankelijkheden van Maven

Omdat we Shiro gebruiken in een Spring Boot-app, hebben we de starter en de shiro-core module:

 org.apache.shiro shiro-spring-boot-web-starter 1.5.3 org.apache.shiro shiro-core 1.5.3 

De nieuwste versies zijn te vinden op Maven Central.

3.2. Een rijk creëren

Om gebruikers aan te geven met hun rollen en permissies in het geheugen, moeten we een rijk maken dat Shiro's uitbreidt JdbcRealm. We definiëren twee gebruikers - Tom en Jerry, met respectievelijk de rollen USER en ADMIN:

openbare klasse CustomRealm breidt JdbcRealm uit {privékaartreferenties = nieuwe HashMap (); privékaartrollen = nieuwe HashMap (); private Map permissions = nieuwe HashMap (); {credentials.put ("Tom", "wachtwoord"); credentials.put ("Jerry", "wachtwoord"); rollen.put ("Jerry", nieuwe HashSet (Arrays.asList ("ADMIN"))); rollen.put ("Tom", nieuwe HashSet (Arrays.asList ("USER"))); permissions.put ("ADMIN", nieuwe HashSet (Arrays.asList ("READ", "WRITE"))); permissions.put ("USER", nieuwe HashSet (Arrays.asList ("READ"))); }}

Om het ophalen van deze authenticatie en autorisatie mogelijk te maken, moeten we vervolgens een aantal methoden negeren:

@Override beschermd AuthenticationInfo doGetAuthenticationInfo (AuthenticationToken-token) genereert AuthenticationException {UsernamePasswordToken userToken = (UsernamePasswordToken) -token; if (userToken.getUsername () == null || userToken.getUsername (). isEmpty () ||! credentials.containsKey (userToken.getUsername ())) {gooi nieuwe UnknownAccountException ("Gebruiker bestaat niet"); } retourneer nieuwe SimpleAuthenticationInfo (userToken.getUsername (), credentials.get (userToken.getUsername ()), getName ()); } @Override beschermde AuthorizationInfo doGetAuthorizationInfo (PrincipalCollection-principals) {Set rollen = nieuwe HashSet (); Stel permissies in = nieuwe HashSet (); voor (Object gebruiker: principals) {probeer {rollen.addAll (getRoleNamesForUser (null, (String) gebruiker)); permissions.addAll (getPermissions (null, null, rollen)); } catch (SQLException e) {logger.error (e.getMessage ()); }} SimpleAuthorizationInfo authInfo = nieuwe SimpleAuthorizationInfo (rollen); authInfo.setStringPermissions (machtigingen); retourneer authInfo; } 

De methode doGetAuthorizationInfo gebruikt een aantal hulpmethoden om de rollen en machtigingen van de gebruiker te krijgen:

@Override protected Set getRoleNamesForUser (Connection conn, String gebruikersnaam) gooit SQLException {if (! Rollen.containsKey (gebruikersnaam)) {gooi nieuwe SQLException ("Gebruiker bestaat niet"); } return rolls.get (gebruikersnaam); } @Override protected Set getPermissions (Connection conn, String gebruikersnaam, Collection rollen) gooit SQLException {Set userPermissions = new HashSet (); voor (String rol: rollen) {if (! permissions.containsKey (role)) {throw new SQLException ("Rol bestaat niet"); } userPermissions.addAll (permissions.get (rol)); } return userPermissions; } 

Vervolgens moeten we dit opnemen CustomRealm als boon in onze Boot-applicatie:

@Bean public Realm customRealm () {retourneer nieuwe CustomRealm (); }

Om authenticatie voor onze eindpunten te configureren, hebben we bovendien nog een bean nodig:

@Bean openbaar ShiroFilterChainDefinition shiroFilterChainDefinition () {DefaultShiroFilterChainDefinition filter = nieuw DefaultShiroFilterChainDefinition (); filter.addPathDefinition ("/ home", "authc"); filter.addPathDefinition ("/ **", "anon"); retourfilter; }

Hier, met behulp van een DefaultShiroFilterChainDefinition we hebben bijvoorbeeld gespecificeerd dat onze /huis endpoint is alleen toegankelijk voor geauthenticeerde gebruikers.

Dat is alles wat we nodig hebben voor de configuratie, Shiro doet de rest voor ons.

4. Spring Security configureren

Laten we nu eens kijken hoe we hetzelfde kunnen bereiken in het voorjaar.

4.1. Afhankelijkheden van Maven

Ten eerste de afhankelijkheden:

 org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-security 

De nieuwste versies zijn te vinden op Maven Central.

4.2. Configuratieklasse

Vervolgens definiëren we onze Spring Security-configuratie in een klas SecurityConfig, verlengend WebSecurityConfigurerAdapter:

@EnableWebSecurity public class SecurityConfig breidt WebSecurityConfigurerAdapter uit {@Override protected void configure (HttpSecurity http) genereert uitzondering {http .authorizeRequests (autoriseren -> autoriseren .antMatchers ("/ index", "/ login"). AllowAll () .antMatchers ("/ home "," / logout "). geverifieerd () .antMatchers (" / admin / ** "). hasRole (" ADMIN ")) .formLogin (formLogin -> formLogin .loginPage (" / login ") .failureUrl (" /login fout")); } @Override protected void configure (AuthenticationManagerBuilder auth) genereert Uitzondering {auth.inMemoryAuthentication () .withUser ("Jerry") .password (passwordEncoder (). Encode ("password")) .authorities ("READ", "WRITE") .roles ("ADMIN") .en () .withUser ("Tom") .password (passwordEncoder (). encode ("wachtwoord")) .authorities ("READ") .roles ("USER"); } @Bean public PasswordEncoder passwordEncoder () {retourneer nieuwe BCryptPasswordEncoder (); }} 

Zoals we kunnen zien, hebben we een AuthenticationManagerBuilder bezwaar maken om onze gebruikers te verklaren met hun rollen en bevoegdheden. Bovendien hebben we de wachtwoorden gecodeerd met een BCryptPasswordEncoder.

Spring Security voorziet ons ook van zijn HttpSecurity object voor verdere configuraties. Voor ons voorbeeld hebben we toegestaan:

  • iedereen om toegang te krijgen tot onze inhoudsopgave en Log in Pagina's
  • alleen geauthenticeerde gebruikers om het huis pagina en uitloggen
  • alleen gebruikers met ADMIN-rol om toegang te krijgen tot het beheerder Pagina's

We hebben ook ondersteuning gedefinieerd voor op formulieren gebaseerde authenticatie om gebruikers naar het Log in eindpunt. Als het inloggen mislukt, worden onze gebruikers doorgestuurd naar /login fout.

5. Controllers en eindpunten

Laten we nu eens kijken naar onze webcontrollertoewijzingen voor de twee applicaties. Hoewel ze dezelfde eindpunten zullen gebruiken, zullen sommige implementaties verschillen.

5.1. Eindpunten voor weergave van weergaven

Voor eindpunten die de weergave weergeven, zijn de implementaties hetzelfde:

@GetMapping ("/") public String index () {return "index"; } @GetMapping ("/ login") public String showLoginPage () {retourneer "login"; } @GetMapping ("/ home") openbare String getMeHome (modelmodel) {addUserAttributes (model); terug naar huis"; }

Zowel onze controllerimplementaties, Shiro als Spring Security, retourneren het index.ftl op het root-eindpunt, login.ftl op het login-eindpunt, en home.ftl op het startpunt.

De definitie van de methode addUserAttributes bij de /huis endpoint zal verschillen tussen de twee controllers. Deze methode onderzoekt de momenteel ingelogde gebruikersattributen.

Shiro biedt een SecurityUtils # getSubject om de huidige op te halen Onderwerpen, en zijn rollen en machtigingen:

private void addUserAttributes (Modelmodel) {Onderwerp currentUser = SecurityUtils.getSubject (); Tekenreeksmachtiging = ""; if (currentUser.hasRole ("ADMIN")) {model.addAttribute ("role", "ADMIN"); } else if (currentUser.hasRole ("USER")) {model.addAttribute ("role", "USER"); } if (currentUser.isPermitted ("LEZEN")) {toestemming = toestemming + "LEZEN"; } if (currentUser.isPermitted ("WRITE")) {toestemming = toestemming + "WRITE"; } model.addAttribute ("gebruikersnaam", currentUser.getPrincipal ()); model.addAttribute ("toestemming", toestemming); }

Aan de andere kant biedt Spring Security een Authenticatie object van zijn SecurityContextHolder'S context voor dit doel:

private void addUserAttributes (Modelmodel) {Authentication auth = SecurityContextHolder.getContext (). getAuthentication (); if (auth! = null &&! auth.getClass (). equals (AnonymousAuthenticationToken.class)) {User user = (User) auth.getPrincipal (); model.addAttribute ("gebruikersnaam", user.getUsername ()); Verzamelingsautoriteiten = user.getAuthorities (); voor (GrantedAuthority autoriteit: autoriteiten) {if (autoriteit.getAuthority (). bevat ("USER")) {model.addAttribute ("rol", "USER"); model.addAttribute ("permissions", "READ"); } else if (autoriteit.getAuthority (). bevat ("ADMIN")) {model.addAttribute ("rol", "ADMIN"); model.addAttribute ("permissies", "LEES SCHRIJVEN"); }}}}

5.2. POST Login-eindpunt

In Shiro brengen we de inloggegevens die de gebruiker invoert in een POJO in kaart:

openbare klasse UserCredentials {private String gebruikersnaam; privé String-wachtwoord; // getters en setters}

Dan maken we een GebruikersnaamPasswordToken om de gebruiker te loggen, of Onderwerpen, in:

@PostMapping ("/ login") openbare String doLogin (HttpServletRequest req, UserCredentials referenties, RedirectAttributes attr) {Subject subject = SecurityUtils.getSubject (); if (! subject.isAuthenticated ()) {UsernamePasswordToken token = nieuwe UsernamePasswordToken (credentials.getUsername (), credentials.getPassword ()); probeer {subject.login (token); } catch (AuthenticationException ae) {logger.error (ae.getMessage ()); attr.addFlashAttribute ("error", "Ongeldige referenties"); retourneer "redirect: / login"; }} retourneer "redirect: / home"; }

Aan de kant van Spring Security is dit slechts een kwestie van omleiden naar de startpagina. Het inlogproces van Spring, afgehandeld door zijn GebruikersnaamPasswordAuthenticationFilter, is transparant voor ons:

@PostMapping ("/ login") public String doLogin (HttpServletRequest req) {return "redirect: / home"; }

5.3. Eindpunt alleen voor beheerders

Laten we nu eens kijken naar een scenario waarin we op rollen gebaseerde toegang moeten uitvoeren. Laten we zeggen dat we een /beheerder eindpunt, waartoe toegang alleen mag worden toegestaan ​​voor de ADMIN-rol.

Laten we eens kijken hoe we dit in Shiro kunnen doen:

@GetMapping ("/ admin") openbare String adminOnly (ModelMap modelMap) {addUserAttributes (modelMap); Onderwerp currentUser = SecurityUtils.getSubject (); if (currentUser.hasRole ("ADMIN")) {modelMap.addAttribute ("adminContent", "alleen admin kan dit zien"); } terug naar huis"; }

Hier hebben we de momenteel aangemelde gebruiker geëxtraheerd, gecontroleerd of deze de ADMIN-rol heeft en dienovereenkomstig inhoud toegevoegd.

In Spring Security is het niet nodig om de rol programmatisch te controleren, we hebben al gedefinieerd wie dit eindpunt kan bereiken in ons SecurityConfig. Dus nu is het gewoon een kwestie van bedrijfslogica toevoegen:

@GetMapping ("/ admin") openbare String adminOnly (HttpServletRequest req, modelmodel) {addUserAttributes (model); model.addAttribute ("adminContent", "alleen admin kan dit zien"); terug naar huis"; }

5.4. Eindpunt uitloggen

Laten we tot slot het uitlog-eindpunt implementeren.

In Shiro bellen we gewoon Onderwerp # uitloggen:

@PostMapping ("/ logout") public String logout () {Subject subject = SecurityUtils.getSubject (); subject.logout (); retourneer "redirect: /"; }

Voor Spring hebben we geen mapping gedefinieerd voor uitloggen. In dit geval treedt het standaard afmeldingsmechanisme in werking, dat automatisch wordt toegepast sinds we uitgebreid zijn WebSecurityConfigurerAdapter in onze configuratie.

6. Apache Shiro versus Spring Security

Nu we naar de implementatieverschillen hebben gekeken, gaan we eens kijken naar een paar andere aspecten.

In termen van gemeenschapsondersteuning, het Spring Framework heeft over het algemeen een enorme community van ontwikkelaars, actief betrokken bij de ontwikkeling en het gebruik ervan. Aangezien Spring Security deel uitmaakt van de paraplu, moet het dezelfde voordelen genieten. Shiro, hoewel populair, heeft niet zo'n gigantische steun.

Qua documentatie is Spring wederom de winnaar.

Er is echter een beetje een leercurve verbonden aan Spring Security. Shiro daarentegen is gemakkelijk te begrijpen. Voor desktoptoepassingen, configuratie via shiro.ini is des te gemakkelijker.

Maar nogmaals, zoals we zagen in onze voorbeeldfragmenten, Spring Security houdt de bedrijfslogica en beveiliging uitstekend in standscheiden en biedt echt zekerheid als een transversale zorg.

7. Conclusie

In deze tutorial we vergeleken Apache Shiro met Spring Security.

We hebben zojuist de oppervlakte van wat deze frameworks te bieden hebben, begraasd en er is nog veel te ontdekken. Er zijn nogal wat alternatieven zoals JAAS en OACC. Toch, met zijn voordelen, lijkt Spring Security op dit punt te winnen.

Zoals altijd is de broncode beschikbaar op GitHub.


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