Technológiák: Spring 3.0.6, Spring Security 3.0.7
A Spring Security egy Apache license alatt futó projekt Java alkalmazások autentikációjának és autorizációjának megvalósítására. Az előbbi azt jelenti, hogy a felhasználó tesz egy állítást, hogy ő kicsoda, és azt bizonyítja is. A legtöbbször ez felhasználónév és jelszó párossal történik, de lehet bonyolultabb megoldás, mint tanúsítvány (akár hardver token-en), ujjlenyomat, stb. Az utóbbi az erőforráshoz való hozzáféréskor ellenőrzi, hogy a felhasználónak van-e hozzá jogosultsága. A Spring Security független projektként indult Acegi Security néven, de azóta már a SpringSource tartja karban. Legkönnyebben Spring-es alkalmazásokkal integrálható, de nem kötelező a Spring használata. Persze az összes Spring-es technológiához illeszthető. Főleg webes alkalmazásoknál szokták használni, de működik vastag klienses környezetben is. Ez alapján egyszerűen beépíthető egy Spring + Spring MVC alkalmazásba, de használható többek között Struts-cal, Swing-gel, de gyakorlatilag bármilyen Java alkalmazásban.
Előnye, hogy nem függ a környezettől (pl. alkalmazásszerver), nem kell az üzleti logikát átfűzni a jogosultságkezelést végző kóddal (, hanem aspektusorientált módon adható meg). Egyszerű módon (XML-lel) konfigurálható, és a legtöbb beállításnak van alapértelmezett értéke is, mellyel működik a biztonság, de tetszőleges mértékben testre szabható, a legtöbb osztály akár saját implementációra is kicserélhető (plugin-elhetőség). Implementálva van benne hozzáférési listák kezelése (Access Control Lists).
Támogatja a HTTP BASIC, HTTP Digest és form alapú autentikációt, valamint az OpenID-t és a X.509 tanúsítványt.
A felhasználók és a hozzá kapcsolódó szerepkörök tárolhatóak properties vagy XML állományban, adatbázisban, LDAP-ban, de saját implementáció is megadható. Támogatja a jelszó kódolását pl. SHA vagy MD5 algoritmussal. A felhaszálóval kapcsolatos információkat képes cache-elni is. Különböző eseményekre eseménykezelőket lehet aggatni, pl. bejelentkezés, így könnyen megoldható pl. audit naplózás. Könnyen illeszthető a CAS single sign on megoldáshoz.
Kompatibilis a Servlet Security API-val, használhatóak vele az EJB 3 annotációi, valamint a WSS-hez(korábban WS-Security) is illeszhető. Képes a security propagation-re, azaz az alkalmazások különböző rétegei között átvinni a security context-et (pl. a vastag kliensről a szerverre).
Webes környezetben egy filter-t kell a web.xml-be betenni. Képes mindarra, amire a web.xml-ben definiálható biztonság, de azt rengeteg egyéb funkcióval egészíti ki, mint pl. a védett URL-eket nem csak a Servlet specifikációban megadott korlátozott URL mintákkal lehet megadni, hanem használható az Ant féle megadási mód is. Konfigurálható, hogy védet tartalmak esetén történjen https-re átirányítás. Alapból implementálva van benne két Remember-Me (Persistent Login) megoldás is, azaz a böngésző cookie-ban jegyezze meg a bejelentkezés tényét. A Spring Security tag library-t is biztosít funkcióinak elérésére JSP oldalból.
Ebben a post-ban egy egyszerű Spring MVC-s webes alkalmazásba illesztését fogom bemutatni. A posthoz egy példa projekt is tartozik, mely elérhető a GitHub-on, és a teljes forrás akár egy zip fájlban is letölthető. Egyszerű Spring MVC-s webes alkalmazás JPA perzisztens réteggel.
Első lépésként szerkesszük meg az web.xml állományt, és adjuk meg a Spring Security-t konfiguráló applicationContext-security.xml állományt (a jó elkülöníthetőség kedvéért konfigurálom külön állományban), valamint a filter-t, mely a http(s) kéréseket elkapja, és ellenőrzi.
<context-param> <param-name>contextConfigLocation</param-name> <param-value> WEB-INF/applicationContext.xml WEB-INF/applicationContext-security.xml </param-value> </context-param> <filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
A következő lépésben írjuk meg az applicationContext-security.xml állományt.
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans" 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/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">
<http auto-config="true">
<intercept-url pattern="/" access="ROLE_USER, ROLE_ADMIN" />
<intercept-url pattern="/addUser.html" access="ROLE_ADMIN" />
<logout />
</http>
<authentication-manager>
<authentication-provider>
<password-encoder hash="md5"/>
<user-service>
<user name="jtechlog" password="26b91b96e2e8adc37cd26cff6a6b2eba" authorities="ROLE_USER" />
</user-service>
</authentication-provider>
</authentication-manager>
</beans:beans>
Az auto-config tulajdonság egy rövidítés, a következő alapértelmezett beállításokat tartalmazza:
<http> <form-login /> <http-basic /> <logout /> </http>
Az authentication-provider elemben az XML szerepel egy "jtechlog" nevű felhasználó, akinek a jelszava MD5-tel kódolva szerepel ("jtechlog12"). Ezzel készen is van. Az alkalmazásunkat elindítva bármelyik URL-re egy (Spring Security által generált) bejelentkezési form jön be, hiszen deklarálva lett, hogy a / URL megtekintéséhez a felhasználónak rendelkeznie kell a ROLE_USER vagy ROLE_ADMIN szerepkörrel, a /addUser.html-hez ROLE_ADMIN szerepkörrel (lásd intercept-url elem). Az azonosítás form-on, jelszóval történik (form-login).
A security névtérban a következőkre adhatunk meg konfigurációkat:
- Web/HTTP Security
- Business Object (Method) Security
- AuthenticationManager
- AccessDecisionManager
- AuthenticationProviders
- UserDetailsService
Amennyiben kijelentkezést is meg akarunk valósítani, a JSP-ben csak helyezzük el a következő linket:
<a href="<c:url value='/j_spring_security_logout'/>">Kijelentkezés</a>
Következő lépésként implementáljuk magunk a felhasználó adatbázisból való betöltését, méghozzá pl. JPA segítségével. Ehhez kell egy User entitás, melynek különlegessége, hogy implementálnia kell a UserDetails interfészt, és annak több metódusát. Pl.:
@Entity
public class User implements UserDetails, Serializable {
@Id
@GeneratedValue
private Long id;
private String username;
private String password;
private String roles;
@Override
public Collection getAuthorities() {
Collection authorities = new ArrayList();
for (String s: roles.split(", ")) {
authorities.add(new GrantedAuthorityImpl("ROLE_" + s.toUpperCase()));
}
return authorities;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
// Többi getter és setter metódus
// ...
}
Valamint definiáljunk egy UserService nevű @Repository osztályt, és a trükk csak annyi, hogy implementálnia kell a UserDetailsService interfészt.
@Repository("userService")
@Transactional
public class DefaultUserService implements UserDetailsService {
@PersistenceContext
private EntityManager em;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
try {
return (UserDetails) em.createQuery("select u from User u where u.username = :username").setParameter("username", username).getSingleResult();
}
catch (EntityNotFoundException enfe) {
throw new UsernameNotFoundException("A felhasznalo a megadott felhasznalonevvel nem talalhato: " + username, enfe);
}
}
// Többi üzleti metódus
// ...
}
Mivel az applicationContext.xml-ben context:annotation-config van beállítva, ami a @Repository annotáció miatt példányosítja a DefaultUserService osztályunkat.
A Spring Security-ben az AuthenticationProvider is cserélhető, és ebben az esetben a DaoAuthenticationProvider-t kell használnunk. Ennek megadhatunk egy userDetailsService tulajdonságot, melynek a UserDetailsService-t kell implementálnia, és ennek fogja meghívni a loadUserByUsername metódusát. Ezt egy rövidebb konfigurációval is megadhatjuk az applicationContext-security.xml állományban a következő módon:
<authentication-provider user-service-ref="userService" />
Egy authentication-manager-en belül több authentication-provider-t is megadhatunk. Ekkor sorban nézi végig a provider-eket, és ahol először sikerül az autentikáció, az nyer. Így előbb az XML-ben szereplő felhasználókat, majd az adatbázisban szereplő felhasználókat fogja alapul venni, a User entitásunk alapján. Ekkor a jelszó még plain text-ben kerül letárolásra, de ha mi MD5-öt szeretnénk, konfiguráljuk így:
<authentication-provider user-service-ref="userService"> <password-encoder hash="md5"/> </authentication-provider>
A Java kódból ezután a következőképpen kérhetjük le a bejelentkezés után a felhasználót:
SecurityContextHolder.getContext().getAuthentication().getPrincipal();
A Context ThreadLocal változó, így szálanként egyedi, webes környezetben nem kell session-ben eltárolni, ezt elvégzi helyettünk a Spring Security. A metódus visszatérési értékét kényszeríthetjük a saját User osztályunkra.
JSP-ben használhatunk tag library-t is, melynek definíciója:
<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags" %>
Az authentication tag visszaadja az Authentication objektumot, és annak tulajdonságait tudjuk lekérni:
<security:authentication property="principal.username" />
Valamint az authorize tag törzse csak a feltétel teljesítésekor jelenik meg. A feltételek a következők lehetnek: ifAllGranted - vesszővel megadott szerepkörök mindegyikével rendelkezik, ifAnyGranted - vesszővel megadott szerepkörök egyikével rendelkezik, ifNotGranted - vesszővel megadott szerepkörök egyikével sem rendelkezik.
<security:authorize ifAllGranted="ROLE_ADMIN">
<!-- Felhasználók felvételére szolgáló form. -->
</security:authorize>
Ez esetben még mindig nem vagyunk megelégedve a Spring Security által biztosított alapértelmezett bejelentkező képernyővel, emiatt szabjuk azt testre. Az intercept-url-lel kell megadni a védendő URL-eket. Természetesen többet is megadhatunk, egy URL-hez több szerepkört is megadhatunk vesszővel elválasztva, valamint használhatunk Ant típusú mintákat. A Spring Security használatakor a leggyakoribb hiba, hogy a bejelentkezési képernyőt is letiltjuk, így végtelenciklus alakulhat ki. Erre a Spring Security egy üzenettel figyelmeztet is: org.springframework.security.config.FilterChainProxyPostProcessor: Anonymous access to the login page doesn't appear to be enabled. This is almost certainly an error. Please check your configuration allows unauthenticated access to the configured login page. (Simulated access was rejected: org.springframework.security.AccessDeniedException: Access is denied).
Ekkor be kell állítani, hogy a login.html oldalhoz ne kelljen bejelentkezés. Sikertelen bejelentkezés esetén történjen átirányítás a /login.htm?login_error=1 oldalra, sikeres bejelentkezés esetén a / oldalra. Kijelentkezés után ismét a /login.htm oldal jön be. A konfiguráció a következőképpen alakul:
<http auto-config="true"> <intercept-url pattern="/login.html" access="IS_AUTHENTICATED_ANONYMOUSLY" /> <intercept-url pattern="/" access="ROLE_ADMIN, ROLE_USER" /> <intercept-url pattern="/addUser.html" access="ROLE_ADMIN" /> <form-login login-page="/login.html" default-target-url="/" authentication-failure-url="/login.htm?login_error=1" /> <logout logout-success-url="/login.html"/> </http>
Majd nézzük a bejelentkező form-ot tartalmazó JSP részletet:
<c:if test="${not empty param.login_error}">
Sikertelen bejelentkezés
</c:if>
<form action="<c:url value='/j_spring_security_check'/>" method="POST">
<input type="text" name="j_username" value='<c:if test="${not empty param.login_error}"><c:out value="${SPRING_SECURITY_LAST_USERNAME}"/></c:if>'/>
<input type="password" name="j_password" value="" />
<input type="submit" value="Bejelentkezés"/>
</form>
A form-ot a j_spring_security_check címre kell post-olni, amit a filter fogad. Tartalmaznia kell egy j_username és j_password mezőt. Amennyiben nem sikerült a bejelentkezés, a session-ben két változó lesz: SPRING_SECURITY_LAST_EXCEPTION a kivételt tartalmazza, a SPRING_SECURITY_LAST_USERNAME pedig a beírt felhasználónevet.
Ezen kívül a Spring Security képes arra is, hogy különböző metódusok meghívása esetén is végezzen jogosultságellenőrzést. Ezt deklaratív módon, annotációval is meg lehet adni. Ekkor egyrész deklarálni kell, hogy metódus szintű hozzáférés ellenőrzést szeretnénk, ekkor a következőt kell elhelyezni a applicationContext-security.xml-ben:
<global-method-security pre-post-annotations="enabled" />
Valamint használjuk a @PreAuthorize annotációt a védendő metóduson:
@PreAuthorize("hasRole('ROLE_ADMIN')")
public void addUser(String name, String password, String roles) {
// ...
}
Látható, hogy már minimális konfigurációval is működik a Spring Security, és minden elemét ki tudjuk cserélni saját implementációra is.

0 megjegyzés:
Megjegyzés küldése