2013-08-29 27 views
11

W aplikacji, którą buduję używamy prostych Java 6 EE i JBoss (bez sprężyny itp.), Z JPA/Hibernate, JSF, CDI i EJB.Wstrzykiwanie zarządzanej fasoli CDI w niestandardowej Shiro AuthorisingRealm

Nie znalazłem wielu dobrych rozwiązań w zakresie bezpieczeństwa ogólnego (zalecenia są mile widziane), ale najlepszym znaleziskiem jest Apache Shiro.

Jednak wydaje się, że ma wiele niedociągnięć. Niektóre z nich można przeczytać w Balus C's strony:

http://balusc.blogspot.com/2013/01/apache-shiro-is-it-ready-for-java-ee-6.html

Ale ja natknąłem się na kolejny duży problem, który jest już wspomniano here dotyczące iniekcji zależność i proxy.

Zasadniczo mam dobrze napisaną UserDAO opartą na JPA, która zapewnia wszystko, co niezbędne do uwierzytelnienia. Moja baza danych jest starannie skonfigurowana w persistence.xml i mydatabase-ds.xml (dla JBoss).

Wydaje się głupim powtórzenie wszystkich tych informacji konfiguracyjnych po raz drugi i dodanie zapytań o tablice użytkowników do pliku shiro.ini. Dlatego właśnie zdecydowałem się napisać własne królestwo zamiast używać JdbcRealm.

Moja pierwsza próba była podklasy AuthorizingRealm ... coś jak:

@Stateless 
public MyAppRealm extends AuthorizingRealm { 
    @Inject private UserAccess userAccess; 

    @Override 
    protected AuthenticationInfo doGetAuthenticationInfo(
     AuthenticationToken token) throws AuthenticationException { 

     UsernamePasswordToken userPassToken = (UsernamePasswordToken) token; 

     User user = userAccess.getUserByEmail(userPassToken.getUsername()); 
     if (user == null) { 
      return null; 
     } 

     AuthenticationInfo info = new SimpleAuthenticationInfo(); 
     // set data in AuthenticationInfo based on data from the user object 

     return info; 

    @Override 
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { 
     // TODO 
     return null; 
    } 
} 

Więc to nie bardzo złe, ponieważ nie można MyAppRealm proxy, ponieważ nie jest ostateczna metody init() w klasie nadrzędnej w górę hierarchii klas.

Moja druga próba polegała na tym, aby MyAppRealm zaimplementował wszystkie potrzebne interfejsy i po prostu przekazał je do instancji AuthorizingRealm. Nie podobało mi się to, ale równie dobrze mógłbym spróbować.

To mnie pogłębia, aplikacja internetowa uruchamia się, ale nadal nie działa. Powodem jest to w pliku konfiguracyjnym, shiro.ini, określić klasę do mojego królestwa:

myAppRealm = com.myapp.MyAppRealm 

To dość dużo mówi mi, że Shiro będzie odpowiedzialny za stworzenie MyAppRealm instancji. Dlatego nie będzie zarządzana CDI, a zatem nie zostanie wstrzyknięta, co jest dokładnie tym, co widzę.

Widziałem to SO answer, ale nie widzę, jak to możliwe, ponieważ ponownie podklasa AuthorizingRealm odziedziczy ostateczną metodę init(), co oznacza, że ​​podklasa nie może być proxy.

Jakieś przemyślenia na temat tego, jak sobie z tym poradzić?

Odpowiedz

8

Jest to klasyczny problem: masz dwie różne struktury, które chcą zarządzać cyklami życia obiektów, i musisz sprawić, by działały, ale obydwa wymagają pełnej kontroli (mój mentalny obraz to coś takiego jak Godzilla i Gamera walka w centrum Tokio). Możesz od razu nie myśleć o Shiro jako o konkurentach dla CDI, ale ponieważ tworzy on instancje swoich obiektów, zawiera on zasadniczo niewielki, podstawowy schemat wtrysku zależności (być może jest to wersja DI Greenspun's tenth rule). Z podobnym problemem spotkałem się tworząc frameworki internetowe, które tworzą i wstrzykują instancje ich backingowych komponentów, współdziałają z CDI.

Podejściem do rozwiązania tego problemu jest stworzenie wyraźnego pomostu między tymi dwoma ramami.Jeśli naprawdę masz szczęście, framework inny niż CDI będzie miał haki, które pozwolą ci dostosować tworzenie obiektów, do których możesz podłączyć coś, co używa CDI (np. W ramce sieci Pasy, możesz napisać ActionResolver, który używa CDI).

Jeśli nie, to most musi przyjąć formę pełnomocnictwa. W tym proxy możesz wykonać jawne wyszukiwanie CDI. Możesz uruchomić program CDI, zdobywając BeanManager, który pozwala skonfigurować kontekst, a następnie utworzyć w nim komponenty. Coś takiego:

BeanManager beanManager = (BeanManager) new InitialContext().lookup("java:comp/BeanManager"); 
Bean<UserDAO> userDAObean = (Bean<UserDAO>) beanManager.resolve(beanManager.getBeans(UserDAO.class)); 
CreationalContext<?> creationalContext = beanManager.createCreationalContext(null); 
UserDAO userDAO = userDAObean.create(creationalContext); 

userDAO jest wtryskiwana, CDI zarządzane fasoli związany z kontekstem teraz trzymać jak creationalContext.

Kiedy skończysz z fasoli (to zależy od ciebie, jeśli zrobić tego odnośnika raz na żądanie lub raz na całe życie wniosku), zwolnij Bean z:

creationalContext.release(); 
+0

Dziękuję za odpowiedź. Jest to bardzo dobra teoretyczna odpowiedź i pod tym względem uderza w gwóźdź w głowę. Myślę, że udało mi się zbudować "most" za pomocą beanmanager. Nie jest to całkiem ładne, ale mam nadzieję, że z czasem się rozwijam i udoskonalam. – lostdorje

8

Można to zrobić poprzez inicjowanie Twoja dziedzina jako część początkowego cyklu życia aplikacji, a następnie Shiro odzyska ją poprzez wyszukiwanie nazw JNDI.

Utwórz komponent bean za pomocą @Singleton i @Startup, aby wymusić jego utworzenie w najwcześniejszym możliwym czasie w cyklu życia aplikacji. W tej klasie będziesz tworzyć instancję nowej instancji klasy "MyAppRealm" i dostarczać wstrzyknięte odwołanie UserAccess jako parametr konstrukcyjny. Co oznacza, że ​​będziesz musiał zaktualizować klasę "MyAppRealm", aby pobrać ten nowy parametr konstruktora.

import java.util.logging.Level; 
import java.util.logging.Logger; 
import javax.annotation.PostConstruct; 
import javax.annotation.PreDestroy; 
import javax.ejb.EJB; 
import javax.ejb.Singleton; 
import javax.ejb.Startup; 
import javax.naming.InitialContext; 
import javax.naming.NamingException; 

@Singleton 
@Startup 
public class ShiroStartup { 

    private final static String CLASSNAME = ShiroStartup.class.getSimpleName(); 
    private final static Logger LOG = Logger.getLogger(CLASSNAME); 

    public final static String JNDI_REALM_NAME = "realms/myRealm"; 

    // Can also be EJB... 
    @Inject private UserAccess userAccess; 

    @PostConstruct 
    public void setup() { 
    final UserAccess service = getService(); 
    final Realm realm = new MyAppRealm(service); 

    try { 
     // Make the realm available to Shiro. 
     bind(JNDI_REALM_NAME, realm); 
    } 
    catch(NamingException ex) { 
     LOG.log(Level.SEVERE, "Could not bind realm: " + JNDI_REALM_NAME, ex); 
    } 
    } 

    @PreDestroy 
    public void destroy() { 
    try { 
     unbind(JNDI_REALM_NAME); 
    } 
    catch(NamingException ex) { 
     LOG.log(Level.SEVERE, "Could not unbind realm: " + JNDI_REALM_NAME, ex); 
    } 
    } 

    /** 
    * Binds a JNDI name to an object. 
    * 
    * @param jndi The JNDI name. 
    * @param object The object to bind to the JNDI name. 
    */ 
    private static void bind(final String jndi, final Object object) 
    throws NamingException { 
    final InitialContext initialContext = createInitialContext(); 

    initialContext.bind(jndi, object); 
    } 

    private static void unbind(final String name) throws NamingException { 
    final InitialContext initialContext = createInitialContext(); 

    initialContext.unbind(name); 
    } 

    private static InitialContext createInitialContext() throws NamingException { 
    return new InitialContext(); 
    } 

    private UserAccess getService() { 
    return this.userAccess; 
    } 
} 

Aktualizacja shiro.ini następująco:

realmFactory = org.apache.shiro.realm.jndi.JndiRealmFactory 
realmFactory.jndiNames = realms/myRealm 

Takie podejście zapewni Ci dostęp do wszystkich swoich CDI udało fasolę bez konieczności wykorzystać wewnętrzne funkcjonowanie CDI. Powodem tego jest to, że "shiro.ini" nie jest ładowany, dopóki nie pojawi się warstwa WWW, która jest po inicjalizacji struktur CDI i EJB.

+0

i jak można by następnie przypisać sferom dostarczonym przez Factory do securitymanager? – billdoor

+0

Powyższy wpis "shiro.ini" to obsługuje. Spójrz na kod źródłowy "org.apache.shiro.config.IniSecurityManagerFactory", jeśli chcesz zobaczyć, jak Shiro to robi. –