/** * Copyright (C) 2015 Valkyrie RCP * * 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.valkyriercp.security.support; import com.google.common.collect.Lists; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.ProviderManager; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.valkyriercp.application.config.ApplicationConfig; import org.valkyriercp.security.*; import org.valkyriercp.util.ValkyrieRepository; import java.util.Collection; import java.util.Map; /** * Default implementation of ApplicationSecurityManager. It provides basic * processing for login and logout actions and the event lifecycle. * <p> * Instances of this class should be configured with an instance of * {@link AuthenticationManager} to be used to handle authentication (login) * requests. This would be done like this: * * <pre> * <bean id="securityManager" * class="org.springframework.richclient.security.support.DefaultApplicationSecurityManager"> * <property name="authenticationManager" ref="authenticationManager"/> * </bean> * * <bean id="authenticationManager" * class="org.springframework.security.providers.ProviderManager"> * <property name="providers"> * <list> * <ref bean="remoteAuthenticationProvider" /> * </list> * </property> * </bean> * * <bean id="remoteAuthenticationProvider" * class="org.springframework.security.providers.rcp.RemoteAuthenticationProvider"> * <property name="remoteAuthenticationManager" ref="remoteAuthenticationManager" /> * </bean> * * <bean id="remoteAuthenticationManager" * class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean"> * <property name="serviceUrl"> * <value>http://localhost:8080/myserver/rootContext/RemoteAuthenticationManager</value> * </property> * <property name="serviceInterface"> * <value>org.springframework.security.providers.rcp.RemoteAuthenticationManager</value> * </property> * </bean> * </pre> * * If this is not done, then an attempt will be made to "auto-configure" by * locating an appropriate authentication manager in the application context. In * order, a search will be made for a bean that implements one of these classes: * <ol> * <li>ProviderManager</li> * <li>AuthenticationProvider</li> * <li>AuthenticationManager</li> * </ol> * The first instance to be located will be used to handle authentication * requests. * <p> * * @author Larry Streepy * */ public class DefaultApplicationSecurityManager implements ApplicationSecurityManager { private final Log logger = LogFactory.getLog(getClass()); private Authentication currentAuthentication = null; private AuthenticationManager authenticationManager; /** * Default constructor. */ public DefaultApplicationSecurityManager() { this(false); } /** * Constructor invoked when we are created as the default implementation by * ApplicationServices. Since this bean won't be defined in the context * under these circumstances, we need to perform some auto-configuration of * our own. * <p> * Auto-configuration consists of trying to locate an AuthenticationManager * (in one of several classes) in the application context. This * auto-configuration is also attempted after the bean is constructed by the * context if the authenticationManager property has not been set. See * {@see #afterPropertiesSet()}. * * @param autoConfigure * pass true to perform auto-configuration * @throws IllegalArgumentException * If the auto-configuration fails * @see #afterPropertiesSet() */ public DefaultApplicationSecurityManager(boolean autoConfigure) { if (autoConfigure) { afterPropertiesSet(); } } /** * Set the authentication manager to use. * * @param authenticationManager * instance to use for authentication requests */ @Override public void setAuthenticationManager( AuthenticationManager authenticationManager) { this.authenticationManager = authenticationManager; } /** * Get the authentication manager in use. * * @return authenticationManager instance used for authentication requests */ @Override public AuthenticationManager getAuthenticationManager() { if(ValkyrieRepository.getInstance().getApplicationConfig().applicationContext().getBeansOfType(AuthenticationManager.class).size() != 0) return ValkyrieRepository.getInstance().getBean(AuthenticationManager.class); else { return null; } } @Override public boolean isSecuritySupported() { return getAuthenticationManager() != null; } /** * Process a login attempt and fire all related events. If the * authentication fails, then a {@link AuthenticationFailedEvent} is * published and the exception is rethrown. If the authentication succeeds, * then an {@link AuthenticationEvent} is published, followed by a * {@link LoginEvent}. * * @param authentication * token to use for the login attempt * @return Authentication token resulting from a successful call to * {@link AuthenticationManager#authenticate(org.springframework.security.core.Authentication)} * . * @see ApplicationSecurityManager#doLogin(org.springframework.security.core.Authentication) * @throws AuthenticationException * If the authentication attempt fails */ @Override public Authentication doLogin(Authentication authentication) { Authentication result = null; try { result = getAuthenticationManager().authenticate(authentication); } catch (AuthenticationException e) { logger.info("authentication failed: " + e.getMessage()); // Fire application event to advise of failed login getApplicationConfig().applicationContext().publishEvent(new AuthenticationFailedEvent( authentication, e)); // rethrow the exception throw e; } // Handle success or failure of the authentication attempt if (logger.isDebugEnabled()) { logger.debug("successful login - update context holder and fire event"); } // Commit the successful Authentication object to the secure // ContextHolder SecurityContextHolder.getContext().setAuthentication(result); setAuthentication(result); // Fire application events to advise of new login getApplicationConfig().applicationContext().publishEvent(new AuthenticationEvent(result)); getApplicationConfig().applicationContext().publishEvent(new LoginEvent(result)); return result; } /** * Return if a user is currently logged in, meaning that a previous call to * doLogin resulted in a valid authentication request. * * @return true if a user is logged in */ @Override public boolean isUserLoggedIn() { return getAuthentication() != null; } /** * Get the authentication token for the currently logged in user. * * @return authentication token, null if not logged in */ @Override public Authentication getAuthentication() { return currentAuthentication; } /** * Set the authenticaiton token. * * @param authentication * token to install as current. */ protected void setAuthentication(Authentication authentication) { currentAuthentication = authentication; } /** * Determine if the currently authenticated user has the role provided. Note * that role comparisons are case sensitive. * * @param role * to check * @return true if the user has the role requested */ @Override public boolean isUserInRole(String role) { boolean inRole = false; Authentication authentication = getAuthentication(); if (authentication != null) { Collection<? extends GrantedAuthority> authorities = authentication .getAuthorities(); for (GrantedAuthority authority : authorities) { if (role.equals(authority.getAuthority())) { inRole = true; break; } } } return inRole; } /** * Perform a logout. Set the current authentication token to null (in both * the per-thread security context and the global context), then publish an * {@link AuthenticationEvent} followed by a {@link LogoutEvent}. * * @return Authentication token that was in place prior to the logout. * @see ApplicationSecurityManager#doLogout() */ @Override public Authentication doLogout() { Authentication existing = getAuthentication(); // Make the Authentication object null if a SecureContext exists SecurityContextHolder.getContext().setAuthentication(null); setAuthentication(null); getApplicationConfig().applicationContext().publishEvent(new AuthenticationEvent(null)); getApplicationConfig().applicationContext().publishEvent(new LogoutEvent(existing)); return existing; } /** * Ensure that we have an authentication manager to work with. If one has * not been specifically wired in, then look for beans to "auto-wire" in. * Look for a bean of one of the following types (in order): * {@link ProviderManager}, {@link AuthenticationProvider}, and * {@link AuthenticationManager}. * * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() */ public void afterPropertiesSet() { // Ensure that we have our authentication manager if (getAuthenticationManager() == null) { if (logger.isDebugEnabled()) { logger.debug("No AuthenticationManager defined, look for one"); } // Try the class types in sequence Class[] types = new Class[] { ProviderManager.class, AuthenticationProvider.class, AuthenticationManager.class }; for (int i = 0; i < types.length; i++) { if (tryToWire(types[i])) { break; } } } } /** * Try to locate and "wire in" a suitable authentication manager. * * @param type * The type of bean to look for * @return true if we found and wired a suitable bean */ protected <T> boolean tryToWire(Class<T> type) { boolean success = false; String className = type.getName(); Map<String, T> map = getApplicationConfig().applicationContext().getBeansOfType(type); if (logger.isDebugEnabled()) { logger.debug("Search for '" + className + "' found: " + map); } if (map.size() == 1) { // Got one - wire it in Map.Entry entry = map.entrySet().iterator().next(); String name = (String) entry.getKey(); AuthenticationManager am = (AuthenticationManager) entry.getValue(); setAuthenticationManager(am); success = true; if (logger.isInfoEnabled()) { logger.info("Auto-configuration using '" + name + "' as authenticationManager"); } } else if (map.size() > 1) { if (logger.isInfoEnabled()) { logger.info("Need a single '" + className + "', found: " + map.keySet()); } } else { // Size 0, no potentials if (logger.isInfoEnabled()) { logger.info("Auto-configuration did not find a suitable authenticationManager of type " + type); } } return success; } protected ApplicationConfig getApplicationConfig() { return ValkyrieRepository.getInstance().getApplicationConfig(); } }