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