001 /* 002 * Copyright (c) 2002-2005 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 005 * use this file except in compliance with the License. You may obtain a copy of 006 * the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 012 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 013 * License for the specific language governing permissions and limitations under 014 * the License. 015 */ 016 package org.springframework.richclient.security; 017 018 import java.lang.ref.WeakReference; 019 import java.util.ArrayList; 020 import java.util.Collections; 021 import java.util.Iterator; 022 import java.util.List; 023 import java.util.Map; 024 025 import org.apache.commons.logging.Log; 026 import org.apache.commons.logging.LogFactory; 027 import org.springframework.beans.BeansException; 028 import org.springframework.beans.factory.config.BeanPostProcessor; 029 import org.springframework.context.ApplicationContext; 030 import org.springframework.context.ApplicationContextAware; 031 import org.springframework.context.ApplicationEvent; 032 import org.springframework.context.ApplicationListener; 033 import org.springframework.security.Authentication; 034 035 /** 036 * This class performs two main functions: 037 * <ol> 038 * <li>It is a bean post-processor that will set the current authentication token on any 039 * newly created beans that implement {@link AuthenticationAware}.</li> 040 * <li>It listens for application {@link ClientSecurityEvent}s and updates all the beans 041 * in the context that implement either {@link AuthenticationAware} or {@link LoginAware} 042 * according to the event received.</li> 043 * </ol> 044 * In order for all this to take place, a singleton, non-lazy instance of this class must 045 * be defined in the Spring ApplicationContext. This would be done like this: 046 * 047 * <pre> 048 * <bean id="securityAwareConfigurer" 049 * class="org.springframework.richclient.security.SecurityAwareConfigurer" 050 * lazy-init="false"/> 051 * </pre> 052 * 053 * @author Larry Streepy 054 * @author Andy Depue 055 * @author Inspiration from Ben Alex 056 * 057 * @see org.springframework.richclient.security.AuthenticationAware 058 * @see org.springframework.richclient.security.LoginAware 059 * @see org.springframework.richclient.security.ClientSecurityEvent 060 * 061 */ 062 public class SecurityAwareConfigurer implements ApplicationListener, ApplicationContextAware, BeanPostProcessor { 063 064 private final Log logger = LogFactory.getLog( getClass() ); 065 066 private ApplicationContext applicationContext; 067 068 private final List nonSingletonListeners = Collections.synchronizedList( new ArrayList() ); 069 070 private Authentication currentAuthentication = null; // Until we know it 071 072 /** 073 * Get the installed application context. 074 * @return context 075 */ 076 public ApplicationContext getApplicationContext() { 077 return applicationContext; 078 } 079 080 /** 081 * Broadcast an authentication event to all the AuthenticationAware beans. 082 * @param authentication token 083 */ 084 protected void broadcastAuthentication(Authentication authentication) { 085 if( logger.isDebugEnabled() ) 086 logger.debug( "BROADCAST authentication: token=" + authentication ); 087 088 // Save this for any new beans that we post-process 089 currentAuthentication = authentication; 090 091 final Iterator iter = getBeansToUpdate( AuthenticationAware.class ).iterator(); 092 while( iter.hasNext() ) { 093 ((AuthenticationAware) iter.next()).setAuthenticationToken( authentication ); 094 } 095 } 096 097 /** 098 * Broadcast a Login event to all the LoginAware beans. 099 * @param authentication token 100 */ 101 protected void broadcastLogin(Authentication authentication) { 102 if( logger.isDebugEnabled() ) 103 logger.debug( "BROADCAST login: token=" + authentication ); 104 105 final Iterator iter = getBeansToUpdate( LoginAware.class ).iterator(); 106 while( iter.hasNext() ) { 107 ((LoginAware) iter.next()).userLogin( authentication ); 108 } 109 } 110 111 /** 112 * Broadcast a Logout event to all the LoginAware beans. 113 * @param authentication token 114 */ 115 protected void broadcastLogout(Authentication authentication) { 116 if( logger.isDebugEnabled() ) 117 logger.debug( "BROADCAST logout: token=" + authentication ); 118 119 final Iterator iter = getBeansToUpdate( LoginAware.class ).iterator(); 120 while( iter.hasNext() ) { 121 ((LoginAware) iter.next()).userLogout( authentication ); 122 } 123 } 124 125 /** 126 * Construct the list of all the beans we need to update. 127 * @param beanType Type of bean to locate 128 * @return List of all beans to udpate. 129 */ 130 protected List getBeansToUpdate(Class beanType) { 131 final ApplicationContext ac = getApplicationContext(); 132 final List listeners = new ArrayList(); 133 134 if( ac != null ) { 135 if( logger.isDebugEnabled() ) 136 logger.debug( "Constructing list of beans to notify; bean type=" + beanType.getName() ); 137 138 final Map map = ac.getBeansOfType( beanType, false, true ); 139 140 if( logger.isDebugEnabled() ) 141 logger.debug( "bean map: " + map ); 142 143 listeners.addAll( map.values() ); 144 listeners.addAll( getNonSingletonListeners( beanType ) ); 145 } 146 147 if( logger.isDebugEnabled() ) 148 logger.debug( "List of beans to notify:" + listeners ); 149 150 return listeners; 151 } 152 153 /** 154 * Get the list of non-singleton beans we have registered that still exist. Update our 155 * registration list if any have been GC'ed. Only return beans of the requested type. 156 * @param beanType Type of bean to locate 157 * @return list of extant non-singleton beans to notify 158 */ 159 protected List getNonSingletonListeners(Class beanType) { 160 161 final List listeners = new ArrayList(); 162 163 synchronized(nonSingletonListeners) { 164 for( Iterator iter = nonSingletonListeners.iterator(); iter.hasNext(); ) { 165 final Object bean = ((WeakReference) iter.next()).get(); 166 if( bean == null ) { 167 if( logger.isDebugEnabled() ) 168 logger.debug( "REMOVED garbage collected AuthorizationAware non-singleton from list." ); 169 iter.remove(); 170 } else if( beanType.isAssignableFrom( bean.getClass() ) ) { 171 listeners.add( bean ); 172 } 173 } 174 } 175 return listeners; 176 } 177 178 /** 179 * Add a non-singleton bean instance to our list for later notification. 180 * @param bean 181 */ 182 protected void addToNonSingletonListeners(final Object bean) { 183 if( logger.isDebugEnabled() ) 184 logger.debug( "Adding Authentication/LoginAware bean to list of non-singleton listeners: bean='" + bean ); 185 186 nonSingletonListeners.add( new WeakReference( bean ) ); 187 } 188 189 // 190 // AplicationListener implementation 191 // 192 193 /* 194 * (non-Javadoc) 195 * @see org.springframework.context.ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent) 196 */ 197 public void onApplicationEvent(ApplicationEvent event) { 198 199 // All events we care about are subtypes of ClientSecurityEvent 200 if( event instanceof ClientSecurityEvent ) { 201 Authentication authentication = (Authentication) event.getSource(); 202 203 if( logger.isDebugEnabled() ) { 204 logger.debug( "RECEIVED ClientSecurityEvent: " + event ); 205 logger.debug( "Authentication token: " + authentication ); 206 } 207 208 // Note that we need to inspect the new authentication token and see if it is 209 // NO_AUTHENTICATION. If so, then we need to use null instead. This little 210 // dance is required because the source of an event can't actually be null. 211 212 if( authentication == ClientSecurityEvent.NO_AUTHENTICATION ) { 213 if( logger.isDebugEnabled() ) { 214 logger.debug( "Converted NO_AUTHENTICATION to null" ); 215 } 216 authentication = null; 217 } 218 219 // And dispatch according to the event type. 220 221 if( event instanceof AuthenticationEvent ) { 222 broadcastAuthentication( authentication ); 223 } else if( event instanceof LoginEvent ) { 224 broadcastLogin( authentication ); 225 } else if( event instanceof LogoutEvent ) { 226 broadcastLogout( authentication ); 227 } else { 228 if( logger.isDebugEnabled() ) { 229 logger.debug( "Unsupported event not processed" ); 230 } 231 } 232 } 233 } 234 235 // 236 // AplicationContextAware implementation 237 // 238 239 /* 240 * (non-Javadoc) 241 * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext) 242 */ 243 public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 244 this.applicationContext = applicationContext; 245 } 246 247 // 248 // BeanPostProcessor implementation 249 // 250 251 /* 252 * (non-Javadoc) 253 * @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessBeforeInitialization(java.lang.Object, 254 * java.lang.String) 255 */ 256 public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { 257 if( bean instanceof AuthenticationAware || bean instanceof LoginAware ) { 258 259 // If the bean isn't a singleton, then add it to our list 260 if( beanName == null || !getApplicationContext().containsBean( beanName ) 261 || !getApplicationContext().isSingleton( beanName ) ) { 262 addToNonSingletonListeners( bean ); 263 } 264 265 // Install the last known authentication token 266 if( bean instanceof AuthenticationAware ) { 267 if( logger.isDebugEnabled() ) 268 logger.debug( "NOTIFY bean '" + bean + "' of new authorization for '" + currentAuthentication 269 + "'" ); 270 271 AuthenticationAware aab = (AuthenticationAware) bean; 272 aab.setAuthenticationToken(currentAuthentication); 273 } 274 } 275 return bean; 276 } 277 278 /* 279 * (non-Javadoc) 280 * @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization(java.lang.Object, 281 * java.lang.String) 282 */ 283 public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { 284 return bean; 285 } 286 287 }