Spring Security vs Apache Shiro

1. Oversikt

Sikkerhet er et hovedanliggende i applikasjonsutviklingsverdenen, spesielt innen bedriftens web- og mobilapplikasjoner.

I denne raske opplæringen, vi sammenligner to populære Java Security-rammer - Apache Shiro og Spring Security.

2. En liten bakgrunn

Apache Shiro ble født i 2004 som JSecurity og ble akseptert av Apache Foundation i 2008. Til dags dato har den sett mange utgivelser, den siste i skrivende stund er 1.5.3.

Spring Security startet som Acegi i 2003 og ble innlemmet i Spring Framework med sin første offentlige utgivelse i 2008. Siden oppstarten har den gått gjennom flere iterasjoner, og den nåværende GA-versjonen fra skriving av dette er 5.3.2.

Begge teknologiene tilbyr autentiserings- og autorisasjonsstøtte sammen med kryptografi og øktadministrasjonsløsninger. I tillegg gir Spring Security førsteklasses beskyttelse mot angrep som CSRF og øktfiksering.

I de neste avsnittene vil vi se eksempler på hvordan de to teknologiene håndterer autentisering og autorisasjon. For å gjøre ting enkelt, bruker vi grunnleggende Spring Boot-baserte MVC-applikasjoner med FreeMarker-maler.

3. Konfigurere Apache Shiro

Til å begynne med, la oss se hvordan konfigurasjoner er forskjellige mellom de to rammene.

3.1. Maven avhengigheter

Siden vi bruker Shiro i en Spring Boot-app, trenger vi startpakke og shiro-kjerne modul:

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

De nyeste versjonene finner du på Maven Central.

3.2. Å skape et rike

For å erklære brukere med sine roller og tillatelser i minnet, må vi lage et rike som utvider Shiro's JdbcRealm. Vi definerer to brukere - Tom og Jerry, med henholdsvis roller USER og ADMIN:

offentlig klasse CustomRealm utvider JdbcRealm {private Map credentials = new HashMap (); private kartroller = nye HashMap (); private Kart tillatelser = ny HashMap (); {credentials.put ("Tom", "passord"); credentials.put ("Jerry", "passord"); roller.put ("Jerry", nye HashSet (Arrays.asList ("ADMIN"))); roller.put ("Tom", nye HashSet (Arrays.asList ("BRUKER"))); permissions.put ("ADMIN", ny HashSet (Arrays.asList ("LES", "SKRIV"))); permissions.put ("USER", nytt HashSet (Arrays.asList ("LES")); }}

Deretter må vi overstyre noen få metoder for å muliggjøre henting av denne godkjenningen og autorisasjonen:

@ Override beskyttet AuthenticationInfo doGetAuthenticationInfo (AuthenticationToken token) kaster AuthenticationException {UsernamePasswordToken userToken = (UsernamePasswordToken) token; if (userToken.getUsername () == null || userToken.getUsername (). isEmpty () ||! credentials.containsKey (userToken.getUsername ())) {kast ny UnknownAccountException ("Bruker eksisterer ikke"); } returner nye SimpleAuthenticationInfo (userToken.getUsername (), credentials.get (userToken.getUsername ()), getName ()); } @ Override-beskyttet AuthorizationInfo doGetAuthorizationInfo (PrincipalCollection-rektorer) {Sett roller = ny HashSet (); Angi tillatelser = nytt HashSet (); for (Objektbruker: rektorer) {prøv {roller.addAll (getRoleNamesForUser (null, (String) bruker)); permissions.addAll (getPermissions (null, null, roller)); } fange (SQLException e) {logger.error (e.getMessage ()); }} SimpleAuthorizationInfo authInfo = ny SimpleAuthorizationInfo (roller); authInfo.setStringPermissions (tillatelser); returner authInfo; } 

Metoden doGetAuthorizationInfo bruker et par hjelpemetoder for å få brukerens roller og tillatelser:

@ Override-beskyttet Sett getRoleNamesForUser (tilkoblingsforbindelse, strengbrukernavn) kaster SQLException {if (!roller.containsKey (brukernavn)) {kast ny SQLException ("Bruker eksisterer ikke"); } returner roller.get (brukernavn); } @ Override-beskyttet Sett getPermissions (tilkoblingsforbindelse, strengbrukernavn, samlingsroller) kaster SQLException {Set userPermissions = new HashSet (); for (String rolle: roller) {if (! permissions.containsKey (rolle)) {kast ny SQLException ("Rollen eksisterer ikke"); } userPermissions.addAll (permissions.get (rolle)); } returner brukerPermisjoner; } 

Deretter må vi inkludere dette CustomRealm som en bønne i vår Boot-applikasjon:

@Bean public Realm customRealm () {returner nytt CustomRealm (); }

I tillegg, for å konfigurere autentisering for endepunktene, trenger vi en annen bønne:

@Bean offentlig ShiroFilterChainDefinition shiroFilterChainDefinition () {DefaultShiroFilterChainDefinition filter = nytt DefaultShiroFilterChainDefinition (); filter.addPathDefinition ("/ home", "authc"); filter.addPathDefinition ("/ **", "anon"); returfilter; }

Her bruker du en DefaultShiroFilterChainDefinition for eksempel spesifiserte vi at vår /hjem endepunkt er bare tilgjengelig av autentiserte brukere.

Det er alt vi trenger for konfigurasjonen, Shiro gjør resten for oss.

4. Konfigurere vårsikkerhet

La oss nå se hvordan vi kan oppnå det samme om våren.

4.1. Maven avhengigheter

For det første avhengighetene:

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

De nyeste versjonene finner du på Maven Central.

4.2. Konfigurasjonsklasse

Deretter definerer vi vårsikkerhetskonfigurasjon i en klasse SecurityConfig, utvide WebSecurityConfigurerAdapter:

@EnableWebSecurity offentlig klasse SecurityConfig utvider WebSecurityConfigurerAdapter {@Override-beskyttet ugyldig konfigurering (HttpSecurity http) kaster Unntak {http .authorizeRequests (autoriser -> godkjenn .antMatchers ("/ index", "/ login"). PermitAll () .antMatchers ("/ home "," / logout "). authenticated () .antMatchers (" / admin / ** "). hasRole (" ADMIN ")) .formLogin (formLogin -> formLogin .loginPage (" / login ") .failureUrl (" /innloggingsfeil")); } @ Override-beskyttet tomkonfigurasjon (AuthenticationManagerBuilder auth) kaster Unntak {auth.inMemoryAuthentication () .withUser ("Jerry") .passord (passwordEncoder (). Koding ("passord")). Autoriteter ("LES", "SKRIV") .roles ("ADMIN"). og () .withUser ("Tom") .passord (passwordEncoder (). encode ("password")) .authorities ("READ") .roles ("USER"); } @Bean public PasswordEncoder passwordEncoder () {return new BCryptPasswordEncoder (); }} 

Som vi ser, bygde vi en AuthenticationManagerBuilder motsetter seg å erklære brukerne våre med sine roller og autoriteter. I tillegg kodet vi passordene ved hjelp av a BCryptPasswordEncoder.

Spring Security gir oss også sine HttpSikkerhet objekt for ytterligere konfigurasjoner. For vårt eksempel har vi tillatt:

  • alle å få tilgang til vår indeks og Logg Inn sider
  • bare autentiserte brukere for å gå inn i hjem side og Logg ut
  • bare brukere med ADMIN-rolle for å få tilgang til admin sider

Vi har også definert støtte for skjemabasert autentisering for å sende brukere til Logg Inn endepunkt. I tilfelle innlogging mislykkes, vil brukerne våre bli omdirigert til /innloggingsfeil.

5. Kontrollere og sluttpunkter

La oss ta en titt på kartleggingen av webkontrollere for de to applikasjonene. Mens de bruker de samme endepunktene, vil noen implementeringer være forskjellige.

5.1. Endepunkter for visning

For endepunkter som gjengir utsikten, er implementeringene de samme:

@GetMapping ("/") offentlig strengindeks () {return "indeks"; } @GetMapping ("/ login") public String showLoginPage () {return "login"; } @GetMapping ("/ home") offentlig streng getMeHome (modellmodell) {addUserAttributes (modell); vende hjem"; }

Både våre kontrollerimplementasjoner, Shiro så vel som Spring Security, returnerer index.ftl på rotendepunktet, login.ftl på påloggingsendepunktet, og hjem.ftl på endepunktet hjemme.

Imidlertid definisjonen av metoden addUserAttributes/hjem endepunktet vil variere mellom de to kontrollerne. Denne metoden introspekterer den nåværende påloggede brukerens attributter.

Shiro gir en SecurityUtils # getSubject for å hente strømmen Emne, og dets roller og tillatelser:

private void addUserAttributes (Model model) {Subject currentUser = SecurityUtils.getSubject (); Strengstillatelse = ""; if (currentUser.hasRole ("ADMIN")) {model.addAttribute ("role", "ADMIN"); } annet hvis (currentUser.hasRole ("USER")) {model.addAttribute ("role", "USER"); } hvis (currentUser.isPermitted ("LES")) {tillatelse = tillatelse + "LES"; } if (currentUser.isPermitted ("WRITE")) {tillatelse = tillatelse + "WRITE"; } model.addAttribute ("brukernavn", currentUser.getPrincipal ()); model.addAttribute ("tillatelse", tillatelse); }

På den annen side gir Spring Security en Godkjenning objekt fra sin SecurityContextHolderSammenheng for dette formålet:

private void addUserAttributes (modellmodell) {Authentication auth = SecurityContextHolder.getContext (). getAuthentication (); hvis (auth! = null &&! auth.getClass (). tilsvarer (AnonymousAuthenticationToken.class)) {User user = (User) auth.getPrincipal (); model.addAttribute ("brukernavn", user.getUsername ()); Innsamlingsmyndigheter = user.getAuthorities (); for (GrantedAuthority autoritet: autoriteter) {if (autoritet.getAuthority (). inneholder ("BRUKER")) {modell.addAttribute ("rolle", "BRUKER"); model.addAttribute ("tillatelser", "LES"); } annet hvis (autoritet.getAuthority (). inneholder ("ADMIN")) {model.addAttribute ("rolle", "ADMIN"); model.addAttribute ("tillatelser", "LES SKRIV"); }}}}

5.2. POST Pålogging Endpoint

I Shiro kartlegger vi legitimasjonen brukeren angir til en POJO:

offentlig klasse UserCredentials {private String brukernavn; privat strengpassord; // getters og setters}

Så lager vi en BrukernavnPassordToken for å logge brukeren, eller Emne, i:

@PostMapping ("/ login") offentlig String doLogin (HttpServletRequest req, UserCredentials credentials, RedirectAttributes attr) {Subject subject = SecurityUtils.getSubject (); if (! subject.isAuthenticated ()) {UsernamePasswordToken token = new UsernamePasswordToken (credentials.getUsername (), credentials.getPassword ()); prøv {subject.login (token); } fange (AuthenticationException ae) {logger.error (ae.getMessage ()); attr.addFlashAttribute ("feil", "Ugyldig legitimasjon"); returner "redirect: / login"; }} returner "redirect: / home"; }

På vårsikkerhetssiden er dette bare et spørsmål om omdirigering til hjemmesiden. Vårens påloggingsprosess, håndtert av dens BrukernavnPassordAuthenticationFilter, er gjennomsiktig for oss:

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

5.3. Endepunkt for administratorer

La oss nå se på et scenario der vi må utføre rollebasert tilgang. La oss si at vi har en / admin endepunkt, hvis tilgang bare skal være tillatt for ADMIN-rollen.

La oss se hvordan du gjør dette i Shiro:

@GetMapping ("/ admin") offentlig String adminOnly (ModelMap modelMap) {addUserAttributes (modelMap); Subject currentUser = SecurityUtils.getSubject (); hvis (currentUser.hasRole ("ADMIN")) {modelMap.addAttribute ("adminContent", "bare admin kan se dette"); } vende hjem"; }

Her hentet vi den påloggede brukeren ut, sjekket om de hadde ADMIN-rollen, og la til innhold deretter.

I vårsikkerhet er det ikke behov for å kontrollere rollen programmatisk, vi har allerede definert hvem som kan nå dette endepunktet i vår SecurityConfig. Så nå er det bare å legge til forretningslogikk:

@GetMapping ("/ admin") offentlig String adminOnly (HttpServletRequest req, Model model) {addUserAttributes (model); model.addAttribute ("adminContent", "bare admin kan se dette"); vende hjem"; }

5.4. Logg ut Endpoint

Til slutt, la oss implementere sluttpunktet for utlogging.

I Shiro vil vi bare ringe Emne # avlogging:

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

For våren har vi ikke definert noen kartlegging for utlogging. I dette tilfellet starter standard avloggingsmekanisme, som brukes automatisk siden vi utvidet WebSecurityConfigurerAdapter i vår konfigurasjon.

6. Apache Shiro vs Spring Security

Nå som vi har sett på implementeringsforskjellene, la oss se på noen andre aspekter.

Når det gjelder fellestøtte, er Spring Framework generelt har et stort fellesskap av utviklere, aktivt involvert i utvikling og bruk. Siden Spring Security er en del av paraplyen, må den ha de samme fordelene. Selv om Shiro er populær, har ikke så enorm støtte.

Når det gjelder dokumentasjon, er Spring igjen vinneren.

Imidlertid er det litt av en læringskurve knyttet til Spring Security. Shiro, derimot, er lett å forstå. For stasjonære applikasjoner, konfigurasjon via shiro.ini er desto lettere.

Men igjen, som vi så i eksemplene våre, Spring Security gjør en god jobb med å holde forretningslogikk og sikkerhetskille og tilbyr virkelig sikkerhet som en tverrgående bekymring.

7. Konklusjon

I denne veiledningen, vi sammenlignet Apache Shiro med Spring Security.

Vi har nettopp beitet overflaten av hva disse rammene har å tilby, og det er mye å utforske videre. Det er ganske mange alternativer der ute som JAAS og OACC. Likevel, med sine fordeler, ser det ut til at Spring Security vinner på dette punktet.

Som alltid er kildekoden tilgjengelig på GitHub.


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