How to create listener for spring session destroyed events? - spring-session

I have a code as below:
#Component
public class LogoutListener implements ApplicationListener<SessionDestroyedEvent>
{
#Override
public void onApplicationEvent(SessionDestroyedEvent event)
{
System.out.println("Application event happened");
for (SecurityContext securityContext : event.getSecurityContexts())
{
System.out.println("session has ended");
}
}
}
Since I am using a spring boot app.I cant use web.xml.Then how can I configure the listener.
This listener is looking for session destroyed events.

I think the answer here might be helpful. In it you'll find that, for applications without web.xml, you have to look at ServletContext.html#addListener

Here is an example:
public class SessionEventListener extends HttpSessionEventPublisher {
public void sessionCreated(HttpSessionEvent event) {
super.sessionCreated(event);
event.getSession().setMaxInactiveInterval(60*3);
}
#Override
public void sessionDestroyed(HttpSessionEvent event) {
String name = null;
SessionRegistry sessionRegistry = getSessionRegistry(event);
SessionInformation sessionInfo = (sessionRegistry != null ? sessionRegistry
.getSessionInformation(event.getSession().getId()) : null);
UserDetails ud = null;
if (sessionInfo != null) {
ud = (UserDetails) sessionInfo.getPrincipal();}
if (ud != null) {
name = ud.getUsername();
// YOUR METHOD IS CALLED HERE
getMyService(event).myMethod(name);
}
super.sessionDestroyed(event);
}
public YourBean4Service getMyService(HttpSessionEvent event) {
HttpSession session = event.getSession();
ApplicationContext ctx =
WebApplicationContextUtils.
getWebApplicationContext(session.getServletContext());
return (YourBean4Service) ctx.getBean("yourBean4Service");
}
public SessionRegistry getSessionRegistry(HttpSessionEvent event) {
HttpSession session = event.getSession();
ApplicationContext ctx =
WebApplicationContextUtils.
getWebApplicationContext(session.getServletContext());
return (SessionRegistry) ctx.getBean("sessionRegistry");
}
}
And my related topic is here

Related

keycloak backchannel logout spring session that use redis as session store

I use keycloak as a Central Authentication Service for (single sign on/out) feature.
I have app1, app2, app3. app1 and app2 is monothetic application. app3 use spring session (use redis as session store),
All feature work fine. But I use the back channel to logout for SSO(single sign out) feature, that's works for app1 and app2. But it not work for this app3.
I wonder how to back channel logout application that use spring session
The keycloak admin url invoke when client user send a logout request to it.I find that KeycloakAutoConfiguration#getKeycloakContainerCustomizer() inject WebServerFactoryCustomizer for add KeycloakAuthenticatorValve, and that Valve
use CatalinaUserSessionManagement, but it have not any info about redis as its session store. So I add a customizer for enhence the Valve.
first i set the order of the autoconfig, because extra customizer must be callback after it.
#Slf4j
#Component
public class BeanFactoryOrderWrapper implements DestructionAwareBeanPostProcessor {
#Override
public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {
}
#Override
public boolean requiresDestruction(Object bean) {
return true;
}
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (beanName.equals("getKeycloakContainerCustomizer")) {
Object wrapRes = this.wrapOrder(bean);
return wrapRes;
}
return bean;
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
private Object wrapOrder(Object bean) {
log.info("rewrite keycloak auto config customizer Order for next custom");
final WebServerFactoryCustomizer origin = (WebServerFactoryCustomizer) bean;
return new KeycloakContainerCustomizerWithOrder(origin);
}
}
class KeycloakContainerCustomizerWithOrder implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>, Ordered {
private final WebServerFactoryCustomizer origin;
public KeycloakContainerCustomizerWithOrder(WebServerFactoryCustomizer origin) {
this.origin = origin;
}
#Override
public void customize(ConfigurableServletWebServerFactory factory) {
origin.customize(factory);
}
#Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE - 1;
}
}
I extra RedisIndexedSessionRepository, and set it to proxy object
#Slf4j
#Configuration
#RequiredArgsConstructor
class ContainerConfig {
private final RedisIndexedSessionRepository sessionRepository;
#Bean
public WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> getKeycloakContainerCustomizerGai() {
return configurableServletWebServerFactory -> {
if (configurableServletWebServerFactory instanceof TomcatServletWebServerFactory) {
TomcatServletWebServerFactory container = (TomcatServletWebServerFactory) configurableServletWebServerFactory;
container.getContextValves().stream().filter(ele -> ele.getClass() == KeycloakAuthenticatorValve.class).findFirst().map(ele -> (AbstractKeycloakAuthenticatorValve) ele).ifPresent(valve -> {
try {
final Field field = AbstractKeycloakAuthenticatorValve.class.getDeclaredField("userSessionManagement");
field.setAccessible(true);
final CatalinaUserSessionManagement origin = (CatalinaUserSessionManagement) field.get(valve);
field.set(valve, new CatalinaUserSessionManagementGai(origin, sessionRepository));
} catch (Exception e) {
log.error("enhence valve fail");
}
});
}
};
}
}
#Slf4j
class CatalinaUserSessionManagementGai extends CatalinaUserSessionManagement {
private final CatalinaUserSessionManagement origin;
private final RedisIndexedSessionRepository sessionRepository;
public CatalinaUserSessionManagementGai(CatalinaUserSessionManagement origin, RedisIndexedSessionRepository sessionRepository) {
this.origin = origin;
this.sessionRepository = sessionRepository;
}
public void login(Session session) {
origin.login(session);
}
public void logoutAll(Manager sessionManager) {
origin.logoutAll(sessionManager);
}
public void logoutHttpSessions(Manager sessionManager, List<String> sessionIds) {
for (String sessionId : sessionIds) {
logoutSession(sessionManager, sessionId);
}
}
protected void logoutSession(Manager manager, String httpSessionId) {
try {
final Method method = CatalinaUserSessionManagement.class.getDeclaredMethod("logoutSession", Manager.class, String.class);
method.setAccessible(true);
method.invoke(origin,manager,httpSessionId);
} catch (Exception e) {
log.error("session manager proxy invoke error");
}
// enhence part
sessionRepository.deleteById(httpSessionId);
}
protected void logoutSession(Session session) {
try {
final Method method = CatalinaUserSessionManagement.class.getDeclaredMethod("logoutSession", Session.class);
method.setAccessible(true);
method.invoke(origin,session);
} catch (Exception e) {
log.error("session manager proxy invoke error");
}
}
public void sessionEvent(SessionEvent event) {
origin.sessionEvent(event);
}
}
that work for me

Why isn't an custom implemented VaadinServiceInitListener is listening in vaadin 13.0.2?

I would like to validate user is signed in or not to achieve it i found something called VaadinServiceInitListener in vaadin 13.0.2 This class is used to listen to BeforeEnter event of all UIs in order to check whether a user is signed in or not before allowing entering any page.
I have created an vaadin 13.0.2 project with app-layout-addon by appreciated implemented login functionality and VaadinServiceInitListener to check whether a user is signed in or not.
public class AAACATInitListener implements VaadinServiceInitListener {
private static final long serialVersionUID = 1L;
private static InAppSessionContextImpl appContextImpl;
#Override
public void serviceInit(ServiceInitEvent event) {
System.out.println("in service init event");
event.getSource().addUIInitListener(new UIInitListener() {
private static final long serialVersionUID = 1L;
#Override
public void uiInit(UIInitEvent event) {
event.getUI().addBeforeEnterListener(new BeforeEnterListener() {
private static final long serialVersionUID = 1L;
#Override
public void beforeEnter(BeforeEnterEvent event) {
appContextImpl = (InAppSessionContextImpl)VaadinSession.getCurrent().getAttribute("context");
if (appContextImpl == null) {
WebBrowser webBrowser = UI.getCurrent().getSession().getBrowser();
String address = webBrowser.getAddress();
if(RememberAuthService.isAuthenticated(address) != null && !RememberAuthService.isAuthenticated(address).isEmpty()) {
//System.out.println("Found Remembered User....");
IBLSessionContext iblSessionContext = null;
try {
iblSessionContext = new UserBLManager().doRememberedStaffUserLogin(RememberAuthService.isAuthenticated(address), "");
if(iblSessionContext != null) {
InAppSessionContextImpl localAppContextImpl = new InAppSessionContextImpl();
localAppContextImpl.setBLSessionContext(iblSessionContext);
localAppContextImpl.setModuleGroupList(iblSessionContext.getSessionAccessControl().getPermittedModuleGroups());
appContextImpl = localAppContextImpl;
event.rerouteTo(ApplicationMainView.class);
}else {
Notification.show("Your access has been expired, Please contact your administrator", 5000, Position.BOTTOM_CENTER);
}
} catch (AuthenticationFailedException e) {
Notification.show("Authentication Failed, Please Reset Cookies And Try Again", 5000, Position.BOTTOM_CENTER);
} catch (Exception e){
e.printStackTrace();
Notification.show("Unexpected Error Occurred, Please Reset Cookies And Try Again", 5000, Position.BOTTOM_CENTER);
}
}else {
System.out.println("Session context is null, creating new context");
appContextImpl = new InAppSessionContextImpl();
VaadinSession.getCurrent().setAttribute("context", appContextImpl);
event.rerouteTo(LoginView.class);
}
} else {
System.out.println("Session context is not null");
InAppSessionContextImpl localAppContextImpl = new InAppSessionContextImpl();
localAppContextImpl.setBLSessionContext(appContextImpl.getBLSessionContext());
localAppContextImpl.setModuleGroupList(appContextImpl.getModuleGroupList());
appContextImpl = localAppContextImpl;
event.rerouteTo(ApplicationMainView.class);
}
}
});
}
});
}
public static void setBLSessionContext(IBLSessionContext iblSessionContext) {
appContextImpl.setBLSessionContext(iblSessionContext);
}
public static void setModuleGroupList(List<ModuleGroupVO> moduleGroupList) {
appContextImpl.setModuleGroupList(moduleGroupList);
}
private class InAppSessionContextImpl implements InAppSessionContext {
private static final long serialVersionUID = 1L;
private List<ModuleGroupVO> moduleGroupList;
private IBLSessionContext iblSessionContext;
private Map<String, Object> attributeMap;
public InAppSessionContextImpl() {
this.attributeMap = new HashMap<String, Object>();
}
#Override
public List<ModuleGroupVO> getModuleGroupList() {
return moduleGroupList;
}
public void setModuleGroupList(List<ModuleGroupVO> moduleGroupList) {
this.moduleGroupList = moduleGroupList;
}
#Override
public IBLSessionContext getBLSessionContext() {
return iblSessionContext;
}
public void setBLSessionContext(IBLSessionContext iblSessionContext) {
this.iblSessionContext = iblSessionContext;
}
#Override
public IBLSession getBLSession() {
if(iblSessionContext != null)
return iblSessionContext.getBLSession();
return null;
}
#Override
public boolean isPermittedAction(String actionAlias) {
if (getBLSessionContext() != null) {
if (getBLSessionContext().getSessionAccessControl() != null) {
return getBLSessionContext().getSessionAccessControl().isPermittedAction(actionAlias);
}
}
return false;
}
#Override
public void setAttribute(String key, Object attribute) {
attributeMap.put(key, attribute);
}
#Override
public Object getAttribute(String key) {
return attributeMap.get(key);
}
}
}
Expected results redirect to login page if user not signed in or else to main application page but AAACATInitListener is not listening.
If you are using Spring, simply add a #Component annotation to the class and it should work. If youre not using Spring, follow #codinghaus' answer.
To make Vaadin recognize the VaadinServiceInitListener you have to create a file called com.vaadin.flow.server.VaadinServiceInitListener and put it under src/main/resources/META-INF/services. Its content should be the full path to the class that implements the VaadinServiceInitListener interface. Did you do that?
You can also find a description on that in the tutorial.
The correct pattern to use beforeEnter(..) is not do it via VaadinServiceInitListener , instead you should implement BeforeEnterObserver interface in the view where you need use it and override beforeEnter(..) method with your implementation.
public class MainView extends VerticalLayout implements RouterLayout, BeforeEnterObserver {
...
#Override
public void beforeEnter(BeforeEnterEvent event) {
...
}
}

System.ArgumentNullException: Value cannot be null - Umbraco HTTPContext on save and publish

source: https://gist.github.com/sniffdk/7600822
The following code is run by an activity outside of an http request, so i need to mock the http context.
I have mocked the http context like so:
public class GetUmbracoServiceMockedHttpContext : IGetUmbracoService
{
private UmbracoHelper umbracoHelper;
public T GetService<T>()
where T : IService
{
UmbracoContext context = UmbracoContext.Current;
if (context == null)
{
var dummyHttpContext = new HttpContextWrapper(new HttpContext(new SimpleWorkerRequest("blah.aspx", "", new StringWriter())));
context = UmbracoContext.EnsureContext(
dummyHttpContext,
ApplicationContext.Current,
new WebSecurity(dummyHttpContext, ApplicationContext.Current),
UmbracoConfig.For.UmbracoSettings(),
UrlProviderResolver.Current.Providers,
false);
}
var serviceTypeProperty = context.Application.Services
.GetType()
.GetProperties()
.SingleOrDefault(x => x.PropertyType == typeof(T));
if (serviceTypeProperty == null)
{
return default(T);
}
return (T)serviceTypeProperty
.GetValue(context.Application.Services);
}
}
I inject this IGetUmbracoService service into a controller and call:
service.GetService<IContentService>().SaveAndPublishWithStatus(item);
... The following error occurs.
System.ArgumentNullException: Value cannot be null. Parameter name:
httpContext at System.Web.HttpContextWrapper..ctor(HttpContext
httpContext) at
Umbraco.Web.SingletonHttpContextAccessor.get_Value() at
Umbraco.Web.RequestLifespanMessagesFactory.Get() at
Umbraco.Core.Services.ContentService.SaveAndPublishDo(IContent
content, Int32 userId, Boolean raiseEvents) at
Umbraco.Core.Services.ContentService.Umbraco.Core.Services.IContentServiceOperations.SaveAndPublish(IContent
content, Int32 userId, Boolean raiseEvents) at
Umbraco.Core.Services.ContentService.SaveAndPublishWithStatus(IContent
content, Int32 userId, Boolean raiseEvents)
How do i mock the http context without using the frowned upon HttpContext.Current = ...?
I assume the relevant issue comes from:
RequestLifespanMessagesFactory.cs
which in turn is calling an implementation of this:
SingletonHttpContextAccessor.cs
I did some work with Umbraco, running it from a console app and then using the Umbraco API to call into Umbraco.
I believe I based it on this project: https://github.com/sitereactor/umbraco-console-example
Might be useful.
Thanks user369142. This is what ended up working:
I also had to make sure that i was not raising any events on the SaveandPublish calls... as the HttpContext expects there to be messages registered in the context but we do not mock any... If you make sure raise events is false, it skips over the code that cares about that.
public class CustomSingletonHttpContextAccessor : IHttpContextAccessor
{
public HttpContextBase Value
{
get
{
HttpContext context = HttpContext.Current;
if (context == null)
{
context = new HttpContext(new HttpRequest(null, "http://mockurl.com", null), new HttpResponse(null));
}
return new HttpContextWrapper(context);
}
}
}
public class CustomRequestLifespanMessagesFactory : IEventMessagesFactory
{
private readonly IHttpContextAccessor _httpAccessor;
public CustomRequestLifespanMessagesFactory(IHttpContextAccessor httpAccessor)
{
if (httpAccessor == null)
{
throw new ArgumentNullException("httpAccessor");
}
_httpAccessor = httpAccessor;
}
public EventMessages Get()
{
if (_httpAccessor.Value.Items[typeof(CustomRequestLifespanMessagesFactory).Name] == null)
{
_httpAccessor.Value.Items[typeof(CustomRequestLifespanMessagesFactory).Name] = new EventMessages();
}
return (EventMessages)_httpAccessor.Value.Items[typeof(CustomRequestLifespanMessagesFactory).Name];
}
}
public class CustomBootManager : WebBootManager
{
public CustomBootManager(UmbracoApplicationBase umbracoApplication)
: base(umbracoApplication)
{
}
protected override ServiceContext CreateServiceContext(DatabaseContext dbContext, IDatabaseFactory dbFactory)
{
//use a request based messaging factory
var evtMsgs = new CustomRequestLifespanMessagesFactory(new CustomSingletonHttpContextAccessor());
return new ServiceContext(
new RepositoryFactory(ApplicationCache, ProfilingLogger.Logger, dbContext.SqlSyntax, UmbracoConfig.For.UmbracoSettings()),
new PetaPocoUnitOfWorkProvider(dbFactory),
new FileUnitOfWorkProvider(),
new PublishingStrategy(evtMsgs, ProfilingLogger.Logger),
ApplicationCache,
ProfilingLogger.Logger,
evtMsgs);
}
}
public class CustomUmbracoApplication : Umbraco.Web.UmbracoApplication
{
...
protected override IBootManager GetBootManager()
{
return new CustomBootManager(this);
}
...
}

MVC IPrincipal User from WebViewPage is null

I've create a base class for my Views like this:
public abstract class BaseViewPage : WebViewPage
{
public virtual new CustomPrincipal User
{
get
{
if (base.User == null) return null;
return base.User as CustomPrincipal;
}
}
}
public abstract class BaseViewPage<TModel> : WebViewPage<TModel>
{
public virtual new CustomPrincipal User
{
get
{
if (base.User == null) return null;
return base.User as CustomPrincipal;
}
}
public override void Execute()
{
throw new NotImplementedException();
}
}
and in my model I have:
public class SecureAreaModel : BaseViewPage
{
public int MyUserID
{
get { return User.ID; }
private set { }
}
public SecureAreaModel(ControllerContext controllerContext)
{
}
public override void Execute()
{
throw new NotImplementedException();
}
}
I want to use the propertiy MyUserID but I receive this error:
Error
At this point the user is autenticated
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
JavaScriptSerializer serializer = new JavaScriptSerializer();
CustomPrincipalSerializeModel serializeModel = serializer.Deserialize<CustomPrincipalSerializeModel>(authTicket.UserData);
CustomPrincipal customer = new CustomPrincipal(serializeModel.Email);
customer.ID = serializeModel.ID;
customer.Email = serializeModel.Email;
customer.FirstName = serializeModel.FirstName;
customer.LastName = serializeModel.LastName;
customer.Roles = serializeModel.Roles;
HttpContext.Current.User = customer;
}
else
{
HttpContext.Current.User = new CustomPrincipal(string.Empty);
}
}
Any help will be appreciated! Thx

ninject 2 and db4o

I am trying to use ninject with db4o and I have a problem. This is the relevant code from the Global.aspx
static IObjectServer _server;
protected override void OnApplicationStarted()
{
AutoMapperConfiguration.Configure();
RegisterRoutes(RouteTable.Routes);
RegisterAllControllersIn(Assembly.GetExecutingAssembly());
if (_server == null)
{
// opening a server for a client/server session
IServerConfiguration serverConfiguration = Db4oClientServer.NewServerConfiguration();
serverConfiguration.File.Storage = new MemoryStorage();
_server = Db4oClientServer.OpenServer(serverConfiguration, "myServerDb.db4o", 0);
}
}
public static IObjectContainer OpenClient()
{
return _server.OpenClient();
}
public MvcApplication()
{
this.EndRequest += MvcApplication_EndRequest;
}
private void MvcApplication_EndRequest(object sender, System.EventArgs e)
{
if (Context.Items.Contains(ServiceModule.SESSION_KEY))
{
IObjectContainer Session = (IObjectContainer)Context.Items[ServiceModule.SESSION_KEY];
Session.Close();
Session.Dispose();
Context.Items[ServiceModule.SESSION_KEY] = null;
}
}
protected override IKernel CreateKernel()
{
return new StandardKernel(new ServiceModule());
}
public override void OnApplicationEnded()
{
_server.Close();
}
and this is the code in ServiceModule
internal const string SESSION_KEY = "Db4o.IObjectServer";
public override void Load()
{
Bind<IObjectContainer>().ToMethod(x => GetRequestObjectContainer(x)).InRequestScope();
Bind<ISession>().To<Db4oSession>();
}
private IObjectContainer GetRequestObjectContainer(IContext Ctx)
{
IDictionary Dict = HttpContext.Current.Items;
IObjectContainer container;
if (!Dict.Contains(SESSION_KEY))
{
container = MvcApplication.OpenClient();
Dict.Add(SESSION_KEY, container);
}
else
{
container = (IObjectContainer)Dict[SESSION_KEY];
}
return container;
}
I then try to inject it into my session as such:
public Db4oSession(IObjectContainer client)
{
db = client;
}
however, after the first call, the client is always closed - as it should be because of the code in MvcApplication_EndRequest. The problem is that the code in GetRequestObjectContainer is only ever called once. What am I doing wrong?
Also, MvcApplication_EndRequest is always called 3 times, is this normal?
Thanks!
This seems to have done the trick... add InRequestScope to the other injection:
Bind<ISession>().To<Db4oSession>().InRequestScope();

Resources