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