Jarná bezpečnosť vs Apache Shiro

1. Prehľad

Bezpečnosť je vo svete vývoja aplikácií primárnym záujmom, najmä v oblasti podnikových webových a mobilných aplikácií.

V tomto rýchlom návode porovnáme dva populárne bezpečnostné rámce Java - Apache Shiro a Spring Security.

2. Malé pozadie

Apache Shiro sa narodil v roku 2004 ako JSecurity a bola prijatá nadáciou Apache Foundation v roku 2008. K dnešnému dňu sa dočkalo mnohých vydaní, najnovšie v čase písania tohto článku: 1.5.3.

Jarná bezpečnosť začala ako Acegi v roku 2003 a bola začlenená do jarného rámca jeho prvým verejným vydaním v roku 2008. Od svojho vzniku prešla niekoľkými iteráciami a aktuálna verzia GA v čase písania tohto článku je 5.3.2.

Obe technológie ponúkajú podpora autentifikácie a autorizácie spolu s kryptografiou a riešeniami správy relácií. Spring Security navyše poskytuje prvotriednu ochranu pred útokmi, ako sú CSRF a fixácia relácie.

V nasledujúcich niekoľkých častiach uvidíme príklady toho, ako tieto dve technológie pracujú s autentifikáciou a autorizáciou. Aby to nebolo jednoduché, budeme používať základné aplikácie MVC založené na Spring Boot so šablónami FreeMarker.

3. Konfigurácia Apache Shiro

Na začiatok sa pozrime, ako sa líšia konfigurácie medzi týmito dvoma rámcami.

3.1. Maven závislosti

Pretože budeme používať Shiro v aplikácii Spring Boot, budeme potrebovať jeho štartér a širo-jadro modul:

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

Najnovšie verzie nájdete na serveri Maven Central.

3.2. Vytvorenie ríše

Aby sme deklarovali používateľov s ich rolami a oprávneniami v pamäti, musíme vytvoriť sféru rozširujúcu Shiro JdbcRealm. Definujeme dvoch používateľov - Toma a Jerryho s rolami USER a ADMIN:

verejná trieda CustomRealm rozširuje JdbcRealm {súkromné ​​poverenia mapy = nové HashMap (); role súkromnej mapy = nová HashMap (); oprávnenia súkromnej mapy = nový HashMap (); {credentials.put ("Tom", "heslo"); credentials.put ("Jerry", "heslo"); role.put ("Jerry", nový HashSet (Arrays.asList ("ADMIN"))); role.put ("Tom", nový HashSet (Arrays.asList ("USER"))); permissions.put ("ADMIN", nový HashSet (Arrays.asList ("READ", "WRITE"))); permissions.put ("USER", nový HashSet (Arrays.asList ("READ"))); }}

Ďalej, aby sme umožnili načítanie tejto autentifikácie a autorizácie, musíme prepísať niekoľko metód:

@Override chránený AuthenticationInfo doGetAuthenticationInfo (token AuthenticationToken) hodí AuthenticationException {UsernamePasswordToken userToken = (UsernamePasswordToken) token; if (userToken.getUsername () == null || userToken.getUsername (). isEmpty () ||! credentials.containsKey (userToken.getUsername ())) {hodiť novú UnknownAccountException ("Používateľ neexistuje"); } vrátiť nové SimpleAuthenticationInfo (userToken.getUsername (), credentials.get (userToken.getUsername ()), getName ()); } @Override chránený AuthorizationInfo doGetAuthorizationInfo (PrincipalCollection principals) {Nastaviť role = nový HashSet (); Nastaviť povolenia = nový HashSet (); pre (Object user: principals) {try {roles.addAll (getRoleNamesForUser (null, (String) user))); permissions.addAll (getPermissions (null, null, role)); } catch (SQLException e) {logger.error (e.getMessage ()); }} SimpleAuthorizationInfo authInfo = nový SimpleAuthorizationInfo (role); authInfo.setStringPermissions (oprávnenia); vrátiť authInfo; } 

Metóda doGetAuthorizationInfo používa niekoľko pomocných metód na získanie rolí a povolení používateľa:

@Override chránený Set getRoleNamesForUser (pripojenie, meno používateľa reťazca) hodí SQLException {if (! Role.containsKey (meno používateľa)) {{hodí novú SQLException ("Používateľ neexistuje"); } vrátiť role.get (meno používateľa); } @Override protected Set getPermissions (Connection conn, String username, Collection role) throws SQLException {Set userPermissions = new HashSet (); pre (Reťazcová rola: role) {if (! permissions.containsKey (role)) {hodiť novú SQLException ("Rola neexistuje"); } userPermissions.addAll (permissions.get (role)); } návrat userPermissions; } 

Ďalej to musíme zahrnúť CustomRealm ako fazuľa v našej bootovacej aplikácii:

@Bean public Realm customRealm () {return new CustomRealm (); }

Okrem toho na nakonfigurovanie autentifikácie pre naše koncové body potrebujeme ešte jednu fazuľu:

@Bean public ShiroFilterChainDefinition shiroFilterChainDefinition () {DefaultShiroFilterChainDefinition filter = nový DefaultShiroFilterChainDefinition (); filter.addPathDefinition ("/ home", "authc"); filter.addPathDefinition ("/ **", "anon"); spätný filter; }

Tu pomocou a DefaultShiroFilterChainDefinition napríklad sme špecifikovali, že náš /Domov ku koncovému bodu majú prístup iba autentifikovaní používatelia.

To je všetko, čo potrebujeme pre konfiguráciu, Shiro urobí zvyšok za nás.

4. Konfigurácia jarnej bezpečnosti

Teraz sa pozrime, ako to isté dosiahnuť na jar.

4.1. Maven závislosti

Po prvé, závislosti:

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

Najnovšie verzie nájdete na serveri Maven Central.

4.2. Trieda konfigurácie

Ďalej definujeme našu konfiguráciu Spring Security v triede SecurityConfig, rozširuje sa WebSecurityConfigurerAdapter:

Verejná trieda @EnableWebSecurity SecurityConfig rozširuje WebSecurityConfigurerAdapter {@Override protected void configure (HttpSecurity http) vyvolá výnimku {http .authorizeRequests (autorizovať -> autorizovať .antMatchers ("/ index", "/ login"). PermitAll () .antMatchers ("/ home "," / logout "). authenticated () .antMatchers (" / admin / ** "). hasRole (" ADMIN ")) .formLogin (formLogin -> formLogin .loginPage (" / login ") .failureUrl (" / login-error ")); } @Override protected void configure (AuthenticationManagerBuilder auth) hodí výnimku {auth.inMemoryAuthentication () .withUser ("Jerry") .password (passwordEncoder (). Encode ("heslo")) .authorities ("ČÍTAŤ", "PÍSAŤ") .roles ("ADMIN") .and () .withUser ("Tom") .password (passwordEncoder (). encode ("heslo")) .authorities ("PREČÍTAŤ") .roles ("USER"); } @Bean public PasswordEncoder passwordEncoder () {vrátiť nový BCryptPasswordEncoder (); }} 

Ako vidíme, postavili sme AuthenticationManagerBuilder vzniesť námietku proti vyhláseniu našich používateľov za ich roly a oprávnenia. Ďalej sme heslá kódovali pomocou a BCryptPasswordEncoder.

Jarná bezpečnosť nám tiež poskytuje svoje HttpSecurity objekt pre ďalšie konfigurácie. Pre náš príklad sme povolili:

  • všetci majú prístup k nášmu index a Prihlásiť sa strán
  • sk Iba autentifikovaní používatelia môžu vstúpiť do Domov stránku a odhlásiť sa
  • iba používatelia s rolou ADMIN majú prístup do admin strán

Definovali sme tiež podporu pre autentifikáciu na základe formulárov, ktorá slúži na zasielanie používateľov do servera Prihlásiť sa koncový bod. V prípade zlyhania prihlásenia budú naši používatelia presmerovaní na / login-error.

5. Kontrolóri a koncové body

Teraz sa pozrime na naše mapovania webového radiča pre tieto dve aplikácie. Aj keď budú používať rovnaké koncové body, niektoré implementácie sa budú líšiť.

5.1. Koncové body pre vykreslenie zobrazenia

Pre koncové body vykresľujúce pohľad sú implementácie rovnaké:

@GetMapping ("/") public String index () {návrat "index"; } @GetMapping ("/ login") public String showLoginPage () {return "login"; } @GetMapping ("/ home") public String getMeHome (modelový model) {addUserAttributes (model); vrátiť sa domov"; }

Obe naše implementácie kontrolóra, Shiro aj Spring Security, vracajú index.ftl v koreňovom koncovom bode, login.ftl na koncovom bode prihlásenia a home.ftl na domácom koncovom bode.

Definícia metódy addUserAttributes na /Domov koncový bod sa bude líšiť medzi týmito dvoma radičmi. Táto metóda nahliada do aktuálne prihlásených atribútov používateľa.

Spoločnosť Shiro poskytuje a SecurityUtils # getSubject načítať prúd Predmeta jeho roly a oprávnenia:

private void addUserAttributes (Model model) {Subject currentUser = SecurityUtils.getSubject (); Povolenie reťazca = ""; if (currentUser.hasRole ("ADMIN")) {model.addAttribute ("role", "ADMIN"); } else if (currentUser.hasRole ("USER")) {model.addAttribute ("role", "USER"); } if (currentUser.isPermitted ("READ")) {povolenie = povolenie + "READ"; } if (currentUser.isPermitted ("WRITE")) {povolenie = povolenie + "WRITE"; } model.addAttribute ("používateľské meno", currentUser.getPrincipal ()); model.addAttribute ("povolenie", povolenie); }

Na druhej strane Spring Security poskytuje Overenie objekt z jeho SecurityContextHolderSúvislosti na tento účel:

private void addUserAttributes (Model model) {Authentication auth = SecurityContextHolder.getContext (). getAuthentication (); if (auth! = null &&! auth.getClass (). equals (AnonymousAuthenticationToken.class)) {User user = (User) auth.getPrincipal (); model.addAttribute ("username", user.getUsername ()); Inkasné orgány = user.getAuthorities (); pre (orgán GrantedAuthority: autority) {if (Authority.getAuthority (). contains ("USER")) {model.addAttribute ("role", "USER"); model.addAttribute ("povolenia", "ČÍTAŤ"); } else if (Authority.getAuthority (). contains ("ADMIN")) {model.addAttribute ("role", "ADMIN"); model.addAttribute ("povolenia", "ČÍTAJTE NAPÍŠTE"); }}}}

5.2. Koncový bod prihlásenia POST

V Shiro mapujeme poverenia, ktoré používateľ zadá, do POJO:

public class UserCredentials {private String username; súkromné ​​reťazcové heslo; // zakladatelia a zakladatelia}

Potom vytvoríme a UsernamePasswordToken - prihlásiť používateľa alebo - Predmet, v:

@PostMapping ("/ login") public String doLogin (požiadavka HttpServletRequest, poverenia UserCredentials, RedirectAttributes attr) {Subject subject = SecurityUtils.getSubject (); if (! subject.isAuthenticated ()) {UsernamePasswordToken token = nový UsernamePasswordToken (credentials.getUsername (), credentials.getPassword ()); skúsiť {subject.login (token); } catch (AuthenticationException ae) {logger.error (ae.getMessage ()); attr.addFlashAttribute ("chyba", "neplatné poverenia"); návrat "redirect: / login"; }} return "redirect: / home"; }

Pokiaľ ide o jarnú bezpečnosť, jedná sa iba o presmerovanie na domovskú stránku. Jarný proces prihlásenia, zvládnutý jeho UsernamePasswordAuthenticationFilter, je pre nás transparentný:

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

5.3. Koncový bod iba pre správcu

Teraz sa pozrime na scenár, v ktorom musíme vykonať prístup na základe rolí. Povedzme, že máme / admin koncový bod, ku ktorému by mal byť prístup povolený iba pre rolu ADMIN.

Pozrime sa, ako to urobiť v Shiro:

@GetMapping ("/ admin") public String adminOnly (ModelMap modelMap) {addUserAttributes (modelMap); Predmet currentUser = SecurityUtils.getSubject (); if (currentUser.hasRole ("ADMIN")) {modelMap.addAttribute ("adminContent", "iba administrátor to môže zobraziť"); } vrátiť sa domov"; }

Tu sme extrahovali aktuálne prihláseného používateľa, skontrolovali, či má rolu ADMIN a podľa toho sme pridali obsah.

V Spring Security nie je potrebné programovo kontrolovať rolu, už sme si definovali, kto môže v tomto dosiahnuť tento koncový bod SecurityConfig. Teraz teda stačí pridať obchodnú logiku:

@GetMapping ("/ admin") public String adminOnly (požiadavka HttpServletRequest, model modelu) {addUserAttributes (model); model.addAttribute ("adminContent", "iba administrátor to môže zobraziť"); vrátiť sa domov"; }

5.4. Koncový bod odhlásenia

Na záver poďme implementovať koncový bod odhlásenia.

V Shiro jednoducho zavoláme Odhlásenie z predmetu:

@PostMapping ("/ logout") verejné odhlásenie reťazca () {predmet subjektu = SecurityUtils.getSubject (); subject.logout (); návrat "presmerovanie: /"; }

Na jar sme nedefinovali žiadne mapovanie pre odhlásenie. V takom prípade sa spustí jeho predvolený mechanizmus odhlásenia, ktorý sa automaticky použije, pretože sme rozšírili WebSecurityConfigurerAdapter v našej konfigurácii.

6. Apache Shiro vs jarná bezpečnosť

Teraz, keď sme sa pozreli na rozdiely v implementácii, pozrime sa na niekoľko ďalších aspektov.

Pokiaľ ide o podporu komunity, Všeobecne má Spring Framework obrovskú komunitu vývojárov, aktívne zapojený do jeho vývoja a používania. Keďže Jarná bezpečnosť je súčasťou dáždniku, musí využívať rovnaké výhody. Shiro, hoci je populárny, nemá takú obrovskú podporu.

Pokiaľ ide o dokumentáciu, víťazom je opäť jar.

S jarnou bezpečnosťou však súvisí trochu krivky učenia. Shiro je naopak ľahko pochopiteľný. Pre desktopové aplikácie konfigurácia cez shiro.ini je o to ľahšia.

Ale ako sme videli v našich ukážkových úryvkoch, Spring Security odvádza skvelú prácu pri udržiavaní obchodnej logiky a bezpečnostioddelene a skutočne ponúka bezpečnosť ako prierezový problém.

7. Záver

V tomto návode porovnali sme Apache Shiro s Spring Security.

Práve sme spásali povrch toho, čo tieto rámce ponúkajú, a je tu ešte čo skúmať. Existuje pomerne veľa alternatív, ako sú JAAS a OACC. Napriek tomu sa zdá, že Spring Security v tejto chvíli vyhráva so svojimi výhodami.

Ako vždy, zdrojový kód je k dispozícii na GitHub.


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