/* * Copyright 2015 herd contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.finra.herd.app.security; import java.io.IOException; import java.util.Date; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationDetailsSource; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationTrustResolver; import org.springframework.security.authentication.AuthenticationTrustResolverImpl; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.WebAttributes; import org.springframework.security.web.authentication.WebAuthenticationDetails; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; import org.springframework.web.filter.GenericFilterBean; import org.finra.herd.model.dto.ApplicationUser; import org.finra.herd.model.dto.SecurityUserWrapper; /** * A Spring pre-authentication filter that works with Http headers. */ public class HttpHeaderAuthenticationFilter extends GenericFilterBean { private static final Logger LOGGER = LoggerFactory.getLogger(HttpHeaderAuthenticationFilter.class); @Autowired private SecurityHelper securityHelper; /** * An authentication trust resolver. */ private AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl(); /** * The authentication details source. The default is a WebAuthenticationDetailsSource. */ private AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> authenticationDetailsSource = new WebAuthenticationDetailsSource(); /** * The authentication manager to authenticate with. */ private AuthenticationManager authenticationManager = null; /** * An application user builder that knows how to build an application user. */ private ApplicationUserBuilder applicationUserBuilder; public HttpHeaderAuthenticationFilter(AuthenticationManager authenticationManager, ApplicationUserBuilder applicationUserBuilder) { this.authenticationManager = authenticationManager; this.applicationUserBuilder = applicationUserBuilder; } /** * Perform pre-authentication processing. This method delegates to doHttpFilter. * * @param servletRequest the servlet request. * @param servletResponse the servlet response. * @param filterChain the filter chain. * * @throws IOException if an IO exception was encountered. * @throws ServletException if a servlet exception was encountered. */ @Override @SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST", justification = "The ServletRequest is cast to an HttpServletRequest which is always the case since all requests use the HTTP protocol.") public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { doHttpFilter((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse, filterChain); } /** * Perform pre-authentication processing for Http Servlets. * * @param servletRequest the servlet request. * @param servletResponse the servlet response. * @param filterChain the filter chain. * * @throws IOException when an exception is thrown executing the next filter in chain. * @throws ServletException if a servlet exception was encountered. */ public void doHttpFilter(HttpServletRequest servletRequest, HttpServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { if (securityHelper.isSecurityEnabled(servletRequest)) { // Build an application user from the current HTTP headers. ApplicationUser applicationUserNoRoles; try { applicationUserNoRoles = applicationUserBuilder.buildNoRoles(servletRequest); } catch (Exception ex) { applicationUserNoRoles = null; } if (applicationUserNoRoles == null) { // We were unable to find/build an application user (i.e. the user isn't logged on) so invalidate the current user if one exists. processUserNotLoggedIn(servletRequest); } else { LOGGER.debug("Current user Id: " + applicationUserNoRoles.getUserId() + ", Session Init Time: " + applicationUserNoRoles.getSessionInitTime()); LOGGER.debug("User is logged in."); invalidateUser(servletRequest, false); // If the user is logged in, but no user information is in the security context holder, then perform the authentication // (which will automatically load the user information for us). This flow can be caused when a new user logs for the first time or // when a different user just logged in. authenticateUser(servletRequest); } } // Continue on to the next filter in the chain. filterChain.doFilter(servletRequest, servletResponse); } /** * Creates the user based on the given request, and puts the user into the security context. Throws if authentication fails. * * @param servletRequest {@link HttpServletRequest} containing the user's request. */ private void authenticateUser(HttpServletRequest servletRequest) { try { // Setup the authentication request and perform the authentication. Perform the authentication based on the fully built user. PreAuthenticatedAuthenticationToken preAuthenticatedAuthenticationToken = new PreAuthenticatedAuthenticationToken(applicationUserBuilder.build(servletRequest), "N/A"); preAuthenticatedAuthenticationToken.setDetails(authenticationDetailsSource.buildDetails(servletRequest)); Authentication authentication = authenticationManager.authenticate(preAuthenticatedAuthenticationToken); // The authentication returned so it was successful. successfulAuthentication(authentication); } catch (AuthenticationException e) { // An authentication exception was thrown so authentication failed. unsuccessfulAuthentication(servletRequest, e); // Throw an exception so we don't continue since there is some problem (e.g. user profile doesn't // exist for the logged in user or it couldn't be retrieved). throw e; } } /** * Perform processing when the user is not logged in. * * @param servletRequest the servlet request. */ protected void processUserNotLoggedIn(HttpServletRequest servletRequest) { LOGGER.debug("No user is currently logged in."); // No user is currently logged in. Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if ((authentication != null) && (!authenticationTrustResolver.isAnonymous(authentication))) { // A previous user was logged in so invalidate the session and null out the user. LOGGER.debug("A previous user with userIdentity " + getExistingUserId() + " was logged in so invalidating the user."); invalidateUser(servletRequest, true); } } /** * Invalidates a user by invalidating their session and removing them from the security context holder. * * @param servletRequest the servlet request. * @param invalidateSession flag to indicate whether the Http Session should be invalidated or not. */ protected void invalidateUser(HttpServletRequest servletRequest, boolean invalidateSession) { if (invalidateSession) { HttpSession session = servletRequest.getSession(false); if (session != null) { LOGGER.debug("Invalidating the session."); session.invalidate(); } } LOGGER.debug("Clearing the security context."); SecurityContextHolder.clearContext(); } /** * Gets the existing user Id. * * @return the existing user Id, session Id, or null if no existing user is present. */ protected String getExistingUserId() { String existingUserId = null; ApplicationUser applicationUser = getExistingUser(); if (applicationUser != null) { existingUserId = applicationUser.getUserId(); } return existingUserId; } /** * Gets the existing session init time. * * @return the existing session init time or null if no existing user is present. */ protected Date getExistingSessionInitTime() { Date existingSessionInitTime = null; ApplicationUser applicationUser = getExistingUser(); if (applicationUser != null) { existingSessionInitTime = applicationUser.getSessionInitTime(); } return existingSessionInitTime; } /** * Gets the existing user. * * @return the existing user or null if no existing user is present. */ protected ApplicationUser getExistingUser() { ApplicationUser applicationUser = null; Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication != null) { SecurityUserWrapper securityUserWrapper = (SecurityUserWrapper) authentication.getPrincipal(); if (securityUserWrapper != null) { applicationUser = securityUserWrapper.getApplicationUser(); LOGGER.trace("Existing Application User: " + applicationUser); return applicationUser; } } return applicationUser; } /** * Puts the <code>Authentication</code> instance returned by the authentication manager into the secure context. * * @param authResult the authorization result. */ protected void successfulAuthentication(Authentication authResult) { // Save the authentication information in the security context holder. LOGGER.debug("Authentication success: " + authResult); SecurityContextHolder.getContext().setAuthentication(authResult); } /** * Ensures the authentication object in the secure context is set to null when authentication fails. * * @param servletRequest the servlet request. * @param authenticationException the authentication exception. */ protected void unsuccessfulAuthentication(HttpServletRequest servletRequest, AuthenticationException authenticationException) { LOGGER.debug("Authentication failure: ", authenticationException); invalidateUser(servletRequest, false); servletRequest.getSession().setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, authenticationException); } }