Невозможно @Inject моего DAO в пользовательском Apache Shiro AuthorizingRealm

Я пытаюсь внедрить свой UserDAO в свой собственный AuthorizingRealm, который использует Apache Shiro, но... я получаю null.

Что я делаю не так?

Широ.ини

[main]
user = demo.shiro.security.FacesAjaxAwareUserFilter
realmA = demo.shiro.security.JpaRealm
credentialsMatcher = org.apache.shiro.authc.credential.SimpleCredentialsMatcher
realmA.credentialsMatcher = $credentialsMatcher
securityManager.realms = $realmA
user.loginUrl = /pages/public/login.xhtml

[users]
admin = admin
user = user

[urls]
# public files and folders
/index.html = anon
/resources/** = anon
/pages/public/** = anon

# restricted files and folders
/pages/admin/** = user
/pages/user/** = user

JpaRealm.java

public class JpaRealm extends AuthorizingRealm {

    @Inject
    private UserDao userDao;

    public JpaRealm() {
        setCredentialsMatcher(new Sha256CredentialsMatcher());
    }

    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authToken) throws AuthenticationException {
        UsernamePasswordToken token = (UsernamePasswordToken) authToken;
        User user = userDao.getForUsername(token.getUsername());
        if (user != null) {
            return new SimpleAuthenticationInfo(user.getId(), user.getPassword(), getName());
        } else {
            return null;
        }
    }

    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        Long userId = (Long) principals.fromRealm(getName()).iterator().next();
        User user = userDao.findByKey(userId);
        if (user != null) {
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
            for (Role role : user.getRoles()) {
                info.addRole(role.getDescription());
                for (Permition permition : role.getPermitions()) {
                    info.addStringPermission(permition.getDescription());
                }
            }
            return info;
        } else {
            return null;
        }
    }

}

Что я должен сделать, чтобы позволить CDI знать о @Inject внутри моей пользовательской области и правильно внедрить мой UserDAO?


person BBacon    schedule 24.03.2013    source источник


Ответы (3)


EnvironmentLoaderListener по умолчанию, используемый Apache Shiro, не поддерживает CDI. Решение состоит в том, чтобы создать тот, который есть, и заменить исходную ссылку в файле web.xml на ваш индивидуальный.

Примечание. Внедрение CDI поддерживается в слушателях. автоматически, но прослушиватели должны запрашивать bean-компоненты через механизм CDI. Пользовательский слушатель будет использовать @Inject для запроса bean-компонентов и создаст JpaRealm в качестве bean-компонента CDI, в который будут внедрены все зависимости. Прослушиватель Shire по умолчанию не будет создавать JpaRealm как bean-компонент с поддержкой CDI через @Inject.

CustomCredentialsMatcher.java

public class CustomCredentialsMatcher extends SimpleCredentialsMatcher {
}

CustomEnvironmentLoaderListener.java

public class CustomEnvironmentLoaderListener extends EnvironmentLoaderListener {

    @Inject
    private JpaRealm jpaRealm;

    @Override
    protected WebEnvironment createEnvironment(ServletContext pServletContext) {
        WebEnvironment environment = super.createEnvironment(pServletContext);
        RealmSecurityManager rsm = (RealmSecurityManager) environment.getSecurityManager();
        PasswordService passwordService = new DefaultPasswordService();
        PasswordMatcher passwordMatcher = new PasswordMatcher();
        passwordMatcher.setPasswordService(passwordService);
        jpaRealm.setCredentialsMatcher(passwordMatcher);
        rsm.setRealm(jpaRealm);
        ((DefaultWebEnvironment) environment).setSecurityManager(rsm);
        return environment;
    }

}

FacesAjaxAwareUserFilter.java

public class FacesAjaxAwareUserFilter extends UserFilter {

    private static final String FACES_REDIRECT_XML = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><partial-response><redirect url=\"%s\"></redirect></partial-response>";

    @Override
    protected void redirectToLogin(ServletRequest req, ServletResponse res) throws IOException {
        HttpServletRequest request = (HttpServletRequest) req;

        if ("partial/ajax".equals(request.getHeader("Faces-Request"))) {
            res.setContentType("text/xml");
            res.setCharacterEncoding("UTF-8");
            res.getWriter().printf(FACES_REDIRECT_XML, request.getContextPath() + getLoginUrl());
        } else {
            super.redirectToLogin(req, res);
        }
    }

}

JpaRealm.java

public class JpaRealm extends AuthorizingRealm {

    private static String REALM_NAME = "jpaRealm";

    @Inject
    private UserDao userDao;

    @Inject
    private RoleDao roleDao;

    @Inject
    private PermissionDao permissionDao;

    public JpaRealm() {
        setName(REALM_NAME); // This name must match the name in the User class's getPrincipals() method
    }

    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authToken) throws AuthenticationException {
        UsernamePasswordToken token = (UsernamePasswordToken) authToken;
        User user = userDao.getForUsername(token.getUsername());
        if (user != null) {
            return new SimpleAuthenticationInfo(user.getId(), user.getPassword(), getName());
        } else {
            return null;
        }
    }

    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        Long userId = (Long) principals.fromRealm(getName()).iterator().next();
        User user = userDao.findByKey(userId);
        if (user != null) {
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
            for (Role role : roleDao.getForUser(user)) {
                info.addRole(role.getDescription());
                for (Permition permition : permissionDao.getForRole(role)) {
                    info.addStringPermission(permition.getDescription());
                }
            }
            return info;
        } else {
            return null;
        }
    }

}

Широ.ини

[main]
user = com.boss.mrfoods.security.FacesAjaxAwareUserFilter
user.loginUrl = /pages/public/login.xhtml

[urls]
/index.html = anon
/pages/index.xhtml = anon
/pages/public/** = anon

/pages/admin/** = user, roles[ADMIN]
/pages/user/** = user, roles[USER]

веб.xml

...

<listener>
    <listener-class>com.boss.mrfoods.security.CustomEnvironmentLoaderListener</listener-class>
</listener>

<filter>
    <filter-name>ShiroFilter</filter-name>
    <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>ShiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
...
person BBacon    schedule 24.03.2013
comment
Отличный ответ! Но у меня есть вопрос: что делает этот пользовательский CDI EnvironmentLoaderListener осведомленным? Вставить аннотацию? И делает ли эта реализация слушателя shiro.ini conf устаревшей? Потому что мы устанавливаем фиксированную область в строке: rsm.setRealm(jpaRealm); - person salihcenap; 23.07.2013
comment
Я включил дополнительный код, чтобы ответить на вопрос о CDI (см. JpaRealm.java). Почему-то shiro.ini не устарел... он нужен для определения некоторых конфигураций... его удаление вызывает много проблем. Если вы готовы создать бесплатную защиту shiro.ini, я думаю, это было бы очень хорошо. - person BBacon; 01.08.2013
comment
Просто вопрос, почему вы удалили realmA из вашего shiro.ini? (или просто для краткости?). спасибо - person timmz; 12.04.2015
comment
Я добавил в ответ некоторые пояснения о CDI-осведомленности: внедрение CDI поддерживается в слушателях автоматически, но слушатели должны запрашивать bean-компоненты через механизм CDI. Пользовательский слушатель будет использовать @Inject для запроса bean-компонентов и создаст JpaRealm в качестве bean-компонента CDI, в который будут внедрены все зависимости. Прослушиватель Shire по умолчанию не будет создавать JpaRealm как bean-компонент с поддержкой CDI через @Inject. - person OndroMih; 03.12.2015

jpaRealm оказался нулевым в «CustomEnvironmentLoaderListener». Я также попытался настроить аннотацию @Service("JpaRealm") для класса JpaRealm, чтобы контейнер мог знать об инъекции, но все равно не повезло. JpaRealm равен нулю в любом случае.

Что еще нужно сделать, если мы хотим, чтобы инъекция работала в Custom Realms.

person Rahat ALi    schedule 16.07.2013
comment
Я отредактировал свой ответ, чтобы включить больше кода из моего программного обеспечения. Если этого недостаточно, дайте мне знать. Извините, если я опоздал на 15 дней. - person BBacon; 01.08.2013

Используйте ShiroWebModule для внедрения вашего пользовательского Realm

public class PocShiroModule extends ShiroWebModule {

    public PocShiroModule(ServletContext servletContext) {
        super(servletContext);
    }

    @Override
    protected void configureShiroWeb() {
        bindConstant().annotatedWith(Names.named("shiro.globalSessionTimeout"))
                .to(30000L);
        bind(Realm.class).to(JPARealm.class);
    }

    @Provides
    @Singleton
    Set<Realm> provideRealmSet(Realm realm) {
        Set<Realm> result = new HashSet<Realm>();
        result.add(realm);
        return result;
    }

}

Зарегистрируйте этот модуль в прослушивателе контекста.

public class PocGuiceServletConfig extends GuiceServletContextListener {

    private ServletContext context = null;

    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        this.context = servletContextEvent.getServletContext();
        super.contextInitialized(servletContextEvent);
    }

    @Override
    protected synchronized Injector getInjector() {
        return Guice.createInjector(
                new PocModule(), 
                new PocShiroModule(context), 
                new ShiroAopModule());
    }
}
person Vincnetas    schedule 22.10.2014