我有一个带有@CustomFormAuthenticationMechanismDefinition
的应用程序,我想在登录和注销时都记录用户名,会话ID,IP地址等。与此批注一起应用的HttpAuthMechanism将给定的会话与主体相关联,我可以通过SecurityContext
访问该主体。使用直接注销,日志记录没有问题,但是我也想在会话超时时进行日志记录。因此,我创建了一个HttpSessionListener,并在其sessionDestroyed()
方法中尝试通过SecurityContext
访问登录的用户,但是它返回一个空集,这可能是因为securityContext已经失效了。
我想到的一个解决方案是将用户主体存储在会话参数中(很可能在HttpAuthMechanism
实现中发生)并从HttpSessionEvent
对象那里访问它,但这并没有感觉不是最干净的解决方案。我可以使用其他监听器还是其他解决方案?
答案 0 :(得分:0)
我使用了自定义的HttpAuthenticationMechanism,这是任何人都需要的(尽管我很高兴收到有关它是否存在任何安全缺陷或改进的反馈)
在实现@ApplicationScoped
的{{1}}类中:
HttpAuthenticationMechanism
在已实现的@Override
public AuthenticationStatus validateRequest(HttpServletRequest request, HttpServletResponse response, HttpMessageContext httpMessageContext) throws AuthenticationException {
if (!httpMessageContext.isProtected()) {
return httpMessageContext.doNothing();
}
HttpSession session = request.getSession(false);
Credential credential = httpMessageContext.getAuthParameters().getCredential();
// If we already have a session, we get the user from it, unless it's a new login
if (session != null && !(credential instanceof UsernamePasswordCredential)) {
User user = (User) session.getAttribute("user");
if (user != null) {
return httpMessageContext.notifyContainerAboutLogin(user, user.getRoles());
}
}
// If we either don't have a session or it has no user attribute, we redirect/forward to login page
if (!(credential instanceof UsernamePasswordCredential)) {
return redirect(request, response, httpMessageContext);
}
// Here we have a Credential, so we validate it with the registered IdentityStoreHandler (injected as idStoreHandler)
CredentialValidationResult validate = idStoreHandler.validate(credential);
Context context = new Context();
context.setIp(request.getRemoteAddr());
if (validate.getStatus() == CredentialValidationResult.Status.VALID) {
session = request.getSession(true);
CallerPrincipal callerPrincipal = validate.getCallerPrincipal();
session.setAttribute("user", callerPrincipal);
context.setUser(callerPrincipal);
context.setSessionId(session.getId());
Logger log = new Logger(logger, "validateRequest", context);
log.debug("Logged in user: " + callerPrincipal.getName());
String redirectPage = "whatYouWant.xhtml";
redirect(request, response, httpMessageContext, redirectPage);
return httpMessageContext.notifyContainerAboutLogin(validate);
} else if (validate.getStatus() == CredentialValidationResult.Status.NOT_VALIDATED) {
return redirect(request, response, httpMessageContext);
} else {
// Logging
return httpMessageContext.responseUnauthorized();
}
}
中:
HttpSessionListener
答案 1 :(得分:0)
您不需要自定义实施HttpAuthenticationMechanism。您可以使用HttpServletRequest.login()进行手动登录(不使用j_security_check操作自动使用容器登录)
LoginServlet
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String userName = req.getParameter("userName");
String password = req.getParameter("password");
if (userName == null) throw new IllegalArgumentException("userName is required.");
if (password == null) throw new IllegalArgumentException("password is required.");
try {
// INVOKE login manually. NOT using j_security_check
// Alternative: Use JavaEE8 Security API - SecurityContext.authenticate()
req.login(userName, password);
if (req.getUserPrincipal() != null) {
// LOGIN Successfully
Context context = createContextFrom(req, req.getSession());
logContext(context);
req.getSession().setAttribute("loginContext", context);
}
} catch (Exception ex) {
// ...
}
}
}
//会话破坏事件
@WebListener
public class SessionListenerImpl implements HttpSessionListener {
@Override
public void sessionDestroyed(HttpSessionEvent hse) {
Context loginContext = hse.getSession().getAttribute("loginContext");
if (loginContext != null) {
// Do whatever with loginContext
}
}
}