From 9755478ec47eced14357cdfe9bba1b85761df4fd Mon Sep 17 00:00:00 2001 From: Boubaker Khanfir Date: Thu, 25 Jul 2024 17:14:31 +0100 Subject: [PATCH] fix: Add RememberMe Token authentication in Spring contexts - MEED-7310 - Meeds-io/meeds#2249 (#940) Prior to this change, when the User HTTPSession is outdated and a REST call is made directly on a Spring Context, the RememberMe Token isn't recognized and authenticating the user. This change will introduce filters to authenticate the user when a RememberMe Token is present as Cookie in user request, same as any call to `/portal/rest`. Besides, this will enhance security of RememberMe Token by increasing the number of used bytes from `144` to `384` bits. --- .../localization/HttpRequestLocaleFilter.java | 0 .../HttpRequestLocaleFilterConfiguration.java | 2 +- .../HttpRequestLocaleWrapper.java | 0 .../security/GrantedAuthorityDefaults.java | 0 .../security/PortalAuthenticationManager.java | 130 ++++++++---- .../web/security/PortalIdentityFilter.java | 147 ++++++++++++++ .../web/security/PortalRememberMeFilter.java | 186 ++++++++++++++++++ ...WebPortalIdentityFilterConfiguration.java} | 6 +- ...bPortalRememberMeFilterConfiguration.java} | 30 +-- .../security/WebSecurityConfiguration.java | 0 .../transaction/PortalTransactionFilter.java | 0 .../WebTransactionFilterConfiguration.java | 2 +- .../security/AbstractTokenService.java | 13 +- .../security/security/CookieTokenService.java | 4 +- .../rest/test/SpringRestIntegrationTest.java | 0 15 files changed, 451 insertions(+), 69 deletions(-) rename component/{common => web/security}/src/main/java/io/meeds/spring/web/localization/HttpRequestLocaleFilter.java (100%) rename component/{common => web/security}/src/main/java/io/meeds/spring/web/localization/HttpRequestLocaleFilterConfiguration.java (97%) rename component/{common => web/security}/src/main/java/io/meeds/spring/web/localization/HttpRequestLocaleWrapper.java (100%) rename component/{common => web/security}/src/main/java/io/meeds/spring/web/security/GrantedAuthorityDefaults.java (100%) rename component/{common => web/security}/src/main/java/io/meeds/spring/web/security/PortalAuthenticationManager.java (56%) create mode 100644 component/web/security/src/main/java/io/meeds/spring/web/security/PortalIdentityFilter.java create mode 100644 component/web/security/src/main/java/io/meeds/spring/web/security/PortalRememberMeFilter.java rename component/{common/src/main/java/io/meeds/spring/web/security/WebSecurityFilterConfiguration.java => web/security/src/main/java/io/meeds/spring/web/security/WebPortalIdentityFilterConfiguration.java} (90%) rename component/{common/src/main/java/io/meeds/spring/web/security/PortalIdentityFilter.java => web/security/src/main/java/io/meeds/spring/web/security/WebPortalRememberMeFilterConfiguration.java} (53%) rename component/{common => web/security}/src/main/java/io/meeds/spring/web/security/WebSecurityConfiguration.java (100%) rename component/{common => web/security}/src/main/java/io/meeds/spring/web/transaction/PortalTransactionFilter.java (100%) rename component/{common => web/security}/src/main/java/io/meeds/spring/web/transaction/WebTransactionFilterConfiguration.java (97%) rename component/{common => web/security}/src/test/java/io/meeds/spring/rest/test/SpringRestIntegrationTest.java (100%) diff --git a/component/common/src/main/java/io/meeds/spring/web/localization/HttpRequestLocaleFilter.java b/component/web/security/src/main/java/io/meeds/spring/web/localization/HttpRequestLocaleFilter.java similarity index 100% rename from component/common/src/main/java/io/meeds/spring/web/localization/HttpRequestLocaleFilter.java rename to component/web/security/src/main/java/io/meeds/spring/web/localization/HttpRequestLocaleFilter.java diff --git a/component/common/src/main/java/io/meeds/spring/web/localization/HttpRequestLocaleFilterConfiguration.java b/component/web/security/src/main/java/io/meeds/spring/web/localization/HttpRequestLocaleFilterConfiguration.java similarity index 97% rename from component/common/src/main/java/io/meeds/spring/web/localization/HttpRequestLocaleFilterConfiguration.java rename to component/web/security/src/main/java/io/meeds/spring/web/localization/HttpRequestLocaleFilterConfiguration.java index f8152979db..9fddfd230f 100644 --- a/component/common/src/main/java/io/meeds/spring/web/localization/HttpRequestLocaleFilterConfiguration.java +++ b/component/web/security/src/main/java/io/meeds/spring/web/localization/HttpRequestLocaleFilterConfiguration.java @@ -30,7 +30,7 @@ public FilterRegistrationBean httpRequestLocaleFilter() FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); registrationBean.setFilter(new HttpRequestLocaleFilter()); registrationBean.addUrlPatterns("/rest/*"); - registrationBean.setOrder(3); + registrationBean.setOrder(4); return registrationBean; } diff --git a/component/common/src/main/java/io/meeds/spring/web/localization/HttpRequestLocaleWrapper.java b/component/web/security/src/main/java/io/meeds/spring/web/localization/HttpRequestLocaleWrapper.java similarity index 100% rename from component/common/src/main/java/io/meeds/spring/web/localization/HttpRequestLocaleWrapper.java rename to component/web/security/src/main/java/io/meeds/spring/web/localization/HttpRequestLocaleWrapper.java diff --git a/component/common/src/main/java/io/meeds/spring/web/security/GrantedAuthorityDefaults.java b/component/web/security/src/main/java/io/meeds/spring/web/security/GrantedAuthorityDefaults.java similarity index 100% rename from component/common/src/main/java/io/meeds/spring/web/security/GrantedAuthorityDefaults.java rename to component/web/security/src/main/java/io/meeds/spring/web/security/GrantedAuthorityDefaults.java diff --git a/component/common/src/main/java/io/meeds/spring/web/security/PortalAuthenticationManager.java b/component/web/security/src/main/java/io/meeds/spring/web/security/PortalAuthenticationManager.java similarity index 56% rename from component/common/src/main/java/io/meeds/spring/web/security/PortalAuthenticationManager.java rename to component/web/security/src/main/java/io/meeds/spring/web/security/PortalAuthenticationManager.java index 86278bfe63..a98a5a076d 100644 --- a/component/common/src/main/java/io/meeds/spring/web/security/PortalAuthenticationManager.java +++ b/component/web/security/src/main/java/io/meeds/spring/web/security/PortalAuthenticationManager.java @@ -37,6 +37,8 @@ import org.springframework.web.context.request.ServletRequestAttributes; import org.exoplatform.container.ExoContainerContext; +import org.exoplatform.services.organization.OrganizationService; +import org.exoplatform.services.organization.UserStatus; import org.exoplatform.services.security.Authenticator; import org.exoplatform.services.security.ConversationRegistry; import org.exoplatform.services.security.ConversationState; @@ -44,41 +46,50 @@ import org.exoplatform.services.security.IdentityConstants; import org.exoplatform.services.security.IdentityRegistry; import org.exoplatform.services.security.StateKey; +import org.exoplatform.services.security.jaas.UserPrincipal; import org.exoplatform.services.security.web.HttpSessionStateKey; import jakarta.servlet.http.HttpServletRequest; +import lombok.SneakyThrows; @Component public class PortalAuthenticationManager implements AuthenticationProvider { + private static OrganizationService organizationService; + + private static ConversationRegistry conversationRegistry; + + private static IdentityRegistry identityRegistry; + + private static Authenticator authenticator; + @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes(); HttpServletRequest request = requestAttributes.getRequest(); try { - ConversationState conversationState = getCurrentState(request); - Principal userPrincipal = request.getUserPrincipal(); - if (conversationState == null - || (!conversationState.getIdentity().isMemberOf("/platform/users") - && !conversationState.getIdentity().isMemberOf("/platform/externals"))) { + Identity identity = getCurrentIdentity(request); + if (isAnonymousUser(identity) + && authentication.getPrincipal() instanceof String username) { + identity = getCurrentIdentity(request, username); + } + if (isAnonymousUser(identity)) { return new AnonymousAuthenticationToken(IdentityConstants.ANONIM, - authentication.getPrincipal(), + IdentityConstants.ANONIM, Collections.singletonList(new JaasGrantedAuthority("guests", - userPrincipal))); - } else { - List authorities = getAuthorities(conversationState.getIdentity(), - userPrincipal); - List extendedAuthorities = getExtendedAuthorities(userPrincipal); - if (CollectionUtils.isNotEmpty(extendedAuthorities)) { - authorities = new ArrayList<>(authorities); - authorities.addAll(extendedAuthorities); - } - authentication.setAuthenticated(true); - return new PreAuthenticatedAuthenticationToken(authentication.getPrincipal(), - null, - authorities); + new UserPrincipal(IdentityConstants.ANONIM)))); + } + Principal userPrincipal = new UserPrincipal(identity.getUserId()); + List authorities = getAuthorities(identity, userPrincipal); + List extendedAuthorities = getExtendedAuthorities(userPrincipal); + if (CollectionUtils.isNotEmpty(extendedAuthorities)) { + authorities = new ArrayList<>(authorities); + authorities.addAll(extendedAuthorities); } + return new PreAuthenticatedAuthenticationToken(userPrincipal, + identity.getUserId(), + authorities); } catch (Exception e) { throw new AuthenticationServiceException("An unknown error is encountered while authenticating user", e); } @@ -89,25 +100,26 @@ public boolean supports(Class authentication) { return true; } - public ConversationState getCurrentState(HttpServletRequest httpRequest) throws Exception { + private Identity getCurrentIdentity(HttpServletRequest httpRequest) throws Exception { ConversationState state = ConversationState.getCurrent(); - if (state != null) { - return state; + if (state == null) { + return getCurrentIdentity(httpRequest, httpRequest.getRemoteUser()); + } else { + return state.getIdentity(); } + } - String userId = httpRequest.getRemoteUser(); + private Identity getCurrentIdentity(HttpServletRequest httpRequest, String userId) throws Exception { // only if user authenticated, otherwise there is no reason to do anythings if (userId != null) { StateKey stateKey = new HttpSessionStateKey(httpRequest.getSession()); - - state = getStateBySessionId(userId, stateKey); + ConversationState state = getStateBySessionId(userId, stateKey); if (state == null) { - return buildState(userId, stateKey); - } else { - return state; + state = buildState(userId, stateKey); } + return state == null ? null : state.getIdentity(); } else { - return new ConversationState(new Identity(IdentityConstants.ANONIM)); + return new Identity(IdentityConstants.ANONIM); } } @@ -139,34 +151,68 @@ private ConversationState buildState(String userId, StateKey stateKey) throws Ex } private ConversationState buildState(Identity identity, StateKey stateKey) { - ConversationRegistry conversationRegistry = ExoContainerContext.getService(ConversationRegistry.class); - ConversationState state = new ConversationState(identity); - conversationRegistry.register(stateKey, state); + getConversationRegistry().register(stateKey, state); return state; } private Identity buildIdentity(String userId) throws Exception { - IdentityRegistry identityRegistry = ExoContainerContext.getService(IdentityRegistry.class); - Authenticator authenticator = ExoContainerContext.getService(Authenticator.class); - - Identity identity = identityRegistry.getIdentity(userId); + Identity identity = getIdentityRegistry().getIdentity(userId); if (identity == null) { - identity = authenticator.createIdentity(userId); - identityRegistry.register(identity); + identity = getAuthenticator().createIdentity(userId); + getIdentityRegistry().register(identity); } return identity; } private ConversationState getStateBySessionId(String userId, StateKey stateKey) { - ConversationRegistry conversationRegistry = ExoContainerContext.getService(ConversationRegistry.class); - - ConversationState state = conversationRegistry.getState(stateKey); + ConversationState state = getConversationRegistry().getState(stateKey); if (state != null && !userId.equals(state.getIdentity().getUserId())) { state = null; - conversationRegistry.unregister(stateKey, false); + getConversationRegistry().unregister(stateKey, false); } return state; } + @SneakyThrows + private boolean isDisabledUser(String username) { + return null == getOrganizationService().getUserHandler() + .findUserByName(username, UserStatus.ENABLED); + } + + private boolean isAnonymousUser(Identity identity) { + return identity == null + || IdentityConstants.ANONIM.equals(identity.getUserId()) + || (!identity.isMemberOf("/platform/users") && !identity.isMemberOf("/platform/externals")) + || isDisabledUser(identity.getUserId()); + } + + private static Authenticator getAuthenticator() { + if (authenticator == null) { + authenticator = ExoContainerContext.getService(Authenticator.class); + } + return authenticator; + } + + private static IdentityRegistry getIdentityRegistry() { + if (identityRegistry == null) { + identityRegistry = ExoContainerContext.getService(IdentityRegistry.class); + } + return identityRegistry; + } + + private static OrganizationService getOrganizationService() { + if (organizationService == null) { + organizationService = ExoContainerContext.getService(OrganizationService.class); + } + return organizationService; + } + + private static ConversationRegistry getConversationRegistry() { + if (conversationRegistry == null) { + conversationRegistry = ExoContainerContext.getService(ConversationRegistry.class); + } + return conversationRegistry; + } + } diff --git a/component/web/security/src/main/java/io/meeds/spring/web/security/PortalIdentityFilter.java b/component/web/security/src/main/java/io/meeds/spring/web/security/PortalIdentityFilter.java new file mode 100644 index 0000000000..3d5cc608a6 --- /dev/null +++ b/component/web/security/src/main/java/io/meeds/spring/web/security/PortalIdentityFilter.java @@ -0,0 +1,147 @@ +/** + * This file is part of the Meeds project (https://meeds.io/). + * + * Copyright (C) 2020 - 2023 Meeds Association contact@meeds.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * This program 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package io.meeds.spring.web.security; + +import java.io.IOException; + +import org.apache.commons.lang3.StringUtils; + +import org.exoplatform.container.ExoContainer; +import org.exoplatform.container.ExoContainerContext; +import org.exoplatform.container.web.AbstractFilter; +import org.exoplatform.services.log.ExoLogger; +import org.exoplatform.services.log.Log; +import org.exoplatform.services.security.Authenticator; +import org.exoplatform.services.security.ConversationRegistry; +import org.exoplatform.services.security.ConversationState; +import org.exoplatform.services.security.Identity; +import org.exoplatform.services.security.IdentityConstants; +import org.exoplatform.services.security.IdentityRegistry; +import org.exoplatform.services.security.StateKey; +import org.exoplatform.services.security.web.HttpSessionStateKey; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpSession; + +/** + * A Web filter to retrieve currently authenticated user Identity to current Web + * context session.
+ * Note: added to be included in class packages scan for Spring + */ +public class PortalIdentityFilter extends AbstractFilter { + + private static final Log LOG = ExoLogger.getLogger(PortalIdentityFilter.class); + + private static ConversationRegistry conversationRegistry; + + private static IdentityRegistry identityRegistry; + + private static Authenticator authenticator; + + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, + ServletException { + + HttpServletRequest httpRequest = (HttpServletRequest) request; + ExoContainer container = getContainer(); + + ExoContainerContext.setCurrentContainer(container); + try { + ConversationState.setCurrent(getCurrentState(container, httpRequest)); + chain.doFilter(request, response); + } finally { + ConversationState.setCurrent(null); + ExoContainerContext.setCurrentContainer(null); + } + } + + private ConversationState getCurrentState(ExoContainer container, + HttpServletRequest httpRequest) { + String userId = httpRequest.getRemoteUser(); + if (StringUtils.isBlank(userId)) { + return new ConversationState(new Identity(IdentityConstants.ANONIM)); + } else { + ConversationState state = null; + HttpSession httpSession = httpRequest.getSession(false); + if (httpSession != null) { + StateKey stateKey = new HttpSessionStateKey(httpSession); + if (LOG.isDebugEnabled()) { + LOG.debug("Looking for Conversation State " + httpSession.getId()); + } + state = getConversationRegistry(container).getState(stateKey); + if (state != null && !userId.equals(state.getIdentity().getUserId())) { + state = null; + conversationRegistry.unregister(stateKey, false); + LOG.warn("The current conversation state with the session ID {} does not belong to the user {}. Identity registries has been cleared.", + httpSession.getId(), + userId); + } + } + + if (state == null) { + Identity identity = getIdentity(container, userId); + if (identity != null) { + state = new ConversationState(identity); + getConversationRegistry(container).register(new HttpSessionStateKey(httpRequest.getSession()), + state); + } + } + return state; + } + } + + private Identity getIdentity(ExoContainer container, String userId) { + Identity identity = getIdentityRegistry(container).getIdentity(userId); + if (identity == null) { + try { + identity = getAuthenticator(container).createIdentity(userId); + identityRegistry.register(identity); + } catch (Exception e) { + LOG.warn("Unable restore identity of user {}", userId, e); + } + } + return identity; + } + + private static IdentityRegistry getIdentityRegistry(ExoContainer container) { + if (identityRegistry == null) { + identityRegistry = container.getComponentInstanceOfType(IdentityRegistry.class); + } + return identityRegistry; + } + + private static ConversationRegistry getConversationRegistry(ExoContainer container) { + if (conversationRegistry == null) { + conversationRegistry = container.getComponentInstanceOfType(ConversationRegistry.class); + } + return conversationRegistry; + } + + private static Authenticator getAuthenticator(ExoContainer container) { + if (authenticator == null) { + authenticator = container.getComponentInstanceOfType(Authenticator.class); + } + return authenticator; + } + +} diff --git a/component/web/security/src/main/java/io/meeds/spring/web/security/PortalRememberMeFilter.java b/component/web/security/src/main/java/io/meeds/spring/web/security/PortalRememberMeFilter.java new file mode 100644 index 0000000000..5c916a3982 --- /dev/null +++ b/component/web/security/src/main/java/io/meeds/spring/web/security/PortalRememberMeFilter.java @@ -0,0 +1,186 @@ +/** + * This file is part of the Meeds project (https://meeds.io/). + * + * Copyright (C) 2020 - 2023 Meeds Association contact@meeds.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * This program 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package io.meeds.spring.web.security; + +import java.io.IOException; + +import org.gatein.wci.ServletContainerFactory; +import org.gatein.wci.security.Credentials; + +import org.exoplatform.container.ExoContainer; +import org.exoplatform.container.ExoContainerContext; +import org.exoplatform.container.web.AbstractFilter; +import org.exoplatform.services.log.ExoLogger; +import org.exoplatform.services.log.Log; +import org.exoplatform.services.security.Authenticator; +import org.exoplatform.services.security.ConversationRegistry; +import org.exoplatform.services.security.ConversationState; +import org.exoplatform.services.security.Identity; +import org.exoplatform.services.security.IdentityRegistry; +import org.exoplatform.services.security.web.HttpSessionStateKey; +import org.exoplatform.web.login.LoginUtils; +import org.exoplatform.web.security.security.CookieTokenService; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequestWrapper; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletResponseWrapper; + +/** + * A Web filter to authenticate user Identity using 'rememberme' cookie if + * present.
+ * Note: added to be included in class packages scan for Spring + */ +public class PortalRememberMeFilter extends AbstractFilter { + + private static final Log LOG = ExoLogger.getLogger(PortalRememberMeFilter.class); + + private static ConversationRegistry conversationRegistry; + + private static IdentityRegistry identityRegistry; + + private static Authenticator authenticator; + + public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { + HttpServletRequest request = (HttpServletRequest) req; + HttpServletResponse response = (HttpServletResponse) resp; + if (request.getRemoteUser() == null) { + login(request, response); + } + chain.doFilter(req, resp); + } + + private void login(HttpServletRequest request, HttpServletResponse response) { + ExoContainer currentContainer = ExoContainerContext.getCurrentContainerIfPresent(); + ExoContainer container = getContainer(); + ExoContainerContext.setCurrentContainer(container); + try { + String username = getRememberMeTokenUser(request); + if (username != null) { + try { + login(request, response, new Credentials(username, "")); + if (request.getRemoteUser() != null) { + Identity identity = getIdentity(container, username); + if (identity != null) { + ConversationState state = new ConversationState(identity); + getConversationRegistry(container).register(new HttpSessionStateKey(request.getSession()), + state); + ConversationState.setCurrent(state); + } + } + } catch (Exception e) { + clearInvalidToken(request, response); + LOG.warn("Error while logging in user {} using rememberme token, invalidate token", username, e); + } + } + } finally { + ExoContainerContext.setCurrentContainer(currentContainer); + } + } + + private void login(HttpServletRequest request, HttpServletResponse response, Credentials credentials) throws ServletException, + IOException { + HttpServletRequest wrappedRequest = new HttpServletRequestWrapper(request) { + @Override + public String getContextPath() { + return "/portal"; + } + + @Override + public String getRequestURI() { + return "/portal/login"; + } + }; + HttpServletResponse wrappedResponse = new HttpServletResponseWrapper(response) { + @Override + public void sendRedirect(String location) throws IOException { + // Nothing + } + + @Override + public void setStatus(int sc) { + // Nothing + } + }; + ServletContainerFactory.getServletContainer() + .login(wrappedRequest, wrappedResponse, credentials); + } + + private String getRememberMeTokenUser(HttpServletRequest request) { + String token = LoginUtils.getRememberMeTokenCookie(request); + if (token != null) { + ExoContainer container = getContainer(); + CookieTokenService tokenservice = container.getComponentInstanceOfType(CookieTokenService.class); + return tokenservice.validateToken(token, false); + } + return null; + } + + private void clearInvalidToken(HttpServletRequest request, HttpServletResponse response) { + if (request.getRemoteUser() == null) { + Cookie cookie = new Cookie(LoginUtils.COOKIE_NAME, ""); + cookie.setPath("/"); + cookie.setMaxAge(0); + cookie.setHttpOnly(true); + cookie.setSecure(request.isSecure()); + response.addCookie(cookie); + } + } + + private Identity getIdentity(ExoContainer container, String userId) { + Identity identity = getIdentityRegistry(container).getIdentity(userId); + if (identity == null) { + try { + identity = getAuthenticator(container).createIdentity(userId); + identityRegistry.register(identity); + } catch (Exception e) { + LOG.warn("Unable restore identity of user {}", userId, e); + } + } + return identity; + } + + private static IdentityRegistry getIdentityRegistry(ExoContainer container) { + if (identityRegistry == null) { + identityRegistry = container.getComponentInstanceOfType(IdentityRegistry.class); + } + return identityRegistry; + } + + private static ConversationRegistry getConversationRegistry(ExoContainer container) { + if (conversationRegistry == null) { + conversationRegistry = container.getComponentInstanceOfType(ConversationRegistry.class); + } + return conversationRegistry; + } + + private static Authenticator getAuthenticator(ExoContainer container) { + if (authenticator == null) { + authenticator = container.getComponentInstanceOfType(Authenticator.class); + } + return authenticator; + } + +} diff --git a/component/common/src/main/java/io/meeds/spring/web/security/WebSecurityFilterConfiguration.java b/component/web/security/src/main/java/io/meeds/spring/web/security/WebPortalIdentityFilterConfiguration.java similarity index 90% rename from component/common/src/main/java/io/meeds/spring/web/security/WebSecurityFilterConfiguration.java rename to component/web/security/src/main/java/io/meeds/spring/web/security/WebPortalIdentityFilterConfiguration.java index dae4a5fd53..c2df95f997 100644 --- a/component/common/src/main/java/io/meeds/spring/web/security/WebSecurityFilterConfiguration.java +++ b/component/web/security/src/main/java/io/meeds/spring/web/security/WebPortalIdentityFilterConfiguration.java @@ -19,15 +19,15 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -@Configuration("webSecurityFilterConfiguration") -public class WebSecurityFilterConfiguration { +@Configuration("webPortalIdentityFilterConfiguration") +public class WebPortalIdentityFilterConfiguration { @Bean public FilterRegistrationBean identityFilter() { FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); registrationBean.setFilter(new PortalIdentityFilter()); registrationBean.addUrlPatterns("/rest/*"); - registrationBean.setOrder(1); + registrationBean.setOrder(2); return registrationBean; } diff --git a/component/common/src/main/java/io/meeds/spring/web/security/PortalIdentityFilter.java b/component/web/security/src/main/java/io/meeds/spring/web/security/WebPortalRememberMeFilterConfiguration.java similarity index 53% rename from component/common/src/main/java/io/meeds/spring/web/security/PortalIdentityFilter.java rename to component/web/security/src/main/java/io/meeds/spring/web/security/WebPortalRememberMeFilterConfiguration.java index 40538f854c..a8710ebcd4 100644 --- a/component/common/src/main/java/io/meeds/spring/web/security/PortalIdentityFilter.java +++ b/component/web/security/src/main/java/io/meeds/spring/web/security/WebPortalRememberMeFilterConfiguration.java @@ -1,8 +1,6 @@ -/** +/* * This file is part of the Meeds project (https://meeds.io/). - * - * Copyright (C) 2020 - 2023 Meeds Association contact@meeds.io - * + * Copyright (C) 2020 - 2022 Meeds Association contact@meeds.io * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either @@ -11,20 +9,26 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ - package io.meeds.spring.web.security; -import org.exoplatform.services.security.web.SetCurrentIdentityFilter; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; -/** - * A Web filter to retrieve currently authenticated user Identity to current Web - * context session - */ -public class PortalIdentityFilter extends SetCurrentIdentityFilter { +@Configuration("webPortalRememberMeFilterConfiguration") +public class WebPortalRememberMeFilterConfiguration { + + @Bean + public FilterRegistrationBean rememberMeFilter() { + FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); + registrationBean.setFilter(new PortalRememberMeFilter()); + registrationBean.addUrlPatterns("/rest/*"); + registrationBean.setOrder(3); + return registrationBean; + } -} \ No newline at end of file +} diff --git a/component/common/src/main/java/io/meeds/spring/web/security/WebSecurityConfiguration.java b/component/web/security/src/main/java/io/meeds/spring/web/security/WebSecurityConfiguration.java similarity index 100% rename from component/common/src/main/java/io/meeds/spring/web/security/WebSecurityConfiguration.java rename to component/web/security/src/main/java/io/meeds/spring/web/security/WebSecurityConfiguration.java diff --git a/component/common/src/main/java/io/meeds/spring/web/transaction/PortalTransactionFilter.java b/component/web/security/src/main/java/io/meeds/spring/web/transaction/PortalTransactionFilter.java similarity index 100% rename from component/common/src/main/java/io/meeds/spring/web/transaction/PortalTransactionFilter.java rename to component/web/security/src/main/java/io/meeds/spring/web/transaction/PortalTransactionFilter.java diff --git a/component/common/src/main/java/io/meeds/spring/web/transaction/WebTransactionFilterConfiguration.java b/component/web/security/src/main/java/io/meeds/spring/web/transaction/WebTransactionFilterConfiguration.java similarity index 97% rename from component/common/src/main/java/io/meeds/spring/web/transaction/WebTransactionFilterConfiguration.java rename to component/web/security/src/main/java/io/meeds/spring/web/transaction/WebTransactionFilterConfiguration.java index c54b5b6656..a8e34d5ac0 100644 --- a/component/common/src/main/java/io/meeds/spring/web/transaction/WebTransactionFilterConfiguration.java +++ b/component/web/security/src/main/java/io/meeds/spring/web/transaction/WebTransactionFilterConfiguration.java @@ -27,7 +27,7 @@ public FilterRegistrationBean transactionFilter() { FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); registrationBean.setFilter(new PortalTransactionFilter()); registrationBean.addUrlPatterns("/rest/*"); - registrationBean.setOrder(2); + registrationBean.setOrder(1); return registrationBean; } diff --git a/component/web/security/src/main/java/org/exoplatform/web/security/security/AbstractTokenService.java b/component/web/security/src/main/java/org/exoplatform/web/security/security/AbstractTokenService.java index e2f1cf2118..5a3f961020 100644 --- a/component/web/security/src/main/java/org/exoplatform/web/security/security/AbstractTokenService.java +++ b/component/web/security/src/main/java/org/exoplatform/web/security/security/AbstractTokenService.java @@ -48,9 +48,6 @@ /** * Created by The eXo Platform SAS Author : liem.nguyen ncliam@gmail.com Jun 5, 2009 * - * todo julien : - make delay configuration from init param and @Managed setter - start/stop expiration daemon - manually invoke - * the daemon via @Managed - * * @param the token type * @param the token key type */ @@ -66,10 +63,12 @@ public abstract class AbstractTokenService implements Starta protected static final String CLEANUP_PERIOD_TIME = "cleanup.period.time"; /** - * See {@link #tokenByteLength}. 8 bytes (64 bits) would be enough, but we want to get padding-less Byte64 representation, - * so we take the next greater number divisible by 3 which is 9. 9 bytes is equal to 72 bits. + * See {@link #tokenByteLength}. 16 bytes (128 bits) would be enough + * (Validator + Selector = 256bits), but we want to get padding-less Byte64 + * representation, so we take a greater number divisible by 3.
+ * (24 bytes is equal to 192 bits.) */ - public static final int DEFAULT_TOKEN_BYTE_LENGTH = 9; + public static final int DEFAULT_TOKEN_BYTE_LENGTH = 24; /** * The number of random bits generared by {@link #nextRandom()}. Use values divisible by 3 to produce random strings @@ -233,7 +232,7 @@ protected String nextTokenId() { protected String nextRandom() { byte[] randomBytes = new byte[tokenByteLength]; PortalContainer container = PortalContainer.getInstance(); - SecureRandom random = ((SecureRandomService) container.getComponentInstanceOfType(SecureRandomService.class)).getSecureRandom(); + SecureRandom random = container.getComponentInstanceOfType(SecureRandomService.class).getSecureRandom(); random.nextBytes(randomBytes); return Base64.encodeBytes(randomBytes, EncodingOption.USEURLSAFEENCODING); } diff --git a/component/web/security/src/main/java/org/exoplatform/web/security/security/CookieTokenService.java b/component/web/security/src/main/java/org/exoplatform/web/security/security/CookieTokenService.java index 33733162c1..0059abd08b 100644 --- a/component/web/security/src/main/java/org/exoplatform/web/security/security/CookieTokenService.java +++ b/component/web/security/src/main/java/org/exoplatform/web/security/security/CookieTokenService.java @@ -91,8 +91,8 @@ public String createToken(String username, String type) { String cookieTokenString = null; while (cookieTokenString == null) { - String selector = nextTokenId(); //9 bytes selector - String validator = nextRandom(); //9 bytes validator + String selector = nextTokenId(); + String validator = nextRandom(); String hashedRandomString = hashToken(validator+SEPARATOR_CHAR+type); long expirationTimeMillis = System.currentTimeMillis() + validityMillis; diff --git a/component/common/src/test/java/io/meeds/spring/rest/test/SpringRestIntegrationTest.java b/component/web/security/src/test/java/io/meeds/spring/rest/test/SpringRestIntegrationTest.java similarity index 100% rename from component/common/src/test/java/io/meeds/spring/rest/test/SpringRestIntegrationTest.java rename to component/web/security/src/test/java/io/meeds/spring/rest/test/SpringRestIntegrationTest.java