From 596a007bee818c41ce0e40ab239a0b57caa1899a Mon Sep 17 00:00:00 2001 From: Daniel Gerhardt <code@dgerhardt.net> Date: Sat, 10 Feb 2018 17:36:21 +0100 Subject: [PATCH] Use aspect to populate SecurityContextHolder from WebSocket listeners --- .../java/de/thm/arsnova/security/User.java | 9 ++ .../websocket/ArsnovaSocketioServerImpl.java | 19 +---- .../WebsocketAuthenticationAspect.java | 85 +++++++++++++++++++ src/main/resources/META-INF/aop.xml | 3 +- 4 files changed, 97 insertions(+), 19 deletions(-) create mode 100644 src/main/java/de/thm/arsnova/websocket/WebsocketAuthenticationAspect.java diff --git a/src/main/java/de/thm/arsnova/security/User.java b/src/main/java/de/thm/arsnova/security/User.java index 155074d10..a6dad6e6b 100644 --- a/src/main/java/de/thm/arsnova/security/User.java +++ b/src/main/java/de/thm/arsnova/security/User.java @@ -17,6 +17,7 @@ */ package de.thm.arsnova.security; +import de.thm.arsnova.entities.UserAuthentication; import de.thm.arsnova.entities.UserProfile; import org.springframework.security.core.GrantedAuthority; @@ -56,6 +57,14 @@ public class User implements org.springframework.security.core.userdetails.UserD providerUserDetails = details; } + public User(final UserAuthentication userAuthentication, final Collection<? extends GrantedAuthority> authorities) { + id = userAuthentication.getId(); + loginId = userAuthentication.getUsername(); + authProvider = userAuthentication.getAuthProvider(); + this.authorities = authorities; + enabled = true; + } + @Override public Collection<? extends GrantedAuthority> getAuthorities() { return authorities; diff --git a/src/main/java/de/thm/arsnova/websocket/ArsnovaSocketioServerImpl.java b/src/main/java/de/thm/arsnova/websocket/ArsnovaSocketioServerImpl.java index 1baf2d054..671cc61eb 100644 --- a/src/main/java/de/thm/arsnova/websocket/ArsnovaSocketioServerImpl.java +++ b/src/main/java/de/thm/arsnova/websocket/ArsnovaSocketioServerImpl.java @@ -48,11 +48,6 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Required; import org.springframework.scheduling.annotation.Async; -import org.springframework.security.authentication.AnonymousAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; import javax.annotation.PreDestroy; @@ -96,7 +91,6 @@ public class ArsnovaSocketioServerImpl implements ArsnovaSocketioServer, Arsnova private String storepass; private final Configuration config; private SocketIOServer server; - private boolean securityInitialized; public ArsnovaSocketioServerImpl() { config = new Configuration(); @@ -244,9 +238,7 @@ public class ArsnovaSocketioServerImpl implements ArsnovaSocketioServer, Arsnova @Override @Timed public void onConnect(final SocketIOClient client) { - if (!securityInitialized) { - initializeSecurity(); - } + } }); @@ -689,13 +681,4 @@ public class ArsnovaSocketioServerImpl implements ArsnovaSocketioServer, Arsnova @Override public void visit(DeleteRoomEvent event) { } - - private void initializeSecurity() { - Authentication auth = new AnonymousAuthenticationToken("websocket", "websocket", - AuthorityUtils.createAuthorityList("ROLE_WEBSOCKET_ACCESS")); - SecurityContext context = SecurityContextHolder.createEmptyContext(); - context.setAuthentication(auth); - SecurityContextHolder.setContext(context); - securityInitialized = true; - } } diff --git a/src/main/java/de/thm/arsnova/websocket/WebsocketAuthenticationAspect.java b/src/main/java/de/thm/arsnova/websocket/WebsocketAuthenticationAspect.java new file mode 100644 index 000000000..6f86f6056 --- /dev/null +++ b/src/main/java/de/thm/arsnova/websocket/WebsocketAuthenticationAspect.java @@ -0,0 +1,85 @@ +/* + * This file is part of ARSnova Backend. + * Copyright (C) 2012-2018 The ARSnova Team + * + * ARSnova Backend is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ARSnova Backend is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +package de.thm.arsnova.websocket; + +import com.corundumstudio.socketio.SocketIOClient; +import de.thm.arsnova.entities.UserAuthentication; +import de.thm.arsnova.security.User; +import de.thm.arsnova.services.UserService; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Configurable; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +/** + * This aspect populates the SecurityContextHolder of Spring Security when data are received via WebSockets. + * It allows WebSocket listeners to access service methods which are secured by Spring Security annotations. + * + * @author Daniel Gerhardt + */ +@Aspect +@Configurable +public class WebsocketAuthenticationAspect { + private static final Logger logger = LoggerFactory.getLogger(WebsocketAuthenticationAspect.class); + private static final GrantedAuthority WEBSOCKET_AUTHORITY = new SimpleGrantedAuthority("ROLE_WEBSOCKET_ACCESS"); + + private UserService userService; + + @Around("execution(void com.corundumstudio.socketio.listener.DataListener+.onData(..)) && args(client, message, ..)") + public <T> void handleWebsocketAuthentication(final ProceedingJoinPoint pjp, + final SocketIOClient client, final T message) throws Throwable { + logger.debug("Executing WebsocketAuthenticationAspect for onData event: Session Id: {}, Message Class: {}", + client.getSessionId(), message.getClass()); + populateSecurityContext(client.getSessionId()); + pjp.proceed(); + clearSecurityContext(); + } + + private void populateSecurityContext(final UUID socketId) { + SecurityContext context = SecurityContextHolder.getContext(); + UserAuthentication userAuth = userService.getUserToSocketId(socketId); + Set<GrantedAuthority> authorities = new HashSet<>(); + authorities.add(WEBSOCKET_AUTHORITY); + User user = new User(userAuth, authorities); + Authentication auth = new UsernamePasswordAuthenticationToken(user, null, authorities); + context.setAuthentication(auth); + SecurityContextHolder.setContext(context); + } + + private void clearSecurityContext() { + SecurityContextHolder.clearContext(); + } + + @Autowired + public void setUserService(final UserService userService) { + this.userService = userService; + } +} diff --git a/src/main/resources/META-INF/aop.xml b/src/main/resources/META-INF/aop.xml index 3b810cd0d..d897ff7f6 100644 --- a/src/main/resources/META-INF/aop.xml +++ b/src/main/resources/META-INF/aop.xml @@ -1,11 +1,12 @@ <?xml version="1.0" encoding="UTF-8"?> <aspectj> <weaver options="-verbose -showWeaveInfo"> - <!-- only weave classes in our application-specific packages --> <include within="de.thm.arsnova..*"/> + <include within="com.corundumstudio.socketio.listener.*"/> </weaver> <aspects> <aspect name="de.thm.arsnova.aop.RangeAspect"/> + <aspect name="de.thm.arsnova.websocket.WebsocketAuthenticationAspect"/> </aspects> </aspectj> -- GitLab