Zobrazují se příspěvky se štítkemSpring security. Zobrazit všechny příspěvky
Zobrazují se příspěvky se štítkemSpring security. Zobrazit všechny příspěvky

29. září 2013

Architektura integračního řešení

V dnešním článku se opět vracím k mému poslednímu projektu, na kterém jsme realizovali nového virtuálního operátora, viz článek Zkušenosti s Apache Camel. Já konkrétně jsem měl na starosti integrační část celého řešení.

Integrační část je centrálním bodem celého řešení, a proto musí být vždy dostupná. Kromě jiného musí splňovat ještě tyto požadavky:

  • spolehlivost
  • škálovatelnost
  • výkonnost
Pro úplnost uvedu přehled externích systémů, se kterými integrační platforma komunikuje:
Při návrhu aplikací a vlastně i při jejich realizaci se snažím držet mého přesvědčení, že nemá cenu dělat nic zbytečně dopředu, dokud není jasné, že je to opravdu potřeba. Pamatuji si na dobu, kdy jsem se snažil vše řešit co nejobecněji a naivně jsem si myslel, že až se ukáže v budoucnu, že to bude potřeba, tak to na to už bude připravené. Bohužel k tomu nikdy nedocházelo - buď se změnily požadavky nebo k tomu ani nedošlo, aby se něco dodělávalo. Jsem přesvědčený o tom, že pokud jsou věci jednoduché (tedy jednoduchá a jasná architektura), pak je to i srozumitelné a lépe udržovatelné. 

S tímto přístupem jsme navrhovali i toto integrační řešení:
  • operační systém CentOS 6.4 64bit
  • Apache Tomcat 7 - krásný příklad, jak v jednoduchosti je síla. Nevidím žádný (technický) důvod, proč používat něco složitějšího.
  • PostgreSQL 9 - databázi používáme pro persistenci vstupních zpráv, se kterými dále pracujeme a zaručujeme se o jejich zpracování. Mě osobně by se zde více líbilo JMS (např. Apache ActiveMQ nebo RabbitMQ), ale výběr databáze byl již dán, takže jsem s tím musel pracovat jak to bylo dáno. Obával jsem se zde úzkého výkonnostního hrdla, zejména v momentě, kdy se více ESB instancí bude "prát" o nové zprávy ke zpracování, ale zatím se ukazuje, že moje obavy byly zbytečné.
  • Load balancing je prováděn komponentou apache http serveru mod_proxy_balancer pomocí round robbin algoritmu. Balancing zajišťuje rozkládání zátěže na jednotlivé nody a primárně je  použit z důvodu vysoké dostupnosti ESB nodu, jelikož v případě výpadku jednoho z ESB nodů automaticky na daný chybný node přestane směrovat requesty.
  • Samotný apache http server je zároveň provozován také na dvou nodech, mezi kterými je pak pohyblivá IP adresa, která je pak aktivní pouze na jednom ze serverů a přes tento server je pak primárně směřován provoz. Apache na druhém serveru je tak pouze ve stavu standby a přes něj by byl směrován provoz až v případě migrace této „pohyblivé“ IP adresy na tento server, ze kterého je pak prováděn balancing stejným způsobem na oba dva aktivní aplikační servery čímž je zajištěna vysoká dostupnost systému.
  • Na HTTP proxy je zakončena zabezpečená SSL komunikace, dále směrem k aplikačnímu serveru je již komunikace realizována pouze pomocí HTTP protokolu.
Co se týče výběru aplikačního stacku, tak základ je tvořený knihovnou Apache Camel, která zajišťuje samotné routování zpráv. Zbylé věci jsou řešeny Spring knihovnami:
  • Spring Framework tvoří základní stavební kámen pro konfiguraci aplikace
  • Spring Security pro zabezpečení webové admin části a pro zabezpečení komunikace s externími systémy
  • Spring Web Services jako hlavní Camel komponenta pro komunikaci pomocí webových služeb

5. srpna 2009

Přechod z Acegi na Spring security - pokračování

O zkušenostech z upgradu Acegi security na Spring security jsem již jeden článek napsal. Teď jsem dělal upgrade podruhé a narazil jsem na dvě nekompatibilní změny v API.

Jedná se o tyto změny:


Ono těch změn bude asi více, ale bohužel jsem nikde nenašel jejich seznam.

20. května 2009

Spring security namespaces

Koncept "namespaců" resp. možnost vytváření vlastních konfiguračních XML tagů je ve Springu již od verze 2.0 a již je celkem hodně zajímavých tagů - ať už přímo ve Spring frameworku nebo v jiných Spring knihovnách nebo i v knihovnách třetích stran, např. DWR. Cíl je jasný - umožnit jednodušší (= rychlejší, přehlednější, jasnější, ...) konfiguraci Spring beanů.

Spring security přišel s podporou namespaců ve verzi 2.0. Sice moc namespaci nevyužívám (zvyk je železná košile), ale nedalo mi to, abych možnosti namespaců ve Spring security nevyzkoušel na jedné menší testovací aplikaci.

Začal jsem následující magickou ukázkou v dokumentaci:

<http auto-config='true'>
<intercept-url pattern="/**" access="ROLE_USER" />
</http>

<authentication-provider>
<user-service>
<user name="jimi" password="jimispassword" authorities="ROLE_USER, ROLE_ADMIN" />
<user name="bob" password="bobspassword" authorities="ROLE_USER" />
</user-service>
</authentication-provider>

A pak jsem to začal upravovat podle mojí "standardní" konfigurace Spring security. Většinu jsem dokázal pořešit pouze konfigurací na úrovní namespaců, ale ne vždy to bylo dostačující:
  • u LogoutFiltru jsem zvyklý používat vlastní LogoutHandler pro zalogování potřebných informací při odhlášení. Abych mohl použít vlastní handler, tak jsem si musel filter nakonfigurovat standardním způsobem s použitím <custom-filter position="LOGOUT_FILTER"/>

  • podobně u použití vlastního AccessDeniedHandleru. V rámci konfigurace <http> je již implicitní ExceptionTranslationFilter nastaven. Stejně jako u LogoutFiltru jsem musel provést konfiguraci filtru standardní cestou a uvést, že se jedná o vlastní filtr. Pozice vlastního filtru nesmí kolidovat z pozicí implicitního filtru, takže buď je potřeba dát implicitní filtr úplně pryč (neuvádět ho v <http>) a nebo umístit vlastní filtr před/za nějaký již existující, např. u ExceptionTranslationFilteru.

Takto jsem se dostal hodně daleko a konfigurace aplikace již vypadala skoro dle mých představ - uměla vše co jsem požadoval, místy byl zápis opravdu čitelnější a kratší. A i s použitím namespaců jsem mohl být flexibilní, snad všechny implicitní konfigurace jsem mohl nahradit svými, např. použít vlastní AccessDecisionManager

Jen jedna věc mi pořád vadila - při použití <http> se všechny uvedené filtry aplikují na všechny URL. Nemám tedy možnost říci, že na nějaké URL použiji nějaké filtry a na jiné URL zase jiné filtry. Použité filtry je např. vhodné odlišit pro dynamický a statický obsah. Z tohoto důvodu jsem opustil konfiguraci pomocí <http> a použil jsem standardní FilterChainProxy s <filter-chain-map>.

Na začátku jsem měl pár tagů a na konci jsem skončil skoro u "normálního" nastavení bez namespaců. I tak jsem byl velice mile překvapen, jak pěkně to mají navržené a jakou míru flexibility to má. Já Spring Security již celkem znám a i při použití namespaců vím, co se děje pod pokličkou, proto nevím, zda je tato zjednodušená forma konfigurace vhodná i pro začátečníky. Začátek s namespaci bude určitě rychlý, ale v každé aplikaci je potřeba něco nastavit jinak než standardně a pak bude problém.

Pro možnou inspiraci posílám moji výslednou konfiguraci:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
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-2.0.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-2.0.4.xsd"
>

<description>
Konfigurace Spring security.
</description>

<!--
Base filter chain of used filters. Filter's order is important,
more info here http://static.springframework.org/spring-security/site/reference/html/ns-config.html#ns-custom-filters.
-->
<bean id="springSecurityFilterChain" class="org.springframework.security.util.FilterChainProxy">
<security:filter-chain-map path-type="ant">
<security:filter-chain pattern="/img/**" filters="none" />
<security:filter-chain pattern="/css/**" filters="none" />
<security:filter-chain pattern="/js/**" filters="none" />
<security:filter-chain pattern="/accessdenied.*" filters="anonymousProcessingFilter" />
<security:filter-chain pattern="/error*.*" filters="anonymousProcessingFilter" />
<security:filter-chain pattern="/**"
filters="sessionContextIntegrationFilter, logoutFilter, userProfileStubFilter, authProcessingFilter,
userProfileAwareFilter, anonymousProcessingFilter, exceptionTranslationFilter,
filterInvocationInterceptor"
/>
</security:filter-chain-map>
</bean>


<!-- Musi byt, i kdyz neni mozne vyuzivat session. -->
<bean id="sessionContextIntegrationFilter"
class="org.springframework.security.context.HttpSessionContextIntegrationFilter">
<property name="allowSessionCreation" value="true"/>
</bean>

<bean id="logoutFilter" class="org.springframework.security.ui.logout.LogoutFilter">
<!-- URL redirected to after logout -->
<constructor-arg value="/"/>
<constructor-arg>
<list>
<bean class="com.o2bs.globals.web.springsecurity.utils.LoggingLogoutHandlerImpl"/>
<bean class="org.springframework.security.ui.logout.SecurityContextLogoutHandler"/>
</list>
</constructor-arg>
<property name="filterProcessesUrl" value="/j_spring_security_logout"/>
</bean>

<!-- "natvrdo" nastaveni prihlaseneho uzivatele (pouze pro vyvoj) -->
<bean id="userProfileStubFilter" class="com.o2bs.globals.web.springsecurity.utils.UserProfileStubFilter">
<constructor-arg value="123"/>
<constructor-arg value="bob"/>
<constructor-arg value="heslo"/>
<constructor-arg>
<list>
<value>ROLE_WRITE</value>
<value>ROLE_READ</value>
</list>
</constructor-arg>
</bean>

<!-- Processes an authentication form. -->
<bean id="authProcessingFilter"
class="com.o2bs.globals.web.springsecurity.auth.blocking.AuthenticationBlockingProcessingFilter">
<security:custom-filter after="AUTHENTICATION_PROCESSING_FILTER" />
<property name="authenticationManager" ref="authenticationManager"/>
<property name="authenticationFailureUrl" value="/login.do?login_error=1"/>
<property name="defaultTargetUrl" value="/"/>
<property name="authenticationBlockingManager" ref="authBlockingManager"/>
<property name="mandatoryRoles">
<list>
<value>ROLE_WRITE</value>
<value>ROLE_READ</value>
</list>
</property>
</bean>

<bean id="authBlockingManager"
class="com.o2bs.globals.web.springsecurity.auth.blocking.PeriodBlockingManagerMemoryImpl">
</bean>

<bean class="com.o2bs.globals.web.springsecurity.auth.blocking.AuthenticationFailureListener">
<property name="authenticationBlockingManager" ref="authBlockingManager"/>
</bean>

<bean id="userProfileAwareFilter"
class="com.o2bs.globals.web.springsecurity.utils.UserProfileHolderAwareRequestFilter">
</bean>

<!-- I need to use IS_AUTHENTICATED_ANONYMOUSLY - anonymous user has to be handled -->
<bean id="anonymousProcessingFilter"
class="org.springframework.security.providers.anonymous.AnonymousProcessingFilter">
<property name="key" value="anonymKey"/>
<property name="userAttribute" value="anonymousUser,ROLE_READ"/>
</bean>

<bean id="exceptionTranslationFilter" class="org.springframework.security.ui.ExceptionTranslationFilter">
<property name="authenticationEntryPoint">
<bean class="org.springframework.security.ui.webapp.AuthenticationProcessingFilterEntryPoint">
<property name="loginFormUrl" value="/login.do"/>
</bean>
</property>
<property name="accessDeniedHandler">
<!-- errorPage neni definovana, protoze se vyhazuje 403 a ta je namapovana ve web.xml -->
<bean class="com.o2bs.globals.web.springsecurity.utils.LoggingAccessDeniedHandlerImpl"/>
</property>
</bean>

<!-- protect web URIs -->
<bean id="filterInvocationInterceptor" class="org.springframework.security.intercept.web.FilterSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="accessDecisionManager" ref="accessDecisionManager"/>
<property name="objectDefinitionSource">
<security:filter-invocation-definition-source>
<security:intercept-url pattern="/create*" access="ROLE_WRITE" />
<security:intercept-url pattern="/**" access="ROLE_READ" />
</security:filter-invocation-definition-source>
</property>
</bean>





<!--
AffirmativeBased implementation will grant access if one or more ACCESS_GRANTED votes were
received (ie a deny vote will be ignored, provided there was at least one grant vote).
In other words principal must have corresponding ROLE and particular level of authentication.
-->
<bean id="accessDecisionManager" class="org.springframework.security.vote.AffirmativeBased">
<property name="allowIfAllAbstainDecisions" value="false"/>
<property name="decisionVoters">
<list>
<!-- class will vote if any ConfigAttribute begins with PERM_. -->
<bean class="org.springframework.security.vote.RoleVoter">
<property name="rolePrefix" value="ROLE_"/>
</bean>
<!-- allow attributes IS_AUTHENTICATED_FULLY or IS_..._REMEMBERED or IS_..._ANONYMOUSLY -->
<bean class="org.springframework.security.vote.AuthenticatedVoter"/>
</list>
</property>
</bean>

<!-- There is implicit authenticationManager when namespaces are used. -->
<security:authentication-manager alias="authenticationManager" />

<security:authentication-provider>
<security:user-service>
<security:user name="jimi" password="heslo" authorities="ROLE_READ"/>
<security:user name="bob" password="heslo" authorities="ROLE_READ, ROLE_WRITE"/>
</security:user-service>
</security:authentication-provider>

<security:global-method-security access-decision-manager-ref="accessDecisionManager">
<security:protect-pointcut
expression="execution(* com.o2bs.globals.example.competence.service.*.save*(..))"
access="ROLE_WRITE"/>
</security:global-method-security>

<bean class="com.o2bs.globals.web.springsecurity.utils.LoggingAuthenticationListener"/>

</beans>

13. května 2009

Jaké jiné zabezpečení místo Spring security?

Při přípravě školení o Spring security jsem se zamýšlel nad tím, jaké jiné způsoby zabezpečení aplikace jsou možné, když bych vynechal Spring security. Já osobně jsem vždy používal Spring security, proto mě samotného tato otázka trochu zaskočila.

Našel jsem (= vymyslel, vyhledal, znal) následující způsoby:

  • self-made řešení - pod tím si představuji taková řešení, kde využiji základních možností JEE, tedy filtrů a servletů, a na základě toho si vytvořím své vlastní filtry, kontroly. K tomu si dodělám přihlašovací formulář, napojím to na databázi a mám základní řešení hotové.

  • JEE servery - zabezpečení aplikace nechat na serverech.

  • Seam a Drools - framework Seam integruje knihovnu Drools a dohromady to vytváří pěkné řešení pro kompletní zabezpečení aplikace.

Jednotlivé způsoby lze vhodně kombinovat, např. použít servery pro autentifikaci uživatelů a autorizaci řešit pomocí Spring security.

První řešení mi není sympatické z toho důvodu, že nerad vytvářím něco, co již někdo jiný udělal (a většinou mnohem lépe). Snažím se držet zásady, že čím méně sám naprogramuji, tím lépe :).
Druhé řešení nemám rád z důvodu závislosti na aplikačním serveru, tedy omezené možnosti přenositelnosti a hlavně při každé instalaci to musím řešit znovu a znovu. Také nabízené možnosti zabezpečení (zejména co se týče autorizace) nejsou takové jako u jiných řešení.

Znáte prosím nějaké další způsoby zabezpečení aplikací?

6. července 2008

NTLM a Spring security

Ještě před pár dny jsem skoro nevěděl, co to je NTLM a dnes tento autentifikační protokol používám v mé aplikaci.
Našel jsem na jednom blogu parádní článek, kde je víceméně vše podstatné k implementaci pomocí Spring security řečeno. Nemá cenu se tedy opakovat, spíše bych přidal některé moje poznámky a doplnění:

  • autor článku místo ukázky zřetězení filtrů (Virtual filter chain resp. FilterChainProxy) znovu vložil již dříve uvedený kus kódu. Zde bych to tedy napravil. Pořadí filtrů není libovolné, je nutné si dát pozor na to, že filtr NtlmProcessingFilter musí být za filtrem ExceptionTranslationFilter. Já osobně jsem použil toto zřetězení:
    /ntlm/**=httpSessionContextIntegrationFilter,logoutFilter,ntlmExceptionTranslationFilter,ntlmFilter,filterInvocationInterceptor

  • druhá věc, na kterou je potřeba si dát pozor je ta, že filtr NtlmProcessingFilter slouží pouze pro autentifikaci, nikoliv pro autorizaci uživatelů. Je zde potřeba myslet na to, že při načítání UserDetails pomocí UserDetailsService musí být hodnota uživatelského hesla prázdná. Pro testovací účely si načítám informace o uživatelích z properties souboru (InMemoryDaoImpl), kde mám tento řádek (pozice pro heslo není vyplněna):
    pjuza=,ROLE_ANALYST, ROLE_READER

  • V článku jsem nepochopil autorovu implementaci UserDetailsAuthenticationProvider. Já jsem nic podobného dělat nemusel. Jediné co jsem musel nakonfigurovat pro NTLM byly filtry NtlmProcessingFilter, ExceptionTranslationFilter a musel jsem tyto filtry přidat do FilterChainProxy.

  • NTLM je oficiálně až ve Spring security. Nicméně i v Acegi security lze využít NTLM, viz článek Acegi Security and NTLM.


Další zdroje k implementaci NTLM:

27. června 2008

Přechod z Acegi na Spring security

Na minulých projektech jsme používali Acegi security se spoustou vlastních doplňků a vychytávek. Teď začínáme psát nový projekt a tak jsme si řekli, že je už čas se posunout dát a začít použít Spring security (jeden z důvodů byla podpora NTLM ve Spring security, ale o tom budu psát v dalších příspěvku).

V tomto článku bych rád uvedl moje zkušenosti s touto migrace. Základní naše konfigurace vypadala asi jako ta uvedená v článku Ukázka konfigurace Acegi security plus jsme si vytvořili tzv. bezpečností modul, který obsahuje různá rozšíření, které standardně v Acegi implementovány nejsou (např. zablokování účtu po třech špatných přihlášeních, kontrola na existenci rolí při přihlašování apod.).

Vzal jsem tedy konfiguraci a pár stránek ze starého projektu a nasadil do nového. Musel jsem provést následující úpravy:

  • přejmenovat balíky org.acegisecurity na org.springframework.security. Takže když jsem měl v konfiguraci org.acegisecurity.ui.ExceptionTranslationFilter, tak jsem to musel změnit na org.springframework.security.ui.ExceptionTranslationFilter.

  • přejmenovat některé statické proměnné, např. AbstractProcessingFilter.ACEGI_SECURITY_LAST_EXCEPTION_KEY na AbstractProcessingFilter.SPRING_SECURITY_LAST_EXCEPTION_KEY nebo AuthenticationProcessingFilter.ACEGI_SECURITY_LAST_USERNAME_KEY na AuthenticationProcessingFilter.SPRING_SECURITY_LAST_USERNAME_KEY. Tyto proměnné nepoužívám přímo v konfiguraci, ale již v rámci našeho kódu.

  • přejmenovat URL pro odhlášení z j_acegi_logout na j_spring_security_logout.

  • přejmenovat URL pro zpracování přihlašovacího formuláře z j_acegi_security_check na j_spring_security_check.


A to je všechno, vše bez problémů. Zatím jsem migroval vše kromě naší knihovny, ale tam již žádné problémy neočekávám. Pouze bude nutné všechny použité třídy z Acegi přejmenovat na Spring security.
Jediné co mě překvapilo je, že jsem nikde na stránkách Springu, ani v dokumentaci Spring security nenašel návod k migraci.

Zdroje informací:

18. října 2007

Zajímavé články o Acegi security

Dnes jsem narazil na (z mého pohledu) velice zajímavé články o Acegi. Kdyby podobné články byly již před pár lety, kdy jsem začínal s Acegi, tak bych si určitě ušetřil spoustu času :).

Acegi Security in one hour

Securing Java applications with Acegi


From Java EE security to Acegi

23. září 2007

Ukázka konfigurace Acegi security

Pro tento článek jsem vybral konfigurační soubor Acegi security pro náš jeden projekt. Rád bych pár slovy popsal jednotlivé body konfigurace a částečně tím prezentoval možnosti této knihovny. Já osobně považuji Acegi security za nejlepší knihovnu pro řešení bezpečnostních problémů spojených s vývojem aplikací, tedy hlavně s autentizací a autorizací uživatelů.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">

<!--
- Acegi Security configuration.
-->
<beans>

Definice filtrů pro různé adresy. Zmínil bych zde jen dvě důležité věci - pořadí filtrů je velice důležité a je možné definovat různé filtry pro různé URL adresy. Zápis je pomocí ANT stylu.

<!-- Zakladni bean s definici pouzitych filtru.
Poradi pouzitych filtru je dulezite! -->
<bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">
<property name="filterInvocationDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/remoting/**=basicProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor
/**=concurrentSessionFilter,httpSessionContextIntegrationFilter,logoutFilter,authenticationProcessingFilter,securityContextHolderAwareRequestFilter,anonymousProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor
</value>
</property>
</bean>

ConcurrentSessionFilter aktualizuje informace o session a tím pádem je možné kontrolovat vypršení session pro daného uživatele.

<!-- Filter required by concurrent session handling package -->
<bean id="concurrentSessionFilter" class="org.acegisecurity.concurrent.ConcurrentSessionFilter">
<property name="expiredUrl" value="${acegi.expiredUrl}"/>
<property name="sessionRegistry" ref="sessionRegistry"/>
</bean>

HttpSessionContextIntegrationFilter se stará o přenos SecurityContext mezi jednotlivými HTTP voláními.

<!-- HttpSessionContextIntegrationFilter is responsible for storing a SecurityContext between HTTP requests -->
<bean id="httpSessionContextIntegrationFilter" class="org.acegisecurity.context.HttpSessionContextIntegrationFilter"/>

LogoutFilter slouží k odhlášení přihlášeného uživatele (vymazání kontextu z registru). Definice obsahuje URL adresu, kam se má uživatel přesměrovat po odhlášení a seznam handlerů (implementace LogoutHandler), které se mají provést. Nezbytným handlerem je SecurityContextLogoutHandler, který vymazává informace o přihlášeném uživateli z registru. Kromě toho jsem si vytvořil handler pro účely logování.

<!-- Logs a principal out.
Use <a href="j_acegi_logout">Logout</a> on the page. -->
<bean id="logoutFilter" class="org.acegisecurity.ui.logout.LogoutFilter">
<constructor-arg value="${acegi.urlAfterLogout}"/> <!-- URL redirected to after logout -->
<constructor-arg>
<list>
<bean class="org.acegisecurity.ui.logout.SecurityContextLogoutHandler"/>
<bean class="cz.anect.securitymodule.ldap.LogoutHandlerImpl"/>
</list>
</constructor-arg>
</bean>

Filter pro zpracování požadavku na přihlášení. Standardně je možné použít AuthenticationProcessingFilter. Já jsem si standardní filtr upravil, protože jsem požadoval zablokování účtu po několika špatných pokusech o přihlášení. Tuto implementaci jsem popisoval v tomto článku.

<!-- Processes an authentication form. -->
<bean id="authenticationProcessingFilter" class="cz.anect.securitymodule.DisableAuthenticationProcessingFilter">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="authenticationFailureUrl" value="${acegi.authenticationFailureUrl}"/>
<property name="defaultTargetUrl" value="/"/>
<property name="filterProcessesUrl" value="/j_acegi_security_check"/>
<property name="disabledAccountManager" ref="disabledAccountManager"/>
</bean>

BasicProcessingFilter zpracovává BASIC hlavičky z HTTP požadavků. Tento filtr používám pouze pro relativní adresy začínající "remoting". Na této adrese jsou totiž publikované vzdálené služby (remote services) a já pomocí Acegi zajišťuji autorizovaný přístup k těmto službám.
  
<bean id="basicProcessingFilter" class="org.acegisecurity.ui.basicauth.BasicProcessingFilter">
<property name="authenticationManager"><ref bean="authenticationManager"/></property>
<property name="authenticationEntryPoint"><ref bean="authenticationEntryPoint"/></property>
</bean>

<bean id="authenticationEntryPoint" class="org.acegisecurity.ui.basicauth.BasicProcessingFilterEntryPoint">
<property name="realmName" value="MIS_REALM"/>
</bean>

<!-- A Filter which populates the ServletRequest with a new request wrapper. -->
<bean id="securityContextHolderAwareRequestFilter" class="org.acegisecurity.wrapper.SecurityContextHolderAwareRequestFilter"/>

AnonymousProcessingFilter slouží k vytváření anonymních uživatelů (= uživatelů, kteří nejsou přihlášeni). To má tu velkou výhodu, že já si zde mohu nadefinovat parametry anonymního uživatele a pak v aplikaci s tím pracovat jako s jakýmkoliv dalším uživatelem. Nepřihlášení uživatel tedy není null objekt, ale normální uživatel se specifickými vlastnostmi.

<bean id="anonymousProcessingFilter" class="org.acegisecurity.providers.anonymous.AnonymousProcessingFilter">
<property name="key" value="anonymKey"/>
<property name="userAttribute" value="anonymousUser,ROLE_ANONYMOUS"/>
</bean>

ExceptionTranslationFilter zpracovává Java výjimky a vytváří odpovídající HTTP odpovědi.

<!-- Handles any AccessDeniedException and AuthenticationException thrown within the filter chain. -->
<bean id="exceptionTranslationFilter" class="org.acegisecurity.ui.ExceptionTranslationFilter">
<property name="authenticationEntryPoint">
<bean class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">
<property name="loginFormUrl" value="${acegi.loginFormUrl}"/>
<property name="forceHttps" value="true"/>
</bean>
</property>
<property name="accessDeniedHandler">
<bean class="cz.anect.securitymodule.ldap.CustomAccessDeniedHandlerImpl">
<property name="errorPage" value="${acegi.accessDeniedUrl}"/>
</bean>
</property>
</bean>

FilterSecurityInterceptor řídí přístup k jednotlivým URL adresám. Nastavení URL adres mám v odděleném properties souboru, který uvedu na konci.

<!-- protect web URIs -->
<bean id="filterInvocationInterceptor" class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="accessDecisionManager" ref="accessDecisionManager"/>
<property name="objectDefinitionSource">
<value>${acegi.urlMapping}</value>
</property>
</bean>

ProviderManager prochází zaregistrované providery a snaží se získat odpověď na autentikační požadavek (např. přihlášení uživatele k aplikaci). Jednotlivé providery se procházejí v uvedeném pořadí a to do té doby, než nejaký provider vrátí odpověď.
    
<!-- authority providers -->
<bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">
<property name="providers">
<list>
<ref bean="daoAuthenticationProvider"/>
<ref bean="ldapAuthProvider"/>
<bean class="org.acegisecurity.providers.anonymous.AnonymousAuthenticationProvider">
<property name="key" value="anonymKey"/>
</bean>
</list>
</property>
</bean>

DaoAuthenticationProvider je provider pro přístup k datům, které jsou uloženy v databázi nebo v paměti. Tento provider toho sám o sobě moc neumí, proto je potřeba zaregistrovat userDetailsService. Já tento provider používám pouze pro účely autorizace přístupu k publikovaným službám (=remote services).

<bean id="daoAuthenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
<property name="userDetailsService" ref="userDetailsService"/>
</bean>

InMemoryDaoImpl je nejjednodušší implementace UserDetailsService a umožňuje mi nadefinovat si uživatele např. pomocí properties souborů. Jak jsem již zmiňoval v minulém bodě, tak tento provider resp. UserDetailService používám pouze pro oveření přístupu ke vzdáleným službám. V properties souboru si nadefinuji fiktivní uživatele pro jednotlivé klienty vzdálených služeb a mám jednoduchý způsob, jak mohu ověřovat přístup.

<!-- In-memory implementation; all information is loaded from properties file -->
<bean id="userDetailsService" class="org.acegisecurity.userdetails.memory.InMemoryDaoImpl">
<property name="userProperties">
<bean class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<property name="location" value="classpath:/config/common/users.properties"/>
</bean>
</property>
</bean>

DefaultInitialDirContextFactory slouží pro připojení k LDAP serveru. Jednotlivé hodnoty jsou uloženy v properties souboru, který uvedu na konci článku.

<!-- =========================== LDAP ========================== -->
<!-- Pripojeni k LDAP serveru -->
<bean id="initialDirContextFactory" class="org.acegisecurity.ldap.DefaultInitialDirContextFactory">
<constructor-arg value="${ldap.providerUrl}"/>
<property name="managerDn" value="${ldap.managerDn}"/>
<property name="managerPassword" value="${ldap.password}"/>
</bean>

LdapAuthenticationProvider je základní provider pro integraci s LDAP serverem. Nastavení LDAP provideru spočívá ve dvou hlavních věcech - jak bude probíhat autentikace uživatele a jak se budou načítat role k uživatelům. Tyto věci jsou závislé na struktuře LDAP serveru a proto je nutné toto nastavení upravit dle aktuální podoby. S ohledem na specifické požadavky autentikace na základě různých atributů v LDAP serveru jsem si vytvořil vlastní implementaci LDAP provideru.

<bean id="ldapAuthProvider" class="cz.anect.securitymodule.ldap.CustomLdapAuthenticationProvider">
<constructor-arg>
<!-- autentifikace uzivatele -->
<bean class="org.acegisecurity.providers.ldap.authenticator.BindAuthenticator">
<constructor-arg><ref local="initialDirContextFactory"/></constructor-arg>
<property name="userDnPatterns">
<list>
<value>${ldap.userDnPatterns1}</value>
<value>${ldap.userDnPatterns2}</value>
<value>${ldap.userDnPatterns3}</value>
<value>${ldap.userDnPatterns4}</value>
<value>${ldap.userDnPatterns5}</value>
<value>${ldap.userDnPatterns6}</value>
<value>${ldap.userDnPatterns7}</value>
<value>${ldap.userDnPatterns8}</value>
<value>${ldap.userDnPatterns9}</value>
<value>${ldap.userDnPatterns10}</value>
<value>${ldap.userDnPatterns11}</value>
<value>${ldap.userDnPatterns12}</value>
<value>${ldap.userDnPatterns13}</value>
<value>${ldap.userDnPatterns14}</value>
<value>${ldap.userDnPatterns15}</value>
<value>${ldap.userDnPatterns16}</value>
<value>${ldap.userDnPatterns17}</value>
<value>${ldap.userDnPatterns18}</value>
<value>${ldap.userDnPatterns19}</value>
<value>${ldap.userDnPatterns20}</value>
</list>
</property>
</bean>
</constructor-arg>
<constructor-arg>
<!-- nacteni roli k uzivateli -->
<bean class="org.acegisecurity.providers.ldap.populator.DefaultLdapAuthoritiesPopulator">
<constructor-arg><ref local="initialDirContextFactory"/></constructor-arg>
<constructor-arg value="${ldap.groupSearchBase}"/>
<property name="groupSearchFilter" value="${ldap.groupSearchFilter}"/>
<property name="groupRoleAttribute" value="${ldap.groupRoleAttribute}"/>
<property name="rolePrefix" value="${ldap.rolePrefix}"/>
<property name="convertToUpperCase" value="${ldap.convertToUpperCase}"/>
<property name="searchSubtree" value="true"/>
</bean>
</constructor-arg>
</bean>
<!-- =========================== LDAP ========================== -->

AffirmativeBased je jednoduchou implementací AccessDecisionManager. AccessDecisionManager rozhoduje o tom, zda přihlášený uživatel má dostatečná práva pro přístup k cílovému zdroji (to může být např. webová stránky na určité adrese, to může být metoda v Java rozhraní apod.). Pro můj konkrétní případ se kontroluje role daného uživatele (RoleVoter) a (pokud není první kontrola úspěšná) speciální atributy IS_AUTHENTICATED_FULLY nebo IS_AUTHENTICATED_REMEMBERED nebo IS_AUTHENTICATED_ANONYMOUSLY (AuthenticatedVoter) , které mohu použít v definici přístupu ke stránkám.

<!--
AffirmativeBased implementation will grant access if one or more ACCESS_GRANTED votes were
received (ie a deny vote will be ignored, provided there was at least one grant vote).
In other words principal must have corresponding ROLE and particular level of authentication.
-->
<bean id="accessDecisionManager" class="org.acegisecurity.vote.AffirmativeBased">
<property name="allowIfAllAbstainDecisions" value="false"/>
<property name="decisionVoters">
<list>
<!-- class will vote if any ConfigAttribute begins with ROLE_. -->
<bean class="org.acegisecurity.vote.RoleVoter"/>
<bean class="org.acegisecurity.vote.AuthenticatedVoter"/>
</list>
</property>
</bean>

MethodSecurityInterceptor slouží k ověřování přístupu na úrovni Java kódu.

<!-- method authorization in FACADE LAYER -->
<bean id="facadeSecurity" class="org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">
<property name="validateConfigAttributes"><value>true</value></property>
<property name="authenticationManager"><ref bean="authenticationManager"/></property>
<property name="accessDecisionManager" ref="accessDecisionManager"/>
<property name="objectDefinitionSource">
<value>
cz.anect.mis.web.facade.DocumentFacade.addDocuments=ROLE_UZIVATELE, ROLE_SPRAVCIGLOBALNI, ROLE_SPRAVCILOKALNI, ROLE_ADDDOCUMENT_WITHOUTEDIT, ROLE_ADDDOCUMENT_WITHEDIT
cz.anect.mis.web.facade.DocumentFacade.addDocumentData=ROLE_ADDDOCUMENTDATA
</value>
</property>
</bean>

<!-- auto proxy for beans which we want to intercepts by security interceptor -->
<bean id="autoProxySecurityCreator" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="interceptorNames">
<list>
<idref bean="facadeSecurity"/>
</list>
</property>
<property name="beanNames">
<list>
<idref bean="DocumentFacade"/>
</list>
</property>
</bean>

Registrace listenerů pro účely logování a zachycení neúspěšných pokusů o přihlášení.
    
<!-- This bean is optional; it isn't used by any other bean as it only listens and logs -->
<bean id="loggerListener" class="cz.anect.securitymodule.CustomLoggerListener"/>

<bean id="failureListener" class="cz.anect.securitymodule.AuthentificationFailureListener">
<property name="disabledAccountManager" ref="disabledAccountManager"/>
</bean>

<bean id="disabledAccountManager" class="cz.anect.securitymodule.BasicDisabledAccountManager">
</bean>

<bean id="sessionRegistry" class="org.acegisecurity.concurrent.SessionRegistryImpl"/>

</beans>

Nyní ještě uvedu použité properties soubory.
###############################################
# property file: acegi_base.properties #
# format : key = value #
# Zakladni nastaveni pro Acegi knihovnu #
###############################################

acegi.expiredUrl =/expired.jsp
acegi.urlAfterLogout =/index.jsp
acegi.authenticationFailureUrl =/login.jsp?login_error=1
acegi.loginFormUrl =/login.jsp
acegi.accessDeniedUrl =/accessDenied.jsp



<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>
Mapovani mezi URLs a rolemi.
Poradi je dulezite, konstanty jsou definovany v AuthenticatedVoter:
IS_AUTHENTICATED_FULLY or IS_AUTHENTICATED_REMEMBERED or IS_AUTHENTICATED_ANONYMOUSLY.
!!! prvni dva radky nechat beze zmeny !!!
Dalsi radky jsou ve formatu: url = jmeno role (popr. vyse uvedene promenne)
</comment>
<entry key="acegi.urlMapping">
<![CDATA[
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/adddocument.*=ROLE_UZIVATELE, ROLE_SPRAVCIGLOBALNI, ROLE_SPRAVCILOKALNI
/adddocumentas.*=ROLE_UZIVATELE, ROLE_SPRAVCIGLOBALNI, ROLE_SPRAVCILOKALNI
/adddocumentdataonly.*=ROLE_ADDDOCUMENTDATA
/adddocumenttodata.*=IS_AUTHENTICATED_FULLY
/analogdocstoreadmin.*=IS_AUTHENTICATED_FULLY
/newdigiuser.*=IS_AUTHENTICATED_FULLY

/remoting/*=ROLE_REMOTE_SERVICES, ROLE_BINDED_APPLICATION
]]>
</entry>
</properties>


###########################################################
# property file: users.properties #
# format : key (username) = value (password, roles #
# List of technical users for accessing remote services #
###########################################################

system1=673yuededjei3yuy3bhyu3,ROLE_REMOTE_SERVICES
system2=eddjeihyu63jeui3j7337,ROLE_REMOTE_SERVICES


###############################################
# property file: ldap.properties #
# format : key = value #
# Udaje pro pripojeni k LDAP serveru #
###############################################

#-------------------- pripojeni k LDAPu
#This should be in the form ldap://monkeymachine.co.uk:389/dc=acegisecurity,dc=org
ldap.providerUrl =ldap://
#directory user to authenticate (including base DN)
ldap.managerDn =
ldap.password =

#-------------------- autentizace uzivatele
#DN sablona pro vyhledavani uzivatele (max. 20 polozek)
ldap.userDnPatterns1 =uid={0},ou=uzivatele,ou=302
ldap.userDnPatterns2 =uid={0},ou=uzivatele,ou=311
ldap.userDnPatterns3 =uid={0},ou=uzivatele,ou=321
ldap.userDnPatterns4 =uid={0},ou=uzivatele,ou=331
ldap.userDnPatterns5 =uid={0},ou=uzivatele,ou=341
ldap.userDnPatterns6 =uid={0},ou=uzivatele,ou=342
ldap.userDnPatterns7 =uid={0},ou=uzivatele,ou=351
ldap.userDnPatterns8 =uid={0},ou=uzivatele,ou=353
ldap.userDnPatterns9 =uid={0},ou=uzivatele,ou=361
ldap.userDnPatterns10 =uid={0},ou=uzivatele,ou=362
ldap.userDnPatterns11 =uid={0},ou=uzivatele,ou=371
ldap.userDnPatterns12 =uid={0},ou=uzivatele,ou=373
ldap.userDnPatterns13 =uid={0},ou=uzivatele,ou=381
ldap.userDnPatterns14 =uid={0},ou=uzivatele,ou=391
ldap.userDnPatterns15 =uid={0},ou=uzivatele,ou=partneri
ldap.userDnPatterns16 =uid={0},ou=uzivatele,ou=verejnost
ldap.userDnPatterns17 =
ldap.userDnPatterns18 =
ldap.userDnPatterns19 =
ldap.userDnPatterns20 =

#-------------------- nacteni roli k uzivateli
#kontejner pro vyhledavani roli k teto aplikaci (DN bez zakladniho DN)
ldap.groupSearchBase =
#jmeno atributu u role, ktery obsahuje odkazy na uzivatele v dane skupine
ldap.groupSearchFilter =(roleOccupant={0})
#jmeno atributu, ktery se pouzije pro ziskani nazvu role
ldap.groupRoleAttribute =cn
#prefix ke jmenu role v LDAPu
ldap.rolePrefix =ROLE_
#maji se jmena roli prevadet na velka pismena? true|false
ldap.convertToUpperCase =true