I have a Jakarta EE8 ( CDI 2 / Weld / JSF 2.3 / Wildfly ) app where I need to execute some cleanup code when the user logs out, manual logout are fine however I now need to fire an event when logout happens automatically due to session timeout, to handle this I have tried the following two methods already...
#Named
#SessionScoped
public class HttpSessionObservers implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
#Inject private Logger log;
#Inject SecurityContext securityContext;
#PreDestroy
public void processSessionScopedDestroying() {
log.debug("Http Session predestroy");
Principal principal = securityContext.getCallerPrincipal(); //<----is null
//...do some cleanup/logging operations on user account
}
}
The above #PreDestroy callback fires when the session times out but the logged in user (principal) is always null so it seems they have already been logged out so I cannot obtain the user.
#Named
public class HttpSessionObservers implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
#Inject private Logger log;
#Inject private Event<UserLogoutEvent> userLogoutEvent;
public void processSessionScopedInit(#Observes #Initialized(SessionScoped.class) HttpSession payload) {
log.debug("Http Session initialised");
}
public void processSessionScopedDestroying(#Observes #BeforeDestroyed(SessionScoped.class) HttpSession payload) {
//Never fires
log.debug("Http Session predestroy");
Principal principal = securityContext.getCallerPrincipal();
//...do some cleanup/logging operations on user account
}
public void processSessionScopedDestroyed(#Observes #Destroyed(SessionScoped.class) HttpSession payload) {
log.debug("Http Session destroyed");
}
}
Here, the #BeforeDestroyed event is never fired, cannot seem to find any examples of this working, it does work ok for other scopes.
Implement your own HttpSessionListener and annotate it with #WebListener The container should recognize this and will call the the sessionDestroyed() method in your implementation. Your class is eligible for CDI Injection so in sessionDestroyed(), you can fire an event and listen for it with a CDI Observer.
Ref: https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpSessionListener.html
EDIT:
Try this then: Scope your HttpSessionListener impl to #ApplicationScoped. Have a synchronized Map of sessionId/username. In sessionCreated() insert into the map, and on sessionDestroyed() remove it in a finally block.
The container will also have several implicit HttpSessionListeners, so there's a chance yours is being called last long after the session is invalidated. If thats the case, there's another technique you can use to insert your HttpSessionListener impl first. Try the above technique first then we can dive into this.
Related
When the first runnable is submitted is an inject ExecutorService, the security Principal is correctly set for that runnable. Each subsequently submitted runnable is given the security principal of the original user instead of keeping the current runnable. My development machine is running Wildfly 8.2 .
I am creating a reporting system for asynchronous processing. I created a service that checks which user created the task and ensures that only that user can start or complete the task. The code for the service is below.
#Stateless
public class ReportingService {
//EE injection security context
#Resource
SessionContext context;
//CDI security Principal
#Inject
Principal principal;
//this method handles getting the username for EE injection or CDI
private String getCurrentUser() {
if (context != null) {
return context.getCallerPrincipal().getName();
}
if (principal != null) {
return principal.getName();
}
return null;
}
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
#Transactional
public void registerTask(String taskId) {
//Create task
//set task.submittedBy = getCurrentUser()
//persist task
//code has been omitted since it is working
}
private void validateCurrentUserRegisteredJob(String taskId) {
String user = //get user that created task with id = id from DB
String currentUser = getCurrentUser();
if (!user.equals(currentUser)) {
throw new EJBAccesException("Current user "+currentUser+" did not register task");
}
}
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
#Transactional
public void startTask(String taskId) {
validateCurrentUserRegisteredJob(taskid);
//retrieve task entity, set start time to now, and merge
}
...
}
Below is my Runnable code
public TaskRunner() implements Runnable {
//CDI principal
#Inject
Principal principal;
#Inject
ReportingService rs;
private taskId;
public void setTaskId() {...}
public void run() {
log.debug("Inside Runner Current User: "+principal.getName());
rs.startTask(taskId);
....
}
}
The following is the code from the Stateless Bean that is called by a REST endpoint that kicks off the process
#Stateless
public ProjectService() {
#Inject
Instance<TaskRunner> taskRunner;
#Inject
ReportingService reportingService;
//ExecutorService that is create from Adam Bien's porcupine project
#Inject
#Dedicated
ExecutorService es;
//method that is called by rest enpoint to kick off
public void performAsynchAction(List<String> taskIds, ...rest of args...) {
taskIds.stream().forEach(t -> {
//registers task with user that made REST call
reportingService.registerTask(t);
TaskRunner runner = taskRunner.get();
runner.setTaskId(t);
log.debug("Created runner. Principal: "+runner.principal.getName());
es.submit(runner);
});
}
}
Here is the chart of the call flow
REST -> ProjectService.performAsynchAction(...)
-> reportingService.registerTask(...)
-> create CDI injected Runnable
-> submit runner to executor service
-> ExecutorService calls Runner.run()
-> rs.startTask(taskId)
I call the Rest end point as user1 for the first time and register tasks: 1-2. They all work as expected and I get the following output in my log.
Created runner. Principal: user1
Created runner. Principal: user1
Inside Runner Current User: user1
Inside Runner Current User: user1
The next time I make the same REST call as user2 and I get the following output in the log
Created runner. Principal: user2
Inside Runner Current User: user1
EJBAccessException Current user user1 did not register task
It appears that the Security Principal of the Runnable is correctly set the first time a Runnable is submitted to the ExecutorService. But for each subsequent Runneable that is submitted to the ExecutorService uses the security Principal of the first submitted runnable. Is this a bug or the intended behavior? Does anyone know of a potential work around?
EDIT: I figure out that the porcupine project I was using to create the ExecutorService was not being managed by the container. Once I switched to a ManagedExecutorService, the SessionContext was being properly propagated.
#Resource(lookup = "java:jboss/ee/concurrency/executor/customExecutor")
private ManagedExecutorService es;
I figured out the issue. I looked into the porcupine code and found out that the ExecutorService was not being managed by the Container. I created a ManagerExecutorService and the SessionContext was then being properly propogated.
#Resource(lookup = "java:jboss/ee/concurrency/executor/customExecutor")
private ManagedExecutorService es_;
I think the problem is, that you #Inject a #Dependent scoped ExecutorService into a #Stateless bean.
#Stateless beans can be pooled and reused, while #Dependent CDI beans are stored by reference and therefore are not recreated when the #Stateless bean is reused.
Without knowing the implementation of your ExecutorService, I guess that it creates contextual threads during the first run and reused them in the second run without adjusting the context.
You could "force" your ProjectService to create a new ExecutorService by encapsulating it into a #RequestScoped bean:
#RequestScoped
public class ESHolder {
#Inject
#Dedicated
ExecutorService eS;
public ExecutorService getES() {
return eS;
}
}
#Stateless
public ProjectService() {
// ...
#Inject
ESHolder esHolder;
public void performAsynchAction(List<String> taskIds, ...rest of args...) {
taskIds.stream().forEach(t -> {
// ...
esHolder.getES().submit(runner);
});
}
}
I am using Spring Security 3.2.5.RELEASE and am having difficulty catching failed login attempts. I currently have an ApplicationListener configured as follows:
#Component
public class MyApplicationListener implements ApplicationListener<AbstractAuthenticationEvent> {
private static Logger logger = LogManager.getLogger(MyApplicationListener);
#Override
public void onApplicationEvent(AbstractAuthenticationEvent abstractAuthenticationEvent) {
if (abstractAuthenticationEvent instanceof AbstractAuthenticationFailureEvent) {
logger.warn("Detected an invalid login");
} else if (abstractAuthenticationEvent instanceof AuthenticationSuccessEvent) {
logger.info("A user logged in successfully");
}
}
}
If a user logs in successfully I can see the success message as expected. However, if a user supplies a bad password etc. the failure message never shows.
I changed the listener so it logs all ApplicationEvent as below:
#Component
public class MyApplicationListener implements ApplicationListener<ApplicationEvent> {
private static Logger logger = LogManager.getLogger(MyApplicationListener);
#Override
public void onApplicationEvent(ApplicationEvent applicationEvent) {
logger.info("Got an event of type {}", applicationEvent.getClass());
}
}
However the only security-related events that are logged are of type org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent. A failed login does not trigger any events. I would appreciate any pointers, thanks.
Maybe is a bit late, but i got the same problem and solve it this way:
In your configureGlobal method:
auth.authenticationEventPublisher(defaultAuthenticationEventPublisher());
Dont forget to declare the DefaultAuthenticationEventPublisher as a Bean
#Bean
public DefaultAuthenticationEventPublisher defaultAuthenticationEventPublisher(){
return new DefaultAuthenticationEventPublisher();
}
More info here:
Why the event AbstractAuthenticationFailureEvent is never triggered in spring security?
I have a Java project where I use Jersey (1.17) and Guice (3.0). SessionScoped beans work in local dev, but don't work when deployed on GAE. The problem is that they don't keep session state.
Sessions are enabled in web.xml: <sessions-enabled>true</sessions-enabled>
My Session bean (SessionService) is:
#SessionScoped
public class SessionService implements Serializable {
#Inject transient Logger log;
private Locale locale = Locale.US;
public synchronized Locale getLocale() { return locale; }
public synchronized void setLocale(Locale locale) { this.locale = locale; }
}
and it's bound to Session scope in ServletModule bind(SessionService.class).in(ServletScopes.SESSION);
Controller where I use it is:
#Path("/settings")
public class SettingsController {
#Inject SessionService sessionService;
#GET
#Path("/setLocale")
public Object setLocale(#QueryParam("languageTag") String languageTag) {
sessionService.setLocale(Locale.forLanguageTag(languageTag));
return "OK";
}
#GET
#Path("/getLocale")
public Object getLocale() { return sessionService.getLocale().getLanguage(); }
}
With local dev server it works fine. When deployed on GAE (1.9.5) it sets the locale the first time and then it stays the same forever even though I call setLocale again and again. Why does it not work ?
Strangely enough, I found an obscure way to make it work, but I don't know why it makes it work. To have it running, it's necessary to touch HttpSession before setting locale. Like request.getSession(true).setAttribute("whatever", "bar"). As if server needed to be recalled that SessionService wants to do something with Session. Why is that?
I've found a way how to get desired SessionScoped-like functionality. Don't use #SessionScoped as it apparently does not work on GAE, but instead use Provider<HttpSession>.
So your code will be like
public class SessionService {
#Inject Provider<HttpSession> httpSessionProvider;
public void saveSecurityInfo(Object securityInfo) {
httpSessionProvider.get().setAttribute('sec_info', securityInfo);
}
public Object loadSecurityInfo() {
return httpSessionProvider.get().getAttribute('sec_info');
}
}
In controller you'd insert it as #Inject SessionService sessionService;
I've tested this approach on GAE and it works (keeps information in browser session).
I am trying to open Hibernate Session on each request and close it at the end.
This seems like it could work, but I have now idea how am I supposed to put my Session object in ThreadLocal and answer does not explain that.
Is there any Struts2 specific way to do this?
You can add a HttpFilter that is in front of the Struts2 Servlet. In the filter:
public class SessionProviderFilter implements Filter {
private static ThreadLocal<Session> sessionStore = new ThreadLocal<Session>();
public void doFilter(...) {
Session session = ... // get the session
sessionStore.set(session);
chain.doFilter(...);
sessionStore.set(null);
}
public static Session getCurrentSession() {
return sessionStore.get();
}
}
Now from any code, to get current hibernate session, you call SessionProviderFilter.getCurrentSession().
I'm using Wicket + EJB3 in an application and I face a problem but I can't find any topic related, so here it is:
I'm using Wicket authentication, and need to use methods from an EJB in the overrided methods authenticate(...).
I can use this EJB in any wicket page, but when it comes to the WebSession, it stays Null, the injection is not working somehow.
My WicketSession class looks something like this:
public class WicketSession extends AuthenticatedWebSession {
#EJB(name = "UserService")
private UserService userService;
private User user = null;
public WicketSession(Request request) {
super(request);
}
#Override
public boolean authenticate(final String login, final String password) {
user = userService.findByLoginPwd(login, password);
return user != null;;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
And my EJB3:
#Remote
public interface UserService {
public User findByLoginPwd(final String login, final String pwd);
}
#Stateless
public class UserServiceImpl implements UserService {
public User findByLoginPwd(final String login, final String pwd) {
[...]
}
}
The Web part with Wicket is packaged in a war, the business part with EJBs is packaged in a jar, and then I make a ear to deploy it on a JOnAS server.
Any help would be highly appreciated =)
Nicolas
I'm pretty sure the injection works with an IComponentInstantiationListener (at least that's how the Spring version works). Update: it does, see this document.
However, Sessions are not components, so a different mechanism is needed. Perhaps there is a way to wire your Session in the Application.newSession() method? You will have to take a look at the implementation of JavaEEComponentInjector and copy what it does when creating your session.
As Sean Patrick Floyd noted, Sessions are not components, so the automatic injection supplied for Wicket components doesn't apply.
A common idiom for injecting stuff in a non-component is to add the line
InjectorHolder.getInjector().inject(this);
to the constructor.
I haven't used this for a WicketSession extension, but I don't know of a reason it won't work.
In JavaEEComponentInjector, the inject method is almost certainly doing a JNDI lookup, and you could do a JNDI lookup yourself to get the object, but this is reusing the existing injection, and if you decide to change injectors (say by extending JavaEEComponentInjector), it ensures that you'll continue to use the same injection.