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