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 }