Inleiding tot Apache Shiro

1. Overzicht

In dit artikel kijken we naar Apache Shiro, een veelzijdig Java-beveiligingsraamwerk.

Het framework is in hoge mate aanpasbaar en modulair, aangezien het authenticatie, autorisatie, cryptografie en sessiebeheer biedt.

2. Afhankelijkheid

Apache Shiro heeft veel modules. In deze zelfstudie gebruiken we echter de shiro-core alleen artefact.

Laten we het toevoegen aan ons pom.xml:

 org.apache.shiro shiro-core 1.4.0 

De nieuwste versie van de Apache Shiro-modules is te vinden op Maven Central.

3. Security Manager configureren

De Beveiligingsmanager is het middelpunt van het raamwerk van Apache Shiro. Toepassingen hebben meestal een enkele instantie die wordt uitgevoerd.

In deze tutorial verkennen we het framework in een desktopomgeving. Om het framework te configureren, moeten we een shiro.ini bestand in de bronnenmap met de volgende inhoud:

[gebruikers] gebruiker = wachtwoord, admin gebruiker2 = wachtwoord2, editor user3 = wachtwoord3, auteur [rollen] admin = * editor = artikelen: * auteur = artikelen: opstellen, artikelen: opslaan

De [gebruikers] sectie van de shiro.ini config-bestand definieert de gebruikersreferenties die worden herkend door het Beveiligingsmanager. Het formaat is: principal (gebruikersnaam) = wachtwoord, rol1, rol2,…, rol.

De rollen en de bijbehorende machtigingen worden gedeclareerd in het [rollen] sectie. De beheerder rol krijgt toestemming en toegang tot elk onderdeel van de applicatie. Dit wordt aangegeven door het jokerteken (*) symbool.

De editor role heeft alle rechten die zijn gekoppeld aan Lidwoord Terwijl de schrijver rol kan alleen componeren en sparen een artikel.

De Beveiligingsmanager wordt gebruikt om het SecurityUtils klasse. Van de SecurityUtils we kunnen de interactie van de huidige gebruiker met het systeem verkrijgen en authenticatie- en autorisatiebewerkingen uitvoeren.

Laten we de IniRealm om onze gebruikers- en roldefinities te laden vanuit het shiro.ini bestand en gebruik het vervolgens om het DefaultSecurityManager voorwerp:

IniRealm iniRealm = nieuwe IniRealm ("classpath: shiro.ini"); SecurityManager securityManager = nieuwe DefaultSecurityManager (iniRealm); SecurityUtils.setSecurityManager (securityManager); Onderwerp currentUser = SecurityUtils.getSubject ();

Nu we een Beveiligingsmanager die op de hoogte is van gebruikersreferenties en rollen die zijn gedefinieerd in het shiro.ini bestand, gaan we verder met gebruikersauthenticatie en autorisatie.

4. Authenticatie

In de terminologie van Apache Shiro, a Onderwerpen is elke entiteit die interactie heeft met het systeem. Het kan een mens, een script of een REST-client zijn.

Roeping SecurityUtils.getSubject () geeft een instantie terug van de huidige Onderwerpen, dat is de huidige gebruiker.

Nu we de huidige gebruiker Object, we kunnen authenticatie uitvoeren op de verstrekte inloggegevens:

if (! currentUser.isAuthenticated ()) {UsernamePasswordToken token = nieuwe UsernamePasswordToken ("gebruiker", "wachtwoord"); token.setRememberMe (true); probeer {currentUser.login (token); } catch (UnknownAccountException vae) {log.error ("Gebruikersnaam niet gevonden!", vae); } catch (IncorrectCredentialsException ice) {log.error ("Ongeldige referenties!", ice); } catch (LockedAccountException lae) {log.error ("Uw account is vergrendeld!", lae); } catch (AuthenticationException ae) {log.error ("Onverwachte fout!", ae); }}

Eerst controleren we of de huidige gebruiker nog niet is geauthenticeerd. Vervolgens maken we een authenticatietoken met de principal van de gebruiker (gebruikersnaam) en referentie (wachtwoord).

Vervolgens proberen we in te loggen met het token. Als de verstrekte inloggegevens correct zijn, zou alles goed moeten gaan.

Er zijn verschillende uitzonderingen voor verschillende gevallen. Het is ook mogelijk om een ​​aangepaste uitzondering te genereren die beter past bij de toepassingsvereisten. Dit kan worden gedaan door de AccountException klasse.

5. Autorisatie

Authenticatie probeert de identiteit van een gebruiker te valideren, terwijl autorisatie de toegang tot bepaalde bronnen in het systeem probeert te regelen.

Bedenk dat we een of meer rollen toewijzen aan elke gebruiker die we hebben gemaakt in het shiro.ini het dossier. Verder definiëren we in de sectie rollen verschillende machtigingen of toegangsniveaus voor elke rol.

Laten we nu eens kijken hoe we dat in onze applicatie kunnen gebruiken om gebruikerstoegangscontrole af te dwingen.

In de shiro.ini bestand, geven we de admin volledige toegang tot elk deel van het systeem.

De redacteur heeft volledige toegang tot elke bron / bewerking met betrekking tot Lidwoord, en een auteur is beperkt tot alleen maar schrijven en opslaan Lidwoord enkel en alleen.

Laten we de huidige gebruiker verwelkomen op basis van rol:

if (currentUser.hasRole ("admin")) {log.info ("Welcome Admin"); } else if (currentUser.hasRole ("editor")) {log.info ("Welkom, redacteur!"); } else if (currentUser.hasRole ("auteur")) {log.info ("Welkom, auteur"); } else {log.info ("Welkom, gast"); }

Laten we nu eens kijken wat de huidige gebruiker in het systeem mag doen:

if (currentUser.isPermitted ("artikelen: opstellen")) {log.info ("U kunt een artikel opstellen"); } else {log.info ("Het is niet toegestaan ​​een artikel samen te stellen!"); } if (currentUser.isPermitted ("artikelen: opslaan")) {log.info ("U kunt artikelen opslaan"); } else {log.info ("U kunt geen artikelen opslaan"); } if (currentUser.isPermitted ("artikelen: publiceren")) {log.info ("U kunt artikelen publiceren"); } else {log.info ("U kunt geen artikelen publiceren"); }

6. Realm-configuratie

In echte toepassingen hebben we een manier nodig om gebruikersreferenties uit een database te halen in plaats van uit het shiro.ini het dossier. Dit is waar het concept van Realm in het spel komt.

In de terminologie van Apache Shiro is een Realm een ​​DAO die verwijst naar een opslag met gebruikersreferenties die nodig zijn voor authenticatie en autorisatie.

Om een ​​realm te creëren, hoeven we alleen het Rijk koppel. Dat kan vervelend zijn; het framework wordt echter geleverd met standaardimplementaties waaruit we kunnen subclassificeren. Een van deze implementaties is JdbcRealm.

We maken een aangepaste realm-implementatie die zich uitbreidt JdbcRealm class en overschrijft de volgende methoden: doGetAuthenticationInfo (), doGetAuthorizationInfo (), getRoleNamesForUser () en getPermissions ().

Laten we een realm maken door het JdbcRealm klasse:

openbare klasse MyCustomRealm breidt JdbcRealm {// ...}

Eenvoudigheidshalve gebruiken we java.util.Map om een ​​database te simuleren:

persoonlijke kaartgegevens = nieuwe HashMap (); privékaart rollen = nieuwe HashMap (); privékaart perm = nieuwe HashMap (); {credentials.put ("gebruiker", "wachtwoord"); credentials.put ("gebruiker2", "wachtwoord2"); credentials.put ("user3", "password3"); rollen.put ("gebruiker", nieuwe HashSet (Arrays.asList ("admin"))); rollen.put ("gebruiker2", nieuwe HashSet (Arrays.asList ("editor"))); rollen.put ("gebruiker3", nieuwe HashSet (Arrays.asList ("auteur"))); perm.put ("admin", nieuwe HashSet (Arrays.asList ("*"))); perm.put ("editor", nieuwe HashSet (Arrays.asList ("artikelen: *"))); perm.put ("auteur", nieuwe HashSet (Arrays.asList ("artikelen: opstellen", "artikelen: opslaan"))); }

Laten we doorgaan en het doGetAuthenticationInfo ():

protected AuthenticationInfo doGetAuthenticationInfo (AuthenticationToken-token) genereert AuthenticationException {UsernamePasswordToken uToken = (UsernamePasswordToken) -token; if (uToken.getUsername () == null || uToken.getUsername (). isEmpty () ||! credentials.containsKey (uToken.getUsername ())) {throw new UnknownAccountException ("gebruikersnaam niet gevonden!"); } retourneer nieuwe SimpleAuthenticationInfo (uToken.getUsername (), credentials.get (uToken.getUsername ()), getName ()); }

We hebben eerst de AuthenticationToken verstrekt aan GebruikersnaamPasswordToken. Van de uToken, we extraheren de gebruikersnaam (uToken.getUsername ()) en gebruik het om de gebruikersreferenties (wachtwoord) uit de database op te halen.

Als er geen record wordt gevonden, gooien we een UnknownAccountException, anders gebruiken we de referentie en gebruikersnaam om een SimpleAuthenticatioInfo object dat wordt geretourneerd door de methode.

Als de gebruikersreferentie is gehasht met een salt, moeten we een SimpleAuthenticationInfo met het bijbehorende zout:

retourneer nieuwe SimpleAuthenticationInfo (uToken.getUsername (), credentials.get (uToken.getUsername ()), ByteSource.Util.bytes ("salt"), getName ());

We moeten ook het doGetAuthorizationInfo (), net zoals getRoleNamesForUser () en getPermissions ().

Laten we tot slot de aangepaste realm aansluiten op het beveiligingsmanager. Het enige wat we hoeven te doen is het IniRealm hierboven met onze aangepaste realm, en geef deze door aan de DefaultSecurityManager‘S constructor:

Realm realm = nieuwe MyCustomRealm (); SecurityManager securityManager = nieuwe DefaultSecurityManager (realm);

Elk ander deel van de code is hetzelfde als voorheen. Dit is alles wat we nodig hebben om het beveiligingsmanager met een aangepaste realm op de juiste manier.

Nu is de vraag: hoe komt het framework overeen met de referenties?

Standaard is het JdbcRealm gebruikt de SimpleCredentialsMatcher, die alleen controleert op gelijkheid door de referenties in het AuthenticationToken en de AuthenticatieInfo.

Als we onze wachtwoorden hashen, moeten we het framework informeren om een HashedCredentialsMatcher in plaats daarvan. De INI-configuraties voor realms met gehashte wachtwoorden zijn hier te vinden.

7. Uitloggen

Nu we de gebruiker hebben geverifieerd, is het tijd om uitloggen te implementeren. Dat wordt eenvoudig gedaan door een enkele methode aan te roepen - die de gebruikerssessie ongeldig maakt en de gebruiker uitlogt:

currentUser.logout ();

8. Sessiebeheer

Het framework wordt natuurlijk geleverd met het sessiebeheersysteem. Indien gebruikt in een webomgeving, wordt standaard de HttpSession implementatie.

Voor een zelfstandige applicatie gebruikt het zijn enterprise session management-systeem. Het voordeel is dat u zelfs in een desktopomgeving een sessieobject kunt gebruiken zoals u zou doen in een typische webomgeving.

Laten we een snel voorbeeld bekijken en communiceren met de sessie van de huidige gebruiker:

Sessiesessie = currentUser.getSession (); session.setAttribute ("sleutel", "waarde"); Stringwaarde = (String) session.getAttribute ("key"); if (waarde.equals ("waarde")) {log.info ("De juiste waarde opgehaald! [" + waarde + "]"); }

9. Shiro voor een webapplicatie met lente

Tot dusver hebben we de basisstructuur van Apache Shiro geschetst en we hebben deze geïmplementeerd in een desktopomgeving. Laten we doorgaan door het framework te integreren in een Spring Boot-applicatie.

Merk op dat de belangrijkste focus hier Shiro is, niet de Spring-applicatie - we gaan die alleen gebruiken om een ​​eenvoudige voorbeeld-app van stroom te voorzien.

9.1. Afhankelijkheden

Eerst moeten we de Spring Boot-ouderafhankelijkheid toevoegen aan onze pom.xml:

 org.springframework.boot spring-boot-starter-parent 2.2.6.RELEASE 

Vervolgens moeten we de volgende afhankelijkheden hieraan toevoegen pom.xml het dossier:

 org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-freemarker org.apache.shiro shiro-spring-boot-web-starter $ {apache-shiro-core-version} 

9.2. Configuratie

Het toevoegen van de shiro-spring-boot-web-starter afhankelijkheid van onze pom.xml zal standaard enkele functies van de Apache Shiro-applicatie configureren, zoals de Beveiligingsmanager.

We moeten echter nog steeds het Rijk en Shiro-beveiligingsfilters. We zullen dezelfde aangepaste realm gebruiken die hierboven is gedefinieerd.

En dus laten we in de hoofdklasse waarin de Spring Boot-applicatie wordt uitgevoerd, het volgende toevoegen Boon definities:

@Bean public Realm realm () {retourneer nieuwe MyCustomRealm (); } @Bean openbaar ShiroFilterChainDefinition shiroFilterChainDefinition () {DefaultShiroFilterChainDefinition filter = nieuw DefaultShiroFilterChainDefinition (); filter.addPathDefinition ("/ secure", "authc"); filter.addPathDefinition ("/ **", "anon"); retourfilter; }

In de ShiroFilterChainDefinitionhebben we de authc filteren op / veilig pad en paste het anon filter op andere paden met behulp van het Ant-patroon.

Beide authc en anon Filters worden standaard meegeleverd voor webapplicaties. Andere standaardfilters zijn hier te vinden.

Als we het Rijk Boon, ShiroAutoConfiguration zal standaard een IniRealm implementatie die verwacht een shiro.ini bestand in src / main / resources of src / main / resources / META-INF.

Als we een ShiroFilterChainDefinition bean, beveiligt het framework alle paden en stelt het de inlog-URL in als login.jsp.

We kunnen deze standaard inlog-URL en andere standaardinstellingen wijzigen door de volgende vermeldingen toe te voegen aan onze application.properties:

shiro.loginUrl = / login shiro.successUrl = / beveiligde shiro.unauthorizedUrl = / login

Nu dat de authc filter is toegepast op / veilig, vereisen alle verzoeken naar die route een formulierauthenticatie.

9.3. Authenticatie en authorisatie

Laten we een ShiroSpringController met de volgende padtoewijzingen: /inhoudsopgave, /Log in Log uit en / veilig.

De Log in() methode is waar we daadwerkelijke gebruikersauthenticatie implementeren zoals hierboven beschreven. Als de authenticatie is gelukt, wordt de gebruiker omgeleid naar de beveiligde pagina:

Onderwerp subject = SecurityUtils.getSubject (); if (! subject.isAuthenticated ()) {UsernamePasswordToken token = nieuw UsernamePasswordToken (cred.getUsername (), cred.getPassword (), cred.isRememberMe ()); probeer {subject.login (token); } catch (AuthenticationException ae) {ae.printStackTrace (); attr.addFlashAttribute ("error", "Ongeldige referenties"); retourneer "redirect: / login"; }} retourneer "redirect: / secure";

En nu in de veilig () implementatie, de huidige gebruiker werd verkregen door een beroep te doen op de SecurityUtils.getSubject (). De rol en machtigingen van de gebruiker worden doorgegeven aan de beveiligde pagina, evenals de opdrachtgever van de gebruiker:

Onderwerp currentUser = SecurityUtils.getSubject (); String role = "", permissie = ""; if (currentUser.hasRole ("admin")) {rol = rol + "U bent een beheerder"; } else if (currentUser.hasRole ("editor")) {role = role + "Jij bent een editor"; } else if (currentUser.hasRole ("author")) {role = role + "Jij bent een auteur"; } if (currentUser.isPermitted ("artikelen: opstellen")) {toestemming = toestemming + "U kunt een artikel opstellen"; } anders {toestemming = toestemming + "Het is niet toegestaan ​​een artikel samen te stellen !,"; } if (currentUser.isPermitted ("artikelen: opslaan")) {toestemming = toestemming + "U kunt artikelen opslaan"; } anders {toestemming = toestemming + "\ nU kunt geen artikelen opslaan"; } if (currentUser.isPermitted ("artikelen: publiceren")) {toestemming = toestemming + "\ nU kunt artikelen publiceren"; } anders {toestemming = toestemming + "\ nU kunt geen artikelen publiceren"; } modelMap.addAttribute ("gebruikersnaam", currentUser.getPrincipal ()); modelMap.addAttribute ("toestemming", toestemming); modelMap.addAttribute ("rol", rol); retourneer "veilig";

En we zijn klaar. Dat is hoe we Apache Shiro kunnen integreren in een Spring Boot-applicatie.

Merk ook op dat het framework aanvullende annotaties biedt die naast filterketendefinities kunnen worden gebruikt om onze applicatie te beveiligen.

10. JEE-integratie

Het integreren van Apache Shiro in een JEE-applicatie is gewoon een kwestie van het configureren van het web.xml het dossier. Zoals gewoonlijk verwacht de configuratie shiro.ini om in het klassenpad te zijn. Een gedetailleerde voorbeeldconfiguratie is hier beschikbaar. Ook de JSP-tags zijn hier te vinden.

11. Conclusie

In deze tutorial hebben we gekeken naar de authenticatie- en autorisatiemechanismen van Apache Shiro. We hebben ons ook gericht op het definiëren van een aangepaste realm en deze in het Beveiligingsmanager.

Zoals altijd is de volledige broncode beschikbaar op GitHub.