All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.granite.spring.security.SpringSecurity3Service Maven / Gradle / Ivy

The newest version!
/**
 *   GRANITE DATA SERVICES
 *   Copyright (C) 2006-2014 GRANITE DATA SERVICES S.A.S.
 *
 *   This file is part of the Granite Data Services Platform.
 *
 *   Granite Data Services 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 2.1 of the License, or (at your option) any later version.
 *
 *   Granite Data Services 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 library; if not, write to the Free Software
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
 *   USA, or see .
 */
package org.granite.spring.security;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.Principal;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.granite.context.GraniteContext;
import org.granite.logging.Logger;
import org.granite.messaging.service.security.AbstractSecurityContext;
import org.granite.messaging.service.security.AbstractSecurityService;
import org.granite.messaging.service.security.SecurityServiceException;
import org.granite.messaging.webapp.HttpGraniteContext;
import org.granite.messaging.webapp.ServletGraniteContext;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.encoding.PasswordEncoder;
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.session.SessionAuthenticationException;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy;
import org.springframework.security.web.context.HttpRequestResponseHolder;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.web.context.support.WebApplicationContextUtils;


/**
 * @author Bouiaw
 * @author wdrai
 */
@SuppressWarnings("deprecation")
public class SpringSecurity3Service extends AbstractSecurityService implements ApplicationContextAware, ApplicationEventPublisherAware {
        
	private static final Logger log = Logger.getLogger(SpringSecurity3Service.class);
	
    private static final String FILTER_APPLIED = "__spring_security_scpf_applied";
    private static final String SECURITY_SERVICE_APPLIED = "__spring_security_granite_service_applied";
	
    private ApplicationContext applicationContext = null;
    private ApplicationEventPublisher eventPublisher = null;
    private AuthenticationExtension authenticationExtension = new DefaultAuthenticationExtension();
	private AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl();
	private SecurityContextRepository securityContextRepository = new HttpSessionSecurityContextRepository();
	private AbstractSpringSecurity3Interceptor securityInterceptor = null;
	private SessionAuthenticationStrategy sessionAuthenticationStrategy = new SessionFixationProtectionStrategy();
	private PasswordEncoder passwordEncoder = null;
	private boolean allowAnonymousAccess = false;
	private Method getRequest = null;
	private Method getResponse = null;
    
	
	public SpringSecurity3Service() {
		log.debug("Starting Spring 3 Security Service");
		try {
	    	getRequest = HttpRequestResponseHolder.class.getDeclaredMethod("getRequest");
	    	getRequest.setAccessible(true);
	    	getResponse = HttpRequestResponseHolder.class.getDeclaredMethod("getResponse");
	    	getResponse.setAccessible(true);
		}
		catch (Exception e) {
			throw new RuntimeException("Could not get methods from HttpRequestResponseHolder", e);
		}
    }
	
	public void setApplicationContext(ApplicationContext applicationContext) {
		this.applicationContext = applicationContext;
	}

    public void setAuthenticationExtension(AuthenticationExtension authenticationExtension) {
        if (authenticationExtension == null)
            throw new NullPointerException("AuthenticationBuilder cannot be null");
        this.authenticationExtension = authenticationExtension;
    }
	
	public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) {
		this.eventPublisher = eventPublisher;
	}
	
	public void setAuthenticationManager(AuthenticationManager authenticationManager) {
        authenticationExtension.setAuthenticationManager(authenticationManager);
	}
	
	public void setAuthenticationTrustResolver(AuthenticationTrustResolver authenticationTrustResolver) {
		this.authenticationTrustResolver = authenticationTrustResolver;
	}
	
	public void setAllowAnonymousAccess(boolean allowAnonymousAccess) {
		this.allowAnonymousAccess = allowAnonymousAccess;
	}
	
	public void setSecurityContextRepository(SecurityContextRepository securityContextRepository) {
		this.securityContextRepository = securityContextRepository;
	}
	
	public void setSecurityInterceptor(AbstractSpringSecurity3Interceptor securityInterceptor) {
		this.securityInterceptor = securityInterceptor;
	}
	
	public void setSessionAuthenticationStrategy(SessionAuthenticationStrategy sessionAuthenticationStrategy) {
		if (sessionAuthenticationStrategy == null)
			throw new NullPointerException("SessionAuthenticationStrategy cannot be null");
		this.sessionAuthenticationStrategy = sessionAuthenticationStrategy;
	}
	
	public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
		this.passwordEncoder = passwordEncoder;
	}

    public void configure(Map params) {
        log.debug("Configuring with parameters %s: ", params);

        if (params.containsKey("authentication-manager-bean-name"))
        	authenticationExtension.setAuthenticationManagerBeanName(params.get("authentication-manager-bean-name"));

        if (Boolean.TRUE.toString().equals(params.get("allow-anonymous-access")))
        	allowAnonymousAccess = true;
    }
    
    public Principal login(Object credentials, String charset) {
        List decodedCredentials = Arrays.asList(decodeBase64Credentials(credentials, charset));
        
        if (!(GraniteContext.getCurrentInstance() instanceof HttpGraniteContext)) {
        	log.debug("Login from non HTTP granite context ignored");
        	return null;
        }

        HttpGraniteContext graniteContext = (HttpGraniteContext)GraniteContext.getCurrentInstance();
        HttpServletRequest httpRequest = graniteContext.getRequest();
        boolean springFilterNotApplied = graniteContext.getRequest().getAttribute(FILTER_APPLIED) == null;

        ApplicationContext appContext = applicationContext != null ? applicationContext
                : WebApplicationContextUtils.getWebApplicationContext(graniteContext.getServletContext());
        if (appContext == null)
            throw new IllegalStateException("No application context defined for Spring security service");
        authenticationExtension.setApplicationContext(appContext);
        
        String user = decodedCredentials.get(0);
        String password = decodedCredentials.get(1);
        if (passwordEncoder != null)
        	password = passwordEncoder.encodePassword(password, null);
        
        Authentication auth = authenticationExtension.buildAuthentication(user, password);
        Principal principal = null;
        
        AuthenticationManager authenticationManager = authenticationExtension.selectAuthenticationManager(auth);
        
        try {
            Authentication authentication = authenticationManager.authenticate(auth);

            if (authentication != null && !authenticationTrustResolver.isAnonymous(authentication)) {
                try {
                    sessionAuthenticationStrategy.onAuthentication(authentication, httpRequest, graniteContext.getResponse());
                }
                catch (SessionAuthenticationException e) {
                    log.debug(e, "SessionAuthenticationStrategy rejected the authentication object");
                    SecurityContextHolder.clearContext();
                    handleAuthenticationExceptions(e);
                    return null;
                }
            }
            
            log.debug("Define authentication and save to repo: %s", authentication != null ? authentication.getName() : "none");
            HttpRequestResponseHolder holder = new HttpRequestResponseHolder(graniteContext.getRequest(), graniteContext.getResponse());
            SecurityContext securityContext = securityContextRepository.loadContext(holder);
            securityContext.setAuthentication(authentication);
            SecurityContextHolder.setContext(securityContext);
            principal = authentication;
            graniteContext.setPrincipal(principal);
            
            try {
                securityContextRepository.saveContext(securityContext, (HttpServletRequest)getRequest.invoke(holder), (HttpServletResponse)getResponse.invoke(holder));
            }
            catch (Exception e) {
                log.error(e, "Could not save context after authentication");
            }

            if (eventPublisher != null)
                eventPublisher.publishEvent(new AuthenticationSuccessEvent(authentication));

            endLogin(credentials, charset);
        }
        catch (AuthenticationException e) {
            handleAuthenticationExceptions(e);
        }
        finally {
            // Should not cleanup when authentication managed by the Spring filter
            if (springFilterNotApplied) {
                log.debug("Clear authentication after login");
                SecurityContextHolder.clearContext();
            }
        }

        log.debug("User %s logged in", user);

        return principal;
    }
    
    protected void handleAuthenticationExceptions(AuthenticationException e) {
    	if (e instanceof BadCredentialsException || e instanceof UsernameNotFoundException)
            throw SecurityServiceException.newInvalidCredentialsException(e.getMessage());
        
    	throw SecurityServiceException.newAuthenticationFailedException(e.getMessage());
    }

    
    public Object authorize(AbstractSecurityContext context) throws Exception {
        log.debug("Authorize %s on destination %s (secured: %b)", context, context.getDestination().getId(), context.getDestination().isSecured());
        
        startAuthorization(context);
        
        ServletGraniteContext graniteContext = (ServletGraniteContext)GraniteContext.getCurrentInstance();
        
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        HttpRequestResponseHolder holder = null;

    	boolean springFilterNotApplied = graniteContext.getRequest().getAttribute(FILTER_APPLIED) == null;
        boolean reentrant = graniteContext.getRequest().getAttribute(SECURITY_SERVICE_APPLIED) != null;
        // Manage security context here only if the Spring security filter has not already been applied and if we are not reentrant
        try {
	        if (springFilterNotApplied && !reentrant) {
	        	holder = new HttpRequestResponseHolder(graniteContext.getRequest(), graniteContext.getResponse());
                SecurityContext contextBeforeChainExecution = securityContextRepository.loadContext(holder);
                SecurityContextHolder.setContext(contextBeforeChainExecution);
                graniteContext.getRequest().setAttribute(SECURITY_SERVICE_APPLIED, true);
                
                if (isAuthenticated(authentication)) {
                    log.debug("Thread was already authenticated: %s", authentication.getName());
                    contextBeforeChainExecution.setAuthentication(authentication);
                }
                else {
                    authentication = contextBeforeChainExecution.getAuthentication();
                    log.debug("Restore authentication from repository: %s", authentication != null ? authentication.getName() : "none");
                }
                
                graniteContext.setPrincipal(authentication);
            }
	        
	        if (context.getDestination().isSecured()) {
	            if (!isAuthenticated(authentication) || (!allowAnonymousAccess && authentication instanceof AnonymousAuthenticationToken)) {
	                log.debug("User not authenticated!");
	                throw SecurityServiceException.newNotLoggedInException("User not logged in");
	            }
	            if (!userCanAccessService(context, authentication)) { 
	                log.debug("Access denied for user %s", authentication != null ? authentication.getName() : "not authenticated");
	                throw SecurityServiceException.newAccessDeniedException("User not in required role");
	            }
	        }
	        
        	Object returnedObject = securityInterceptor != null 
        		? securityInterceptor.invoke(context)
        		: endAuthorization(context);
            
            return returnedObject;
        }
        catch (SecurityServiceException e) {
        	throw e;
        }
        catch (AccessDeniedException e) {
        	throw SecurityServiceException.newAccessDeniedException(e.getMessage());
        }
        catch (InvocationTargetException e) {
            handleAuthorizationExceptions(e);
            throw e;
        }
        finally {
            if (springFilterNotApplied && !reentrant) {
	            SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
        		log.debug("Clear authentication and save to repo: %s", contextAfterChainExecution.getAuthentication() != null ? contextAfterChainExecution.getAuthentication().getName() : "none");
	            SecurityContextHolder.clearContext();
	            try {
	            	securityContextRepository.saveContext(contextAfterChainExecution, (HttpServletRequest)getRequest.invoke(holder), (HttpServletResponse)getResponse.invoke(holder));
	            }
	            catch (Exception e) {
	            	log.error(e, "Could not extract wrapped context from holder");
	            }
	            graniteContext.getRequest().removeAttribute(SECURITY_SERVICE_APPLIED);
            }
        }
    }
    
    @Override
    public boolean acceptsContext() {
    	return GraniteContext.getCurrentInstance() instanceof ServletGraniteContext;
    }

    public void logout() {
    	ServletGraniteContext graniteContext = (ServletGraniteContext)GraniteContext.getCurrentInstance();
    	boolean springFilterNotApplied = graniteContext.getRequest().getAttribute(FILTER_APPLIED) == null;
    	HttpSession session = graniteContext.getSession(false);
    	
    	try {
	    	if (session != null && securityContextRepository.containsContext(graniteContext.getRequest())) {
	            authenticationExtension.endSession(session);
	            session.invalidate();
	        }
    	}
    	catch (IllegalStateException e) {
    		// Session already invalid
    	}
        
    	if (springFilterNotApplied)
    		SecurityContextHolder.clearContext();
    }

    protected boolean isUserInRole(Authentication authentication, String role) {
        for (GrantedAuthority ga : authentication.getAuthorities()) {
            if (ga.getAuthority().matches(role))
                return true;
        }
        return false;
    }

    protected boolean isAuthenticated(Authentication authentication) {
        return authentication != null && authentication.isAuthenticated();
    }

    protected boolean userCanAccessService(AbstractSecurityContext context, Authentication authentication) {
        log.debug("Is authenticated as: %s", authentication.getName());

        for (String role : context.getDestination().getRoles()) {
            if (isUserInRole(authentication, role)) {
                log.debug("Allowed access to %s in role %s", authentication.getName(), role);
                return true;
            }
            log.debug("Access denied for %s not in role %s", authentication.getName(), role);
        }
        return false;
    }

    protected void handleAuthorizationExceptions(InvocationTargetException e) {
        for (Throwable t = e; t != null; t = t.getCause()) {
            // Don't create a dependency to javax.ejb in SecurityService...
            if (t instanceof SecurityException ||
                t instanceof AccessDeniedException ||
                "javax.ejb.EJBAccessException".equals(t.getClass().getName())) {
                throw SecurityServiceException.newAccessDeniedException(t.getMessage());
            }
            else if (t instanceof AuthenticationException) {
            	throw SecurityServiceException.newNotLoggedInException(t.getMessage());
            }
        }
    }


    public static class DefaultAuthenticationExtension implements AuthenticationExtension {

        private ApplicationContext applicationContext = null;
        private AuthenticationManager authenticationManager = null;
        private String authenticationManagerBeanName = null;

        public void setApplicationContext(ApplicationContext applicationContext) {
            this.applicationContext = applicationContext;
        }

        public void setAuthenticationManager(AuthenticationManager authenticationManager) {
            this.authenticationManager = authenticationManager;
        }

        public void setAuthenticationManagerBeanName(String authenticationManagerBeanName) {
            this.authenticationManagerBeanName = authenticationManagerBeanName;
        }

        @Override
        public Authentication buildAuthentication(String user, String password) {
            return new UsernamePasswordAuthenticationToken(user, password);
        }

        @Override
        public AuthenticationManager selectAuthenticationManager(Authentication authentication) {
            if (this.authenticationManager != null)
                return this.authenticationManager;

            Map authManagers = BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, AuthenticationManager.class);

            if (authenticationManagerBeanName != null) {
                AuthenticationManager authenticationManager = authManagers.get(authenticationManagerBeanName);
                if (authenticationManager == null) {
                    log.error("AuthenticationManager bean not found " + authenticationManagerBeanName);
                    throw SecurityServiceException.newAuthenticationFailedException("Authentication failed");
                }
                return authenticationManager;
            }
            else if (authManagers.size() > 1) {
                log.error("More than one AuthenticationManager beans found, specify which one to use in Spring config  or in granite-config.xml ");
                throw SecurityServiceException.newAuthenticationFailedException("Authentication failed");
            }

            return authManagers.values().iterator().next();
        }

        @Override
        public void endSession(HttpSession session) {
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy