Browse Source
Support integration of the Spring SecurityContext on Callable's used with WebAsyncManager by registering SecurityContextCallableProcessingInterceptor.pull/17/head
29 changed files with 583 additions and 35 deletions
@ -0,0 +1,49 @@
@@ -0,0 +1,49 @@
|
||||
/* |
||||
* Copyright 2002-2012 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
*/ |
||||
package org.springframework.security.core; |
||||
|
||||
import static org.fest.assertions.Assertions.assertThat; |
||||
|
||||
import java.io.DataInputStream; |
||||
import java.io.InputStream; |
||||
|
||||
import org.junit.Test; |
||||
|
||||
/** |
||||
* |
||||
* @author Rob Winch |
||||
* |
||||
*/ |
||||
public class JavaVersionTests { |
||||
|
||||
private static final int JDK5_CLASS_VERSION = 49; |
||||
|
||||
@Test |
||||
public void authenticationCorrectJdkCompatibility() throws Exception { |
||||
assertClassVersion(Authentication.class); |
||||
} |
||||
|
||||
private void assertClassVersion(Class<?> clazz) throws Exception { |
||||
String classResourceName = clazz.getName().replaceAll("\\.", "/") + ".class"; |
||||
InputStream input = Thread.currentThread().getContextClassLoader().getResourceAsStream(classResourceName); |
||||
try { |
||||
DataInputStream data = new DataInputStream(input); |
||||
data.readInt(); |
||||
data.readShort(); // minor
|
||||
int major = data.readShort(); |
||||
assertThat(major).isEqualTo(JDK5_CLASS_VERSION); |
||||
} finally { |
||||
try { input.close(); } catch(Exception e) {} |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,79 @@
@@ -0,0 +1,79 @@
|
||||
/* |
||||
* Copyright 2002-2012 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
*/ |
||||
package org.springframework.security.web.context.request.async; |
||||
|
||||
import java.util.concurrent.Callable; |
||||
|
||||
import org.springframework.security.core.context.SecurityContext; |
||||
import org.springframework.security.core.context.SecurityContextHolder; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.web.context.request.NativeWebRequest; |
||||
import org.springframework.web.context.request.async.CallableProcessingInterceptor; |
||||
import org.springframework.web.context.request.async.CallableProcessingInterceptorAdapter; |
||||
|
||||
/** |
||||
* <p> |
||||
* Allows for integration with Spring MVC's {@link Callable} support. |
||||
* </p> |
||||
* <p> |
||||
* A {@link CallableProcessingInterceptor} that establishes the injected {@link SecurityContext} on the |
||||
* {@link SecurityContextHolder} when {@link #preProcess(NativeWebRequest, Callable)} is invoked. It also clear out the |
||||
* {@link SecurityContextHolder} by invoking {@link SecurityContextHolder#clearContext()} in the |
||||
* {@link #afterCompletion(NativeWebRequest, Callable)} method. |
||||
* </p> |
||||
* |
||||
* @author Rob Winch |
||||
* @since 3.2 |
||||
*/ |
||||
public final class SecurityContextCallableProcessingInterceptor extends CallableProcessingInterceptorAdapter { |
||||
private SecurityContext securityContext; |
||||
|
||||
/** |
||||
* Create a new {@link SecurityContextCallableProcessingInterceptor} that uses the {@link SecurityContext} from the |
||||
* {@link SecurityContextHolder} at the time {@link #beforeConcurrentHandling(NativeWebRequest, Callable)} is invoked. |
||||
*/ |
||||
public SecurityContextCallableProcessingInterceptor() { |
||||
} |
||||
|
||||
/** |
||||
* Creates a new {@link SecurityContextCallableProcessingInterceptor} with the specified {@link SecurityContext}. |
||||
* @param securityContext the {@link SecurityContext} to set on the {@link SecurityContextHolder} in |
||||
* {@link #preProcess(NativeWebRequest, Callable)}. Cannot be null. |
||||
* @throws IllegalArgumentException if {@link SecurityContext} is null. |
||||
*/ |
||||
public SecurityContextCallableProcessingInterceptor(SecurityContext securityContext) { |
||||
Assert.notNull(securityContext, "securityContext cannot be null"); |
||||
setSecurityContext(securityContext); |
||||
} |
||||
|
||||
@Override |
||||
public <T> void beforeConcurrentHandling(NativeWebRequest request, Callable<T> task) throws Exception { |
||||
if(securityContext == null) { |
||||
setSecurityContext(SecurityContextHolder.getContext()); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public <T> void afterCompletion(NativeWebRequest request, Callable<T> task) throws Exception { |
||||
SecurityContextHolder.clearContext(); |
||||
} |
||||
|
||||
@Override |
||||
public <T> void preProcess(NativeWebRequest request, Callable<T> task) throws Exception { |
||||
SecurityContextHolder.setContext(securityContext); |
||||
} |
||||
|
||||
private void setSecurityContext(SecurityContext securityContext) { |
||||
this.securityContext = securityContext; |
||||
} |
||||
} |
||||
@ -0,0 +1,52 @@
@@ -0,0 +1,52 @@
|
||||
/* |
||||
* Copyright 2002-2012 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
*/ |
||||
package org.springframework.security.web.context.request.async; |
||||
|
||||
import java.io.IOException; |
||||
import java.util.concurrent.Callable; |
||||
|
||||
import javax.servlet.FilterChain; |
||||
import javax.servlet.ServletException; |
||||
import javax.servlet.http.HttpServletRequest; |
||||
import javax.servlet.http.HttpServletResponse; |
||||
|
||||
import org.springframework.security.core.context.SecurityContext; |
||||
import org.springframework.web.context.request.async.WebAsyncManager; |
||||
import org.springframework.web.context.request.async.WebAsyncUtils; |
||||
import org.springframework.web.filter.OncePerRequestFilter; |
||||
|
||||
/** |
||||
* Provides integration between the {@link SecurityContext} and Spring Web's {@link WebAsyncManager} by using the |
||||
* {@link SecurityContextCallableProcessingInterceptor#beforeConcurrentHandling(org.springframework.web.context.request.NativeWebRequest, Callable)} |
||||
* to populate the {@link SecurityContext} on the {@link Callable}. |
||||
* |
||||
* @author Rob Winch |
||||
* @see SecurityContextCallableProcessingInterceptor |
||||
*/ |
||||
public final class WebAsyncManagerIntegrationFilter extends OncePerRequestFilter { |
||||
private static final Object CALLABLE_INTERCEPTOR_KEY = new Object(); |
||||
|
||||
@Override |
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) |
||||
throws ServletException, IOException { |
||||
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); |
||||
|
||||
SecurityContextCallableProcessingInterceptor securityProcessingInterceptor = (SecurityContextCallableProcessingInterceptor) asyncManager.getCallableInterceptor(CALLABLE_INTERCEPTOR_KEY); |
||||
if (securityProcessingInterceptor == null) { |
||||
asyncManager.registerCallableInterceptor(CALLABLE_INTERCEPTOR_KEY, |
||||
new SecurityContextCallableProcessingInterceptor()); |
||||
} |
||||
|
||||
filterChain.doFilter(request, response); |
||||
} |
||||
} |
||||
@ -0,0 +1,77 @@
@@ -0,0 +1,77 @@
|
||||
/* |
||||
* Copyright 2002-2012 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
*/ |
||||
package org.springframework.security.web.context.request.async; |
||||
|
||||
import static org.fest.assertions.Assertions.assertThat; |
||||
|
||||
import java.util.concurrent.Callable; |
||||
|
||||
import org.junit.After; |
||||
import org.junit.Test; |
||||
import org.junit.runner.RunWith; |
||||
import org.mockito.Mock; |
||||
import org.mockito.runners.MockitoJUnitRunner; |
||||
import org.springframework.security.core.context.SecurityContext; |
||||
import org.springframework.security.core.context.SecurityContextHolder; |
||||
import org.springframework.web.context.request.NativeWebRequest; |
||||
|
||||
/** |
||||
* |
||||
* @author Rob Winch |
||||
* |
||||
*/ |
||||
@RunWith(MockitoJUnitRunner.class) |
||||
public class SecurityContextCallableProcessingInterceptorTests { |
||||
@Mock |
||||
private SecurityContext securityContext; |
||||
@Mock |
||||
private Callable<?> callable; |
||||
@Mock |
||||
private NativeWebRequest webRequest; |
||||
|
||||
@After |
||||
public void clearSecurityContext() { |
||||
SecurityContextHolder.clearContext(); |
||||
} |
||||
|
||||
@Test(expected = IllegalArgumentException.class) |
||||
public void constructorNull() { |
||||
new SecurityContextCallableProcessingInterceptor(null); |
||||
} |
||||
|
||||
@Test |
||||
public void currentSecurityContext() throws Exception { |
||||
SecurityContextCallableProcessingInterceptor interceptor = new SecurityContextCallableProcessingInterceptor(); |
||||
SecurityContextHolder.setContext(securityContext); |
||||
interceptor.beforeConcurrentHandling(webRequest, callable); |
||||
SecurityContextHolder.clearContext(); |
||||
|
||||
interceptor.preProcess(webRequest, callable); |
||||
assertThat(SecurityContextHolder.getContext()).isSameAs(securityContext); |
||||
|
||||
interceptor.afterCompletion(webRequest, callable); |
||||
assertThat(SecurityContextHolder.getContext()).isNotSameAs(securityContext); |
||||
} |
||||
|
||||
@Test |
||||
public void specificSecurityContext() throws Exception { |
||||
SecurityContextCallableProcessingInterceptor interceptor = new SecurityContextCallableProcessingInterceptor( |
||||
securityContext); |
||||
|
||||
interceptor.preProcess(webRequest, callable); |
||||
assertThat(SecurityContextHolder.getContext()).isSameAs(securityContext); |
||||
|
||||
interceptor.afterCompletion(webRequest, callable); |
||||
assertThat(SecurityContextHolder.getContext()).isNotSameAs(securityContext); |
||||
} |
||||
} |
||||
@ -0,0 +1,127 @@
@@ -0,0 +1,127 @@
|
||||
/* |
||||
* Copyright 2002-2012 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
*/ |
||||
package org.springframework.security.web.context.request.async; |
||||
|
||||
import static org.fest.assertions.Assertions.assertThat; |
||||
import static org.mockito.Mockito.when; |
||||
|
||||
import java.util.concurrent.Callable; |
||||
import java.util.concurrent.ThreadFactory; |
||||
|
||||
import javax.servlet.http.HttpServletRequest; |
||||
import javax.servlet.http.HttpServletResponse; |
||||
|
||||
import org.junit.After; |
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
import org.junit.runner.RunWith; |
||||
import org.mockito.Mock; |
||||
import org.mockito.runners.MockitoJUnitRunner; |
||||
import org.springframework.core.task.SimpleAsyncTaskExecutor; |
||||
import org.springframework.mock.web.MockFilterChain; |
||||
import org.springframework.security.core.context.SecurityContext; |
||||
import org.springframework.security.core.context.SecurityContextHolder; |
||||
import org.springframework.web.context.request.async.AsyncWebRequest; |
||||
import org.springframework.web.context.request.async.WebAsyncManager; |
||||
import org.springframework.web.context.request.async.WebAsyncUtils; |
||||
|
||||
|
||||
/** |
||||
* |
||||
* @author Rob Winch |
||||
* |
||||
*/ |
||||
@RunWith(MockitoJUnitRunner.class) |
||||
public class WebAsyncManagerIntegrationFilterTests { |
||||
@Mock |
||||
private SecurityContext securityContext; |
||||
@Mock |
||||
private HttpServletRequest request; |
||||
@Mock |
||||
private HttpServletResponse response; |
||||
@Mock |
||||
private AsyncWebRequest asyncWebRequest; |
||||
private WebAsyncManager asyncManager; |
||||
private JoinableThreadFactory threadFactory; |
||||
|
||||
private MockFilterChain filterChain; |
||||
|
||||
private WebAsyncManagerIntegrationFilter filter; |
||||
|
||||
@Before |
||||
public void setUp() { |
||||
when(asyncWebRequest.getNativeRequest(HttpServletRequest.class)).thenReturn(request); |
||||
when(request.getRequestURI()).thenReturn("/"); |
||||
filterChain = new MockFilterChain(); |
||||
|
||||
threadFactory = new JoinableThreadFactory(); |
||||
SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor(); |
||||
executor.setThreadFactory(threadFactory); |
||||
|
||||
asyncManager = WebAsyncUtils.getAsyncManager(request); |
||||
asyncManager.setAsyncWebRequest(asyncWebRequest); |
||||
asyncManager.setTaskExecutor(executor); |
||||
when(request.getAttribute(WebAsyncUtils.WEB_ASYNC_MANAGER_ATTRIBUTE)).thenReturn(asyncManager); |
||||
|
||||
filter = new WebAsyncManagerIntegrationFilter(); |
||||
} |
||||
|
||||
@After |
||||
public void clearSecurityContext() { |
||||
SecurityContextHolder.clearContext(); |
||||
} |
||||
|
||||
@Test |
||||
public void doFilterInternalRegistersSecurityContextCallableProcessor() throws Exception { |
||||
SecurityContextHolder.setContext(securityContext); |
||||
filter.doFilterInternal(request, response, filterChain); |
||||
|
||||
VerifyingCallable verifyingCallable = new VerifyingCallable(); |
||||
asyncManager.startCallableProcessing(verifyingCallable); |
||||
threadFactory.join(); |
||||
assertThat(asyncManager.getConcurrentResult()).isSameAs(securityContext); |
||||
} |
||||
|
||||
@Test |
||||
public void doFilterInternalRegistersSecurityContextCallableProcessorContextUpdated() throws Exception { |
||||
SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext()); |
||||
filter.doFilterInternal(request, response, filterChain); |
||||
SecurityContextHolder.setContext(securityContext); |
||||
|
||||
VerifyingCallable verifyingCallable = new VerifyingCallable(); |
||||
asyncManager.startCallableProcessing(verifyingCallable); |
||||
threadFactory.join(); |
||||
assertThat(asyncManager.getConcurrentResult()).isSameAs(securityContext); |
||||
} |
||||
|
||||
private static final class JoinableThreadFactory implements ThreadFactory { |
||||
private Thread t; |
||||
|
||||
public Thread newThread(Runnable r) { |
||||
t = new Thread(r); |
||||
return t; |
||||
} |
||||
|
||||
public void join() throws InterruptedException { |
||||
t.join(); |
||||
} |
||||
} |
||||
|
||||
private class VerifyingCallable implements Callable<SecurityContext> { |
||||
|
||||
public SecurityContext call() throws Exception { |
||||
return SecurityContextHolder.getContext(); |
||||
} |
||||
|
||||
} |
||||
} |
||||
Loading…
Reference in new issue