2010. július 30., péntek

Tomcat JMX tűzfalon keresztül

Technológiák: Tomcat 6.0.24, JMX

Elég nagy problémám volt sokáig, hogyha egy Tomcat-et akartam JMX-en monitorozni, melyet tőlem egy tűzfal választott el. Írtam már erről részletesebben egy korábbi postban, álljon itt csak annyi, hogy le kellett kérdeznem egy menedzsment és monitorozó eszközből a következő értékeket: adatbázis connection pool mérete (várakozó és aktív kapcsolatok száma), session-ök száma az alkalmazáson belül, Connector várakozó és kiszolgáló szálak száma, memóriafelhaszálás, stb.

Ha nincs tűzfal, a megoldás egyszerű. A Tomcat-et úgy kell indítani, hogy JMX-en keresztül is kapcsolódni lehessen hozzá. Ezt úgy lehet megtenni, hogy bizonyos környezeti paramétereket át kell neki adni. Javasolt a bin könyvtárába egy setenv.sh-t elhelyezni, és a következőt írni:

  set CATALINA_OPTS=-Dcom.sun.management.jmxremote \
   -Dcom.sun.management.jmxremote.port=%my.jmx.port% \
   -Dcom.sun.management.jmxremote.ssl=false \
   -Dcom.sun.management.jmxremote.authenticate=false

Meg kell jegyezni, hogy ezek akár egy külön properties állományban is megadhatóak, akkor a -Dcom.sun.management.config.file paramétert kell átadni a Tomcat-et indító JVM-nek.

Ekkor akár jconsole, akár jvisualvm (persze csak az MBean plugin telepítése után) kapcsolódni lehet a Tomcat-hez, a host és a port megadásával, és lehet kezelni JMX-en keresztül.

Tűzfal esetén ez nem működött, nosza engedjük át a %my.jmx.port% helyett megadott port-ot a tűzfalon, vagy SSL tunel-lel hozzuk ki. Sajnos ez sem jó megoldás, ugyanis igaz, hogy a JMX kliens az ott megadott port-on próbál kapcsolódni, de utána megbeszélnek egy port-ot, melyen a további kommunikáció történik. Mivel itt a szerver választ egy következő szabad port-ot, ezt is át kéne engedni, vagy kihozni, csakhogy ez nem definit.

Egy működő megoldás volt, hogy a JMX klienst az adott szerveren indítjuk el. Webes eszköz esetén böngészőből lehetett csatlakozni, vastag kliens esetén azonban az X-et kellett ssh-n keresztül kihozni. Egyik sem szerencsés megoldás, hiszen viszonylag sok konfigurációt igényel.

A JDK dokumentációja szerint megvalósítható az, hogy rákényszerítjük a JDK-t arra, hogy az RMIServer-t, melyben a távolról meghívható RMI objektumok vannak, egy már általunk a megadott porton létrehozott RMI registry-be regisztrálja be. Ezt Mimicking Out-of-the-Box Management-nek hívja. Tehát ehhez el kell indítanunk egy RMI registry-t, majd létre kell hoznunk egy JMXServiceURL példányt, valamint egy JMXConnectorServer-t, melynek átadjuk ezt. A környezeti változókat nem írva, ez kb. így néz ki:

LocateRegistry.createRegistry(3000);
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
JMXServiceURL url = 
  new JMXServiceURL("service:jmx:rmi://localhost:3001/jndi/rmi://localhost:3000/jmxrmi");
JMXConnectorServer cs = JMXConnectorServerFactory
  .newJMXConnectorServer(url, env, mbs);
cs.start();

Ekkor a 3000-es porton indul el az RMI registry, és a 3001-esen az RMI server.

De nekünk arra van szükségünk, hogy a Tomcat végezze el ezt. A Tomcat 6.0.24-es verziójától jelent meg az extras csomagban a org.apache.catalina.mbeans.JmxRemoteLifecycleListener osztály. Ehhez a letöltésnél az extras könyvtárból le kell töltenünk a catalina-jmx-remote.jar állományt, és elhelyeznünk a $CATALINA_HOME/lib könyvtárunkban. Ha ez megvan, akkor a következő konfigurációt kell beillesztenünk a server.xml állományba:

<Listener 
  className="org.apache.catalina.mbeans.JmxRemoteLifecycleListener" 
  rmiRegistryPortPlatform="3000" 
  rmiServerPortPlatform="3001" />

Amennyiben ez megvan, az RMI registry a 3000-es, az RMI server a 3001-es port-on fog elindulni, hasonlóan az előző példához. Innentől kezdve ha a következő URL-t adjuk meg a JMX kliensünkben, és átengedjük a két port-ot a tűzfalon, vagy SSH tunnel-lel kihozzuk, láthatjuk a Tomcat MBean-jeit.

service:jmx:rmi://localhost:3001/jndi/rmi://localhost:3000/jmxrmi

2010. július 27., kedd

Spring Recipes: A Problem-Solution Approach

Egyikőtök hívta fel a figyelmem a Spring Recipes: A Problem-Solution Approach könyvre a Spring In Action könyvről szóló post-om után. Mivel a könyv a Spring In Action-höz képest más megközelítést alkalmaz, és a Spring 2.5-t is lefedi szemben az előzővel, kíváncsiságból elolvastam. Jelen post-omban próbálok azoknak is csemegét nyújtani, akiket nem érdekel a könyvismertető, ezért megpróbálom kérdés-felelet formájában ismertetni az érdekesebb részeket, így mindenki ellenőrizheti kicsit a Spring-es tudását.

Spring Recipes könyvborító

A Spring Recipes könyv tematikája szinte teljesen megegyezik a Spring In Action könyv tematikájával, a tartalmi különbségek főleg a a 2.5-ös Spring verzió újdonságaiból adódnak, valamint a más megközelításből. Ugyanis a könyv felépítése úgy néz ki, hogy minden részben felvet egy problémát (Problem), aminek leírja a megoldását (Solution), valamint leírja ábrákkal, kóddal illusztrálva, hogy hogyan is működik (How It Works). Ebből adódik, hogy nem egy-két példát visz végig, mint a Spring in Action könyv, hanem mindig újat vesz elő. Ezzel néha feleslegesen telik a hely.

Szerintem nagyon hasznos, hogy az első fejezet a konténer, interfészekkel való programozás, service locator, Inversion of Control, Dependency Injection és a konténer konfigurációs állománnyal való konfigurációját úgy mutatja be, hogy a Java SE eszközeivel (Spring nélkül) le is programoz egy megoldást. Ez nagyon hasznos lehet azoknak, akik nem az elméleti oldalról szeretnék megközelíteni ezen fogalmakat, hanem látni akarnak egy működő példát, gyakorlatilag ebből kiderül, hogy hogyan is indulhatott a Spring, mi is lehet a motorháztető alatt.

Következő fejezetben kitér a Spring IDE használatára is. Tapasztalatom alapján a Spring IDE már nem nagyon érhető el, a website-ja is áll, a SpringSource is a SpringSource Tool Suite már ingyenesen használható eszközét preferálja. Ki is próbáltam, de számomra olyan minősíthetetlen lassú volt, hogy visszaálltam NetBeans-re.

A 2007 novemberében kiadott Spring 2.5 a következő újításokat hozta, melyekre ki is tér a könyv.

  • Bean-ek konfigurációja annotációkkal, mint az @Autowired, vagy a JSR-250 által bevezetett @Resource, @PostConstruct, @PreDestroy annotációk.
  • Component scanning: stereotype annotációkkal jelölt bean-ek automatikus feltérképezése, konfigurációja.
  • AspectJ load-time weaving: AspectJ osztálybetöltés közben képes az osztályainkat buherálni, így elérhető, hogy nem a konténerben definiált bean-ekre is működjön az AOP, valamint a teljes AspectJ arzenált bevethessük.
  • Kontrollerek konfigurációja annotációkkal: talán a legnagyobb újdonság, mely a Spring MVC-t érinti. Már nem kell a kontrollereknek Spring-es Controller osztályokból leszármaznia, nem kell azok metódusait ismerni, hanem sokminden rugalmasan annotációkkal is megadható.
  • Spring TestContext framework: annotációk használata a teszt eseteknél, úgy, hogy akár el is szakadhatunk a konkrét teszt implementációktól.

Nézzük, hogy milyen kérdéseket válaszol meg ez a könyv, melyeket a Spring In Action nem említett, a Spring alapelemeivel kapcsolatban:

Constructor Ambiguity, azaz hogyan lehet a túlterhelt (overload) konstruktorok közül kiválasztani a nekünk megfelelőt a konstruktor alapú dependency injection-nél?
Az applicationContext.xml-ben használhatjuk a constructor-arg tag-nek a type és index attribútumát, ahol az elsőnek lehet megadni a konstruktor típusát, másodiknak a pozícióját. Ezzel egyértelműen definiálható a konstruktor szignatúra, így nem esik a Spring abba a hibába, hogy a property editor-ok miatt nem tud választani a konstruktorok közül, hiszen mindre tudna konvertálni.
Hogyan állítható be, hogy ellenőrizze a Spring, hogy bizonyos attribútumainkra megtörtént-e a dependency injection (dependency-checking)?
Egyrészt bean szinten is beállíthatunk dependency-check attribútumot (none, simple, objects, all - egyszerű típusok - primitívek és Collection-ök - és objektumok tetszőleges kombinációjára). Másrészt használhatjuk a @Required annotációt.
Milyen annnotációkkal lehet a dependency injection-t kérni?
Vagy a Spring-es @Autowired vagy a JSR-250-es @Resource annotációval.
Mi van, ha egy interfész több implementációját is le akarjuk kérni, valamint lehet-e választani, hogy melyikre van szükségünk.
A típusnál megadhatunk interfész tömböt vagy collection-t is, ekkor az összes implementáló bean-t injektálni fogja a Spring. Amennyiben válogatni akarunk, egyrészt megtehetjük a @Qualifier annotációval, vagy a @Resource annotáció name attribútumával.
Szülő-gyerek bean-nél lehet-e egy collection elemeit a szülőnél és a gyermeknél is meghatározni?
Igen, lehetőség van a szülő bean-nél deklarált collection elemeinek merge-ölésére a gyermek bean-nél (merge="true" attribútum).
Lehet-e szabályozni, hogy a collection mely implementációja legyen példányosítva, valamint az elemeknek mi legyen a pontos típusa?
A kollekcióknál a type attribútummal adható meg a konkrét implementáció (pl. ha rendezettet akarunk, akkor egy SortedSet), az elemeknél a value-type-pal adható meg a típus. Amennyiben a bean implementációban generikust használunk, a konverzió automatikusan megtörténik.
Van-e a collection-ök megadására rövidebb mód?
A util sémát kell használni, mellyel rövidebben adhatóak meg az elemek, és a konfigurációs állomány is séma alapján validálható.
Állhat-e bean-ként önmagában egy collection?
A util sémát nem csak bean property-n belül használhatjuk, hanem bean szinten is, ekkor egy id attribútumot kell neki adni, amivel aztán a ref-ben hivatkozhatunk rá (un. stand-alone collection).
Hogyan adhatunk meg bean-eket annotációval?
A @Component annotációt használhatjuk, aminek attribútumként a bean nevét is megadhatjuk. Ahhoz, hogy a Spring fel is olvassa ezeket, a konfig állományban a context:component-scan tag-et kell használnunk. Különböző rétegeknek megfelelően használhatjuk a @Repository és @Service annotációkat is. A felolvasást filter-elhetjük is a minősített osztálynévre (FQN - fully qualified name - csomag és osztálynév), reguláris kifejezéssel.
Mivel lehet egy bean-t egy osztály statikus attibútuma alapján definiálni?
A FieldRetrievingFactoryBean osztályt használhatjuk.
Mivel lehet egy bean-t egy másik bean attibútuma alapján definiálni?
A PropertyPathFactoryBean osztályt használhatjuk.
Hogyan tölthetünk be egy külső erőforrást, állományt?
ResourceLoader-t alkalmazhatunk, melyre referenciát a ResourceLoaderAware interfész implementálásával kaphatunk, mely a Resource címe alapján különböző helyről töltheti be azt (pl. ClassPath, fájl, URL, stb.). Természetesen itt is működik a Dependency Injection is.

A Spring In Action könyv szerintem sokkal érthetőbben, nagyon jó ábrával magyarázza el a bean-ek életciklusát, ezt hiányoltam ebből a könyvből. Persze a Spring Recipes viszont ír a 2.5-ben megjelent @PostConstruct, @PreDestroy annotációkról, melyekhez a annotation-config tag is kell a konfig állományban. Valamint a script-elés is külön fejezetet kapott, ahol megemlíti a lang sémát is.

Az AOP rész leírása, elméleti háttere is jobb a Spring In Action könyvben, viszont nem hangsúlyozza eléggé, hogy hol a határ a régi, Classic Spring AOP és az új, 2.x-ben használt AOP között. Persze mindkettő mögött a JDK Dynamic Proxy van, melyet a Spring Recipes szintén bemutat, első körben még Spring nélkül (naplózásra, validálásra). Szépen kifejti, hogy az AspectJ használata a POJO-kon, akár az xml konfiguráció, akár az annotációval végzett konfiguráció (melyet mellesleg preferál az xml-lel szemben) még nem jelenti a teljes AspectJ-s arzenált (pl. csak a konténerben definiált bean-ekre lehet használni). Az AspectJ pointcut-okat deklaráló nyelvét is jobban leírja. Ír az advice precedence-ről (Ordered interface, @Order annotáció). Részletesen ír az introduction-ről is, tudunk futásidőben mellyel interfészeket adni egy bean-hez, és implementálni azokat úgy, hogy implementáló osztályokat adunk meg. Ennek hatása gyakorlatilag megegyezik azzal, mintha többszörös öröklődésünk lenne. Az implementáló osztály akár attribútumokat is tartalmazhat, ezeket state-nek nevezi.

Amennyiben az AspectJ többi részét is ki akarjuk használni, pl. AspectJ-s aspect-ek, additional pointcut types, konténeren kívüli bean-ek AOP-ozása, Load-Time Weaving-et kell alkalmaznunk. Ehhez a context:load-time-weaver tag használata szükséges az xml konfigurációban, valamint a -javaagent kapcsoló a JDK-nak.

Egy nagyon gyakorlatias fejezet azt mutatja be, hogy mit kell tennünk, ha Domain Driven Design alapján pl. Domain Object-be akarunk Dependency Injection-t. (Ugye itt az a probléma, hogy nincs a konténerben a példányunk.) Ekkor kell a context:spring-configured tag az xml konfigurációban, valamint a @Configurable annotáció.

Az adatbázis kezelést bemutató részben nem sok különbség van, a Spring Recipes annyiban több, hogy leírja a PreparedStatementCreator, (Batch)PreparedStatementSetter interfészek használatát, valamint a operation object-ek kezelését, valamint egy gyakorlatias példát hoz arra, hogy hogy hozzunk létre saját kivételt egy adatbázis specifikus hibához. Nagy hibája, hogy nem ír a pool-ozásról, míg a Spring In Action szépen leírja az Ehcache konfigurációját (később ez a könyv is megemlíti a Security részben). A Spring In Action elméleti háttere a tranzakciókezelésnél szintén jobb, de a Spring Recipes az izolációs szintekre kódot mutat, amivel jobban megérthető. Itt is megemlíti a Load-Time Weaving-et (tranzakciókezelés nem publikus metódusokban, nem a konténerben lévő bean-ekben).

A Spring MVC fejezetben a Spring In Action megint jobb alapozást ad, szépen felsorolja az összes interceptor-t. A Spring Recipes ezzel szemben saját interceptor megvalósítását is leírja (HandlerInterceptor), valamint leírja, hogyan választja ki a Spring MVC a Locale-t (LocaleResolver), és hogyan lehet Locale-t váltani (LocaleChangeInterceptor) nemzetköziesített alkalmazásoknál. Ír a HandlerExceptionResolver-ről a kivéltelek kezelésére, valamint a ParameterizableViewController-ről, ahol nem kell a view neveit a Controller-be égetnünk. Nagy piros pont, hogy leírja, hogy kell a Post/Redirect/Get Design Pattern-t alkalmazni. Írja, hogy hogyan használjuk a MultiActionController-t, ha egy Controller-be több műveletet akarunk lekezelni (pl CRUD alkalmazásnál a különböző képernyők, vagy egy form-on több gomb). Viszont a Spring beépített validációján kívül nem ír sem a 3rd party commons, sem a valang validációról. Viszont részletesen ír a Spring 2.5 újdonságáról az annotációkkal való Controller fejlesztésről, valamint nekem új volt a SessionStatus is.

A Spring In Action viszont említi a következő témákat, amikről a Spring Recipes nem ír: Tiles, Velocity, Freemarker integráció, RSS tartalom gyártása, Struts2, Tapestry integráció.

A Spring In Action a tesztelést csak függelékben említi, és az annotációkat még csak egy külön projekttel, a Gienah Testing-gel ismertette. A Spring Recipes részletezi a JUnit 3.x, 4.x és TestNG tesztelést is (@RunWith(SpringJUnit4ClassRunner.class), @ContextConfiguration). Külön megemlítendő a @IfProfileValue annotáció, amivel profilokat tudunk deklarálni JUnit esetén is, hasonlóan a TestNG-hez.

A Spring Security leírásánál ismerteti a saját névteret, saját AccessDecisionVoter-t implementál az ip-cím ellenőrzésére, sőt az ACL-t is részletesen kifejti. Persze nem a 3-asban bevezetett Expression Language-t használja, hanem after invocation providers-eket.

A Spring Web Flow-nál részletezi a Spring Security integrációt, a perzisztenciát és a JSF-fel való integrációt is, de nem szól a Struts integrációról.

A Spring-WS-nél már ír az annotációkról, valamint hogy hogyan lehet Marsheller-t váltani.

Az EJB integrációnál csupán a JNDI-ből való lekérést írja le. A Spring In Action használja a PitchFork-ot, ami egy pehelysúlyú konténer, mely implementálja az EJB IoC-t és interception-t.

JMS-nél szintén ismerteti a jms séma használatát. Viszont a Spring In Action-ben nagyon érdekes rész volt a Lingo integráció, mellyel úgy tudtunk metódust hívni, hogy alatta észrevétlenül JMS volt az átviteli protokoll. Ez a Spring Recipes könyvben nem szerepel.

JMS esetén új tag a context:mbean-export a konfig xml-ben.

Levélküldésnél érdekes, hogy leírja, hogy kell belőni a JAMES Java-ban írt mail szervert, valamint mime levelet is küld (nem sima szöveg, hanem HTML tartalom - akár CSS-sel, képekkel).

Ütemezésnél leírja, hogy kell Task-ot, Quartz Job-ot hívni, de nem írja, hogy kell egy egyszerű metódust hívni ütemezetten.

Összességében elmondható, hogy a Spring Recipes könyv már haladó Spring fejlesztőknek szól inkább. Jobban referencia jellegű, és felépítéséből adódóan gyakrabban leveszi az ember a polcról, ha valami problémába ütközik. A Spring In Action ezzel szemben jobban elméleti jellegű, segítségével megérthetjük, hogy a Spring fejlesztésben honnan indultak, hogyan jutottak el egy-egy megoldásig, milyen döntések születtek közben, stb. Inkább kezdőknek javaslom, vagy akit érdekel a mélyebb elméleti áttekintés. A részletes leírások miatt tapasztalatlanabb programozónak működő kódig eljutnia is egyszerűbb a Spring In Action használatával.

De itt van már a Spring 3, melyet a Spring Recipes könyv augusztusra ígért második kiadása fog lefedni (ilyen izgalmas témaköröket ígér, mint Spring Batch, jBPM integráció, Terracotta és GridGrain integráció, Spring Roo, Grails Framework (és Groovy) integráció, REST web szolgáltatások, OSGi használata Spring Dynamic Modules-szal és SpringSource dm Server - ami közben beolvadt az Eclipse Virgo projektbe.)

És még Spring oktatás is lesz augusztus 3-6 között Budapesten.

2010. július 11., vasárnap

Spring Security 3 ACL

A Spring Security-ről már volt szó egy korábbi post-ban, ebben a cikkben az üzleti objektumok biztonságáról (Domain Object Security) írok, melyet a Spring Security ACL-ekkel old meg, valamint megemlíteném a Spring Security 3 egyik fő újdonságát.

A post írásának pillanatában a Spring Security legújabb verziója a 3.0.3.RELEASE, mely már az előző post-hoz is képes is hozott konfigurációs változtatásokat. A projekt dokumentációjában is van pár hiba a friss kiadásban.

A Spring Security többek között deklaratív autentikációt (durván bejelentkezés, azonosítás) és autorizációt (jogosultságkezelés) biztosít. Ez utóbbit webes alkalmazásoknál úgy használjuk ki, hogy URL pattern-eket, védett erőforrásokat határozunk meg, és ezekhez szerepköröket rendelünk, így ezen oldalakat csak azon felhasználók tudják megnézni, akik rendelkeznek az elvárt szerepkör(ök)kel. Lehetőség van ezen kívül metódushívás szintű jogosultságkezelés megvalósítására is, ilyenkor azt tudjuk megmondani, hogy az adott metódust milyen szerepkörrel rendelkező felhasználó tudja meghívni. Amennyiben a felhasználó nem rendelkezik a megadott szerepkörrel, kivétel keletkezik.

Azonban ez sok esetben nem elegendő, komolyabb üzleti alkalmazásnál szükségünk lehet arra, hogy megmondjuk, hogy melyik üzleti objektumon, melyik felhasználó (vagy milyen szerepkörrel rendelkező felhasználó), milyen műveleteket tudjon elvégezni. Pl. vegyünk egy szerkesztőségi rendszert, ahol minden cikkre megmondhatjuk, hogy melyik felhasználó tudja az adott cikket megtekinteni, ki tudja szerkeszteni, esetleg törölni.

Ezt persze a már említett post-ban lévő eszközökkel is meg lehet valósítani. Pl. lehet az üzleti metódusban lekérni a SecurityContextHolder.getContext().getAuthentication() metódussal a bejelentkezett felhasználót, és a szerint végezni el az üzleti műveletet. Amennyiben szépen akarjuk csinálni, ezt akár AOP-vel is csinálhatjuk, és külön kódba szervezhetjük ki. Ennek használatával szoros lesz a kapcsolat az üzleti logika és a jogosultág-ellenőrzés között, kevésbé lesz átlátható, újrafelhasználható. Csinálhatjuk azt is, hogy saját AccessDecisionVoter és GrantedAuthority implementációkat használunk, és minden üzleti objektumon végezhető művelethez egy GrantedAuthority tartozik. Itt az üzleti objektumok elszaporodásával a GrantedAuthority példányok száma is megnövekszik. Valamint az AccessDecisionVoter is hozzáférhetne az üzleti objektumokhoz, és ez alapján dönthetne a jogosultságról. Ilyenkor kell neki egy dao referencia, és minden műveletnél az AccessDecisionVoter is el fog végezni egy plusz műveletet. Azonban minden esetben meg kell oldanunk a jogosultságok perzisztálását, valamint az üzleti logikát is módosítanunk kell.

Szerencsére a Spring Security beépített megoldást ad a problémára, melynek neve a Domain Object Security, melyet ACL-ek (access control list), hozzáférési listákkal valósít meg. Az ACL-ek használata a számítástechnikában máshol is igen elterjedt. Egy ACL egy erőforráshoz tartozik, egy lista, ami tartalmazza, hogy az adott erőforráson mely felhasználók milyen műveleteket végezhetnek. Tipikus példa erre a fájlrendszerek, ahol minden fájlhoz/könyvtárhoz tartozik egy ACL, mely leírja, hogy mely felhasználók és csoportok olvashatják/írhatják/futtathatják (listázhatják) az adott fájlt/könyvtárat. Az ACL elemei az ACE-k (access control entry), mely egy sor az ACL-ben.

Nézzük, a Spring Security milyen fogalmakat definiál, és ezekhez milyen interfészek kapcsolódnak. Először is egy UML diagram.

Forrás: Grzegorz Borkowski: Spring Security ACL - very basic tutorial.

A Spring Security alapban biztosít egy Spring JDBCTemplate-tel megvalósított perzisztenciát is a Domain Object Security-hez, mely a legtöbb adatbáziskezelőn működik, hiszen ANSI SQL utasításokat használnak benne. Így az interfészeknél leírom, hogy példányait milyen táblában tárolja a Spring Security.

  • A fő interfész az Acl, mely a listát tartalmazza egy üzleti objektumhoz. Ahogy említettem, egy üzleti objektumhoz egy Acl tartozik, és ez tárolja a listaelemeket, melyek felhasználó/művelet párosok, és egy ilyen elem adja meg, hogy az adott üzleti objektumra van-e az adott felhasználónak jogosultsága. Az Acl-ek fa hierarchiát alkothatnak, így a szülőre is tartalmaz egy referenciát (parent). Az Acl isGranted metódusát hívva lehet eldönteni, hogy az adott felhasználó(k)nak van-e joga az adott üzleti objektumon a kérdéses műveletet elvégeznie. Az első paramétere a Permission lista, melyben a műveleteket adhatjuk meg, a második paramétere a felhasználók listája, és a harmadik paramétere, hogy kell-e audit naplózni a műveletet, vagy adminisztratív okokból történt az esemény, ekkor nem kell naplózni. Erre választ a kapcsolódó példányok alapján tud választ adni. Valamint tartalmaz egy referenciát a tulajdonos felhasználóra is (owner).
  • A felhasználót Sid-nek nevezi (Security Identity), ugyanis lehet principal név (ami vagy egy személyhez, vagy egy rendszerhez kapcsolódó felhasználónév), vagy lehet szerepkör is, azaz a Spring Security szóhasználatban GrantedAuthority név. A Sid-en kívül a másik gyűjtőnevük a recipient. Így a Sid interfésznek két leszármazottja is van, PrincipalSid, valamint GrantedAuthoritySid.
  • Az Acl nem tartalmaz közvetlen referenciát az üzleti objektumra, hanem helyette egy ObjectIdentity példányra. Ennek két attribútuma az üzleti objektum osztálya (javaType), valamint egyedi azonosítója (identifier). Ennek egy implementációja a ObjectIdentityImpl. A Domain Object Security úgy lett megvalósítva, hogy megfelelően kezeli a Long, vagy Long-gá konvertálható egyedi azonosítóval rendelkező üzleti objektumokat. Amennyiben nem ilyen üzleti objektumaink vannak bizonyos interfészeket magunknak kell implementálnunk. A Spring Security készítői később sem kívánják támogatni a Long-tól eltérő egyedi azonosítókat. (Én személy szerint amúgy is mindenkinek javaslom, hogy minden üzleti objektumnak legyen egy Long egyedi azonosítója, és kerüljük pl. szöveget, dátumot tartalmazó, valamint az összetett elsődleges kulcsokat.
  • Az Acl ACE elemeket tartalmaz, melyeket az AccessControlEntry interfész reprezentálja. Ez tartalmaz egy referenciát egy műveletre (Permission), valamint egy Sid-re, van egy egyedi azonosítója, valamint egy granting attribútuma, ami azt mondja meg, hogy az adott jogosultság aktív-e, vagy visszavonásra került.
  • Lehet saját Permisson megvalósítást is alkalmazni, de a Spring Security alapértelmezetten tartalmazza a BasePermission osztályt, mely a következő műveleteket definiálja: CREATE, READ, WRITE, DELETE, ADMINISTRATION, a legtöbb esetben ez is elég lehet.

A Spring Security ezen interfészek példányaihoz, illetve a példányok által ábrázolt információkhoz a AclService interfészen keresztül enged hozzáférést, ez egy normál service osztály, melyet a szokásos módon deklarálhatunk az applicationContext.xml-ben, és kérhetünk rá referenciát dependency injection-nel. Ennek a readAclById és readAclsById metódusaivel férhetünk hozzá az Acl-ekhez. Nem mindig használjuk ezt direktben, később látni fogjuk, hogy lehet deklaratív módon használni.

Az AclService leszármazottja a MutableAclService, mely már módosítási műveleteket is megenged az Acl-eken. Ugyanis ezen alkalmazásokban már nem lehet deklaratív módon felvenni az Acl-eket, hiszen maguk az üzleti objektumok is folyamatosan változnak, jönnek létre és szűnnek meg, ezért az üzleti logikának kell arról is gondoskodnia, hogy az Acl-eket is az üzleti objektumokkal együtt módosítsa. Az AclService egyik implementációja a JdbcAclService, a MutableAclService implementációja a JdbcMutableAclService, mely ANSI SQL műveletekkel kezeli az Acl-ek perzisztenciáját relációs adatbázisokban. A JdbcMutableAclService a lekérdezéseket egy LookupStrategy implementációnak delegálja, mely az adatbázisra optimalizált lekérdezést tartalmazhatja. Ennek egy implementációja megtalálható a Spring Security-ban is BasicLookupStrategy néven. Ha az adott adatbázis speciális lehetőségeit (pl. hierarchikus lekérdezések, materializált nézet, reduce normalization) ki akarjuk használni, saját osztályt kell írni, mely implementálja a LookupStrategy interfészt. A BasicLookupStrategy nem támogatja a leszármaztatást. Mind a JdbcMutableAclService-nek, mind a BasicLookupStrategy-nek szüksége van egy DataSource-ra.

Ahogy említettem a jogosultság ellenőrzéséhez használhatnánk az AclService megfelelő metódusait, de van egy egyszerűbb deklaratív módszer is, mely a Spring Security 3-asban jelent meg, és erősen támaszkodik a Spring 3 Spring Expression Language-ére. Ezt Expression-Based Access Control-nak nevezi, és akár XML leíróban (security namespace), akár annotációban (@Pre és @Post) is alkalmazhatjuk. Ebből az ACL is elérhető, és pl. deklarálni lehet, hogy az adott metódus hívása előtt vagy után milyen üzleti objektumon (metódus paraméter vagy visszatérési érték), milyen műveletre való jogosultságot kell ellenőrizni. Valamint lehetőség van filterezésre is, ami pl. egy metódus visszatérési értékeként szereplő listából eltávolítja azon üzleti objektumokat, melyekre a bejelentkezett felhasználónak nincs meg a megfelelő műveletre a megfelelő jogosultsága. Persze a háttérben ebből ugyanúgy egy Acl lekérdezés, majd metódushívás lesz.

A Domain Object Security ezeken felül a következőket biztosítja:

  • Caching: Ehcache backend-del
  • Transzparens adatbázis műveletek
  • Perzisztencia úgy megvalósítva, hogy minimális legyen a deadlock valószínűsége
  • ORM-től való függetlenség, hiszen plain JDBC van a háttérben
  • Egységbezárás

Most, hogy ismerjük a Java interfészeket, nézzük, hogy történik a kapcsolódó objektumok perzisztenciája.

Egy ACL_OBJECT_IDENTITY sor tartozik minden egyes üzleti objektumhoz. Ez tartalmaz egy külső kulcsot az ACL_CLASS táblára, mely tartalmazza az üzelti objektum osztályának teljes nevét (fully qualified name - csomaggal együtt). Egy ACL_OBJECT_IDENTITY sorhoz több ACL_ENTRY sor tartozhat, mely összeköti az üzleti objektumot, a Sid-et, valamint a MASK mezőben (32 biten) tárolja, hogy mely műveletek megengedettek. Ebből pl. a BasePermission csak 5 bitet használ fel, hiszen 5 műveletet definiál (read - bit 0, write - bit 1, create - bit 2, delete - bit 3 és administer - bit 4). Az ACL_SID tábla tartalmazza a Sid-eket, vagyis a felhasználók vagy szerepkörök neveit.

Bár a disztribúció tartalmaz egy példa alkalmazást (spring-security-samples-contacts-3.0.3.RELEASE.war), forrással (spring-security-samples-contacts-3.0.3.RELEASE-sources.jar), készítettem én is egyet, mely talán egy kicsit egyszerűbb. A teljes alkalmazás letölthető innen.

Az alkalmazás Maven-nel build-elhető. Nem webes alkalmazás, mint a legtöbb ilyen példa, hanem egy egyszerű Java alkalmazás, hiszen itt nagyon kevés web alkalmazás specifikus megoldás van. A különböző funkciókat JUnit teszt esetek tesztelik, szóval a mvn test parancs kiadásával fordítható és tesztelhető a projekt.

A függőségekkel kapcsolatban nem másolnám ide a pom.xml megfelelő részét, akit érdekel, megnézheti itt. Amit érdemes megemlíteni, hogy az ACL-lel kapcsolatos interfészek és osztályok külön JAR-ba kerültek, spring-security-acl-3.0.3.RELEASE-sources.jar néven. Szükség van még az Ehcache-re is, és a teszteléshez a spring-test-3.0.3.jar-ra, valamint a HyperSQL-re.

Az alkalmazás egy egyszerű szerkesztőségi rendszer, melyben cikkeket lehet felvenni, és be lehet állítani, hogy melyik cikken melyik felhasználó milyen műveletet végezhet. Az entitás az Article osztály, a service interfész a ArticleService, melynek egy megvalósítása a ArticleServiceImpl. Az egyszerűség kedvéért egy Map-ben tárolja el a cikkeket. Az alkalmazás felépítése az UML diagram alapján egyértelmű.

Az alkalmazás egy applicationContext.xml-lel rendelkezik, de bonyolultabb alkalmazás esetén javasolt a biztonsággal kapcsolatos konfigurációt egy másik (applicationContext-security.xml) állományba kiszervezni. Az adatforrás (DataSource) és a tranzakciókezelő beállításait külön XML-be szerveztem (applicationContext-test.xml), hiszen feltehetőleg egy éles alkalmazás komolyabb adatbáziskezelővel futna, csak a teszt esetek futnak HyperSQL-lel.

Az ACL használatához először létre kell hozni a fentebb bemutatott táblákat. A létrehozó script megtalálható a dokumentációban HyperSQL-re és PostgreSQL-re, de megtalálhatóak a spring-security-acl-3.0.3.RELEASE-sources.jar-ban is createAclSchema.sql néven. A példában a DataSourcePopulator implements InitializingBean futtatja le az inicializáló műveleteket, azaz a táblák létrehozásáért felelős SQL-eket.

A következő lépésként hozzuk létre az applicationContext.xml állományt.

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.3.xsd">

<bean id="dataSourcePopulator" class="jtechlog.acltutorial.DataSourcePopulator" />

<bean id="articleService" class="jtechlog.acltutorial.ArticleServiceImpl" />

<security:global-method-security pre-post-annotations="enabled">
<security:expression-handler ref="expressionHandler"/>
</security:global-method-security>

<bean id="expressionHandler"
 class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler">
<property name="permissionEvaluator" ref="aclPermissionEvaluator"/>
</bean>

<bean id="aclPermissionEvaluator" class="org.springframework.security.acls.AclPermissionEvaluator">
<constructor-arg ref="aclService" />
</bean>

<bean id="aclCache"
 class="org.springframework.security.acls.domain.EhCacheBasedAclCache">
<constructor-arg>
   <bean class="org.springframework.cache.ehcache.EhCacheFactoryBean">
       <property name="cacheManager">
           <bean class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" />
       </property>
       <property name="cacheName" value="aclCache" />
   </bean>
</constructor-arg>
</bean>

<bean id="lookupStrategy"
 class="org.springframework.security.acls.jdbc.BasicLookupStrategy">
<constructor-arg ref="dataSource" />
<constructor-arg ref="aclCache" />
<constructor-arg>
   <bean
       class="org.springframework.security.acls.domain.AclAuthorizationStrategyImpl">
       <constructor-arg>
           <list>
               <!-- authority for taking ownership -->
               <bean class="org.springframework.security.core.authority.GrantedAuthorityImpl">
                   <constructor-arg value="ROLE_ADMIN" />
               </bean>
               <!-- authority to modify auditing -->
               <bean class="org.springframework.security.core.authority.GrantedAuthorityImpl">
                   <constructor-arg value="ROLE_ADMIN" />
               </bean>
               <!-- authority to make general changes -->
               <bean class="org.springframework.security.core.authority.GrantedAuthorityImpl">
                   <constructor-arg value="ROLE_ADMIN" />
               </bean>
           </list>
       </constructor-arg>
   </bean>
</constructor-arg>
<constructor-arg>
   <bean class="org.springframework.security.acls.domain.ConsoleAuditLogger" />
</constructor-arg>
</bean>

<bean id="aclService"
 class="org.springframework.security.acls.jdbc.JdbcMutableAclService">
<constructor-arg ref="dataSource" />
<constructor-arg ref="lookupStrategy" />
<constructor-arg ref="aclCache" />
</bean>

</beans>

Az első két bean a saját bean-jeink. A dataSourcePopulator hozza létre a táblákat, az articleService pedig a saját service osztályunk. A következő global-method-security tag pre-post-annotations attribútuma mondja meg, hogy annotációkban adjuk meg a biztonsági beállításokat. Amennyiben nem használunk ACL-t, ennyi elegendő is, de esetünkben meg kell adni azt is, hogy Expression-Based Access Control-t használunk, és használni akarjuk majd a hasPermission függvényt, mellyel az ACL alapján lehet majd jogosultságot ellenőrizni. Erre kell a expressionHandler és a aclPermissionEvaluator is. Az utóbbi dolga a expression system és a Spring Security's ACL system közötti híd megépítése. A aclCache bean állítja be, hogy az ACL az Ehcache-t használja cache-eléshez. Majd megadjuk az ACL-ek lekérdezéséhez szükséges lookupStrategy bean-t (használja a cache-t és a DataSource-ot is). Valamint konstruktorának első paramétere egy AclAuthorizationStrategyImpl, mely azt adja meg, hogy milyen jogosultságok szükségesek ahhoz, hogy módosítani lehessen az ACL tulajdonosát, módosítani az audit naplózás beállíásokat, valamint módosítani egyéb ACL és ACE beállításokat. A második paramétere egy AuditLogger, melynek egyetlen beépített implementációja a ConsoleAuditLogger. Az utolsó bean a aclService, melyet az alkalmazásunkból is használni fogunk.

A applicationContext-test.xml állományban állítjuk be az adatforrást, a tranzakciókezelőt, valamint azt, hogy a tranzakció konfigurációja annotációkban történjen.

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">

<tx:annotation-driven />

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
<property name="url" value="jdbc:hsqldb:mem:test"/>
<property name="username" value="sa" />
<property name="password" value="" />
</bean>

<bean id="transactionManager"
 class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>

</beans>

Elengedhetetlen az ACL használatához a tranzakciós környezet, hiszen az ACL-ek adatbázisba kerülnek mentésre (JdbcTemplate-tel).

Nézzük először, hogy hogyan történik az üzleti objektumhoz ACL rendelése, ehhez nézzük meg a grantPermission metódus implementációját.

@Override
@Transactional
public void grantPermission(String principal, Article article, Permission[] permissions) {
LOGGER.info("Grant {} permission to principal {} on article {}", new Object[]{permissions, principal, article});
ObjectIdentity oi = new ObjectIdentityImpl(Article.class, article.getId());
Sid sid = new PrincipalSid(principal);

MutableAcl acl = null;
try {
acl = (MutableAcl) aclService.readAclById(oi);
} catch (NotFoundException nfe) {
acl = aclService.createAcl(oi);
}

for (Permission permission : permissions) {
switch (permission) {
 case READ:
  acl.insertAce(acl.getEntries().size(), BasePermission.READ, sid, true);
  break;
 case WRITE:
  acl.insertAce(acl.getEntries().size(), BasePermission.WRITE, sid, true);
  break;
}
}
aclService.updateAcl(acl);
}

Először példányosítunk az üzleti objektum alapján egy ObjectIdentityImpl példányt. Majd lekérdezzük, ha nincs, létrehozunk hozzá egy Acl-t. Majd az Acl-be felveszünk egy Ace-t, sorrendben a végére, mely tartalmaz egy referenciát a BasePermission.READ műveletre, valamint a paraméterként átadott felhasználóra. Azaz a ArticleServiceTest teszt osztályban a következő sor (@Before annotációval ellátott init() metódusban található) a user1 felhasználónak megadja a jogosultságot, hogy az 1-es azonosítójú cikket szerkeszteni és módosítani tudja.

articleService.grantPermission("user1", article1, new ArticleService.Permission[]{ArticleService.Permission.READ, ArticleService.Permission.WRITE});

A következő lépésben meg kell valósítani a jogosultságkezelést. Ez történhet kódból is, javasolt AOP-vel, pl. metódushívás előtt AccessDecisionVoter használata, metódushívás után AfterInvocationProvider használata. Ezekben az aclService használatával le kell kérni az üzleti objektumhoz tartozó Acl-t, majd annek kell meghívni a isGranted metódusát. De vannak erre az ACL-ben megfelelő osztályok is, mint az AclEntryVoter, AclEntryAfterInvocationProvider vagy a AclEntryAfterInvocationCollectionFilteringProvider. De sokkal egyszerűbb a Expression-Based Access Control használata, amit használhatunk XML konfigurációban is a security:intercept-url access paraméterének, vagy a protect-pointcut expression paraméterének, vagy a @PreAuthorize, @PostAuthorize, @PreFilter, @PostFilter annotációk paraméterének. Én az utóbbit, az annotációk használatát javasoltam, ezért kellett a global-method-security pre-post-annotations attribútuma az applicationContext.xml-ben.

Nézzük is a findArticleById és updateArticle metódusok implementációját.

@Override
@PreAuthorize("hasPermission(#id, 'jtechlog.acltutorial.Article', 'read') or hasRole('ADMIN')")
public Article findArticleById(long id) {
return articles.get(id);
}

@Override
@PreAuthorize("hasPermission(#article, 'write') or hasRole('ADMIN')")
public void updateArticle(Article article) {
articles.put(article.getId(), article);
}

Az első PreAuthorize annotációban lévő kifejezés azt jelenti, hogy a metódust csak az futtathatja, akinek az id paraméterben átadott id-jú Article típusú üzleti objektum-hoz van read jogosultsága (azaz az 1-es Article ACL-jének ACE-i között szerepel), vagy ADMIN szerepkörű. A második PreAuthorize annotációban szereplő kifejezés azt jelenti, hogy csak az hívhatja meg a metódust, akinek a paraméterként átadott Article üzleti objektra van write jogosultsága vagy ADMIN szerepkörű. Tehát látható, hogy feltételt lehet megadni üzleti objektum azonosítójára és magára az objektumra is. Az ehhez tartozó teszt eset pl. a testUserWithRead, akinek van olvasási de nincs írási jogosultsága az 1-es Article üzleti objektumhoz. A #-os megadási módhoz szükséges a debug információkat is a kódba fordítani.

@Test
public void testUserWithRead() {
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken("user2", "pass2", Collections.
 singletonList(new GrantedAuthorityImpl("USER"))));
articleService.findArticleById(1);
try {
articleService.updateArticle(new Article(1, "test"));
fail("Access denied");
} catch (AccessDeniedException ade) {
}
}

Az első sorban beállítjuk a bejelentkezett felhasználót, majd meghívjuk a két üzleti metódust, amiből az elsőhöz lesz, a másodikhoz nem lesz jogosultsága. És ne feledjük, az üzleti objektum alapján!

Még egy érdekes funkció, hogy az ACL képes filter-elni, szűrni az üzleti objektumokat egy kollekcióból a jogosultság alapján.

@Override
@PostFilter("hasPermission(filterObject, 'read') or hasRole('ADMIN')")
public List<Article> findAllArticles() {
return new ArrayList<Article>(articles.values());
}

A PostFilter annotáció hatására a visszaadott listából kiveszi az ACL azon üzleti objektumokat, melyekre nincs olvasási jogosultsága a bejelentkezett felhasználónak. Vigyázzunk, hogy nagy listák esetén ne így használjuk, mert az adatbázisból lekérdezésre kerül, majd onnan lesznek kiszórva. Hatékonyabb megoldás, ha már eleve csak a megfelelő üzleti objektumokat kérdezzük le. A hozzá tartozó teszt eset a testFilterUser2, ugyanis a user2-nek a három cikk közül csak kettőre van jogosultsága.

@Test
public void testFilterUser2() {
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken("user2", "pass2", Collections.
 singletonList(new GrantedAuthorityImpl("USER"))));
List<Article> articles = articleService.findAllArticles();
assertEquals(1, articles.size());
}

Az ACL funkcióit JSP-ből is kihasználhatjuk a security:accesscontrollist tag segítségével.

Tesztelés közben tudatosult bennem, hogy ne módosítgassuk a táblákat az ACL alatt, hiszen a cache miatt az adatbázis módosítás nem fog látszani.

Heraclitus on software blog: Simple web application with Spring Security Grzegorz Borkowski (Continuous Investigation blog): Spring Security ACL - very basic tutorial Denksoft Blog: ACL Spring Security tutorial

2010. július 1., csütörtök

JBoss in Action

A JBoss AS 5 a jelenlegi egyik legelterjedtebb alkalmazásszerver, köszönhetően annak, hogy időben jelentkeztek egy ingyenes, nyílt forráskódú Java EE implementációval, alternatívát teremtve a kereskedelmi, drága, de nehézkes alkalmazásszervereknek. Jeleleg is sokan használják arra, hogy JBoss-ra fejlesztenek, kihasználva annak relatív gyorsaságát, könnyű telepíthetőségét és egyszerű felépítését.

A Java EE megjelenése óta az alkalmazásszerverek a senki földjét képviselik. A fejlesztők hajlamosak adottságnak tekinteni, és megírják az alkalmazásukat a standardok felhasználásával, és akkor foglalkoznak az alkalmazásszerverrel, ha valami probléma adódik vele, pl. az osztálybetöltők táján. Az üzemeltetés viszont szeretne úgy tekinteni rá, mint az alkalmazás része, annak futtatókörnyezete, és nem foglalkozni vele, hiszen mit is érdekli, hogy mi is az a JMS, EJB, connection pool, HTTP Connector és így tovább. Így tehát az alkalmazásszerver telepítése, üzemeltetése, netalántán üzemeltetése általában vagy egy üzemeltetői hajlamokkal megáldott fejlesztőre marad, vagy egy szorgalmas, ezen technológiákba is belelátni kívánó üzemeltető végzi. Sajnos gyakori probléma, hogy nincsenek a szerepkörök, jogosultságok, de legfőképpen a felelősségi körök a megfelelőképp kiosztva, dokumentálva. Legtöbbször az alkalmazásszerver üzemeltetése szükséges rosszként nyomja valakinek a vállát.

Ezen szakadék áthidalását tűzte ki célul a JBoss In Action című könyv, mely a JBoss alkalmazásszerver üzemeltetésének, konfigurálásának leírását tűzte ki célul, és szól egyaránt a fejlesztőkhöz és üzemeltetőkhöz is. Teszi ezt mindazzal, hogy elmagyarázza a szükséges fejlesztői fogalmakat (pl. Java EE, EJB, JMS, WAR, EAR,stb.), de elmagyarázza a gyakori üzemeltetési fogalmakat is, mint load balancing, session affinity, scale up/out, SLA, stb (látható, hogy különösen a cluster-ezési részben). Ebben a post-ban megint keverni fogom a könyvkritikát, valamint a véleményemet a JBoss alkalmazásszerverről magáról.

A könyv írását akkor fejezték be véglegesen, amikor a JBoss AS 5.0 CR2 állapotban volt. A különböző kiadások módosításait (Beta2, 3, 4, CR1) már addig is többször át kellett vezetniük a könyvben. Viszont a B függelék tartalmazza a GA-ban történt változásokat, valamint megtalálhatók a könyv honlapján is a hibák és változások, melyeket érdemes elolvasni, komoly változások is történtek pl. a konfigurációs állományok és a naplózás körül is.

A könyv felépítése nagyon logikus és áttekinthető. Négy nagyobb részből épül fel, melyeket kisebb fejezetekből állnak.

Az első rész egy bevezetés, mely azzal indul, hogy miért érdemes a JBoss alkalmazásszervert választani. Remélhetőleg az olvasó ezzel már tisztában van, és ezért veszi kezébe a könyvet. Röviden: nyílt forráskódú, annak minden előnyével. Leírja, hogy mik az előfeltételek (JDK) hogyan kell telepíteni az alkalmazásszerver (rendkívül szimpatikus, hogy ki kell csomagolni a zip-et, és a saját könyvtárán kívül sehova máshova nem dolgozik), és nagyon részletesen kifejti, milyen könyvtárakból áll, és azokban milyen fontosabb állományok találhatóak. Itt részletezi a konfigurációk szerepét is. Egy konfiguráció gyakorlatilag egy könyvtár, mely az összes konfigurációs állományt, telepített szolgáltatást és alkalmazást tartalmazza. Konfigurációt választani egyszerűen úgy lehet, hogy a -c kapcsoló után beírjuk a könyvtár nevét. Saját konfigurációt létre lehet hozni egy már létező konfiguráció (öt előregyártott van a JBoss-ban, ebből kettő az 5.0-ban jelent meg) lemásolásával, és ebből lehet eltávolítani a nem használt szolgáltatásokat (ezt hívja slimming-nek). Nagyon hatékony megoldás. Leírja gyorstalpaló módon, hogy gyorsan sikerélményünk legyen, hogy hogyan kell indítani, leállítani az alkalmazásszerver, és hogyan kell alkalmazást telepíteni és eltávolítani.

A következő rész az alkalmazásszerver részletes bemutatását tűzte ki célul. Itt leírja a microcontainer-t, mely az 5.0-ás JBoss-ban jelent meg, és az előző verziókban lévő JMX microkernel-t. Amíg az utóbbi az MBean-eken alapul, addig a modernebb microcontainer már az aktuális divatnak megfelelően egy Inversion of Control container, mely POJO-kat alkalmaz. Sajnos az 5-ös verzióban még nem sikerült minden komponenst átírni erre, ezért a microcontainer-en fut a JMX kernel is, és azon az át nem írt szolgáltatások, mely szerintem egy felemás felépítést kölcsönöz az egésznek, valamint a konfigurációkon is látszik, hogy melyik az ami az új elv szerint, és melyik az, amit a régi struktúrában kell konfigurálni. Hasonló felemásság figyelhető meg a JBoss build folyamatában is, ugyanis átálltak Maven-re, de bizonyos részeket még nem sikerült beilleszteni, így azok még mindig Ant alapúak. A JBoss üzemeltetése során szoros barátságba kell kerülnünk a JMX-szel, ugyanis sok mindent ezen keresztül lehet futás közben lekérdezni és buherálni, a könyv is nagyszerűen részletezi, elérését a webes JMX konzolból, valamint a parancssoros Twiddle eszközből. Furcsa, de nem írja, hogy a JConsole-ból úgy érhetjük el az MBean-eket, hogy a remote process-t választjuk, és a következő URL-t írjuk be:

service:jmx:rmi://127.0.0.1/jndi/rmi://127.0.0.1:1090/jmxconnector

Azért furcsa nekem ez a JMX mánia, mert a mai napig nem láttam használható JMX konzolt (a parancssorosak a legjobbak!), valamint vagy nem jön át a tűzfalon (ha RMI felett megy), vagy rusnya és bonyolult telepíteni, ha webes, és az alkalmazásszerveren fut az alkalmazás mellett.

Részletesen ír még a naplózásról, könyvtárakra hivatkozó system property-kről, és a system property-k beállításáról. A "Deploying applications" fejezetből kiderül hogy nem csak a standard .ear, .war, .jar, .rar típusú alkalmazásokat képes telepíteni a JBoss, hanem maguk a deployer-ek is plug-and-play szolgáltatások, melyekből rengeteg van, így a hozzájuk tartozó telepíthető komponensekből is. Ide tartoznak pl. a .aop (aspect oriented), .sar (service), .zip, .wsr (web szolgáltatás), .bsh (bean shell), stb. Itt írja le a classloader-ek működését, melyekről saját bevallása szerint is keveset ír, de mégis sok hasznos információ megtalálható itt. Olvashatunk a class loader repository-król, melyekkel megoldható, hogy az alkalmazások a saját osztályaikat előnyben részesítség (scoping), a delegációról, valamint különösen hasznos, hogy leírja a legtipikusabb hibákat és azok megoldásait (pl. hiányzik egy osztály, sérült egy archív, vagy egy JAR két példányban van a classpath-ban, és ezért ClassCastException jön). Itt írja le, hogyan kell DataSource-t vagy Hibernate archive-ot installálni. A következő, "Securing applications" fejezet a biztonsági alapfogalmakat írja le. A biztonság a JBoss SX szolgáltatásra épül, amiben security domain-eket lehet definiálni, és azokhoz login module-okat rendelni. Persze ehhez is, mint mindenhez, JNDI-n keresztül fér hozzá az alkalmazás, megoldva a fejlesztő és az üzemeltető közötti izolációt. A fejezet ismerti az alapfogalmakat, mint autentication és authorization, principal, credential. Valamint érthető folyamatábrán el is magyarázza a folyamatot. Valamint itt tér ki arra is, hogy hogyan lehet a kapcsolatot titkosítani, megmagyarázza a PKI-t is, a szerver és kliens oldali tanúsítványok szerepét is. Megmutatja, hogyan lehet a felhasználók és szerepkörök adatait fájlból, adatbázisból, LDAP-ból és tanúsítványokból betölteni. A biztonság a könyv egyik nagy erőssége, ugyanis erre az összes fejezetben visszatér, a webes alkalmazások biztonságára külön fejezetet szán, és részletesen ír az EJB-kkel, JMS-sel, web szolgáltatásokkal és portletekkel kapcsolatos biztonsági szolgáltatásokról is.

A következő nagy fejezet a különböző szolgáltatásokról ír, melyből első a JBoss Web Server, ami nem más, mint egy Apache Tomcat. A konfigurációja nem is tér el attól, a server.xml-ben lehet beállítani a legtöbb dolgot, mint a virtual host-ot, a használt port-okat, a HTTP és AJP connector-t, a HTTPS-t, thread pool-t, timeout-okat és várakozási sorokat, valve-okat (kérések elkapására, használatos naplózásra, ip-címek kitiltására, SSO-ra, stb.). Részletesen ír a context-ről és a ROOT alkalmazásról. A következő fejezet ír a web alkalmazások biztonságáról, a basic, digest, form és certificate alapú autentikációs módokról és standard valamint a gyártófüggő deployment descriptor-okról, és leírja a https konfigurációját, a szerver és kliens oldali tanúsítványokat és a keytool használatát. A következő fejezet az EJB-kről (valamint a JPA-ról) szól, itt talán túl részletesen belemegy forráskód szinten, hogy hogy is néz ki egy ilyen alkalmazás. A mintapéldák nem a könyv erősségei, szerintem feleslegesen hoz több, nem kidolgozott példát, elég lett volna egy alkalmazáson bemutatni a fogalmakat. Itt viszonylag kevés konfigurációs lehetőségről ír, ebből hasznos talán a JNDI nevek konfigurálása, a session bean-eknél a pool és az aktiváció/passziváció finomhangolása. Érdekes lehetőség a JMX service object, mellyel nagyon egyszerűen, a @Management és @Service JBoss-os annotációkkal tudunk JMX MBean-eket létrehozni. Érdekes lehet még, hogy a EJB-k közötti távoli metódushívás protokollját lehet változtatni, pl. HTTP-n, de akár SSL-len is át.

Itt ír még a JMS-ről, és nem point-to-point, hanem publish and subscribe példát hoz. Amúgy nagyon tetszetős az a megoldás a JBoss-ban, hogy DataSource-t, JMX Connection Factory-t és Destination-t, Mail Session-t külön XML fájlokban lehet konfigurálni, és annak létrehozásával futásidőben telepíteni, törlésével undeploy-olni. A konfigurációs lehetőségekről itt sem ír sokat, sajnos sokat áldoz a mintapéldára. Ugyanez igaz a web szolgáltatásokra is. Mindkét esetben a biztonság azért rendesen ki van fejtve. A következő fejezet a JBoss Portal-ról szól, ami azonban a JBoss Portal 2.6.4-ről szól, mely a JBoss AS 4.2.2-n fut. Bevallom, ezt a fejezetet át is ugrottam. A könyvre amúgy jellemző, hogy referencia jelleggel is forgathatjuk, mindig elővehetjük azt a fejezetet, melyre szükségünk van.

A Going to production első fejezete a cluster-ezésről szól. A cluster-ezés a JGroups-ra és JBoss Cache-re épül. Lehet konfigurálni session replikációt, lehet cluster-ezni az EJB-ket, entitásokat és fel lehet állítani magas rendelkezésre állású JNDI-t is. Alapvetően nem hiszek az állapottal rendelkező alkalmazásokban, így HA-nál elegendőnek szoktam érezni a terheléselosztást, ha állapot van, akkor sticky session-nel. Lehetőleg kerülöm az állapot replikálást. Nagyságrendekkel bonyolítja ugyanis az architektúrát és hibakeresést. A két legérdekesebb fejezet számomra a Tuning the JBoss Application Server és Going to production volt. Itt először olyan aranyszabályokat ír le, melyeket sok mindenkinek meg kéne tartania a performance tuning-ra vonatkozóan. Ilyen pl. hogy induljunk egy olyan állapotból, amit reprodukálni tudunk, azaz a terheléses tesztelést lefuttatva mindig ugyanazt az eredményt kapjuk. Utána minden lépésben egyszerre csak egy dolgot változtassunk. Valamint ne megérzéseinkre, hanem a mérésekre hagyatkozzunk. A teljesítményfokozást az alapoktól, a hardvertől, OS-től kezdi. Még alkalmazásszerver függetlenül leírja a Java memóriahasználatát és a GC működését. Ez volt az egyik legjobb írás, amit valaha erről a témáról olvastam. Se nem túl mély, se nem túl általános, és sok hasznos gyakorlati tanácsot is leír, mind monitorozásra vonatkozóan, mind azzal kapcsolóan, hogy hogyan és mi alapján állítgassuk a titokzatos -Xms, -Xms, --XXNewSize, --XXMaxNewSize, --XX:NewRatio, stb. parancssori paramétereket. Talán az AS hangolásáról sem ír ilyen jól, ahol a DataSource-okat, HTTP thread pool-t és JSP servlet fordítást említi. Ami nekem külön tetszik, mert jó ötlet, hogy abban az esetben, ha egy gépen több példányt akarunk futtatni, akkor nem kell az összes konfig állományban átírni az összes port-ot, hogy ne legyen port ütközés, hanem a PortBinding-nál meg lehet adni egy increment paramétert, mely az összes port-ot megemeli az itt megadott számmal. Esik itt még szó arról, hogy hogy lehet a JBoss-t szolgáltatásként futtatni Windows-on és Linux-on, valamint hogyan cserélhetjük le a beépített Hipersonic SQL-t egy másik adatbázisra.

Az első függelék leírja a JNDI nevek problémáját, hogy a globális JNDI neveket a Java EE 5 szabvány még nem rögzítette, melyről már én is írtam. Erre javasol egy megoldást. A második függelék a 5.0 GA-ban történt változásokról ír. Esik itt szó a webes adminisztrációs felületről, ami régóta hiányzik a JBoss-ból (hát a mostani sem egy lenyűgöző megoldás), a common/lib könyvtárról, a profile service repository-ról, konfigurációs állományok helyének módosításáról és a naplózás változásairól.

Összességében a könyv nagyon nehéz feladatra vállalkozott, hiszen a fejlesztőknek sok alapvető fogalmat magyaráz el, melyet átugorhatnak, az üzemeltetőknek viszont nem tudom mennyire van értelme ennyire belemászniuk a forráskódokba. Az biztos, hogy több szép megvillanás van a könyvben melyek miatt mind a két tábornak érdemes, ha nem is az elejétől a végéig átolvasnia, de legalább az érdekes fejezeteket átlapoznia.