使用 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 相关操作?
- 我找不到触发 rememberMe 机制的过滤器
- 我尝试使用会话侦听器(实现
org.apache.shiro.session.SessionListener
)但我找不到如何注入 CDI bean 或如何在这样的 "POJO" 中使用 EJB
- 如果我使用像扩展
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);
}
}
这不是优化的解决方案
这听起来不是一个优化的解决方案,但至少它有效。如果您有任何关于更好设计的提示,请随时告诉我。
我在 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 相关操作?
- 我找不到触发 rememberMe 机制的过滤器
- 我尝试使用会话侦听器(实现
org.apache.shiro.session.SessionListener
)但我找不到如何注入 CDI bean 或如何在这样的 "POJO" 中使用 EJB
- 如果我使用像扩展
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);
}
}
这不是优化的解决方案
这听起来不是一个优化的解决方案,但至少它有效。如果您有任何关于更好设计的提示,请随时告诉我。