【问题标题】:Load remembered user data with Apache Shiro使用 Apache Shiro 加载记住的用户数据
【发布时间】:2017-11-07 19:13:39
【问题描述】:

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

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

标准的 RememberMe 功能在 page1.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", ...); 将一些信息(名字、姓氏、语言首选项等)添加到会话属性中。

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

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

【问题讨论】:

    标签: jakarta-ee shiro remember-me


    【解决方案1】:

    自我回复以供我自己记录,如果它可以帮助其他人

    RememberMeManager

    好吧,我是个白痴,这是一个错误的提示。配置 RememberMeManager 只会更改“如何记住成功记录的主体”。由于我使用的是 Web 环境,因此记住我的功能是基于 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 内。此方法确保主题始终链接到正确的上下文并在必要时生成主题。这通过doCreateSubject(SubjectContext) 方法导致SubjectFactory

    主题工厂

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

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

    首先,我在网络环境中工作,因此安全管理器的默认实现不是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);
        }
    }
    

    这不是一个优化的解决方案

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

    【讨论】:

      猜你喜欢
      • 2014-03-09
      • 2013-09-14
      • 2016-09-22
      • 2020-09-14
      • 2016-10-16
      • 2021-06-07
      • 1970-01-01
      • 2012-10-29
      • 2015-03-05
      相关资源
      最近更新 更多