4

I'm trying to inject my UserDAO inside my custom AuthorizingRealm that Apache Shiro is using but... I get null.

What am I doing wrong?

shiro.ini

[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;
        }
    }

}

What I must do to allow CDI to be aware of the @Inject inside my custom realm and inject my UserDAO properly?

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
BBacon
  • 2,456
  • 5
  • 32
  • 52

3 Answers3

11

The default EnvironmentLoaderListener used by Apache Shiro is not CDI aware. The solution is to build one that is and replace the original reference in the web.xml to point for your customized one.

Note: CDI injection is supported in listeners automatically, but the listeners must request beans via CDI mechanism. The custom listener will use @Inject to request beans and will create JpaRealm as CDI bean, which will have all dependencies injected. The default Shire listener would not create JpaRealm as a CDI-enabled bean via @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;
        }
    }

}

shiro.ini

[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]

web.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>
...
Eng.Fouad
  • 115,165
  • 71
  • 313
  • 417
BBacon
  • 2,456
  • 5
  • 32
  • 52
  • 1
    Excellent answer! But I have a question: What makes this custom EnvironmentLoaderListener "CDI aware"? Inject annotation? And does this listener implementation make shiro.ini conf obsolete? Because we are setting a fixed realm on line: rsm.setRealm(jpaRealm); – salihcenap Jul 23 '13 at 10:57
  • I included more code to answer the "CDI awareness" part (see JpaRealm.java). Somehow shiro.ini is not obsolete... you need it to define some configurations... removing it causes many problems. If you are abre to build a shiro.ini free security, I think that would be very good. – BBacon Aug 01 '13 at 12:33
  • Juste a question, why did you remove realmA from your shiro.ini ? ( or just for brievty ?). tnx – timmz Apr 11 '15 at 21:23
  • I added some xplanation about CDI-awareness into the answer: CDI injection is supported in listeners automatically, but the listeners must request beans via CDI mechanism. The custom listener will use `@Inject` to request beans and will create `JpaRealm` as CDI bean, which will have all dependencies injected. The default Shire listener would not create `JpaRealm` as a CDI-enabled bean via `@Inject`. – OndroMih Dec 03 '15 at 10:33
0

jpaRealm is happened to be null in 'CustomEnvironmentLoaderListener'. I tried setting up @Service("JpaRealm") annotation on JpaRealm class as well so that container may know about the injection but still no luck. JpaRealm is null in either case.

What else is necessary to do in case we ant the injection working in Custom Realms.

Rahat ALi
  • 61
  • 2
  • 4
  • I edited my aswer to include more code from my software. If it is not enough let me know. I'm sorry if I am 15 days late. – BBacon Aug 01 '13 at 12:29
0

Use ShiroWebModule to inject your custom 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;
    }

}

Register this module in your context listener

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());
    }
}
Vincnetas
  • 146
  • 1
  • 4