001/** 002 * GRANITE DATA SERVICES 003 * Copyright (C) 2006-2013 GRANITE DATA SERVICES S.A.S. 004 * 005 * This file is part of the Granite Data Services Platform. 006 * 007 * Granite Data Services is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * Granite Data Services is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 015 * General Public License for more details. 016 * 017 * You should have received a copy of the GNU Lesser General Public 018 * License along with this library; if not, write to the Free Software 019 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 020 * USA, or see <http://www.gnu.org/licenses/>. 021 */ 022package org.granite.spring.security; 023 024import java.lang.reflect.InvocationTargetException; 025import java.lang.reflect.Method; 026import java.util.Arrays; 027import java.util.List; 028import java.util.Map; 029 030import javax.servlet.http.HttpServletRequest; 031import javax.servlet.http.HttpServletResponse; 032import javax.servlet.http.HttpSession; 033 034import org.granite.context.GraniteContext; 035import org.granite.logging.Logger; 036import org.granite.messaging.service.security.AbstractSecurityContext; 037import org.granite.messaging.service.security.AbstractSecurityService; 038import org.granite.messaging.service.security.SecurityServiceException; 039import org.granite.messaging.webapp.HttpGraniteContext; 040import org.granite.messaging.webapp.ServletGraniteContext; 041import org.springframework.beans.factory.BeanFactoryUtils; 042import org.springframework.context.ApplicationContext; 043import org.springframework.context.ApplicationContextAware; 044import org.springframework.context.ApplicationEventPublisher; 045import org.springframework.context.ApplicationEventPublisherAware; 046import org.springframework.security.access.AccessDeniedException; 047import org.springframework.security.authentication.AnonymousAuthenticationToken; 048import org.springframework.security.authentication.AuthenticationManager; 049import org.springframework.security.authentication.AuthenticationTrustResolver; 050import org.springframework.security.authentication.AuthenticationTrustResolverImpl; 051import org.springframework.security.authentication.BadCredentialsException; 052import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 053import org.springframework.security.authentication.encoding.PasswordEncoder; 054import org.springframework.security.authentication.event.AuthenticationSuccessEvent; 055import org.springframework.security.core.Authentication; 056import org.springframework.security.core.AuthenticationException; 057import org.springframework.security.core.GrantedAuthority; 058import org.springframework.security.core.context.SecurityContext; 059import org.springframework.security.core.context.SecurityContextHolder; 060import org.springframework.security.core.userdetails.UsernameNotFoundException; 061import org.springframework.security.web.authentication.session.SessionAuthenticationException; 062import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; 063import org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy; 064import org.springframework.security.web.context.HttpRequestResponseHolder; 065import org.springframework.security.web.context.HttpSessionSecurityContextRepository; 066import org.springframework.security.web.context.SecurityContextRepository; 067import org.springframework.web.context.support.WebApplicationContextUtils; 068 069 070/** 071 * @author Bouiaw 072 * @author wdrai 073 */ 074@SuppressWarnings("deprecation") 075public class SpringSecurity3Service extends AbstractSecurityService implements ApplicationContextAware, ApplicationEventPublisherAware { 076 077 private static final Logger log = Logger.getLogger(SpringSecurity3Service.class); 078 079 private static final String FILTER_APPLIED = "__spring_security_scpf_applied"; 080 private static final String SECURITY_SERVICE_APPLIED = "__spring_security_granite_service_applied"; 081 082 private ApplicationContext applicationContext = null; 083 private ApplicationEventPublisher eventPublisher = null; 084 private AuthenticationManager authenticationManager = null; 085 private AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl(); 086 private SecurityContextRepository securityContextRepository = new HttpSessionSecurityContextRepository(); 087 private AbstractSpringSecurity3Interceptor securityInterceptor = null; 088 private SessionAuthenticationStrategy sessionAuthenticationStrategy = new SessionFixationProtectionStrategy(); 089 private PasswordEncoder passwordEncoder = null; 090 private String authenticationManagerBeanName = null; 091 private boolean allowAnonymousAccess = false; 092 private Method getRequest = null; 093 private Method getResponse = null; 094 095 096 public SpringSecurity3Service() { 097 log.debug("Starting Spring 3 Security Service"); 098 try { 099 getRequest = HttpRequestResponseHolder.class.getDeclaredMethod("getRequest"); 100 getRequest.setAccessible(true); 101 getResponse = HttpRequestResponseHolder.class.getDeclaredMethod("getResponse"); 102 getResponse.setAccessible(true); 103 } 104 catch (Exception e) { 105 throw new RuntimeException("Could not get methods from HttpRequestResponseHolder", e); 106 } 107 } 108 109 public void setApplicationContext(ApplicationContext applicationContext) { 110 this.applicationContext = applicationContext; 111 } 112 113 public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) { 114 this.eventPublisher = eventPublisher; 115 } 116 117 public void setAuthenticationManager(AuthenticationManager authenticationManager) { 118 this.authenticationManager = authenticationManager; 119 } 120 121 public void setAuthenticationTrustResolver(AuthenticationTrustResolver authenticationTrustResolver) { 122 this.authenticationTrustResolver = authenticationTrustResolver; 123 } 124 125 public void setAllowAnonymousAccess(boolean allowAnonymousAccess) { 126 this.allowAnonymousAccess = allowAnonymousAccess; 127 } 128 129 public void setSecurityContextRepository(SecurityContextRepository securityContextRepository) { 130 this.securityContextRepository = securityContextRepository; 131 } 132 133 public void setSecurityInterceptor(AbstractSpringSecurity3Interceptor securityInterceptor) { 134 this.securityInterceptor = securityInterceptor; 135 } 136 137 public void setSessionAuthenticationStrategy(SessionAuthenticationStrategy sessionAuthenticationStrategy) { 138 if (sessionAuthenticationStrategy == null) 139 throw new NullPointerException("SessionAuthenticationStrategy cannot be null"); 140 this.sessionAuthenticationStrategy = sessionAuthenticationStrategy; 141 } 142 143 public void setPasswordEncoder(PasswordEncoder passwordEncoder) { 144 this.passwordEncoder = passwordEncoder; 145 } 146 147 public void configure(Map<String, String> params) { 148 log.debug("Configuring with parameters %s: ", params); 149 if (params.containsKey("authentication-manager-bean-name")) 150 authenticationManagerBeanName = params.get("authentication-manager-bean-name"); 151 if (Boolean.TRUE.toString().equals(params.get("allow-anonymous-access"))) 152 allowAnonymousAccess = true; 153 } 154 155 public void login(Object credentials, String charset) { 156 List<String> decodedCredentials = Arrays.asList(decodeBase64Credentials(credentials, charset)); 157 158 if (!(GraniteContext.getCurrentInstance() instanceof HttpGraniteContext)) { 159 log.info("Login from non HTTP granite context ignored"); 160 return; 161 } 162 163 HttpGraniteContext graniteContext = (HttpGraniteContext)GraniteContext.getCurrentInstance(); 164 HttpServletRequest httpRequest = graniteContext.getRequest(); 165 166 String user = decodedCredentials.get(0); 167 String password = decodedCredentials.get(1); 168 if (passwordEncoder != null) 169 password = passwordEncoder.encodePassword(password, null); 170 Authentication auth = new UsernamePasswordAuthenticationToken(user, password); 171 172 ApplicationContext appContext = applicationContext != null ? applicationContext 173 : WebApplicationContextUtils.getWebApplicationContext(graniteContext.getServletContext()); 174 175 if (appContext != null) { 176 lookupAuthenticationManager(appContext, authenticationManagerBeanName); 177 178 try { 179 Authentication authentication = authenticationManager.authenticate(auth); 180 181 if (authentication != null && !authenticationTrustResolver.isAnonymous(authentication)) { 182 try { 183 sessionAuthenticationStrategy.onAuthentication(authentication, httpRequest, graniteContext.getResponse()); 184 } 185 catch (SessionAuthenticationException e) { 186 log.debug(e, "SessionAuthenticationStrategy rejected the authentication object"); 187 SecurityContextHolder.clearContext(); 188 handleAuthenticationExceptions(e); 189 return; 190 } 191 } 192 193 log.debug("Define authentication and save to repo: %s", authentication != null ? authentication.getName() : "none"); 194 HttpRequestResponseHolder holder = new HttpRequestResponseHolder(graniteContext.getRequest(), graniteContext.getResponse()); 195 SecurityContext securityContext = securityContextRepository.loadContext(holder); 196 securityContext.setAuthentication(authentication); 197 SecurityContextHolder.setContext(securityContext); 198 try { 199 securityContextRepository.saveContext(securityContext, (HttpServletRequest)getRequest.invoke(holder), (HttpServletResponse)getResponse.invoke(holder)); 200 } 201 catch (Exception e) { 202 log.error(e, "Could not save context after authentication"); 203 } 204 205 if (eventPublisher != null) 206 eventPublisher.publishEvent(new AuthenticationSuccessEvent(authentication)); 207 208 endLogin(credentials, charset); 209 } 210 catch (AuthenticationException e) { 211 handleAuthenticationExceptions(e); 212 } 213 finally { 214 log.debug("Clear authentication"); 215 SecurityContextHolder.clearContext(); 216 } 217 } 218 219 log.debug("User %s logged in", user); 220 } 221 222 protected void handleAuthenticationExceptions(AuthenticationException e) { 223 if (e instanceof BadCredentialsException || e instanceof UsernameNotFoundException) 224 throw SecurityServiceException.newInvalidCredentialsException(e.getMessage()); 225 226 throw SecurityServiceException.newAuthenticationFailedException(e.getMessage()); 227 } 228 229 public void lookupAuthenticationManager(ApplicationContext ctx, String authenticationManagerBeanName) throws SecurityServiceException { 230 if (this.authenticationManager != null) 231 return; 232 233 Map<String, AuthenticationManager> authManagers = BeanFactoryUtils.beansOfTypeIncludingAncestors(ctx, AuthenticationManager.class); 234 235 if (authenticationManagerBeanName != null) { 236 this.authenticationManager = authManagers.get(authenticationManagerBeanName); 237 if (authenticationManager == null) { 238 log.error("AuthenticationManager bean not found " + authenticationManagerBeanName); 239 throw SecurityServiceException.newAuthenticationFailedException("Authentication failed"); 240 } 241 return; 242 } 243 else if (authManagers.size() > 1) { 244 log.error("More than one AuthenticationManager beans found, specify which one to use in Spring config <graniteds:security-service authentication-manager='myAuthManager'/> or in granite-config.xml <security type='org.granite.spring.security.SpringSecurity3Service'><param name='authentication-manager-bean-name' value='myAuthManager'/></security>"); 245 throw SecurityServiceException.newAuthenticationFailedException("Authentication failed"); 246 } 247 248 this.authenticationManager = authManagers.values().iterator().next(); 249 } 250 251 252 public Object authorize(AbstractSecurityContext context) throws Exception { 253 log.debug("Authorize %s on destination %s (secured: %b)", context, context.getDestination().getId(), context.getDestination().isSecured()); 254 255 startAuthorization(context); 256 257 ServletGraniteContext graniteContext = (ServletGraniteContext)GraniteContext.getCurrentInstance(); 258 259 Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); 260 HttpRequestResponseHolder holder = null; 261 262 if (graniteContext.getRequest().getAttribute(FILTER_APPLIED) == null) { 263 if (graniteContext.getRequest().getAttribute(SECURITY_SERVICE_APPLIED) == null) { 264 holder = new HttpRequestResponseHolder(graniteContext.getRequest(), graniteContext.getResponse()); 265 SecurityContext contextBeforeChainExecution = securityContextRepository.loadContext(holder); 266 SecurityContextHolder.setContext(contextBeforeChainExecution); 267 if (isAuthenticated(authentication)) { 268 log.debug("Restore authentication: %s", authentication.getName()); 269 contextBeforeChainExecution.setAuthentication(authentication); 270 } 271 else { 272 authentication = contextBeforeChainExecution.getAuthentication(); 273 log.debug("Restore authentication from repository: %s", authentication != null ? authentication.getName() : "none"); 274 } 275 276 graniteContext.getRequest().setAttribute(SECURITY_SERVICE_APPLIED, 0); 277 } 278 else { 279 log.debug("Increment service reentrance counter"); 280 graniteContext.getRequest().setAttribute(SECURITY_SERVICE_APPLIED, (Integer)graniteContext.getRequest().getAttribute(SECURITY_SERVICE_APPLIED)+1); 281 } 282 } 283 284 try { 285 if (context.getDestination().isSecured()) { 286 if (!isAuthenticated(authentication) || (!allowAnonymousAccess && authentication instanceof AnonymousAuthenticationToken)) { 287 log.debug("User not authenticated!"); 288 throw SecurityServiceException.newNotLoggedInException("User not logged in"); 289 } 290 if (!userCanAccessService(context, authentication)) { 291 log.debug("Access denied for user %s", authentication != null ? authentication.getName() : "not authenticated"); 292 throw SecurityServiceException.newAccessDeniedException("User not in required role"); 293 } 294 } 295 296 Object returnedObject = securityInterceptor != null 297 ? securityInterceptor.invoke(context) 298 : endAuthorization(context); 299 300 return returnedObject; 301 } 302 catch (SecurityServiceException e) { 303 throw e; 304 } 305 catch (AccessDeniedException e) { 306 throw SecurityServiceException.newAccessDeniedException(e.getMessage()); 307 } 308 catch (InvocationTargetException e) { 309 handleAuthorizationExceptions(e); 310 throw e; 311 } 312 finally { 313 if (graniteContext.getRequest().getAttribute(FILTER_APPLIED) == null) { 314 if ((Integer)graniteContext.getRequest().getAttribute(SECURITY_SERVICE_APPLIED) == 0) { 315 SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext(); 316 log.debug("Clear authentication and save to repo: %s", contextAfterChainExecution.getAuthentication() != null ? contextAfterChainExecution.getAuthentication().getName() : "none"); 317 SecurityContextHolder.clearContext(); 318 try { 319 securityContextRepository.saveContext(contextAfterChainExecution, (HttpServletRequest)getRequest.invoke(holder), (HttpServletResponse)getResponse.invoke(holder)); 320 } 321 catch (Exception e) { 322 log.error(e, "Could not extract wrapped context from holder"); 323 } 324 graniteContext.getRequest().removeAttribute(SECURITY_SERVICE_APPLIED); 325 } 326 else if (graniteContext.getRequest().getAttribute(SECURITY_SERVICE_APPLIED) == null) { 327 log.debug("Clear authentication"); 328 SecurityContextHolder.clearContext(); 329 } 330 else { 331 log.debug("Decrement service reentrance counter"); 332 graniteContext.getRequest().setAttribute(SECURITY_SERVICE_APPLIED, (Integer)graniteContext.getRequest().getAttribute(SECURITY_SERVICE_APPLIED)-1); 333 } 334 } 335 } 336 } 337 338 @Override 339 public boolean acceptsContext() { 340 return GraniteContext.getCurrentInstance() instanceof ServletGraniteContext; 341 } 342 343 public void logout() { 344 ServletGraniteContext context = (ServletGraniteContext)GraniteContext.getCurrentInstance(); 345 HttpSession session = context.getSession(false); 346 if (session != null && securityContextRepository.containsContext(context.getRequest())) 347 session.invalidate(); 348 349 SecurityContextHolder.clearContext(); 350 } 351 352 protected boolean isUserInRole(Authentication authentication, String role) { 353 for (GrantedAuthority ga : authentication.getAuthorities()) { 354 if (ga.getAuthority().matches(role)) 355 return true; 356 } 357 return false; 358 } 359 360 protected boolean isAuthenticated(Authentication authentication) { 361 return authentication != null && authentication.isAuthenticated(); 362 } 363 364 protected boolean userCanAccessService(AbstractSecurityContext context, Authentication authentication) { 365 log.debug("Is authenticated as: %s", authentication.getName()); 366 367 for (String role : context.getDestination().getRoles()) { 368 if (isUserInRole(authentication, role)) { 369 log.debug("Allowed access to %s in role %s", authentication.getName(), role); 370 return true; 371 } 372 log.debug("Access denied for %s not in role %s", authentication.getName(), role); 373 } 374 return false; 375 } 376 377 protected void handleAuthorizationExceptions(InvocationTargetException e) { 378 for (Throwable t = e; t != null; t = t.getCause()) { 379 // Don't create a dependency to javax.ejb in SecurityService... 380 if (t instanceof SecurityException || 381 t instanceof AccessDeniedException || 382 "javax.ejb.EJBAccessException".equals(t.getClass().getName())) { 383 throw SecurityServiceException.newAccessDeniedException(t.getMessage()); 384 } 385 else if (t instanceof AuthenticationException) { 386 throw SecurityServiceException.newNotLoggedInException(t.getMessage()); 387 } 388 } 389 } 390 391}