Skip to content

Commit 12bba54

Browse files
committed
Handle SessionExpiredEvent for counting users
1 parent e02a914 commit 12bba54

7 files changed

Lines changed: 153 additions & 24 deletions

File tree

src/main/java/eu/openanalytics/containerproxy/ContainerProxyApplication.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import org.springframework.core.env.Environment;
4242
import org.springframework.data.redis.connection.RedisConnectionFactory;
4343
import org.springframework.security.core.session.SessionRegistry;
44+
import org.springframework.security.web.session.HttpSessionEventPublisher;
4445
import org.springframework.session.FindByIndexNameSessionRepository;
4546
import org.springframework.session.Session;
4647
import org.springframework.session.data.redis.config.ConfigureRedisAction;
@@ -157,10 +158,10 @@ public JSR353Module jsr353Module() {
157158
*
158159
* @return
159160
*/
160-
@Bean
161-
public static ConfigureRedisAction configureRedisAction() {
162-
return ConfigureRedisAction.NO_OP;
163-
}
161+
// @Bean TODO TODO TODO https://aws.amazon.com/premiumsupport/knowledge-center/elasticache-redis-keyspace-notifications/ https://docs.spring.io/spring-session/docs/current/reference/html5/#api-redisindexedsessionrepository-sessiondestroyedevent
162+
// public static ConfigureRedisAction configureRedisAction() {
163+
// return ConfigureRedisAction.NO_OP;
164+
// }
164165

165166
@Bean
166167
@ConditionalOnProperty(name = "spring.session.store-type", havingValue = "redis")
@@ -190,6 +191,11 @@ public Health health() {
190191
}
191192
}
192193

194+
@Bean
195+
public HttpSessionEventPublisher httpSessionEventPublisher() {
196+
return new HttpSessionEventPublisher();
197+
}
198+
193199
private static void setDefaultProperties(SpringApplication app) {
194200
Properties properties = new Properties();
195201

src/main/java/eu/openanalytics/containerproxy/event/ProxyStartEvent.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,23 @@
1+
/**
2+
* ContainerProxy
3+
*
4+
* Copyright (C) 2016-2020 Open Analytics
5+
*
6+
* ===========================================================================
7+
*
8+
* This program is free software: you can redistribute it and/or modify
9+
* it under the terms of the Apache License as published by
10+
* The Apache Software Foundation, either version 2 of the License, or
11+
* (at your option) any later version.
12+
*
13+
* This program is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
* Apache License for more details.
17+
*
18+
* You should have received a copy of the Apache License
19+
* along with this program. If not, see <http://www.apache.org/licenses/>
20+
*/
121
package eu.openanalytics.containerproxy.event;
222

323
import org.springframework.context.ApplicationEvent;

src/main/java/eu/openanalytics/containerproxy/event/ProxyStartFailedEvent.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,23 @@
1+
/**
2+
* ContainerProxy
3+
*
4+
* Copyright (C) 2016-2020 Open Analytics
5+
*
6+
* ===========================================================================
7+
*
8+
* This program is free software: you can redistribute it and/or modify
9+
* it under the terms of the Apache License as published by
10+
* The Apache Software Foundation, either version 2 of the License, or
11+
* (at your option) any later version.
12+
*
13+
* This program is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
* Apache License for more details.
17+
*
18+
* You should have received a copy of the Apache License
19+
* along with this program. If not, see <http://www.apache.org/licenses/>
20+
*/
121
package eu.openanalytics.containerproxy.event;
222

323
import org.springframework.context.ApplicationEvent;

src/main/java/eu/openanalytics/containerproxy/event/ProxyStopEvent.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,23 @@
1+
/**
2+
* ContainerProxy
3+
*
4+
* Copyright (C) 2016-2020 Open Analytics
5+
*
6+
* ===========================================================================
7+
*
8+
* This program is free software: you can redistribute it and/or modify
9+
* it under the terms of the Apache License as published by
10+
* The Apache Software Foundation, either version 2 of the License, or
11+
* (at your option) any later version.
12+
*
13+
* This program is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
* Apache License for more details.
17+
*
18+
* You should have received a copy of the Apache License
19+
* along with this program. If not, see <http://www.apache.org/licenses/>
20+
*/
121
package eu.openanalytics.containerproxy.event;
222

323
import org.springframework.context.ApplicationEvent;

src/main/java/eu/openanalytics/containerproxy/event/UserLogoutEvent.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,20 @@ public class UserLogoutEvent extends ApplicationEvent {
2626

2727
private final String userId;
2828
private final String sessionId;
29+
private final Boolean wasExpired;
2930

30-
public UserLogoutEvent(Object source, String userId, String sessionId) {
31+
/**
32+
*
33+
* @param source
34+
* @param userId
35+
* @param sessionId
36+
* @param wasExpired whether the user is logged autoamtically because the session has expired
37+
*/
38+
public UserLogoutEvent(Object source, String userId, String sessionId, Boolean wasExpired) {
3139
super(source);
3240
this.userId = userId;
3341
this.sessionId = sessionId;
42+
this.wasExpired = wasExpired;
3443
}
3544

3645
public String getSessionId() {
@@ -40,6 +49,10 @@ public String getSessionId() {
4049
public String getUserId() {
4150
return userId;
4251
}
52+
53+
public Boolean getWasExpired() {
54+
return wasExpired;
55+
}
4356
}
4457

4558

src/main/java/eu/openanalytics/containerproxy/service/UserService.java

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,26 @@
2020
*/
2121
package eu.openanalytics.containerproxy.service;
2222

23-
import java.util.ArrayList;
24-
import java.util.HashSet;
25-
import java.util.List;
26-
import java.util.Set;
23+
import java.util.*;
24+
import java.util.concurrent.ExecutionException;
25+
import java.util.concurrent.TimeUnit;
2726

27+
import javax.annotation.PostConstruct;
2828
import javax.inject.Inject;
29+
import javax.servlet.http.HttpSession;
2930

31+
import com.google.common.cache.*;
3032
import eu.openanalytics.containerproxy.event.AuthFailedEvent;
3133
import eu.openanalytics.containerproxy.event.UserLoginEvent;
3234
import eu.openanalytics.containerproxy.event.UserLogoutEvent;
35+
import org.apache.commons.lang3.tuple.Pair;
3336
import org.apache.logging.log4j.LogManager;
3437
import org.apache.logging.log4j.Logger;
38+
import org.apache.xpath.operations.Bool;
3539
import org.springframework.context.ApplicationEventPublisher;
3640
import org.springframework.context.ApplicationListener;
3741
import org.springframework.context.annotation.Lazy;
42+
import org.springframework.context.event.EventListener;
3843
import org.springframework.core.env.Environment;
3944
import org.springframework.security.authentication.AnonymousAuthenticationToken;
4045
import org.springframework.security.authentication.event.AbstractAuthenticationEvent;
@@ -43,7 +48,12 @@
4348
import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent;
4449
import org.springframework.security.core.Authentication;
4550
import org.springframework.security.core.GrantedAuthority;
51+
import org.springframework.security.core.context.SecurityContext;
4652
import org.springframework.security.core.context.SecurityContextHolder;
53+
import org.springframework.security.core.session.SessionDestroyedEvent;
54+
import org.springframework.security.core.userdetails.User;
55+
import org.springframework.session.Session;
56+
import org.springframework.session.events.SessionExpiredEvent;
4757
import org.springframework.stereotype.Service;
4858

4959
import eu.openanalytics.containerproxy.auth.IAuthenticationBackend;
@@ -183,7 +193,8 @@ public void onApplicationEvent(AbstractAuthenticationEvent event) {
183193
this,
184194
userId,
185195
RequestContextHolder.currentRequestAttributes().getSessionId()));
186-
} else if (event instanceof AuthenticationSuccessEvent || event instanceof InteractiveAuthenticationSuccessEvent) {
196+
} else if (event instanceof AuthenticationSuccessEvent) {
197+
// } else if (event instanceof AuthenticationSuccessEvent || event instanceof InteractiveAuthenticationSuccessEvent) {
187198
String userName = source.getName();
188199
log.info(String.format("User logged in [user: %s]", userName));
189200
eventService.post(EventType.Login.toString(), userName, null);
@@ -198,22 +209,46 @@ public void onApplicationEvent(AbstractAuthenticationEvent event) {
198209
}
199210

200211
public void logout(Authentication auth) {
212+
// TODO test for anonymous users
201213
String userId = getUserId(auth);
202214
if (userId == null) return;
203-
204-
// if (authentication.getPrincipal() instanceof UserDetails) {
205-
// userName = ((UserDetails) authentication.getPrincipal()).getUsername();
206-
// }
207-
215+
208216
eventService.post(EventType.Logout.toString(), userId, null);
209217
if (logoutStrategy != null) logoutStrategy.onLogout(userId);
210218
log.info(String.format("User logged out [user: %s]", userId));
211219

212-
// TODO test for anonymous users
220+
String sessionId = RequestContextHolder.currentRequestAttributes().getSessionId();
213221
applicationEventPublisher.publishEvent(new UserLogoutEvent(
214222
this,
215223
userId,
216-
RequestContextHolder.currentRequestAttributes().getSessionId()));
224+
sessionId,
225+
false));
226+
}
227+
228+
@EventListener
229+
public void onSessionExpiredEvent(SessionExpiredEvent event) {
230+
Session session = event.getSession();
231+
String sessionId = session.getId();
232+
233+
Set<String> attributes = session.getAttributeNames();
234+
235+
for (String attributeName : attributes) {
236+
Object attributeValue = session.getAttribute(attributeName);
237+
if (attributeValue instanceof SecurityContext) {
238+
239+
Authentication authentication = ((SecurityContext) attributeValue).getAuthentication();
240+
String userId = ((User) authentication.getPrincipal()).getUsername();
241+
242+
applicationEventPublisher.publishEvent(new UserLogoutEvent(
243+
this,
244+
userId,
245+
sessionId,
246+
true
247+
));
248+
249+
break;
250+
}
251+
}
217252
}
218253

219254
}

src/main/java/eu/openanalytics/containerproxy/stat/impl/Micrometer.java

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,18 @@
2929
import io.micrometer.core.instrument.Timer;
3030
import org.springframework.context.event.EventListener;
3131
import org.springframework.core.env.Environment;
32+
import org.springframework.security.core.Authentication;
33+
import org.springframework.security.core.context.SecurityContext;
34+
import org.springframework.security.core.session.SessionDestroyedEvent;
35+
import org.springframework.security.core.userdetails.User;
3236
import org.springframework.stereotype.Component;
37+
import org.springframework.web.context.request.RequestContextHolder;
3338

3439
import javax.annotation.PostConstruct;
3540
import javax.inject.Inject;
41+
import javax.servlet.http.HttpSession;
3642
import java.io.IOException;
43+
import java.security.Principal;
3744
import java.util.concurrent.atomic.AtomicInteger;
3845
import java.util.concurrent.atomic.AtomicLong;
3946

@@ -58,6 +65,10 @@ public class Micrometer implements IStatCollector {
5865

5966
private Counter authFailedCounter;
6067

68+
private Counter userLogins;
69+
70+
private Counter userLogouts;
71+
6172
@PostConstruct
6273
public void init() {
6374
appsGauge = registry.gauge("apps", new AtomicInteger(0));
@@ -66,6 +77,8 @@ public void init() {
6677
appUsageTimer = registry.timer("usageTime");
6778
appStartFailedCounter = registry.counter("startFailed");
6879
authFailedCounter = registry.counter("authFailed");
80+
userLogins = registry.counter("userLogins");
81+
userLogouts = registry.counter("userLogouts");
6982
}
7083

7184
@Override
@@ -74,48 +87,50 @@ public void accept(EventService.Event event, Environment env) throws IOException
7487
appsGauge.incrementAndGet();
7588
} else if (event.type.equals(EventService.EventType.ProxyStop.toString())) {
7689
appsGauge.decrementAndGet();
77-
} else {
78-
System.out.println("not processing events of this type yet");
7990
}
8091
}
8192

8293
@EventListener
8394
public void onUserLogoutEvent(UserLogoutEvent event) {
95+
// TODO in a HA setup this event should only be processed by one server
96+
System.out.printf("UserLogoutEvent %s, %s, %s\n", event.getUserId(), event.getSessionId(), event.getWasExpired());
8497
loggedInUsersGauge.set(sessionInformation.getLoggedInUsersCount());
98+
userLogouts.increment();
8599
}
86100

87101
@EventListener
88102
public void onUserLoginEvent(UserLoginEvent event) {
103+
System.out.printf("UserLoginEvent, %s, %s \n", event.getUserId(), event.getSessionId());
89104
loggedInUsersGauge.set(sessionInformation.getLoggedInUsersCount());
105+
userLogins.increment();
90106
}
91107

92108
@EventListener
93109
public void onProxyStartEvent(ProxyStartEvent event) {
94-
System.out.printf("ProxyStartEvent %s, %s", event.getUserId(), event.getStartupTime());
110+
System.out.printf("ProxyStartEvent %s ,%s\n", event.getUserId(), event.getStartupTime());
95111

96112
appStartupTimer.record(event.getStartupTime());
97113
}
98114

99115
@EventListener
100116
public void onProxyStopEvent(ProxyStopEvent event) {
101-
System.out.printf("ProxyStopEvent %s, %s", event.getUserId(), event.getUsageTime());
117+
System.out.printf("ProxyStopEvent %s, %s\n", event.getUserId(), event.getUsageTime());
102118

103119
appUsageTimer.record(event.getUsageTime());
104120
}
105121

106122
@EventListener
107123
public void onProxyStartFailedEvent(ProxyStartFailedEvent event) {
108-
System.out.printf("ProxyStartFailedEvent %s, %s", event.getUserId(), event.getSpecId());
124+
System.out.printf("ProxyStartFailedEvent %s, %s\n", event.getUserId(), event.getSpecId());
109125

110126
appStartFailedCounter.increment();
111127
}
112128

113129
@EventListener
114130
public void onAuthFailedEvent(AuthFailedEvent event) {
115-
System.out.printf("AuthFailedEvent %s, %s", event.getUserId(), event.getSessionId());
131+
System.out.printf("AuthFailedEvent %s, %s\n", event.getUserId(), event.getSessionId());
116132

117133
authFailedCounter.increment();
118134
}
119135

120-
121136
}

0 commit comments

Comments
 (0)