使用 Apache Shiro 加载记住的用户数据

Load remembered user data with Apache Shiro

我在 Payara 4.1.1.x 上使用带有 Java EE 7 的 Apache Shiro 1.4.0。我的 shiro.ini 看起来像:

[urls]
page1.xhtml = user
must_be_logged.xhtml = authc

标准 RememberMe 功能在页面上完美运行1.xhtml

关于登录,我使用的是程序化登录from BalusC's article。由于它是一个 CDI bean,我可以注入一些 EJB 并执行一些操作:

@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
        }
    }
}

addSomeUserInfoToSession 主要是从数据库中检索用户实体,并通过 session.setAttribute("first_name", ...);.[=17= 添加一些信息(名字、姓氏、语言偏好等)到会话属性中]

我找不到如何为记住的用户执行此类操作:当从 rememberMe cookie 中识别主题时,如何执行 EJB 相关操作?

  1. 我找不到触发 rememberMe 机制的过滤器
  2. 我尝试使用会话侦听器(实现 org.apache.shiro.session.SessionListener)但我找不到如何注入 CDI bean 或如何在这样的 "POJO"
  3. 中使用 EJB
  4. 如果我使用像扩展 org.apache.shiro.mgt.AbstractRememberMeManager 这样的自定义 RememberMeManager,我找不到如何在其中注入 @EJB。

自己回复记录一下是否对别人有帮助

RememberMeManager

好吧,我是个白痴,这是一个错误的提示。配置 RememberMeManager 只会改变 "how the successfully logged principal will be remembered"。当我使用网络环境时,记住我的功能是基于 cookie 的。

不过,也不是完全没用。通过配置RememberMeManager的cookie,我可以选择remember me cookie的有效期。默认情况下,此类 cookie 的有效期为一年。然后很容易将此持续时间设置为 30 天:

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

会话侦听器

实例化会话时,主题已经创建,所以我必须向前搜索一步

安全管理器

通过深入研究 TRACE 日志,我找到了 DefaultSecurityManager 方法:resolvePrincipals(SubjectContext)。好的,这就是捕获 rememberMe cookie 的地方。主要信息由适当的 RememberMeManager 获取(需要解密)并放入上下文中。到目前为止,主体已提取,但我无法进行任何操作。

resolvePrincipals(SubjectContext)createSubject(SubjectContext) 调用,仍在 DefaultSecurityManager 内。此方法确保主题始终链接到正确的上下文并在必要时生成主题。这导致 SubjectFactory 通过 doCreateSubject(SubjectContext) 方法

主题工厂

所以我尝试扩展 org.apache.shiro.mgt.DefaultSubjectFactory 并将其分配给 SecurityManager:

mySubjectFactory = com.example.shiro.MySubjectFactory
securityManager.subjectFactory = $mySubjectFactory

首先,我在 web 环境中工作,因此安全管理器的默认实现不是 DefaultSecurityManager,而是 org.apache.shiro.web.mgt.DefaultWebSecurityManager,这要求所有主题都符合 WebSubject 界面。因此,我不得不改为扩展 org.apache.shiro.web.mgt.DefaultWebSubjectFactory

好的,现在我记住了主题:我只需要注入一些@EJB 就可以了!嗯...不:Shiro 不是依赖注入的最好朋友。我最快的解决方法是使用一些 CDI BeanManager 来通知将完成这项工作的 ApplicationScoped CDI bean。所以我的主题工厂看起来像:

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

另一方面,我有

@ApplicationScoped
public class MyShiroSessionManager{

    @EJB
    private MyFacade myEjb;

    public void onRememberedSubject(@Observes Session session){
        myEjb.updateUserInformation(session);
    }
}

这不是优化的解决方案

这听起来不是一个优化的解决方案,但至少它有效。如果您有任何关于更好设计的提示,请随时告诉我。