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.support;
017
018 import java.util.Map;
019
020 import org.apache.commons.logging.Log;
021 import org.apache.commons.logging.LogFactory;
022 import org.springframework.beans.factory.InitializingBean;
023 import org.springframework.context.ApplicationContext;
024 import org.springframework.richclient.application.Application;
025 import org.springframework.richclient.security.ApplicationSecurityManager;
026 import org.springframework.richclient.security.AuthenticationEvent;
027 import org.springframework.richclient.security.AuthenticationFailedEvent;
028 import org.springframework.richclient.security.LoginEvent;
029 import org.springframework.richclient.security.LogoutEvent;
030 import org.springframework.security.Authentication;
031 import org.springframework.security.AuthenticationManager;
032 import org.springframework.security.GrantedAuthority;
033 import org.springframework.security.SpringSecurityException;
034 import org.springframework.security.context.SecurityContextHolder;
035 import org.springframework.security.providers.AuthenticationProvider;
036 import org.springframework.security.providers.ProviderManager;
037
038 /**
039 * Default implementation of ApplicationSecurityManager. It provides basic processing for
040 * login and logout actions and the event lifecycle.
041 * <p>
042 * Instances of this class should be configured with an instance of
043 * {@link org.springframework.security.AuthenticationManager} to be used to handle authentication
044 * (login) requests. This would be done like this:
045 *
046 * <pre>
047 * <bean id="securityManager"
048 * class="org.springframework.richclient.security.support.DefaultApplicationSecurityManager">
049 * <property name="authenticationManager" ref="authenticationManager"/>
050 * </bean>
051 *
052 * <bean id="authenticationManager"
053 * class="org.springframework.security.providers.ProviderManager">
054 * <property name="providers">
055 * <list>
056 * <ref bean="remoteAuthenticationProvider" />
057 * </list>
058 * </property>
059 * </bean>
060 *
061 * <bean id="remoteAuthenticationProvider"
062 * class="org.springframework.security.providers.rcp.RemoteAuthenticationProvider">
063 * <property name="remoteAuthenticationManager" ref="remoteAuthenticationManager" />
064 * </bean>
065 *
066 * <bean id="remoteAuthenticationManager"
067 * class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
068 * <property name="serviceUrl">
069 * <value>http://localhost:8080/myserver/rootContext/RemoteAuthenticationManager</value>
070 * </property>
071 * <property name="serviceInterface">
072 * <value>org.springframework.security.providers.rcp.RemoteAuthenticationManager</value>
073 * </property>
074 * </bean>
075 * </pre>
076 *
077 * If this is not done, then an attempt will be made to "auto-configure" by locating an
078 * appropriate authentication manager in the application context. In order, a search will
079 * be made for a bean that implements one of these classes:
080 * <ol>
081 * <li>ProviderManager</li>
082 * <li>AuthenticationProvider</li>
083 * <li>AuthenticationManager</li>
084 * </ol>
085 * The first instance to be located will be used to handle authentication requests.
086 * <p>
087 *
088 * @author Larry Streepy
089 *
090 */
091 public class DefaultApplicationSecurityManager implements ApplicationSecurityManager, InitializingBean {
092
093 private final Log logger = LogFactory.getLog( getClass() );
094
095 private AuthenticationManager authenticationManager = null;
096
097 private Authentication currentAuthentication = null;
098
099 /**
100 * Default constructor.
101 */
102 public DefaultApplicationSecurityManager() {
103 this( false );
104 }
105
106 /**
107 * Constructor invoked when we are created as the default implementation by
108 * ApplicationServices. Since this bean won't be defined in the context under these
109 * circumstances, we need to perform some auto-configuration of our own.
110 * <p>
111 * Auto-configuration consists of trying to locate an AuthenticationManager (in one of
112 * several classes) in the application context. This auto-configuration is also
113 * attempted after the bean is constructed by the context if the authenticationManager
114 * property has not been set. See {@see #afterPropertiesSet()}.
115 *
116 * @param autoConfigure pass true to perform auto-configuration
117 * @throws IllegalArgumentException If the auto-configuration fails
118 * @see #afterPropertiesSet()
119 */
120 public DefaultApplicationSecurityManager(boolean autoConfigure) {
121 if( autoConfigure ) {
122 afterPropertiesSet();
123 }
124 }
125
126 /**
127 * Set the authentication manager to use.
128 * @param authenticationManager instance to use for authentication requests
129 */
130 public void setAuthenticationManager(AuthenticationManager authenticationManager) {
131 this.authenticationManager = authenticationManager;
132 }
133
134 /**
135 * Get the authentication manager in use.
136 * @return authenticationManager instance used for authentication requests
137 */
138 public AuthenticationManager getAuthenticationManager() {
139 return authenticationManager;
140 }
141
142 /**
143 * Process a login attempt and fire all related events. If the authentication fails,
144 * then a {@link AuthenticationFailedEvent} is published and the exception is
145 * rethrown. If the authentication succeeds, then an {@link AuthenticationEvent} is
146 * published, followed by a {@link LoginEvent}.
147 *
148 * @param authentication token to use for the login attempt
149 * @return Authentication token resulting from a successful call to
150 * {@link AuthenticationManager#authenticate(org.springframework.security.Authentication)}.
151 * @see org.springframework.richclient.security.ApplicationSecurityManager#doLogin(org.springframework.security.Authentication)
152 * @throws SpringSecurityException If the authentication attempt fails
153 */
154 public Authentication doLogin(Authentication authentication) {
155 final ApplicationContext appCtx = Application.instance().getApplicationContext();
156
157 Authentication result = null;
158
159 try {
160 result = getAuthenticationManager().authenticate( authentication );
161 } catch( SpringSecurityException e ) {
162 logger.info( "authentication failed: " + e.getMessage() );
163
164 // Fire application event to advise of failed login
165 appCtx.publishEvent( new AuthenticationFailedEvent( authentication, e ) );
166
167 // rethrow the exception
168 throw e;
169 }
170
171 // Handle success or failure of the authentication attempt
172 if( logger.isDebugEnabled() ) {
173 logger.debug( "successful login - update context holder and fire event" );
174 }
175
176 // Commit the successful Authentication object to the secure ContextHolder
177 SecurityContextHolder.getContext().setAuthentication( result );
178 setAuthentication( result );
179
180 // Fire application events to advise of new login
181 appCtx.publishEvent( new AuthenticationEvent( result ) );
182 appCtx.publishEvent( new LoginEvent( result ) );
183
184 return result;
185 }
186
187 /**
188 * Return if a user is currently logged in, meaning that a previous call to doLogin
189 * resulted in a valid authentication request.
190 * @return true if a user is logged in
191 */
192 public boolean isUserLoggedIn() {
193 return getAuthentication() != null;
194 }
195
196 /**
197 * Get the authentication token for the currently logged in user.
198 * @return authentication token, null if not logged in
199 */
200 public Authentication getAuthentication() {
201 return currentAuthentication;
202 }
203
204 /**
205 * Set the authenticaiton token.
206 * @param authentication token to install as current.
207 */
208 protected void setAuthentication(Authentication authentication) {
209 currentAuthentication = authentication;
210 }
211
212 /**
213 * Determine if the currently authenticated user has the role provided. Note that role
214 * comparisons are case sensitive.
215 *
216 * @param role to check
217 * @return true if the user has the role requested
218 */
219 public boolean isUserInRole(String role) {
220 boolean inRole = false;
221
222 Authentication authentication = getAuthentication();
223 if( authentication != null ) {
224 GrantedAuthority[] authorities = authentication.getAuthorities();
225 for( int i = 0; i < authorities.length; i++ ) {
226 if( role.equals( authorities[i].getAuthority() ) ) {
227 inRole = true;
228 break;
229 }
230 }
231 }
232 return inRole;
233 }
234
235 /**
236 * Perform a logout. Set the current authentication token to null (in both the
237 * per-thread security context and the global context), then publish an
238 * {@link AuthenticationEvent} followed by a {@link LogoutEvent}.
239 * @return Authentication token that was in place prior to the logout.
240 * @see org.springframework.richclient.security.ApplicationSecurityManager#doLogout()
241 */
242 public Authentication doLogout() {
243 Authentication existing = getAuthentication();
244
245 // Make the Authentication object null if a SecureContext exists
246 SecurityContextHolder.getContext().setAuthentication( null );
247 setAuthentication( null );
248
249 // Fire application event to advise of logout
250 ApplicationContext appCtx = Application.instance().getApplicationContext();
251 appCtx.publishEvent( new AuthenticationEvent( null ) );
252 appCtx.publishEvent( new LogoutEvent( existing ) );
253
254 return existing;
255 }
256
257 /**
258 * Ensure that we have an authentication manager to work with. If one has not been
259 * specifically wired in, then look for beans to "auto-wire" in. Look for a bean of
260 * one of the following types (in order): {@link ProviderManager},
261 * {@link AuthenticationProvider}, and {@link AuthenticationManager}.
262 *
263 * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
264 */
265 public void afterPropertiesSet() {
266 // Ensure that we have our authentication manager
267 if( authenticationManager == null ) {
268 if( logger.isDebugEnabled() ) {
269 logger.debug( "No AuthenticationManager defined, look for one" );
270 }
271
272 // Try the class types in sequence
273 Class[] types = new Class[] { ProviderManager.class, AuthenticationProvider.class,
274 AuthenticationManager.class };
275
276 for( int i = 0; i < types.length; i++ ) {
277 if( tryToWire( types[i] ) ) {
278 break;
279 }
280 }
281 }
282
283 // If we still don't have one, then that's it
284 if( authenticationManager == null ) {
285 throw new IllegalArgumentException( "authenticationManager must be defined" );
286 }
287 }
288
289 /**
290 * Try to locate and "wire in" a suitable authentication manager.
291 * @param type The type of bean to look for
292 * @return true if we found and wired a suitable bean
293 */
294 protected boolean tryToWire(Class type) {
295 boolean success = false;
296 String className = type.getName();
297 Map map = Application.instance().getApplicationContext().getBeansOfType( type );
298 if( logger.isDebugEnabled() ) {
299 logger.debug( "Search for '" + className + "' found: " + map );
300 }
301
302 if( map.size() == 1 ) {
303 // Got one - wire it in
304 Map.Entry entry = (Map.Entry) map.entrySet().iterator().next();
305 String name = (String) entry.getKey();
306 AuthenticationManager am = (AuthenticationManager) entry.getValue();
307
308 setAuthenticationManager( am );
309 success = true;
310
311 if( logger.isInfoEnabled() ) {
312 logger.info( "Auto-configuration using '" + name + "' as authenticationManager" );
313 }
314 } else if( map.size() > 1 ) {
315 if( logger.isInfoEnabled() ) {
316 logger.info( "Need a single '" + className + "', found: " + map.keySet() );
317 }
318 } else {
319 // Size 0, no potentials
320 if( logger.isInfoEnabled() ) {
321 logger.info( "Auto-configuration did not find a suitable authenticationManager of type " + type );
322 }
323 }
324
325 return success;
326 }
327 }