I'm using Apache Shiro 1.4.0 with Java EE 7 on a Payara 4.1.1.x. My shiro.ini looks like:
[urls]
page1.xhtml = user
must_be_logged.xhtml = authc
The standard RememberMe functionality works perfectly on page1.xhtml
Regarding the login, I'm using a programmatic login from BalusC's article. As it's a CDI bean, I can inject some EJB and perform some actions:
@Named
@RequestScoped
public class Login{
// username, password, rememberMe attribute definition with getter and setter
@EJB
private SomeService someServiceImpl;
public void submit(){
try {
SecurityUtils.getSubject().login(new UsernamePasswordToken(username, password, remember));
Session session = SecurityUtils.getSubject().getSession(false);
if(session != null){
someServiceImpl.addUserInfoToSession(username, session);
}
// redirect to page
}
catch(AuthenticationException e) {
// send error message
}
}
}
The addSomeUserInfoToSession
basically retrieves a User entity from the database and add some information (first name, last name, language preferences, etc) to the session attributes via session.setAttribute("first_name", ...);
.
I could not find how to perform such action for remembered users: When a subject is identified from a rememberMe cookie, how to perform a EJB-involved action?
org.apache.shiro.session.SessionListener
) but I could not find how to inject CDI bean or how to use EJB in such "POJO"org.apache.shiro.mgt.AbstractRememberMeManager
, I could not find how to inject @EJB in it.Self reply for my own record and if it may help others
Well, I'm an idiot, this was a wrong hint. Configuring RememberMeManager will only change "how the successfully logged principal will be remembered". As I'm using a web environment, the remember me functionality is cookie-based.
However, it was not completely useless. By configuration RememberMeManager's cookie, I can choose the remember me cookie validity. By default, such cookie is valid for one year. It's then easy to set this duration at let's say 30 days:
rememberMeCookie = org.apache.shiro.web.servlet.SimpleCookie
rememberMeCookie.name = my_remember_me_name
# 60*60*24*30
rememberMeCookie.maxAge = 2592000
rememberMeManager = org.apache.shiro.web.mgt.CookieRememberMeManager
rememberMeManager.cookie = $rememberMeCookie
rememberMeManager.cipherKey = **whatever you want but don't leave default value!**
securityManager.rememberMeManager = $rememberMeManager
When sessions are instantiated, subject is already created so I had to search one step ahead
By digging into TRACE logs, I reached the DefaultSecurityManager
method: resolvePrincipals(SubjectContext)
. Okay this is where rememberMe cookie are caught. Principal information are fetched by the appropriate RememberMeManager (deciphering required) and put into a context. So far, the principal is fetched but I cannot proceed to any manipulation.
resolvePrincipals(SubjectContext)
is called by createSubject(SubjectContext)
still within DefaultSecurityManager
. This method ensures that a subject is always linked to the proper context and generate a subject if necessary. This leads to the SubjectFactory via the doCreateSubject(SubjectContext)
method
So I tried to extend org.apache.shiro.mgt.DefaultSubjectFactory
and assign it to the SecurityManager:
mySubjectFactory = com.example.shiro.MySubjectFactory
securityManager.subjectFactory = $mySubjectFactory
First of all, I'm working in a web environment so the default implementation of security manager is not DefaultSecurityManager
but org.apache.shiro.web.mgt.DefaultWebSecurityManager
which requires that all subjects are compliant with the WebSubject
interface. Consequently, I had to extend the org.apache.shiro.web.mgt.DefaultWebSubjectFactory
instead.
Okay, now I have the remembered subject: I just need to inject some @EJB and that's it! Well...no: Shiro is not the best friend of Dependency Injection. My quickest workaround is to use some CDI BeanManager to notify an ApplicationScoped CDI bean which will do the job. So my subject factory looks like:
public class MySubjectFactory extends DefaultWebSecurityManager{
private final BeanManager beanManager = CDI.current().getBeanManager();
@Override
public Subject createSubject(SubjectContext context){
Subject subject = super.createSubject(context);
// only do the job for remembered subjects. Need to
// check only isRemembered() as DelegatingSubject's
// isRemembered() implementation makes it exclusive with
// isAuthenticated()
if(subject.isRemembered()){
// getSession() with true flag to explicitly shows that
// we have to create a session here. At this stage, session
// is null so the event will have a null session as content
Session session = subject.getSession(true);
// Annotations are stripped for clarity
beanManager.fireEvent(session);
}
}
}
On the other hand, I have
@ApplicationScoped
public class MyShiroSessionManager{
@EJB
private MyFacade myEjb;
public void onRememberedSubject(@Observes Session session){
myEjb.updateUserInformation(session);
}
}
This does not sound an optimised solution but at least it works. If you have any hint regarding a better design, feel free to let me know.