Introduktion til Apache Shiro

1. Oversigt

I denne artikel ser vi på Apache Shiro, en alsidig Java-sikkerhedsramme.

Rammen er meget tilpasselig og modulær, da den tilbyder godkendelse, autorisation, kryptografi og session management.

2. Afhængighed

Apache Shiro har mange moduler. Men i denne vejledning bruger vi shiro-kerne kun artefakt.

Lad os tilføje det til vores pom.xml:

 org.apache.shiro shiro-core 1.4.0 

Den seneste version af Apache Shiro-modulerne kan findes på Maven Central.

3. Konfiguration af Security Manager

Det SecurityManager er det centrale stykke af Apache Shiros ramme. Programmer kører normalt en enkelt forekomst.

I denne vejledning udforsker vi rammerne i et skrivebordsmiljø. For at konfigurere rammen skal vi oprette en shiro.ini fil i ressourcemappen med følgende indhold:

[brugere] bruger = adgangskode, administrator bruger2 = adgangskode2, editor bruger3 = password3, forfatter [roller] admin = * editor = artikler: * forfatter = artikler: komponere, artikler: gem

Det [brugere] sektion af shiro.ini konfigurationsfil definerer de brugeroplysninger, der genkendes af SecurityManager. Formatet er: srincipal (brugernavn) = adgangskode, rolle1, rolle2,…, rolle.

Rollerne og deres tilknyttede tilladelser er angivet i [roller] afsnit. Det admin rolle gives tilladelse og adgang til alle dele af applikationen. Dette er angivet med jokertegnet (*) symbol.

Det redaktør rolle har alle tilladelser tilknyttet artikler mens forfatter rolle kan kun komponere og Gemme en artikel.

Det SecurityManager bruges til at konfigurere SecurityUtils klasse. Fra SecurityUtils vi kan få den aktuelle bruger, der interagerer med systemet og udføre godkendelses- og autorisationshandlinger.

Lad os bruge IniRealm for at indlæse vores bruger- og rolledefinitioner fra shiro.ini filen, og brug den derefter til at konfigurere StandardSecurityManager objekt:

IniRealm iniRealm = ny IniRealm ("klassesti: shiro.ini"); SecurityManager securityManager = ny DefaultSecurityManager (iniRealm); SecurityUtils.setSecurityManager (securityManager); Emne currentUser = SecurityUtils.getSubject ();

Nu hvor vi har en SecurityManager der er opmærksom på brugeroplysninger og roller defineret i shiro.ini fil, lad os fortsætte med brugergodkendelse og autorisation.

4. Godkendelse

I Apache Shiros terminologier er a Emne er enhver enhed, der interagerer med systemet. Det kan enten være et menneske, et script eller en REST-klient.

Ringer SecurityUtils.getSubject () returnerer en forekomst af den aktuelle Emne, det vil sige nuværende bruger.

Nu hvor vi har nuværende bruger Objekt, vi kan udføre godkendelse på de leverede legitimationsoplysninger:

if (! currentUser.isAuthenticated ()) {UsernamePasswordToken token = new UsernamePasswordToken ("user", "password"); token.setRememberMe (sand); prøv {currentUser.login (token); } fange (UnknownAccountException uae) {log.error ("Brugernavn ikke fundet!", uae); } fange (IncorrectCredentialsException ice) {log.error ("Ugyldige legitimationsoplysninger!", ice); } catch (LockedAccountException lae) {log.error ("Din konto er låst!", lae); } fange (AuthenticationException ae) {log.error ("Uventet fejl!", ae); }}

Først kontrollerer vi, om den aktuelle bruger ikke allerede er godkendt. Derefter opretter vi et godkendelsestoken med brugerens hoved (brugernavn) og legitimationsoplysninger (adgangskode).

Dernæst forsøger vi at logge ind med tokenet. Hvis de leverede legitimationsoplysninger er korrekte, skal alt gå fint.

Der er forskellige undtagelser for forskellige sager. Det er også muligt at kaste en brugerdefineret undtagelse, der bedre passer til applikationskravet. Dette kan gøres ved at underklassificere Kontoundtagelse klasse.

5. Autorisation

Godkendelse forsøger at validere en brugers identitet, mens autorisation forsøger at kontrollere adgangen til bestemte ressourcer i systemet.

Husk at vi tildeler en eller flere roller til hver bruger, vi har oprettet i shiro.ini fil. Desuden definerer vi i rollesektionen forskellige tilladelser eller adgangsniveauer for hver rolle.

Lad os nu se, hvordan vi kan bruge det i vores applikation til at håndhæve brugeradgangskontrol.

I shiro.ini fil, giver vi administratoren total adgang til alle dele af systemet.

Editoren har total adgang til alle ressourcer / operationer vedrørende artikler, og en forfatter er begrænset til bare at komponere og gemme artikler kun.

Lad os byde den nuværende bruger velkommen på baggrund af rolle:

hvis (currentUser.hasRole ("admin")) {log.info ("Welcome Admin"); } ellers hvis (currentUser.hasRole ("editor")) {log.info ("Welcome, Editor!"); } ellers hvis (currentUser.hasRole ("forfatter")) {log.info ("Velkommen, forfatter"); } andet {log.info ("Velkommen, gæst"); }

Lad os nu se, hvad den nuværende bruger har tilladelse til at gøre i systemet:

if (currentUser.isPermitted ("articles: compose")) {log.info ("Du kan komponere en artikel"); } andet {log.info ("Du har ikke tilladelse til at komponere en artikel!"); } hvis (currentUser.isPermitted ("articles: save")) {log.info ("Du kan gemme artikler"); } andet {log.info ("Du kan ikke gemme artikler"); } if (currentUser.isPermitted ("articles: publish")) {log.info ("Du kan udgive artikler"); } andet {log.info ("Du kan ikke offentliggøre artikler"); }

6. Realm Configuration

I rigtige applikationer har vi brug for en måde at få brugeroplysninger fra en database snarere end fra shiro.ini fil. Det er her, begrebet Realm spiller ind.

I Apache Shiros terminologi er en verden en DAO, der peger på en butik med brugeroplysninger, der er nødvendige for godkendelse og godkendelse.

For at skabe et rige er vi kun nødt til at implementere Rige interface. Det kan være kedeligt; rammen kommer dog med standardimplementeringer, som vi kan underklasse fra. En af disse implementeringer er JdbcRealm.

Vi opretter en tilpasset realm-implementering, der strækker sig JdbcRealm klasse og tilsidesætter følgende metoder: doGetAuthenticationInfo (), doGetAuthorizationInfo (), getRoleNamesForUser () og getPermissions ().

Lad os skabe et rige ved at underklassere JdbcRealm klasse:

offentlig klasse MyCustomRealm udvider JdbcRealm {// ...}

Af hensyn til enkelheden bruger vi java.util.Kort at simulere en database:

private Map-legitimationsoplysninger = ny HashMap (); privat kort roller = nyt HashMap (); privat kort perm = ny HashMap (); {credentials.put ("bruger", "adgangskode"); credentials.put ("bruger2", "adgangskode2"); credentials.put ("bruger3", "adgangskode3"); roller.put ("bruger", nyt HashSet (Arrays.asList ("admin"))); roller.put ("bruger2", nyt HashSet (Arrays.asList ("editor"))); roller.put ("bruger3", nyt HashSet (Arrays.asList ("forfatter"))); perm.put ("admin", nyt HashSet (Arrays.asList ("*"))); perm.put ("editor", ny HashSet (Arrays.asList ("articles: *"))); perm.put ("forfatter", ny HashSet (Arrays.asList ("articles: compose", "articles: save"))); }

Lad os fortsætte og tilsidesætte doGetAuthenticationInfo ():

beskyttet AuthenticationInfo doGetAuthenticationInfo (AuthenticationToken token) kaster AuthenticationException {UsernamePasswordToken uToken = (UsernamePasswordToken) token; hvis (uToken.getUsername () == null || uToken.getUsername (). isEmpty () ||! credentials.containsKey (uToken.getUsername ())) {smid nyt UnknownAccountException ("brugernavn ikke fundet!"); } returner nyt SimpleAuthenticationInfo (uToken.getUsername (), credentials.get (uToken.getUsername ()), getName ()); }

Vi kastede først AuthenticationToken leveres til BrugernavnPasswordToken. Fra uToken, udtrækker vi brugernavnet (uToken.getUsername ()) og brug den til at hente brugerlegitimationsoplysningerne (adgangskoden) fra databasen.

Hvis der ikke findes nogen rekord - kaster vi en Ukendt kontoundtagelse, ellers bruger vi legitimationsoplysninger og brugernavn til at konstruere en SimpleAuthenticatioInfo objekt, der returneres fra metoden.

Hvis brugerlegitimationen er hashet med et salt, skal vi returnere en SimpleAuthenticationInfo med det tilhørende salt:

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

Vi er også nødt til at tilsidesætte doGetAuthorizationInfo (), såvel som getRoleNamesForUser () og getPermissions ().

Lad os endelig tilslutte den brugerdefinerede verden til sikkerhedManager. Alt, hvad vi skal gøre er at udskifte IniRealm ovenfor med vores brugerdefinerede rige og videregive det til StandardSecurityManager'S konstruktør:

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

Hver anden del af koden er den samme som før. Dette er alt, hvad vi har brug for for at konfigurere sikkerhedManager med en brugerdefineret verden korrekt.

Nu er spørgsmålet - hvordan matcher rammen legitimationsoplysningerne?

Som standard er JdbcRealm bruger SimpleCredentialsMatcher, der blot kontrollerer for lighed ved at sammenligne legitimationsoplysningerne i AuthenticationToken og AuthenticationInfo.

Hvis vi hash vores adgangskoder, skal vi informere rammen for at bruge en HashedCredentialsMatcher i stedet. INI-konfigurationerne til verdener med hashede adgangskoder kan findes her.

7. Log ud

Nu hvor vi har godkendt brugeren, er det tid til at implementere log ud. Det gøres simpelthen ved at kalde en enkelt metode - som ugyldiggør brugersessionen og logger brugeren ud:

currentUser.logout ();

8. Sessionsstyring

Rammen kommer naturligvis med sit session management system. Hvis det bruges i et webmiljø, er det som standard HttpSession implementering.

Til en enkeltstående applikation bruger den sit enterprise session management system. Fordelen er, at selv i et skrivebordsmiljø kan du bruge et sessionsobjekt, som du ville gøre i et typisk webmiljø.

Lad os se på et hurtigt eksempel og interagere med den aktuelle brugers session:

Sessionssession = currentUser.getSession (); session.setAttribute ("nøgle", "værdi"); Strengværdi = (String) session.getAttribute ("nøgle"); hvis (value.equals ("værdi")) {log.info ("Hentet den korrekte værdi! [" + værdi + "]"); }

9. Shiro til en webapplikation med foråret

Indtil videre har vi skitseret den grundlæggende struktur for Apache Shiro, og vi har implementeret den i et desktop-miljø. Lad os fortsætte med at integrere rammen i en Spring Boot-applikation.

Bemærk, at hovedfokus her er Shiro, ikke Spring-applikationen - det skal vi kun bruge til at drive en simpel eksempelapp.

9.1. Afhængigheder

Først skal vi tilføje Spring Boot-forældreafhængighed til vores pom.xml:

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

Dernæst skal vi tilføje følgende afhængigheder til det samme pom.xml fil:

 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. Konfiguration

Tilføjelse af shiro-spring-boot-web-starter afhængighed af vores pom.xml vil som standard konfigurere nogle funktioner i Apache Shiro-applikationen som f.eks SecurityManager.

Vi skal dog stadig konfigurere Rige og Shiro-sikkerhedsfiltre. Vi bruger den samme brugerdefinerede verden defineret ovenfor.

Så lad os tilføje følgende i hovedklassen, hvor Spring Boot-applikationen køres Bønne definitioner:

@Bean public Realm realm () {returner ny MyCustomRealm (); } @Bean offentlig ShiroFilterChainDefinition shiroFilterChainDefinition () {DefaultShiroFilterChainDefinition filter = nyt DefaultShiroFilterChainDefinition (); filter.addPathDefinition ("/ secure", "authc"); filter.addPathDefinition ("/ **", "anon"); returfilter; }

I ShiroFilterChainDefinition, anvendte vi godkendelse filtrer til /sikker sti og anvendte anon filtrer på andre stier ved hjælp af Ant-mønsteret.

Begge godkendelse og anon filtre kommer som standard til webapplikationer. Andre standardfiltre kan findes her.

Hvis vi ikke definerede Rige bønne, ShiroAutoConfiguration vil som standard give en IniRealm implementering, der forventer at finde en shiro.ini fil i src / main / ressourcer eller src / main / resources / META-INF.

Hvis vi ikke definerer en ShiroFilterChainDefinition bean, rammen sikrer alle stier og indstiller login-URL'en som login.jsp.

Vi kan ændre denne standard login-URL og andre standardindstillinger ved at tilføje følgende poster til vores application.properties:

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

Nu hvor godkendelse filter er blevet anvendt på /sikker, alle anmodninger til denne rute kræver en formulargodkendelse.

9.3. Godkendelse og godkendelse

Lad os oprette en ShiroSpringController med følgende kortlægninger: /indeks, / login, / logout og /sikker.

Det Log på() metode er, hvor vi implementerer faktisk brugergodkendelse som beskrevet ovenfor. Hvis godkendelse er vellykket, omdirigeres brugeren til den sikre side:

Emneemne = SecurityUtils.getSubject (); if (! subject.isAuthenticated ()) {UsernamePasswordToken token = new UsernamePasswordToken (cred.getUsername (), cred.getPassword (), cred.isRememberMe ()); prøv {subject.login (token); } fange (AuthenticationException ae) {ae.printStackTrace (); attr.addFlashAttribute ("fejl", "Ugyldige legitimationsoplysninger"); returner "omdirigering: / login"; }} returner "omdirigering: / sikker";

Og nu i sikker() gennemførelse, nuværende bruger blev opnået ved at påberåbe sig SecurityUtils.getSubject (). Brugerens rolle og tilladelser videregives til den sikre side samt brugerens hoved:

Emne currentUser = SecurityUtils.getSubject (); String-rolle = "", tilladelse = ""; hvis (currentUser.hasRole ("admin")) {role = role + "Du er administrator"; } ellers hvis (currentUser.hasRole ("editor")) {role = role + "Du er en editor"; } ellers hvis (currentUser.hasRole ("forfatter")) {role = role + "Du er forfatter"; } if (currentUser.isPermitted ("articles: compose")) {permission = permission + "Du kan komponere en artikel,"; } andet {tilladelse = tilladelse + "Du har ikke tilladelse til at komponere en artikel !,"; } hvis (currentUser.isPermitted ("artikler: gem")) {tilladelse = tilladelse + "Du kan gemme artikler,"; } andet {tilladelse = tilladelse + "\ nDu kan ikke gemme artikler,"; } hvis (currentUser.isPermitted ("artikler: udgiv")) {tilladelse = tilladelse + "\ nDu kan udgive artikler"; } andet {tilladelse = tilladelse + "\ nDu kan ikke offentliggøre artikler"; } modelMap.addAttribute ("brugernavn", currentUser.getPrincipal ()); modelMap.addAttribute ("tilladelse", tilladelse); modelMap.addAttribute ("rolle", rolle); returner "sikkert";

Og vi er færdige. Sådan kan vi integrere Apache Shiro i en Spring Boot-applikation.

Bemærk også, at rammen tilbyder yderligere kommentarer, der kan bruges sammen med definitioner af filterkæder for at sikre vores applikation.

10. JEE-integration

Integrering af Apache Shiro i en JEE-applikation er bare et spørgsmål om at konfigurere web.xml fil. Som normalt forventer konfigurationen shiro.ini at være i klassestien. En detaljeret eksempelkonfiguration er tilgængelig her. JSP-tags kan også findes her.

11. Konklusion

I denne vejledning kiggede vi på Apache Shiros godkendelses- og autorisationsmekanismer. Vi fokuserede også på, hvordan man definerer en brugerdefineret verden og tilslutter den til SecurityManager.

Som altid er den komplette kildekode tilgængelig på GitHub.