Skip to content

Commit 5c5a6e3

Browse files
committed
Add first MicroMeter implementation
1 parent 4300076 commit 5c5a6e3

11 files changed

Lines changed: 444 additions & 2 deletions

pom.xml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,10 @@
240240
<artifactId>mysql-connector-java</artifactId>
241241
</dependency>
242242

243-
243+
<dependency>
244+
<groupId>io.micrometer</groupId>
245+
<artifactId>micrometer-registry-prometheus</artifactId>
246+
</dependency>
244247

245248
<!-- Kubernetes -->
246249
<dependency>

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
package eu.openanalytics.containerproxy;
2222

2323
import com.fasterxml.jackson.datatype.jsr353.JSR353Module;
24+
import eu.openanalytics.containerproxy.session.undertow.CustomSessionManagerFactory;
2425
import eu.openanalytics.containerproxy.util.ProxyMappingManager;
2526
import io.undertow.Handlers;
2627
import io.undertow.servlet.api.ServletSessionConfig;
@@ -31,15 +32,20 @@
3132
import org.springframework.boot.actuate.health.HealthIndicator;
3233
import org.springframework.boot.actuate.redis.RedisHealthIndicator;
3334
import org.springframework.boot.autoconfigure.SpringBootApplication;
35+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
3436
import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
3537
import org.springframework.boot.web.server.PortInUseException;
3638
import org.springframework.boot.web.servlet.FilterRegistrationBean;
3739
import org.springframework.context.annotation.Bean;
3840
import org.springframework.context.annotation.ComponentScan;
3941
import org.springframework.core.env.Environment;
4042
import org.springframework.data.redis.connection.RedisConnectionFactory;
43+
import org.springframework.security.core.session.SessionRegistry;
44+
import org.springframework.session.FindByIndexNameSessionRepository;
45+
import org.springframework.session.Session;
4146
import org.springframework.session.data.redis.config.ConfigureRedisAction;
4247
import org.springframework.session.web.http.DefaultCookieSerializer;
48+
import org.springframework.session.security.SpringSessionBackedSessionRegistry;
4349
import org.springframework.web.filter.FormContentFilter;
4450

4551
import javax.annotation.PostConstruct;
@@ -98,6 +104,9 @@ public void init() {
98104
defaultCookieSerializer.setSameSite(sameSiteCookie);
99105
}
100106

107+
@Inject
108+
private CustomSessionManagerFactory customSessionManagerFactory;
109+
101110
@Bean
102111
public UndertowServletWebServerFactory servletContainer() {
103112
UndertowServletWebServerFactory factory = new UndertowServletWebServerFactory();
@@ -113,6 +122,7 @@ public UndertowServletWebServerFactory servletContainer() {
113122
sessionConfig.setHttpOnly(true);
114123
sessionConfig.setSecure(Boolean.valueOf(environment.getProperty("server.secureCookies", "false")));
115124
info.setServletSessionConfig(sessionConfig);
125+
info.setSessionManagerFactory(customSessionManagerFactory);
116126
});
117127
try {
118128
factory.setAddress(InetAddress.getByName(environment.getProperty("proxy.bind-address", "0.0.0.0")));
@@ -152,6 +162,12 @@ public static ConfigureRedisAction configureRedisAction() {
152162
return ConfigureRedisAction.NO_OP;
153163
}
154164

165+
@Bean
166+
@ConditionalOnProperty(name = "spring.session.store-type", havingValue = "redis")
167+
public <S extends Session> SessionRegistry sessionRegistry(FindByIndexNameSessionRepository<S> sessionRepository) {
168+
return new SpringSessionBackedSessionRegistry<S>(sessionRepository);
169+
}
170+
155171
@Bean
156172
public HealthIndicator redisSessionHealthIndicator(RedisConnectionFactory rdeRedisConnectionFactory) {
157173
if (Objects.equals(environment.getProperty("spring.session.store-type"), "redis")) {

src/main/java/eu/openanalytics/containerproxy/auth/UserLogoutHandler.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
import javax.servlet.http.HttpServletRequest;
2525
import javax.servlet.http.HttpServletResponse;
2626

27+
import eu.openanalytics.containerproxy.session.UserSessionLogoutEvent;
28+
import org.springframework.context.ApplicationEventPublisher;
2729
import org.springframework.security.core.Authentication;
2830
import org.springframework.security.web.authentication.logout.LogoutHandler;
2931
import org.springframework.stereotype.Component;
@@ -35,10 +37,19 @@ public class UserLogoutHandler implements LogoutHandler {
3537

3638
@Inject
3739
private UserService userService;
40+
41+
@Inject
42+
private ApplicationEventPublisher applicationEventPublisher;
3843

3944
@Override
4045
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
4146
userService.logout(authentication);
47+
48+
// TODO test for anonymous users
49+
applicationEventPublisher.publishEvent(new UserSessionLogoutEvent(
50+
this,
51+
authentication.getName(),
52+
request.getSession().getId()));
4253
}
4354

4455
}

src/main/java/eu/openanalytics/containerproxy/security/WebSecurityConfig.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,8 @@ protected void configure(HttpSecurity http) throws Exception {
116116
http.authorizeRequests().antMatchers("/actuator/health").permitAll();
117117
http.authorizeRequests().antMatchers("/actuator/health/readiness").permitAll();
118118
http.authorizeRequests().antMatchers("/actuator/health/liveness").permitAll();
119-
119+
http.authorizeRequests().antMatchers("/actuator/prometheus").permitAll();
120+
120121
// Note: call early, before http.authorizeRequests().anyRequest().fullyAuthenticated();
121122
if (customConfigs != null) {
122123
for (ICustomSecurityConfig cfg: customConfigs) cfg.apply(http);
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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+
*/
21+
package eu.openanalytics.containerproxy.session;
22+
23+
import org.springframework.session.Session;
24+
25+
public interface ISessionInformation {
26+
27+
// public Session findById(String sessionId);
28+
29+
public Long getLoggedInUsersCount();
30+
31+
public void reActivateSession(String sessionId);
32+
33+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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+
*/
21+
package eu.openanalytics.containerproxy.session;
22+
23+
import org.springframework.context.ApplicationEvent;
24+
25+
public class UserSessionLogoutEvent extends ApplicationEvent {
26+
27+
private String userId;
28+
private String sessionId;
29+
30+
public UserSessionLogoutEvent(Object source, String userId, String sessionId) {
31+
super(source);
32+
this.userId = userId;
33+
this.sessionId = sessionId;
34+
}
35+
36+
public String getSessionId() {
37+
return sessionId;
38+
}
39+
40+
public String getUserId() {
41+
return userId;
42+
}
43+
}
44+
45+
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
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+
*/
21+
package eu.openanalytics.containerproxy.session.redis;
22+
23+
import eu.openanalytics.containerproxy.session.ISessionInformation;
24+
import eu.openanalytics.containerproxy.session.UserSessionLogoutEvent;
25+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
26+
import org.springframework.context.annotation.Lazy;
27+
import org.springframework.context.event.EventListener;
28+
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
29+
import org.springframework.security.core.session.SessionRegistry;
30+
import org.springframework.security.web.authentication.session.SessionFixationProtectionEvent;
31+
import org.springframework.stereotype.Component;
32+
import org.springframework.web.context.request.RequestContextHolder;
33+
34+
import javax.inject.Inject;
35+
import java.util.HashMap;
36+
import java.util.HashSet;
37+
38+
// TODO fully implement this as soon as HA is merged
39+
@Component
40+
@ConditionalOnProperty(name = "spring.session.store-type", havingValue = "redis")
41+
public class RedisSessionInformation implements ISessionInformation {
42+
43+
@Inject
44+
@Lazy // TODO still needed?
45+
private SessionRegistry sessionRegistry;
46+
47+
// This map keeps track of the logged-in users and their session in order to give the correct amount of logged in
48+
// users. Since there is no Session Store setup we cannot use any Spring API to get the full list of logged in
49+
// users. Therefore we maintain it ourself.
50+
// we need to store the full Session and not only the SessionId, since the sessionId changes after logging in
51+
private final HashMap<String, HashSet<String>> usersToSessionId = new HashMap<>();
52+
53+
@Override
54+
public Long getLoggedInUsersCount() {
55+
return usersToSessionId.values().stream().filter(v -> !v.isEmpty()).count();
56+
}
57+
58+
@Override
59+
public void reActivateSession(String sessionId) {
60+
sessionRegistry.refreshLastRequest(sessionId);
61+
}
62+
63+
@EventListener
64+
public void onUserSessionLogoutEvent(UserSessionLogoutEvent event) {
65+
synchronized (usersToSessionId) {
66+
if (usersToSessionId.containsKey(event.getUserId())) {
67+
usersToSessionId.get(event.getUserId()).remove(event.getSessionId());
68+
}
69+
}
70+
}
71+
72+
@EventListener
73+
public void onSessionIdChangeEvent(SessionFixationProtectionEvent event) {
74+
String userId = event.getAuthentication().getName(); // TODO anonymous
75+
synchronized (usersToSessionId) {
76+
if (usersToSessionId.containsKey(userId)) {
77+
usersToSessionId.get(userId).remove(event.getOldSessionId());
78+
usersToSessionId.get(userId).add(event.getNewSessionId());
79+
}
80+
}
81+
}
82+
83+
@EventListener
84+
public void onAuthenticationSuccessEvent(AuthenticationSuccessEvent event) {
85+
String sessionId = RequestContextHolder.currentRequestAttributes().getSessionId();
86+
String userId = event.getAuthentication().getName(); // TODO anonymous access
87+
synchronized (usersToSessionId) {
88+
if (!usersToSessionId.containsKey(userId)) {
89+
usersToSessionId.put(userId, new HashSet<>());
90+
}
91+
usersToSessionId.get(userId).add(sessionId);
92+
}
93+
}
94+
95+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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+
*/
21+
package eu.openanalytics.containerproxy.session.undertow;
22+
23+
import io.undertow.server.session.InMemorySessionManager;
24+
import io.undertow.server.session.SessionManager;
25+
import io.undertow.servlet.api.Deployment;
26+
import io.undertow.servlet.api.SessionManagerFactory;
27+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
28+
import org.springframework.stereotype.Component;
29+
30+
@Component
31+
public class CustomSessionManagerFactory implements SessionManagerFactory {
32+
33+
private InMemorySessionManager inMemorySessionManager = null;
34+
35+
@Override
36+
public SessionManager createSessionManager(Deployment deployment) {
37+
// TODO check if already set
38+
inMemorySessionManager = new InMemorySessionManager(
39+
deployment.getDeploymentInfo().getSessionIdGenerator(),
40+
deployment.getDeploymentInfo().getDeploymentName(),
41+
-1,
42+
false,
43+
deployment.getDeploymentInfo().getMetricsCollector() != null);
44+
return inMemorySessionManager;
45+
}
46+
47+
public InMemorySessionManager getInstance() {
48+
return inMemorySessionManager;
49+
}
50+
}

0 commit comments

Comments
 (0)