Unable to @Inject my DAO in a Custom Apache Shiro AuthorizingRealm
cdi, java, jsf, shiro
Solution
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>
...
Problem
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?