Szoftverfejlesztés J2EE platformon - Security

A VIK Wikiből
Ugrás a navigációhoz Ugrás a kereséshez

Gondolom, elég sokan vagyunk, akik úgy képzelünk el egy webes bejelentkezést, hogy a felhasználónak ki kell töltenie valamilyen űrlapot a nevével és a jelszavával, a háttérben erre a program kiszedi a megfelelő sort az adatbázisból, és ennek alapján eldönti, hogy jó-e a jelszó, avagy rossz, admin-e a felhasználó vagy sem, stb. Ez a folyamat az autentikáció. Ezután a felhasználó meg akar nézni valamilyen lapot a biztonságos területről. A rendszernek döntenie kell, hogy beengedje-e, avagy sem. Ez az autorizáció. Aki csinált már valamilyen webes bejelentkező felülettel rendelkező cuccot PHP-ban, az megszokta, hogy mindezt saját magának kell leprogramoznia. Erre természetesen Java EE-ben is van lehetőség (a PHP-hoz képest kisebb csinosításokkal, úgymint filterek beiktatása, entity beanek használata sima query-k helyett, stb.), szükség azonban nincs rá. Tudvalevő, hogy a Java EE biztonsági szolgáltatása a programozó kezébe ad egy JAAS-alapú (Java Authentication & Authorization Service) módszert az autentikáció és az autorizáció (a továbbiakban AA) kezelésére. Hogy miért jó ez?

  • Valakik már megírták helyetted a kódot. Valószínű, hogy az évek során sikerült egy csomó rést kiküszöbölni (lásd pl. SQL-injektálhatóság), úgyhogy feltehetően jobban jársz egy ilyennel.
  • Ha deklaratívan kezeled az AA-t, akkor a jogosultságok nincsenek bedrótozva a programba: egy-két telepítésleíró-bejegyzés beiktatásával vagy elvételével bővíthető vagy kurtítható egy-egy szerepkör felhasználói tábora. További hatalmas előny, hogy az AA adatokat tartalmazó erőforrás jellegétől (Egy fájl? Egy adatbázis? Egy...?) is független maradhatsz.

A Java EE biztonsági szolgáltatásában ún. realmek (ejtsd "relm", nem pedig "rílm") használatosak egy alkalmazás felhasználói táborának azonosítására. Autentikálják a felhasználót, aki egy biztonsági kontextust kap, benne a felhasználói azonosítójával és a csoportazonosítójával (esetleg több ilyenis lehet bizonyos realmeknél). Az autorizáció ezután a konténer feladata, a telepítésleírók alapján.

A továbbiak kizárólag Netbeans 5.5-re, és a hozzácsomagolt AS-re vonatkoznak. (Ezt használom, ezt ismerem...) Az életszerűség kedvéért hagyom a fenébe a konfigfájlalapú (file realm) azonosítást, helyette az adatbázis-alapú azonosítást (JDBCRealm) fogom bemutatni.

Mik egy biztonságos program fejlesztésének lépései, ha Java EE Securityben gondolkozunk?

  • Adatbázistáblák létrehozása a felhasználónév, a jelszó és a csoportazonosító tárolására
  • Adatok beírása az adatbázisba ;)
  • Alkalmazásszerver elindítása, Admin console megnyitása, bejelentkezés (alapértelmezés: admin/adminadmin)
  • Ámulás-bámulás: a Configuration -> Security (a baloldali menüben a kis nyílra kattints!) -> Realms alatt csak olyasmik látszanak, mint a file realm (bármilyen komolyabb célra használhatatlan) és a certificate realm (ennek a valós életben lehet értelme, egy házi feladatban már kevésbé). Hozzá kell adni a listához a JDBCRealmet. A New... gombot megnyomva egy űrlapot kapunk. Az adatok:
    • Realm: JDBCRealm
    • Class name: com.sun.enterprise.security.auth.realm.jdbc.JDBCRealm
    • Hozzá kell továbbá adni egy vagon propertyt az Add Property gombbal. Ezek a következők:
      • datasource-jndi (az adatbázis kapcsolat JNDI neve)
      • user-table (a felhasználói neveket és jelszavakat tároló tábla)
      • user-name-column (a felhasználói név oszlopa)
      • password-column (a jelszó oszlopa)
      • group-table (a csoport táblája, mely akár azonos is lehet a user-table-ben megadottal)
      • group-name-column (a csoportnév oszlopa)
      • jaas-context (kötelezően jdbcRealm, pontosan ilyen betűnagyságokkal)
      • digest-algorithm (a jelszóhoz tartozó digest előállításának algoritmusa - ha ilyet nem akarsz, ez a property none legyen).
    • Ok.
  • A group-table tábla group-name-column mezejéből kivett csoportnevet össze kell rendelni a konténer által azonosítható biztonsági szerepkörökkel (role). Ennek módja a telepítésleíró (sun-application.xml) megbuherálása. Először is meg kell adni neki a fasza kis GUI-n, hogy a JDBCRealmet használja, majd Edit as XML. Be kell szúrni a kívánt mennyiségű összerendelést a következőképp:
<security-role-mapping>
<role-name>client</role-name>
<group-name>client</group-name>
</security-role-mapping>

Asszem nemigen szükséges magyarázat, hogy hova mit írj (a csoport és a szerepkör neve lehet azonos, de ez korántsem kötelező, valamint egy szerepkörhöz több csoport is lehet rendelve).

  • Pl. web réteges AA beállításához security constrainteket kell beállítani a telepítésleíróban (EJB rétegben is lehet használni deklaratív jellegű AA-t, de ott annotációk szükségesek. Ennek sajnos nem néztem utána, majd valaki más... :) ). Nyisd ki a web.xml-t, és menj a Security fülre! Az autentikáció típusa legyen Form, a realm neve JDBCRealm, a Form Login Page és a Form Error Page értelemszerűen kitöltendő egy-egy magunk készítette lap címével. Most a Security Roles rész jön. Ide kell beszúrni az alkalmazásban használni kívánt biztonsági szerekörök nevét. (Nem árt, ha a csoport-szerepkör összerendelésnél mindkét fél létezik...) Végül a Security Constraints részben definiálni kell a jogosultsági korlátokat a megfelelő URL mintákra. Fontos, hogy az Enable Authentication Constraint be legyen ikszelve, és hozzá legyen adva az Edit gombbal az engedélyezni kívánt szerepkör. Az sem árt továbbá, ha a Form Login Page nem a védett területen belül van. A bejelentkeztetést végző űrlap szerkezete kötött:
    • a form tag action paramétere mindenképp j_security_check
    • a felhasználónév egy text típusú, j_username nevű szövegmező
    • a jelszó egy password típusú, j_password nevű szövegmező

No, nagyjából ennyi: ha ezt mind végigcsináltad, kaptál egy működő AA-t a web rétegben, egyetlen sor Java kód beírása nélkül. Ugye, hogy nem rossz? :)

Amit még érdemes tudni:

  • A form HTML-tagnek ne legyen ilyen attribútuma: enctype="multipart/form-data".
  • A login form csak akkor fog feljönni, ha authentikációhoz kötött oldalt szeretnél elérni. Direkt meghívni nem szerencsés (nem fog működni). Ha jól adod meg a felhasználónevet és a jelszót, akkor az elküld gomb után megkapod a kért oldalt.

Források:

Programmatic Login

Ha minden oldalra szeretnél kirakni egy egyedi login form-ot, akkor a _ProgrammaticLogin_-ra lesz szükséged. A ProgrammaticLogin osztály használatához szükség lesz a Glassfish lib könyvtárában lévő lévő appserver-rt.jar fájlra. NetBeansben: a pojectben a Libraries mappára jobb klikk, Add JAR/Folder...

Ezzel megoldható az is, hogy egy JSF-es űrlaphoz tartozó backing bean kezelje a bejelentkezést, illetve a kijelentkezést is. Hátránya, hogy appszerverfüggő. Íme egy Glassfish-es megoldás vázlata:

import com.sun.appserv.security.ProgrammaticLogin;
...
ProgrammaticLogin programmaticLogin;
programmaticLogin = new ProgrammaticLogin();
....
programmaticLogin.login(username, password, "JDBCRealm", getRequest(), getResponse(), true);
...
programmaticLogin.logout(getRequest(), getResponse());

A HttpServletRequest és HttpServletResponse objektumokat így tudod megszerezni backing beanből:

		  FacesContext ctx = FacesContext.getCurrentInstance();
		  ExternalContext ectx = ctx.getExternalContext();
		  this.req = (HttpServletRequest) ectx.getRequest();
		  this.resp = (HttpServletResponse) ectx.getResponse();	 

Ezekre azért van szükség, mert sikeres authentikáció után ide kerül be a session azonosító. Persze ha csak meghívsz egy metódust a jogosultságokkal, és rögtön ki is lépsz (ugyanabban a backing bean metódusban), akkor elég lehet a HttpServlet* paraméterek nélkül login() és logout() metódus is (bár ezt nem próbáltam).

Egy példa login form:

		 <f:view>
				<h:form>
		  <table border="1" cellpadding="1">
					 <tr>
						  <th colspan="2">Login form</th>
					 </tr>
					 <tr>
						  <th><h:outputLabel for="UsernameField" value="Username:" /></th>
						  <td>
								<h:inputText id="UsernameField" 
									 value="#{LoginManagedBean.username}" required="true" 
									 />

						  </td>
						  <td><h:message for="UsernameField" /></td>
					 </tr>
					 <tr>
						  <th><h:outputLabel for="PasswordField" value="Password:" /></th>
						  <td>
								<h:inputText id="PasswordField" 
									 value="#{LoginManagedBean.password}" 
									 required="true" />

						  </td>
						  <td><h:message for="PasswordField" /></td>
					 </tr>
					 <tr>
						  <td>
								<h:commandButton type="submit" value="Login"
												 action="#{LoginManagedBean.login}" />
						  </td>
						  <td colspan="2">
								<h:messages globalOnly="true" />
						  </td>
					 </tr>

		  </table>
	
				</h:form>
		  </f:view>					 

EJB-security

Ha JAAS-el authentikálod magad, akkor azt a webréteg továbbterjeszti az EJB-réter felé is. Session bean-ben így tudod lekérdezni a bejelentkezett felhasználó nevét:

	 @Resource
	 SessionContext sessionContext;
	 ...
	 String username = sessionContext.getCallerPrincipal().getName();

-- Földe - 2006.12.26. -- palacsint - 2007.11.12.