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 }