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
021 package org.granite.spring.security;
022
023 import java.lang.reflect.InvocationTargetException;
024 import java.lang.reflect.Method;
025 import java.util.Arrays;
026 import java.util.List;
027 import java.util.Map;
028
029 import javax.servlet.http.HttpServletRequest;
030 import javax.servlet.http.HttpServletResponse;
031 import javax.servlet.http.HttpSession;
032
033 import org.granite.context.GraniteContext;
034 import org.granite.logging.Logger;
035 import org.granite.messaging.service.security.AbstractSecurityContext;
036 import org.granite.messaging.service.security.AbstractSecurityService;
037 import org.granite.messaging.service.security.SecurityServiceException;
038 import org.granite.messaging.webapp.HttpGraniteContext;
039 import org.springframework.beans.factory.BeanFactoryUtils;
040 import org.springframework.context.ApplicationContext;
041 import org.springframework.security.access.AccessDeniedException;
042 import org.springframework.security.authentication.AnonymousAuthenticationToken;
043 import org.springframework.security.authentication.AuthenticationManager;
044 import org.springframework.security.authentication.AuthenticationTrustResolver;
045 import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
046 import org.springframework.security.authentication.BadCredentialsException;
047 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
048 import org.springframework.security.authentication.encoding.PasswordEncoder;
049 import org.springframework.security.core.Authentication;
050 import org.springframework.security.core.AuthenticationException;
051 import org.springframework.security.core.GrantedAuthority;
052 import org.springframework.security.core.context.SecurityContext;
053 import org.springframework.security.core.context.SecurityContextHolder;
054 import org.springframework.security.core.userdetails.UsernameNotFoundException;
055 import org.springframework.security.web.authentication.session.SessionAuthenticationException;
056 import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
057 import org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy;
058 import org.springframework.security.web.context.HttpRequestResponseHolder;
059 import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
060 import org.springframework.security.web.context.SecurityContextRepository;
061 import org.springframework.web.context.support.WebApplicationContextUtils;
062
063
064 /**
065 * @author Bouiaw
066 * @author wdrai
067 */
068 public class SpringSecurity3Service extends AbstractSecurityService {
069
070 private static final Logger log = Logger.getLogger(SpringSecurity3Service.class);
071
072 private static final String FILTER_APPLIED = "__spring_security_scpf_applied";
073 private static final String SECURITY_SERVICE_APPLIED = "__spring_security_granite_service_applied";
074
075 private AuthenticationManager authenticationManager = null;
076 private AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl();
077 private SecurityContextRepository securityContextRepository = new HttpSessionSecurityContextRepository();
078 private AbstractSpringSecurity3Interceptor securityInterceptor = null;
079 private SessionAuthenticationStrategy sessionAuthenticationStrategy = new SessionFixationProtectionStrategy();
080 private PasswordEncoder passwordEncoder = null;
081 private String authenticationManagerBeanName = null;
082 private boolean allowAnonymousAccess = false;
083 private Method getRequest = null;
084 private Method getResponse = null;
085
086
087 public SpringSecurity3Service() {
088 log.debug("Starting Spring 3 Security Service");
089 try {
090 getRequest = HttpRequestResponseHolder.class.getDeclaredMethod("getRequest");
091 getRequest.setAccessible(true);
092 getResponse = HttpRequestResponseHolder.class.getDeclaredMethod("getResponse");
093 getResponse.setAccessible(true);
094 }
095 catch (Exception e) {
096 throw new RuntimeException("Could not get methods from HttpRequestResponseHolder", e);
097 }
098 }
099
100
101 public void setAuthenticationManager(AuthenticationManager authenticationManager) {
102 this.authenticationManager = authenticationManager;
103 }
104
105 public void setAuthenticationTrustResolver(AuthenticationTrustResolver authenticationTrustResolver) {
106 this.authenticationTrustResolver = authenticationTrustResolver;
107 }
108
109 public void setAllowAnonymousAccess(boolean allowAnonymousAccess) {
110 this.allowAnonymousAccess = allowAnonymousAccess;
111 }
112
113 public void setSecurityContextRepository(SecurityContextRepository securityContextRepository) {
114 this.securityContextRepository = securityContextRepository;
115 }
116
117 public void setSecurityInterceptor(AbstractSpringSecurity3Interceptor securityInterceptor) {
118 this.securityInterceptor = securityInterceptor;
119 }
120
121 public void setSessionAuthenticationStrategy(SessionAuthenticationStrategy sessionAuthenticationStrategy) {
122 if (sessionAuthenticationStrategy == null)
123 throw new NullPointerException("SessionAuthenticationStrategy cannot be null");
124 this.sessionAuthenticationStrategy = sessionAuthenticationStrategy;
125 }
126
127 public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
128 this.passwordEncoder = passwordEncoder;
129 }
130
131 public void configure(Map<String, String> params) {
132 log.debug("Configuring with parameters %s: ", params);
133 if (params.containsKey("authentication-manager-bean-name"))
134 authenticationManagerBeanName = params.get("authentication-manager-bean-name");
135 if (Boolean.TRUE.toString().equals(params.get("allow-anonymous-access")))
136 allowAnonymousAccess = true;
137 }
138
139 public void login(Object credentials, String charset) {
140 List<String> decodedCredentials = Arrays.asList(decodeBase64Credentials(credentials, charset));
141
142 HttpGraniteContext graniteContext = (HttpGraniteContext)GraniteContext.getCurrentInstance();
143 HttpServletRequest httpRequest = graniteContext.getRequest();
144
145 String user = decodedCredentials.get(0);
146 String password = decodedCredentials.get(1);
147 if (passwordEncoder != null)
148 password = passwordEncoder.encodePassword(password, null);
149 Authentication auth = new UsernamePasswordAuthenticationToken(user, password);
150
151 ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(
152 httpRequest.getSession().getServletContext()
153 );
154 if (ctx != null) {
155 lookupAuthenticationManager(ctx, authenticationManagerBeanName);
156
157 try {
158 Authentication authentication = authenticationManager.authenticate(auth);
159
160 if (authentication != null && !authenticationTrustResolver.isAnonymous(authentication)) {
161 try {
162 sessionAuthenticationStrategy.onAuthentication(authentication, httpRequest, graniteContext.getResponse());
163 }
164 catch (SessionAuthenticationException e) {
165 log.debug(e, "SessionAuthenticationStrategy rejected the authentication object");
166 SecurityContextHolder.clearContext();
167 handleAuthenticationExceptions(e);
168 return;
169 }
170 }
171
172 HttpRequestResponseHolder holder = new HttpRequestResponseHolder(graniteContext.getRequest(), graniteContext.getResponse());
173 SecurityContext securityContext = securityContextRepository.loadContext(holder);
174 securityContext.setAuthentication(authentication);
175 SecurityContextHolder.setContext(securityContext);
176 try {
177 securityContextRepository.saveContext(securityContext, (HttpServletRequest)getRequest.invoke(holder), (HttpServletResponse)getResponse.invoke(holder));
178 }
179 catch (Exception e) {
180 log.error(e, "Could not save context after authentication");
181 }
182
183 endLogin(credentials, charset);
184 }
185 catch (AuthenticationException e) {
186 handleAuthenticationExceptions(e);
187 }
188 }
189
190 log.debug("User %s logged in", user);
191 }
192
193 protected void handleAuthenticationExceptions(AuthenticationException e) {
194 if (e instanceof BadCredentialsException || e instanceof UsernameNotFoundException)
195 throw SecurityServiceException.newInvalidCredentialsException(e.getMessage());
196
197 throw SecurityServiceException.newAuthenticationFailedException(e.getMessage());
198 }
199
200 public void lookupAuthenticationManager(ApplicationContext ctx, String authenticationManagerBeanName) throws SecurityServiceException {
201 if (this.authenticationManager != null)
202 return;
203
204 Map<String, AuthenticationManager> authManagers = BeanFactoryUtils.beansOfTypeIncludingAncestors(ctx, AuthenticationManager.class);
205
206 if (authenticationManagerBeanName != null) {
207 this.authenticationManager = authManagers.get(authenticationManagerBeanName);
208 if (authenticationManager == null) {
209 log.error("AuthenticationManager bean not found " + authenticationManagerBeanName);
210 throw SecurityServiceException.newAuthenticationFailedException("Authentication failed");
211 }
212 return;
213 }
214 else if (authManagers.size() > 1) {
215 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>");
216 throw SecurityServiceException.newAuthenticationFailedException("Authentication failed");
217 }
218
219 this.authenticationManager = authManagers.values().iterator().next();
220 }
221
222
223 public Object authorize(AbstractSecurityContext context) throws Exception {
224 log.debug("Authorize %s on destination %s (secured: %b)", context, context.getDestination().getId(), context.getDestination().isSecured());
225
226 startAuthorization(context);
227
228 HttpGraniteContext graniteContext = (HttpGraniteContext)GraniteContext.getCurrentInstance();
229
230 Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
231 HttpRequestResponseHolder holder = null;
232
233 if (graniteContext.getRequest().getAttribute(FILTER_APPLIED) == null) {
234 if (graniteContext.getRequest().getAttribute(SECURITY_SERVICE_APPLIED) == null) {
235 holder = new HttpRequestResponseHolder(graniteContext.getRequest(), graniteContext.getResponse());
236 SecurityContext contextBeforeChainExecution = securityContextRepository.loadContext(holder);
237 SecurityContextHolder.setContext(contextBeforeChainExecution);
238 if (isAuthenticated(authentication))
239 contextBeforeChainExecution.setAuthentication(authentication);
240 else
241 authentication = contextBeforeChainExecution.getAuthentication();
242
243 graniteContext.getRequest().setAttribute(SECURITY_SERVICE_APPLIED, 0);
244 }
245 else
246 graniteContext.getRequest().setAttribute(SECURITY_SERVICE_APPLIED, (Integer)graniteContext.getRequest().getAttribute(SECURITY_SERVICE_APPLIED)+1);
247 }
248
249 if (context.getDestination().isSecured()) {
250 if (!isAuthenticated(authentication) || (!allowAnonymousAccess && authentication instanceof AnonymousAuthenticationToken)) {
251 log.debug("User not authenticated!");
252 throw SecurityServiceException.newNotLoggedInException("User not logged in");
253 }
254 if (!userCanAccessService(context, authentication)) {
255 log.debug("Access denied for user %s", authentication.getName());
256 throw SecurityServiceException.newAccessDeniedException("User not in required role");
257 }
258 }
259
260 try {
261 Object returnedObject = securityInterceptor != null
262 ? securityInterceptor.invoke(context)
263 : endAuthorization(context);
264
265 return returnedObject;
266 }
267 catch (AccessDeniedException e) {
268 throw SecurityServiceException.newAccessDeniedException(e.getMessage());
269 }
270 catch (InvocationTargetException e) {
271 handleAuthorizationExceptions(e);
272 throw e;
273 }
274 finally {
275 if (graniteContext.getRequest().getAttribute(FILTER_APPLIED) == null) {
276 if ((Integer)graniteContext.getRequest().getAttribute(SECURITY_SERVICE_APPLIED) == 0) {
277 SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
278 SecurityContextHolder.clearContext();
279 try {
280 securityContextRepository.saveContext(contextAfterChainExecution, (HttpServletRequest)getRequest.invoke(holder), (HttpServletResponse)getResponse.invoke(holder));
281 }
282 catch (Exception e) {
283 log.error(e, "Could not extract wrapped context from holder");
284 }
285 graniteContext.getRequest().removeAttribute(SECURITY_SERVICE_APPLIED);
286 }
287 else
288 graniteContext.getRequest().setAttribute(SECURITY_SERVICE_APPLIED, (Integer)graniteContext.getRequest().getAttribute(SECURITY_SERVICE_APPLIED)-1);
289 }
290 }
291 }
292
293 public void logout() {
294 HttpGraniteContext context = (HttpGraniteContext)GraniteContext.getCurrentInstance();
295 HttpSession session = context.getSession(false);
296 if (session != null && securityContextRepository.containsContext(context.getRequest()))
297 session.invalidate();
298
299 SecurityContextHolder.clearContext();
300 }
301
302 protected boolean isUserInRole(Authentication authentication, String role) {
303 for (GrantedAuthority ga : authentication.getAuthorities()) {
304 if (ga.getAuthority().matches(role))
305 return true;
306 }
307 return false;
308 }
309
310 protected boolean isAuthenticated(Authentication authentication) {
311 return authentication != null && authentication.isAuthenticated();
312 }
313
314 protected boolean userCanAccessService(AbstractSecurityContext context, Authentication authentication) {
315 log.debug("Is authenticated as: %s", authentication.getName());
316
317 for (String role : context.getDestination().getRoles()) {
318 if (isUserInRole(authentication, role)) {
319 log.debug("Allowed access to %s in role %s", authentication.getName(), role);
320 return true;
321 }
322 log.debug("Access denied for %s not in role %s", authentication.getName(), role);
323 }
324 return false;
325 }
326
327 protected void handleAuthorizationExceptions(InvocationTargetException e) {
328 for (Throwable t = e; t != null; t = t.getCause()) {
329 // Don't create a dependency to javax.ejb in SecurityService...
330 if (t instanceof SecurityException ||
331 t instanceof AccessDeniedException ||
332 "javax.ejb.EJBAccessException".equals(t.getClass().getName()))
333 throw SecurityServiceException.newAccessDeniedException(t.getMessage());
334 }
335 }
336
337 }